All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.sshd.common.file.root.RootedFileSystemProvider Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.sshd.common.file.root;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.FileChannel;
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.FileSystemAlreadyExistsException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.ProviderMismatchException;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.spi.FileSystemProvider;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;

import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.io.IoUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * File system provider which provides a rooted file system. The file system only gives access to files under the root
 * directory.
 *
 * @author Apache MINA SSHD Project
 */
public class RootedFileSystemProvider extends FileSystemProvider {
    protected final Logger log;
    private final Map fileSystems = new HashMap<>();

    public RootedFileSystemProvider() {
        log = LoggerFactory.getLogger(getClass());
    }

    @Override
    public String getScheme() {
        return "root";
    }

    @Override
    public FileSystem newFileSystem(URI uri, Map env) throws IOException {
        return newFileSystem(uri, uriToPath(uri), env);
    }

    @Override
    public FileSystem getFileSystem(URI uri) {
        return getFileSystem(uriToPath(uri));
    }

    @Override
    public FileSystem newFileSystem(Path path, Map env) throws IOException {
        return newFileSystem(path, path, env);
    }

    protected FileSystem newFileSystem(Object src, Path path, Map env) throws IOException {
        Path root = ensureDirectory(path).toRealPath();
        RootedFileSystem rootedFs = null;
        synchronized (fileSystems) {
            if (!this.fileSystems.containsKey(root)) {
                rootedFs = new RootedFileSystem(this, path, env);
                this.fileSystems.put(root, rootedFs);
            }
        }

        // do all the throwing outside the synchronized block to minimize its lock time
        if (rootedFs == null) {
            throw new FileSystemAlreadyExistsException("newFileSystem(" + src + ") already mapped " + root);
        }

        if (log.isTraceEnabled()) {
            log.trace("newFileSystem({}): {}", src, rootedFs);
        }

        return rootedFs;
    }

    protected Path uriToPath(URI uri) {
        String scheme = uri.getScheme();
        String expected = getScheme();
        if ((scheme == null) || (!scheme.equalsIgnoreCase(expected))) {
            throw new IllegalArgumentException("URI scheme (" + scheme + ") is not '" + expected + "'");
        }

        String root = uri.getRawSchemeSpecificPart();
        int i = root.indexOf("!/");
        if (i != -1) {
            root = root.substring(0, i);
        }

        try {
            return Paths.get(new URI(root)).toAbsolutePath();
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException(root + ": " + e.getMessage(), e);
        }
    }

    private static Path ensureDirectory(Path path) {
        return IoUtils.ensureDirectory(path, IoUtils.getLinkOptions(true));
    }

    @Override
    public Path getPath(URI uri) {
        String str = uri.getSchemeSpecificPart();
        int i = str.indexOf("!/");
        if (i == -1) {
            throw new IllegalArgumentException("URI: " + uri + " does not contain path info - e.g., root:file://foo/bar!/");
        }

        FileSystem fs = getFileSystem(uri);
        String subPath = str.substring(i + 1);
        Path p = fs.getPath(subPath);
        if (log.isTraceEnabled()) {
            log.trace("getPath({}): {}", uri, p);
        }
        return p;
    }

    @Override
    public InputStream newInputStream(Path path, OpenOption... options) throws IOException {
        Path r = unroot(path);
        FileSystemProvider p = provider(r);
        return p.newInputStream(r, options);
    }

    @Override
    public OutputStream newOutputStream(Path path, OpenOption... options) throws IOException {
        Path r = unroot(path);
        FileSystemProvider p = provider(r);
        return p.newOutputStream(r, options);
    }

    @Override
    public FileChannel newFileChannel(Path path, Set options, FileAttribute... attrs)
            throws IOException {
        Path r = unroot(path);
        FileSystemProvider p = provider(r);
        return p.newFileChannel(r, options, attrs);
    }

    @Override
    public AsynchronousFileChannel newAsynchronousFileChannel(
            Path path, Set options, ExecutorService executor, FileAttribute... attrs)
            throws IOException {
        Path r = unroot(path);
        FileSystemProvider p = provider(r);
        return p.newAsynchronousFileChannel(r, options, executor, attrs);
    }

    @Override
    public SeekableByteChannel newByteChannel(Path path, Set options, FileAttribute... attrs)
            throws IOException {
        Path r = unroot(path);
        FileSystemProvider p = provider(r);
        return p.newByteChannel(r, options, attrs);
    }

    @Override
    public DirectoryStream newDirectoryStream(Path dir, DirectoryStream.Filter filter) throws IOException {
        Path r = unroot(dir);
        FileSystemProvider p = provider(r);
        return root(((RootedPath) dir).getFileSystem(), p.newDirectoryStream(r, filter));
    }

