/* * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @test * @summary Tests for SimpleFileServer with a root that is not of the default * file system * @library /test/lib * @build jdk.test.lib.Platform jdk.test.lib.net.URIBuilder * @run testng/othervm CustomFileSystemTest */ import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse.BodyHandlers; import java.nio.channels.SeekableByteChannel; import java.nio.file.AccessMode; import java.nio.file.CopyOption; import java.nio.file.DirectoryStream; import java.nio.file.FileStore; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.ProviderMismatchException; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.UserPrincipalLookupService; import java.nio.file.spi.FileSystemProvider; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.Logger; import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.SimpleFileServer; import com.sun.net.httpserver.SimpleFileServer.OutputLevel; import jdk.test.lib.Platform; import jdk.test.lib.net.URIBuilder; import org.testng.annotations.BeforeTest; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import org.testng.SkipException; import static java.net.http.HttpClient.Builder.NO_PROXY; import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.file.StandardOpenOption.CREATE; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; public class CustomFileSystemTest { static final InetSocketAddress LOOPBACK_ADDR = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); static final boolean ENABLE_LOGGING = true; static final Logger LOGGER = Logger.getLogger("com.sun.net.httpserver"); @BeforeTest public void setup() throws Exception { if (ENABLE_LOGGING) { ConsoleHandler ch = new ConsoleHandler(); LOGGER.setLevel(Level.ALL); ch.setLevel(Level.ALL); LOGGER.addHandler(ch); } } @Test public void testFileGET() throws Exception { var root = createDirectoryInCustomFs("testFileGET"); var file = Files.writeString(root.resolve("aFile.txt"), "some text", CREATE); var lastModified = getLastModified(file); var expectedLength = Long.toString(Files.size(file)); var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE); server.start(); try { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "aFile.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); assertEquals(response.statusCode(), 200); assertEquals(response.body(), "some text"); assertEquals(response.headers().firstValue("content-type").get(), "text/plain"); assertEquals(response.headers().firstValue("content-length").get(), expectedLength); assertEquals(response.headers().firstValue("last-modified").get(), lastModified); } finally { server.stop(0); } } @Test public void testDirectoryGET() throws Exception { var expectedBody = openHTML + """

Directory listing for /

""" + closeHTML; var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length); var root = createDirectoryInCustomFs("testDirectoryGET"); var file = Files.writeString(root.resolve("aFile.txt"), "some text", CREATE); var lastModified = getLastModified(root); var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE); server.start(); try { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, BodyHandlers.ofString()); assertEquals(response.statusCode(), 200); assertEquals(response.headers().firstValue("content-type").get(), "text/html; charset=UTF-8"); assertEquals(response.headers().firstValue("content-length").get(), expectedLength); assertEquals(response.headers().firstValue("last-modified").get(), lastModified); assertEquals(response.body(), expectedBody); } finally { server.stop(0); } } @Test public void testFileHEAD() throws Exception { var root = createDirectoryInCustomFs("testFileHEAD"); var file = Files.writeString(root.resolve("aFile.txt"), "some text", CREATE); var lastModified = getLastModified(file); var expectedLength = Long.toString(Files.size(file)); var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE); server.start(); try { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "aFile.txt")) .method("HEAD", HttpRequest.BodyPublishers.noBody()).build(); var response = client.send(request, BodyHandlers.ofString()); assertEquals(response.statusCode(), 200); assertEquals(response.headers().firstValue("content-type").get(), "text/plain"); assertEquals(response.headers().firstValue("content-length").get(), expectedLength); assertEquals(response.headers().firstValue("last-modified").get(), lastModified); assertEquals(response.body(), ""); } finally { server.stop(0); } } @Test public void testDirectoryHEAD() throws Exception { var expectedLength = Integer.toString( (openHTML + """

Directory listing for /

""" + closeHTML).getBytes(UTF_8).length); var root = createDirectoryInCustomFs("testDirectoryHEAD"); var file = Files.writeString(root.resolve("aFile.txt"), "some text", CREATE); var lastModified = getLastModified(root); var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE); server.start(); try { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")) .method("HEAD", HttpRequest.BodyPublishers.noBody()).build(); var response = client.send(request, BodyHandlers.ofString()); assertEquals(response.statusCode(), 200); assertEquals(response.headers().firstValue("content-type").get(), "text/html; charset=UTF-8"); assertEquals(response.headers().firstValue("content-length").get(), expectedLength); assertEquals(response.headers().firstValue("last-modified").get(), lastModified); assertEquals(response.body(), ""); } finally { server.stop(0); } } @DataProvider public Object[][] indexFiles() { var fileContent = openHTML + """

This is an index file

""" + closeHTML; var dirListing = openHTML + """

Directory listing for /

""" + closeHTML; return new Object[][] { {"1", "index.html", "text/html", "116", fileContent, true}, {"2", "index.htm", "text/html", "116", fileContent, true}, {"3", "index.txt", "text/html; charset=UTF-8", "134", dirListing, false} }; } @Test(dataProvider = "indexFiles") public void testDirectoryWithIndexGET(String id, String filename, String contentType, String contentLength, String expectedBody, boolean serveIndexFile) throws Exception { var root = createDirectoryInCustomFs("testDirectoryWithIndexGET"+id); var lastModified = getLastModified(root); if (serveIndexFile) { var file = Files.writeString(root.resolve(filename), expectedBody, CREATE); lastModified = getLastModified(file); } var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE); server.start(); try { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "")).build(); var response = client.send(request, BodyHandlers.ofString()); assertEquals(response.statusCode(), 200); assertEquals(response.headers().firstValue("content-type").get(), contentType); assertEquals(response.headers().firstValue("content-length").get(), contentLength); assertEquals(response.headers().firstValue("last-modified").get(), lastModified); assertEquals(response.body(), expectedBody); } finally { server.stop(0); if (serveIndexFile) { Files.delete(root.resolve(filename)); } } } @Test public void testNotReadableFileGET() throws Exception { if (!Platform.isWindows()) { // not applicable on Windows var expectedBody = openHTML + """

File not found

/aFile.txt

""" + closeHTML; var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length); var root = createDirectoryInCustomFs("testNotReadableFileGET"); var file = Files.writeString(root.resolve("aFile.txt"), "some text", CREATE); file.toFile().setReadable(false, false); assert !Files.isReadable(file); var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE); server.start(); try { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "aFile.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); assertEquals(response.statusCode(), 404); assertEquals(response.headers().firstValue("content-length").get(), expectedLength); assertEquals(response.body(), expectedBody); } finally { server.stop(0); file.toFile().setReadable(true, false); } } } @Test public void testNotReadableSegmentGET() throws Exception { if (!Platform.isWindows()) { // not applicable on Windows var expectedBody = openHTML + """

File not found

/dir/aFile.txt

""" + closeHTML; var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length); var root = createDirectoryInCustomFs("testNotReadableSegmentGET"); var dir = Files.createDirectory(root.resolve("dir")); var file = Files.writeString(dir.resolve("aFile.txt"), "some text", CREATE); dir.toFile().setReadable(false, false); assert !Files.isReadable(dir); assert Files.isReadable(file); var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE); server.start(); try { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "dir/aFile.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); assertEquals(response.statusCode(), 404); assertEquals(response.headers().firstValue("content-length").get(), expectedLength); assertEquals(response.body(), expectedBody); } finally { server.stop(0); dir.toFile().setReadable(true, false); } } } @Test public void testInvalidRequestURIGET() throws Exception { var expectedBody = openHTML + """

File not found

/aFile?#.txt

""" + closeHTML; var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length); var root = createDirectoryInCustomFs("testInvalidRequestURIGET"); var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE); server.start(); try { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "aFile?#.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); assertEquals(response.statusCode(), 404); assertEquals(response.headers().firstValue("content-length").get(), expectedLength); assertEquals(response.body(), expectedBody); } finally { server.stop(0); } } @Test public void testNotFoundGET() throws Exception { var expectedBody = openHTML + """

File not found

/doesNotExist.txt

""" + closeHTML; var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length); var root = createDirectoryInCustomFs("testNotFoundGET"); var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE); server.start(); try { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "doesNotExist.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); assertEquals(response.statusCode(), 404); assertEquals(response.headers().firstValue("content-length").get(), expectedLength); assertEquals(response.body(), expectedBody); } finally { server.stop(0); } } @Test public void testNotFoundHEAD() throws Exception { var expectedBody = openHTML + """

File not found

/doesNotExist.txt

""" + closeHTML; var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length); var root = createDirectoryInCustomFs("testNotFoundHEAD"); var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE); server.start(); try { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "doesNotExist.txt")) .method("HEAD", HttpRequest.BodyPublishers.noBody()).build(); var response = client.send(request, BodyHandlers.ofString()); assertEquals(response.statusCode(), 404); assertEquals(response.headers().firstValue("content-length").get(), expectedLength); assertEquals(response.body(), ""); } finally { server.stop(0); } } @Test public void testSymlinkGET() throws Exception { var expectedBody = openHTML + """

File not found

/symlink

""" + closeHTML; var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length); var root = createDirectoryInCustomFs("testSymlinkGET"); var symlink = root.resolve("symlink"); var target = Files.writeString(root.resolve("target.txt"), "some text", CREATE); createSymLink(symlink, target); var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE); server.start(); try { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "symlink")).build(); var response = client.send(request, BodyHandlers.ofString()); assertEquals(response.statusCode(), 404); assertEquals(response.headers().firstValue("content-length").get(), expectedLength); assertEquals(response.body(), expectedBody); } finally { server.stop(0); } } @Test public void testSymlinkSegmentGET() throws Exception { var expectedBody = openHTML + """

File not found

/symlink/aFile.txt

""" + closeHTML; var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length); var root = createDirectoryInCustomFs("testSymlinkSegmentGET"); var symlink = root.resolve("symlink"); var target = Files.createDirectory(root.resolve("target")); Files.writeString(target.resolve("aFile.txt"), "some text", CREATE); createSymLink(symlink, target); var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE); server.start(); try { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "symlink/aFile.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); assertEquals(response.statusCode(), 404); assertEquals(response.headers().firstValue("content-length").get(), expectedLength); assertEquals(response.body(), expectedBody); } finally { server.stop(0); } } private void createSymLink(Path symlink, Path target) { try { Files.createSymbolicLink(symlink, target); } catch (UnsupportedOperationException uoe) { throw new SkipException("sym link creation not supported", uoe); } catch (IOException ioe) { throw new SkipException("probably insufficient privileges to create sym links (Windows)", ioe); } } @Test public void testHiddenFileGET() throws Exception { var root = createDirectoryInCustomFs("testHiddenFileGET"); var file = createHiddenFile(root); var fileName = file.getFileName().toString(); var expectedBody = openHTML + """

File not found

/""" + fileName + """

""" + closeHTML; var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length); var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE); server.start(); try { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, fileName)).build(); var response = client.send(request, BodyHandlers.ofString()); assertEquals(response.statusCode(), 404); assertEquals(response.headers().firstValue("content-length").get(), expectedLength); assertEquals(response.body(), expectedBody); } finally { server.stop(0); } } @Test public void testHiddenSegmentGET() throws Exception { var root = createDirectoryInCustomFs("testHiddenSegmentGET"); var file = createFileInHiddenDirectory(root); var expectedBody = openHTML + """

File not found

/.hiddenDirectory/aFile.txt

""" + closeHTML; var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length); var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE); server.start(); try { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, ".hiddenDirectory/aFile.txt")).build(); var response = client.send(request, BodyHandlers.ofString()); assertEquals(response.statusCode(), 404); assertEquals(response.headers().firstValue("content-length").get(), expectedLength); assertEquals(response.body(), expectedBody); } finally { server.stop(0); } } private Path createHiddenFile(Path root) throws IOException { Path file; if (Platform.isWindows()) { file = Files.createFile(root.resolve("aFile.txt")); Files.setAttribute(file, "dos:hidden", true, LinkOption.NOFOLLOW_LINKS); } else { file = Files.writeString(root.resolve(".aFile.txt"), "some text", CREATE); } assertTrue(Files.isHidden(file)); return file; } private Path createFileInHiddenDirectory(Path root) throws IOException { Path dir; Path file; if (Platform.isWindows()) { dir = Files.createDirectory(root.resolve("hiddenDirectory")); Files.setAttribute(dir, "dos:hidden", true, LinkOption.NOFOLLOW_LINKS); } else { dir = Files.createDirectory(root.resolve(".hiddenDirectory")); } file = Files.writeString(dir.resolve("aFile.txt"), "some text", CREATE); assertTrue(Files.isHidden(dir)); assertFalse(Files.isHidden(file)); return file; } @Test public void testMovedPermanently() throws Exception { var root = createDirectoryInCustomFs("testMovedPermanently"); Files.createDirectory(root.resolve("aDirectory")); var expectedBody = openHTML + """

Directory listing for /aDirectory/

""" + closeHTML; var expectedLength = Integer.toString(expectedBody.getBytes(UTF_8).length); var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE); server.start(); try { { var client = HttpClient.newBuilder().proxy(NO_PROXY) .followRedirects(HttpClient.Redirect.NEVER).build(); var uri = uri(server, "aDirectory"); var request = HttpRequest.newBuilder(uri).build(); var response = client.send(request, BodyHandlers.ofString()); assertEquals(response.statusCode(), 301); assertEquals(response.headers().firstValue("content-length").get(), "0"); assertEquals(response.headers().firstValue("location").get(), "/aDirectory/"); // tests that query component is preserved during redirect var uri2 = uri(server, "aDirectory", "query"); var req2 = HttpRequest.newBuilder(uri2).build(); var res2 = client.send(req2, BodyHandlers.ofString()); assertEquals(res2.statusCode(), 301); assertEquals(res2.headers().firstValue("content-length").get(), "0"); assertEquals(res2.headers().firstValue("location").get(), "/aDirectory/?query"); } { // tests that redirect to returned relative URI works var client = HttpClient.newBuilder().proxy(NO_PROXY) .followRedirects(HttpClient.Redirect.ALWAYS).build(); var uri = uri(server, "aDirectory"); var request = HttpRequest.newBuilder(uri).build(); var response = client.send(request, BodyHandlers.ofString()); assertEquals(response.statusCode(), 200); assertEquals(response.body(), expectedBody); assertEquals(response.headers().firstValue("content-type").get(), "text/html; charset=UTF-8"); assertEquals(response.headers().firstValue("content-length").get(), expectedLength); } } finally { server.stop(0); } } @Test public void testXss() throws Exception { var root = createDirectoryInCustomFs("testXss"); var server = SimpleFileServer.createFileServer(LOOPBACK_ADDR, root, OutputLevel.VERBOSE); server.start(); try { var client = HttpClient.newBuilder().proxy(NO_PROXY).build(); var request = HttpRequest.newBuilder(uri(server, "beginDelim%3C%3EEndDelim")).build(); var response = client.send(request, BodyHandlers.ofString()); assertEquals(response.statusCode(), 404); assertTrue(response.body().contains("beginDelim%3C%3EEndDelim")); assertTrue(response.body().contains("File not found")); } finally { server.stop(0); } } static Path createDirectoryInCustomFs(String name) throws Exception { var defaultFs = FileSystems.getDefault(); var fs = new CustomProvider(defaultFs.provider()).newFileSystem(defaultFs); var dir = fs.getPath(name); if (Files.notExists(dir)) { Files.createDirectory(dir); } return dir.toAbsolutePath(); } static final String openHTML = """ """; static final String closeHTML = """ """; static URI uri(HttpServer server, String path) { return URIBuilder.newBuilder() .host("localhost") .port(server.getAddress().getPort()) .scheme("http") .path("/" + path) .buildUnchecked(); } static URI uri(HttpServer server, String path, String query) { return URIBuilder.newBuilder() .host("localhost") .port(server.getAddress().getPort()) .scheme("http") .path("/" + path) .query(query) .buildUnchecked(); } static String getLastModified(Path path) throws IOException { return Files.getLastModifiedTime(path).toInstant().atZone(ZoneId.of("GMT")) .format(DateTimeFormatter.RFC_1123_DATE_TIME); } // --- Custom File System --- static class CustomProvider extends FileSystemProvider { private final ConcurrentHashMap map = new ConcurrentHashMap<>(); private final FileSystemProvider defaultProvider; public CustomProvider(FileSystemProvider provider) { defaultProvider = provider; } @Override public String getScheme() { return defaultProvider.getScheme(); } public FileSystem newFileSystem(FileSystem fs) { return map.computeIfAbsent(fs, (sfs) -> new CustomFileSystem(this, fs)); } @Override public FileSystem newFileSystem(URI uri, Map env) throws IOException { FileSystem fs = defaultProvider.newFileSystem(uri, env); return map.computeIfAbsent(fs, (sfs) -> new CustomFileSystem(this, fs) ); } @Override public FileSystem getFileSystem(URI uri) { return map.get(defaultProvider.getFileSystem(uri)); } @Override public Path getPath(URI uri) { Path p = defaultProvider.getPath(uri); return map.get(defaultProvider.getFileSystem(uri)).wrap(p); } @Override public SeekableByteChannel newByteChannel(Path path, Set options, FileAttribute... attrs) throws IOException { Path p = toCustomPath(path).unwrap(); return defaultProvider.newByteChannel(p, options, attrs); } @Override public DirectoryStream newDirectoryStream(Path dir, DirectoryStream.Filter filter) throws IOException { throw new RuntimeException("not implemented"); } @Override public void createDirectory(Path dir, FileAttribute... attrs) throws IOException { Path p = toCustomPath(dir).unwrap(); defaultProvider.createDirectory(p, attrs); } @Override public void delete(Path path) throws IOException { Path p = toCustomPath(path).unwrap(); defaultProvider.delete(p); } @Override public void copy(Path source, Path target, CopyOption... options) throws IOException { Path sp = toCustomPath(source).unwrap(); Path tp = toCustomPath(target).unwrap(); defaultProvider.copy(sp, tp, options); } @Override public void move(Path source, Path target, CopyOption... options) throws IOException { Path sp = toCustomPath(source).unwrap(); Path tp = toCustomPath(target).unwrap(); defaultProvider.move(sp, tp, options); } @Override public boolean isSameFile(Path path, Path path2) throws IOException { Path p = toCustomPath(path).unwrap(); Path p2 = toCustomPath(path2).unwrap(); return defaultProvider.isSameFile(p, p2); } @Override public boolean isHidden(Path path) throws IOException { Path p = toCustomPath(path).unwrap(); return defaultProvider.isHidden(p); } @Override public FileStore getFileStore(Path path) throws IOException { Path p = toCustomPath(path).unwrap(); return defaultProvider.getFileStore(p); } @Override public void checkAccess(Path path, AccessMode... modes) throws IOException { Path p = toCustomPath(path).unwrap(); defaultProvider.checkAccess(p, modes); } @Override public V getFileAttributeView(Path path, Class type, LinkOption... options) { Path p = toCustomPath(path).unwrap(); return defaultProvider.getFileAttributeView(p, type, options); } @Override public A readAttributes(Path path, Class type, LinkOption... options) throws IOException { Path p = toCustomPath(path).unwrap(); return defaultProvider.readAttributes(p, type, options); } @Override public Map readAttributes(Path path, String attributes, LinkOption... options) throws IOException { Path p = toCustomPath(path).unwrap(); return defaultProvider.readAttributes(p, attributes, options); } @Override public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException { Path p = toCustomPath(path).unwrap(); defaultProvider.setAttribute(p, attribute, options); } // Checks that the given file is a CustomPath static CustomPath toCustomPath(Path obj) { if (obj == null) throw new NullPointerException(); if (!(obj instanceof CustomPath cp)) throw new ProviderMismatchException(); return cp; } } static class CustomFileSystem extends FileSystem { private final CustomProvider provider; private final FileSystem delegate; public CustomFileSystem(CustomProvider provider, FileSystem delegate) { this.provider = provider; this.delegate = delegate; } @Override public FileSystemProvider provider() { return provider; } @Override public void close() throws IOException { delegate.close(); } @Override public boolean isOpen() { return true; } @Override public boolean isReadOnly() { return false; } @Override public String getSeparator() { return delegate.getSeparator(); } @Override public Iterable getRootDirectories() { return null; } @Override public Iterable getFileStores() { return null; } @Override public Set supportedFileAttributeViews() { return null; } @Override public Path getPath(String first, String... more) { return delegate.getPath(first, more); } @Override public PathMatcher getPathMatcher(String syntaxAndPattern) { return null; } @Override public UserPrincipalLookupService getUserPrincipalLookupService() { return null; } @Override public WatchService newWatchService() throws IOException { return null; } @Override public String toString() { return delegate.toString(); } Path wrap(Path path) { return (path != null) ? new CustomPath(this, path) : null; } Path unwrap(Path wrapper) { if (wrapper == null) throw new NullPointerException(); if (!(wrapper instanceof CustomPath cp)) throw new ProviderMismatchException(); return cp.unwrap(); } } static class CustomPath implements Path { private final CustomFileSystem fs; private final Path delegate; CustomPath(CustomFileSystem fs, Path delegate) { this.fs = fs; this.delegate = delegate; } @Override public FileSystem getFileSystem() { return fs; } @Override public boolean isAbsolute() { return delegate.isAbsolute(); } @Override public Path getRoot() { return fs.wrap(delegate.getRoot()); } @Override public Path getFileName() { return null; } @Override public Path getParent() { return null; } @Override public int getNameCount() { return 0; } @Override public Path getName(int index) { return null; } @Override public Path subpath(int beginIndex, int endIndex) { return null; } @Override public boolean startsWith(Path other) { return delegate.startsWith(other); } @Override public boolean endsWith(Path other) { return false; } @Override public Path normalize() { return fs.wrap(delegate.normalize()); } @Override public Path resolve(Path other) { return fs.wrap(delegate.resolve(fs.unwrap(other))); } @Override public Path relativize(Path other) { return null; } @Override public URI toUri() { return delegate.toUri(); } @Override public Path toAbsolutePath() { return fs.wrap(delegate.toAbsolutePath()); } @Override public Path toRealPath(LinkOption... options) throws IOException { return null; } @Override public WatchKey register(WatchService watcher, WatchEvent.Kind[] events, WatchEvent.Modifier... modifiers) throws IOException { return null; } @Override public int compareTo(Path other) { return 0; } Path unwrap() { return delegate; } } }