    protected DirectoryStream root(RootedFileSystem rfs, DirectoryStream ds) {
        return new DirectoryStream() {
            @Override
            public Iterator iterator() {
                return root(rfs, ds.iterator());
            }

            @Override
            public void close() throws IOException {
                ds.close();
            }
        };
    }

    protected Iterator root(RootedFileSystem rfs, Iterator iter) {
        return new Iterator() {
            @Override
            public boolean hasNext() {
                return iter.hasNext();
            }

            @Override
            public Path next() {
                return root(rfs, iter.next());
            }
        };
    }

    @Override
    public void createDirectory(Path dir, FileAttribute... attrs) throws IOException {
        Path r = unroot(dir);
        FileSystemProvider p = provider(r);
        p.createDirectory(r, attrs);
    }

    @Override
    public void createSymbolicLink(Path link, Path target, FileAttribute... attrs) throws IOException {
        createLink(link, target, true, attrs);
    }

    @Override
    public void createLink(Path link, Path existing) throws IOException {
        createLink(link, existing, false);
    }

    protected void createLink(Path link, Path target, boolean symLink, FileAttribute... attrs) throws IOException {
        Path l = unroot(link);
        Path t = unroot(target);
        /*
         * For a symbolic link preserve the relative path
         */
        if (symLink && (!target.isAbsolute())) {
            RootedFileSystem rfs = ((RootedPath) target).getFileSystem();
            Path root = rfs.getRoot();
            t = root.relativize(t);
        }

        FileSystemProvider p = provider(l);
        if (symLink) {
            p.createSymbolicLink(l, t, attrs);
        } else {
            p.createLink(l, t);
        }

        if (log.isDebugEnabled()) {
            log.debug("createLink(symbolic={}) {} => {}", symLink, l, t);
        }
    }

    @Override
    public void delete(Path path) throws IOException {
        Path r = unroot(path);
        if (log.isTraceEnabled()) {
            log.trace("delete({}): {}", path, r);
        }
        FileSystemProvider p = provider(r);
        p.delete(r);
    }

    @Override
    public boolean deleteIfExists(Path path) throws IOException {
        Path r = unroot(path);
        if (log.isTraceEnabled()) {
            log.trace("deleteIfExists({}): {}", path, r);
        }
        FileSystemProvider p = provider(r);
        return p.deleteIfExists(r);
    }

    @Override
    public Path readSymbolicLink(Path link) throws IOException {
        Path r = unroot(link);
        FileSystemProvider p = provider(r);
        Path t = p.readSymbolicLink(r);
        Path target = root((RootedFileSystem) link.getFileSystem(), t);
        if (log.isTraceEnabled()) {
            log.trace("readSymbolicLink({})[{}]: {}[{}]", link, r, target, t);
        }
        return target;
    }

    @Override
    public void copy(Path source, Path target, CopyOption... options) throws IOException {
        Path s = unroot(source);
        Path t = unroot(target);
        if (log.isTraceEnabled()) {
            log.trace("copy({})[{}]: {}[{}]", source, s, target, t);
        }
        FileSystemProvider p = provider(s);
        p.copy(s, t, options);
    }

    @Override
    public void move(Path source, Path target, CopyOption... options) throws IOException {
        Path s = unroot(source);
        Path t = unroot(target);
        if (log.isTraceEnabled()) {
            log.trace("move({})[{}]: {}[{}]", source, s, target, t);
        }
        FileSystemProvider p = provider(s);
        p.move(s, t, options);
    }

    @Override
    public boolean isSameFile(Path path, Path path2) throws IOException {
        Path r = unroot(path);
        Path r2 = unroot(path2);
        FileSystemProvider p = provider(r);
        return p.isSameFile(r, r2);
    }

    @Override
    public boolean isHidden(Path path) throws IOException {
        Path r = unroot(path);
        FileSystemProvider p = provider(r);
        return p.isHidden(r);
    }

    @Override
    public FileStore getFileStore(Path path) throws IOException {
        RootedFileSystem fileSystem = getFileSystem(path);
        Path root = fileSystem.getRoot();
        return Files.getFileStore(root);
    }

    protected RootedFileSystem getFileSystem(Path path) throws FileSystemNotFoundException {
        Path real = unroot(path);
        Path rootInstance = null;
        RootedFileSystem fsInstance = null;
        synchronized (fileSystems) {
            // Cannot use forEach because the referenced variable are not effectively final
            for (Map.Entry fse : fileSystems.entrySet()) {
                Path root = fse.getKey();
                RootedFileSystem fs = fse.getValue();
                if (real.equals(root)) {
                    return fs; // we were lucky to have the root
                }

                if (!real.startsWith(root)) {
                    continue;
                }

                // if already have a candidate prefer the longer match since both are prefixes of the real path
                if ((rootInstance == null) || (rootInstance.getNameCount() < root.getNameCount())) {
                    rootInstance = root;
                    fsInstance = fs;
                }
            }
        }

        if (fsInstance == null) {
            throw new FileSystemNotFoundException(path.toString());
        }

        if (log.isTraceEnabled()) {
            log.trace("getFileSystem({}): {}", path, fsInstance);
        }

        return fsInstance;
    }

    @Override
    public void checkAccess(Path path, AccessMode... modes) throws IOException {
        Path r = unroot(path);
        FileSystemProvider p = provider(r);
        p.checkAccess(r, modes);
    }

    @Override
    public  V getFileAttributeView(Path path, Class type, LinkOption... options) {
        Path r = unroot(path);
        FileSystemProvider p = provider(r);
        return p.getFileAttributeView(r, type, options);
    }

    @Override
    public  A readAttributes(Path path, Class type, LinkOption... options)
            throws IOException {
        Path r = unroot(path);
        if (log.isTraceEnabled()) {
            log.trace("readAttributes({})[{}] type={}", path, r, type.getSimpleName());
        }

        FileSystemProvider p = provider(r);
        return p.readAttributes(r, type, options);
    }

    @Override
    public Map readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
        Path r = unroot(path);
        FileSystemProvider p = provider(r);
        Map attrs = p.readAttributes(r, attributes, options);
        if (log.isTraceEnabled()) {
            log.trace("readAttributes({})[{}] {}: {}", path, r, attributes, attrs);
        }
        return attrs;
    }

    @Override
    public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
        Path r = unroot(path);
        if (log.isTraceEnabled()) {
            log.trace("setAttribute({})[{}] {}={}", path, r, attribute, value);
        }
        FileSystemProvider p = provider(r);
        p.setAttribute(r, attribute, value, options);
    }

    protected FileSystemProvider provider(Path path) {
        FileSystem fs = path.getFileSystem();
        return fs.provider();
    }

    protected Path root(RootedFileSystem rfs, Path nat) {
        if (nat.isAbsolute()) {
            Path root = rfs.getRoot();
            Path rel = root.relativize(nat);
            return rfs.getPath("/" + rel.toString());
        } else {
            return rfs.getPath(nat.toString());
        }
    }

    /**
     * @param  path                      The original (rooted) {@link Path}
     * @return                           The actual absolute local {@link Path} represented by the rooted
     *                                   one
     * @see                              #resolveLocalPath(RootedPath)
     * @throws IllegalArgumentException  if {@code null} path argument
     * @throws ProviderMismatchException if not a {@link RootedPath}
     */
    protected Path unroot(Path path) {
        Objects.requireNonNull(path, "No path to unroot");
        if (!(path instanceof RootedPath)) {
            throw new ProviderMismatchException(
                    "unroot(" + path + ") is not a " + RootedPath.class.getSimpleName()
                                                + " but rather a " + path.getClass().getSimpleName());
        }

        return resolveLocalPath((RootedPath) path);
    }

    /**
     * @param  path                 The original {@link RootedPath} - never {@code null}
     * @return                      The actual absolute local {@link Path} represented by the rooted one
     * @throws InvalidPathException If the resolved path is not a proper sub-path of the rooted file system
     */
    protected Path resolveLocalPath(RootedPath path) {
        RootedPath absPath = Objects.requireNonNull(path, "No rooted path to resolve").toAbsolutePath();
        RootedFileSystem rfs = absPath.getFileSystem();
        Path root = rfs.getRoot();
        FileSystem lfs = root.getFileSystem();

        String rSep = ValidateUtils.checkNotNullAndNotEmpty(rfs.getSeparator(), "No rooted file system separator");
        ValidateUtils.checkTrue(rSep.length() == 1, "Bad rooted file system separator: %s", rSep);
        char rootedSeparator = rSep.charAt(0);

        String lSep = ValidateUtils.checkNotNullAndNotEmpty(lfs.getSeparator(), "No local file system separator");
        ValidateUtils.checkTrue(lSep.length() == 1, "Bad local file system separator: %s", lSep);
        char localSeparator = lSep.charAt(0);

        String r = absPath.toString();
        String subPath = r.substring(1);
        if (rootedSeparator != localSeparator) {
            subPath = subPath.replace(rootedSeparator, localSeparator);
        }

        Path resolved = root.resolve(subPath);
        resolved = resolved.normalize();
        resolved = resolved.toAbsolutePath();
        if (log.isTraceEnabled()) {
            log.trace("resolveLocalPath({}): {}", absPath, resolved);
        }

        /*
         * This can happen for Windows since we represent its paths as /C:/some/path, so substring(1) yields
         * C:/some/path - which is resolved as an absolute path (which we don't want).
         */
        if (!resolved.startsWith(root)) {
            throw new InvalidPathException(r, "Not under root");
        }
        return resolved;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy