http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/DirectoryScanner.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/DirectoryScanner.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/DirectoryScanner.java new file mode 100644 index 0000000..8b4e9ba --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/DirectoryScanner.java @@ -0,0 +1,380 @@ +/* + * 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.util.io; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.SelectorUtils; + +/** + * <p>Class for scanning a directory for files/directories which match certain + * criteria.</p> + * + * <p>These criteria consist of selectors and patterns which have been specified. + * With the selectors you can select which files you want to have included. + * Files which are not selected are excluded. With patterns you can include + * or exclude files based on their filename.</p> + * + * <p>The idea is simple. A given directory is recursively scanned for all files + * and directories. Each file/directory is matched against a set of selectors, + * including special support for matching against filenames with include and + * and exclude patterns. Only files/directories which match at least one + * pattern of the include pattern list or other file selector, and don't match + * any pattern of the exclude pattern list or fail to match against a required + * selector will be placed in the list of files/directories found.</p> + * + * <p>When no list of include patterns is supplied, "**" will be used, which + * means that everything will be matched. When no list of exclude patterns is + * supplied, an empty list is used, such that nothing will be excluded. When + * no selectors are supplied, none are applied.</p> + * + * <p>The filename pattern matching is done as follows: + * The name to be matched is split up in path segments. A path segment is the + * name of a directory or file, which is bounded by + * <code>File.separator</code> ('/' under UNIX, '\' under Windows). + * For example, "abc/def/ghi/xyz.java" is split up in the segments "abc", + * "def","ghi" and "xyz.java". + * The same is done for the pattern against which should be matched.</p> + * + * <p>The segments of the name and the pattern are then matched against each + * other. When '**' is used for a path segment in the pattern, it matches + * zero or more path segments of the name.</p> + * + * <p>There is a special case regarding the use of <code>File.separator</code>s + * at the beginning of the pattern and the string to match:<br> + * When a pattern starts with a <code>File.separator</code>, the string + * to match must also start with a <code>File.separator</code>. + * When a pattern does not start with a <code>File.separator</code>, the + * string to match may not start with a <code>File.separator</code>. + * When one of these rules is not obeyed, the string will not + * match.</p> + * + * <p>When a name path segment is matched against a pattern path segment, the + * following special characters can be used:<br> + * '*' matches zero or more characters<br> + * '?' matches one character.</p> + * + * <p>Examples: + * <br> + * <code>"**\*.class"</code> matches all <code>.class</code> files/dirs in a directory tree. + * <br> + * <code>"test\a??.java"</code> matches all files/dirs which start with an 'a', then two + * more characters and then <code>".java"</code>, in a directory called test. + * <br> + * <code>"**"</code> matches everything in a directory tree. + * <br> + * <code>"**\test\**\XYZ*"</code> matches all files/dirs which start with <code>"XYZ"</code> and where + * there is a parent directory called test (e.g. <code>"abc\test\def\ghi\XYZ123"</code>). + * </p> + * + * <p>Case sensitivity may be turned off if necessary. By default, it is + * turned on.</p> + * + * <p>Example of usage:</p> + * <pre> + * String[] includes = {"**\\*.class"}; + * String[] excludes = {"modules\\*\\**"}; + * ds.setIncludes(includes); + * ds.setExcludes(excludes); + * ds.setBasedir(new File("test")); + * ds.setCaseSensitive(true); + * ds.scan(); + * + * System.out.println("FILES:"); + * String[] files = ds.getIncludedFiles(); + * for (int i = 0; i < files.length; i++) { + * System.out.println(files[i]); + * } + * </pre> + * <p>This will scan a directory called test for .class files, but excludes all + * files in all proper subdirectories of a directory called "modules".</p> + * + * @author Arnout J. Kuiper + * <a href="mailto:[email protected]">[email protected]</a> + * @author Magesh Umasankar + * @author <a href="mailto:[email protected]">Bruce Atherton</a> + * @author <a href="mailto:[email protected]">Antoine Levy-Lambert</a> + */ +public class DirectoryScanner { + + /** + * The base directory to be scanned. + */ + protected File basedir; + + /** + * The patterns for the files to be included. + */ + protected String[] includes; + + /** + * The files which matched at least one include and no excludes + * and were selected. + */ + protected List<String> filesIncluded; + + /** + * Whether or not the file system should be treated as a case sensitive + * one. + */ + protected boolean isCaseSensitive = true; + + public DirectoryScanner() { + super(); + } + + public DirectoryScanner(String basedir, String... includes) { + setBasedir(basedir); + setIncludes(includes); + } + + /** + * Sets the base directory to be scanned. This is the directory which is + * scanned recursively. All '/' and '\' characters are replaced by + * <code>File.separatorChar</code>, so the separator used need not match + * <code>File.separatorChar</code>. + * + * @param basedir The base directory to scan. + * Must not be {@code null}. + */ + public void setBasedir(String basedir) { + setBasedir(new File(basedir.replace('/', File.separatorChar).replace('\\', File.separatorChar))); + } + + /** + * Sets the base directory to be scanned. This is the directory which is + * scanned recursively. + * + * @param basedir The base directory for scanning. + * Should not be {@code null}. + */ + public void setBasedir(File basedir) { + this.basedir = basedir; + } + + /** + * Returns the base directory to be scanned. + * This is the directory which is scanned recursively. + * + * @return the base directory to be scanned + */ + public File getBasedir() { + return basedir; + } + + /** + * <p>Sets the list of include patterns to use. All '/' and '\' characters + * are replaced by <code>File.separatorChar</code>, so the separator used + * need not match <code>File.separatorChar</code>.</p> + * + * <p>When a pattern ends with a '/' or '\', "**" is appended.</p> + * + * @param includes A list of include patterns. + * May be {@code null}, indicating that all files + * should be included. If a non-{@code null} + * list is given, all elements must be + * non-{@code null}. + */ + public void setIncludes(String[] includes) { + if (includes == null) { + this.includes = null; + } else { + this.includes = new String[includes.length]; + for (int i = 0; i < includes.length; i++) { + this.includes[i] = normalizePattern(includes[i]); + } + } + } + + /** + * Scans the base directory for files which match at least one include + * pattern and don't match any exclude patterns. If there are selectors + * then the files must pass muster there, as well. + * + * @return the matching files + * @throws IllegalStateException if the base directory was set + * incorrectly (i.e. if it is {@code null}, doesn't exist, + * or isn't a directory). + */ + public String[] scan() throws IllegalStateException { + if (basedir == null) { + throw new IllegalStateException("No basedir set"); + } + if (!basedir.exists()) { + throw new IllegalStateException("basedir " + basedir + + " does not exist"); + } + if (!basedir.isDirectory()) { + throw new IllegalStateException("basedir " + basedir + + " is not a directory"); + } + if (includes == null || includes.length == 0) { + throw new IllegalStateException("No includes set "); + } + + filesIncluded = new ArrayList<>(); + + scandir(basedir, ""); + + return getIncludedFiles(); + } + + /** + * Scans the given directory for files and directories. Found files and + * directories are placed in their respective collections, based on the + * matching of includes, excludes, and the selectors. When a directory + * is found, it is scanned recursively. + * + * @param dir The directory to scan. Must not be {@code null}. + * @param vpath The path relative to the base directory (needed to + * prevent problems with an absolute path when using + * dir). Must not be {@code null}. + */ + protected void scandir(File dir, String vpath) { + String[] newfiles = dir.list(); + if (GenericUtils.isEmpty(newfiles)) { + newfiles = GenericUtils.EMPTY_STRING_ARRAY; + } + + for (String newfile : newfiles) { + String name = vpath + newfile; + File file = new File(dir, newfile); + if (file.isDirectory()) { + if (isIncluded(name)) { + filesIncluded.add(name); + scandir(file, name + File.separator); + } else if (couldHoldIncluded(name)) { + scandir(file, name + File.separator); + } + } else if (file.isFile()) { + if (isIncluded(name)) { + filesIncluded.add(name); + } + } + } + } + + /** + * Returns the names of the files which matched at least one of the + * include patterns and none of the exclude patterns. + * The names are relative to the base directory. + * + * @return the names of the files which matched at least one of the + * include patterns and none of the exclude patterns. + */ + public String[] getIncludedFiles() { + String[] files = new String[filesIncluded.size()]; + return filesIncluded.toArray(files); + } + + /** + * Tests whether or not a name matches against at least one include + * pattern. + * + * @param name The name to match. Must not be {@code null}. + * @return <code>true</code> when the name matches against at least one + * include pattern, or <code>false</code> otherwise. + */ + protected boolean isIncluded(String name) { + for (String include : includes) { + if (SelectorUtils.matchPath(include, name, isCaseSensitive)) { + return true; + } + } + return false; + } + + /** + * Tests whether or not a name matches the start of at least one include + * pattern. + * + * @param name The name to match. Must not be {@code null}. + * @return <code>true</code> when the name matches against the start of at + * least one include pattern, or <code>false</code> otherwise. + */ + protected boolean couldHoldIncluded(String name) { + for (String include : includes) { + if (SelectorUtils.matchPatternStart(include, name, isCaseSensitive)) { + return true; + } + } + return false; + } + + /** + * Normalizes the pattern, e.g. converts forward and backward slashes to the platform-specific file separator. + * + * @param pattern The pattern to normalize, must not be {@code null}. + * @return The normalized pattern, never {@code null}. + */ + private String normalizePattern(String pattern) { + pattern = pattern.trim(); + + if (pattern.startsWith(SelectorUtils.REGEX_HANDLER_PREFIX)) { + if (File.separatorChar == '\\') { + pattern = replace(pattern, "/", "\\\\", -1); + } else { + pattern = replace(pattern, "\\\\", "/", -1); + } + } else { + pattern = pattern.replace(File.separatorChar == '/' ? '\\' : '/', File.separatorChar); + + if (pattern.endsWith(File.separator)) { + pattern += "**"; + } + } + + return pattern; + } + + /** + * <p>Replace a String with another String inside a larger String, + * for the first <code>max</code> values of the search String.</p> + * + * <p>A {@code null} reference passed to this method is a no-op.</p> + * + * @param text text to search and replace in + * @param repl String to search for + * @param with String to replace with + * @param max maximum number of values to replace, or <code>-1</code> if no maximum + * @return the text with any replacements processed + */ + @SuppressWarnings("PMD.AssignmentInOperand") + public static String replace(String text, String repl, String with, int max) { + if ((text == null) || (repl == null) || (with == null) || (repl.length() == 0)) { + return text; + } + + StringBuilder buf = new StringBuilder(text.length()); + int start = 0; + for (int end = text.indexOf(repl, start); end != -1; end = text.indexOf(repl, start)) { + buf.append(text.substring(start, end)).append(with); + start = end + repl.length(); + + if (--max == 0) { + break; + } + } + buf.append(text.substring(start)); + return buf.toString(); + } +}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/EmptyInputStream.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/EmptyInputStream.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/EmptyInputStream.java new file mode 100644 index 0000000..8a1a68d --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/EmptyInputStream.java @@ -0,0 +1,66 @@ +/* + * 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.util.io; + +import java.io.IOException; +import java.io.InputStream; + +/** + * A {@code /dev/null} implementation - always open + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class EmptyInputStream extends InputStream { + public static final EmptyInputStream DEV_NULL = new EmptyInputStream(); + + public EmptyInputStream() { + super(); + } + + @Override + public int read() throws IOException { + return -1; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return -1; + } + + @Override + public long skip(long n) throws IOException { + return 0L; + } + + @Override + public int available() throws IOException { + return 0; + } + + @Override + public synchronized void mark(int readlimit) { + throw new UnsupportedOperationException("mark(" + readlimit + ") called despite the fact that markSupported=" + markSupported()); + } + + @Override + public synchronized void reset() throws IOException { + // ignored + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/FileInfoExtractor.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/FileInfoExtractor.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/FileInfoExtractor.java new file mode 100644 index 0000000..feafd18 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/FileInfoExtractor.java @@ -0,0 +1,53 @@ +/* + * 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.util.io; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Set; + +/** + * @param <T> Type of information being extracted + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@FunctionalInterface +public interface FileInfoExtractor<T> { + + FileInfoExtractor<Boolean> EXISTS = Files::exists; + + FileInfoExtractor<Boolean> ISDIR = Files::isDirectory; + + FileInfoExtractor<Boolean> ISREG = Files::isRegularFile; + + FileInfoExtractor<Boolean> ISSYMLINK = (file, options) -> Files.isSymbolicLink(file); + + FileInfoExtractor<Long> SIZE = (file, options) -> Files.size(file); + + FileInfoExtractor<Set<PosixFilePermission>> PERMISSIONS = IoUtils::getPermissions; + + FileInfoExtractor<FileTime> LASTMODIFIED = Files::getLastModifiedTime; + + T infoOf(Path file, LinkOption... options) throws IOException; + +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/InputStreamWithChannel.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/InputStreamWithChannel.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/InputStreamWithChannel.java new file mode 100644 index 0000000..d847079 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/InputStreamWithChannel.java @@ -0,0 +1,32 @@ +/* + * 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.util.io; + +import java.io.InputStream; +import java.nio.channels.Channel; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public abstract class InputStreamWithChannel extends InputStream implements Channel { + protected InputStreamWithChannel() { + super(); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/IoUtils.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/IoUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/IoUtils.java new file mode 100644 index 0000000..10aa59a --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/IoUtils.java @@ -0,0 +1,556 @@ +/* + * 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.util.io; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.EOFException; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.CopyOption; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.UserPrincipal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.OsUtils; + +/** + * TODO Add javadoc + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public final class IoUtils { + + public static final OpenOption[] EMPTY_OPEN_OPTIONS = new OpenOption[0]; + public static final CopyOption[] EMPTY_COPY_OPTIONS = new CopyOption[0]; + public static final LinkOption[] EMPTY_LINK_OPTIONS = new LinkOption[0]; + public static final FileAttribute<?>[] EMPTY_FILE_ATTRIBUTES = new FileAttribute<?>[0]; + + public static final List<String> WINDOWS_EXECUTABLE_EXTENSIONS = Collections.unmodifiableList(Arrays.asList(".bat", ".exe", ".cmd")); + + /** + * Size of preferred work buffer when reading / writing data to / from streams + */ + public static final int DEFAULT_COPY_SIZE = 8192; + + /** + * The local O/S line separator + */ + public static final String EOL = System.lineSeparator(); + + /** + * A {@link Set} of {@link StandardOpenOption}-s that indicate an intent + * to create/modify a file + */ + public static final Set<StandardOpenOption> WRITEABLE_OPEN_OPTIONS = + Collections.unmodifiableSet( + EnumSet.of( + StandardOpenOption.APPEND, StandardOpenOption.CREATE, + StandardOpenOption.CREATE_NEW, StandardOpenOption.DELETE_ON_CLOSE, + StandardOpenOption.DSYNC, StandardOpenOption.SYNC, + StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)); + + private static final byte[] EOL_BYTES = EOL.getBytes(StandardCharsets.UTF_8); + + private static final LinkOption[] NO_FOLLOW_OPTIONS = new LinkOption[]{LinkOption.NOFOLLOW_LINKS}; + + /** + * Private Constructor + */ + private IoUtils() { + throw new UnsupportedOperationException("No instance allowed"); + } + + /** + * @return The local platform line separator bytes as UTF-8. <B>Note:</B> + * each call returns a <U>new</U> instance in order to avoid inadvertent + * changes in shared objects + * @see #EOL + */ + public static byte[] getEOLBytes() { + return EOL_BYTES.clone(); + } + + public static LinkOption[] getLinkOptions(boolean followLinks) { + if (followLinks) { + return EMPTY_LINK_OPTIONS; + } else { // return a clone that modifications to the array will not affect others + return NO_FOLLOW_OPTIONS.clone(); + } + } + + public static long copy(InputStream source, OutputStream sink) throws IOException { + return copy(source, sink, DEFAULT_COPY_SIZE); + } + + public static long copy(InputStream source, OutputStream sink, int bufferSize) throws IOException { + long nread = 0L; + byte[] buf = new byte[bufferSize]; + for (int n = source.read(buf); n > 0; n = source.read(buf)) { + sink.write(buf, 0, n); + nread += n; + } + + return nread; + } + + /** + * Closes a bunch of resources suppressing any {@link IOException}s their + * {@link Closeable#close()} method may have thrown + * + * @param closeables The {@link Closeable}s to close + * @return The <U>first</U> {@link IOException} that occurred during closing + * of a resource - if more than one exception occurred, they are added as + * suppressed exceptions to the first one + * @see Throwable#getSuppressed() + */ + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") + public static IOException closeQuietly(Closeable... closeables) { + IOException err = null; + for (Closeable c : closeables) { + try { + if (c != null) { + c.close(); + } + } catch (IOException e) { + err = GenericUtils.accumulateException(err, e); + } + } + + return err; + } + + /** + * @param fileName The file name to be evaluated - ignored if {@code null}/empty + * @return {@code true} if the file ends in one of the {@link #WINDOWS_EXECUTABLE_EXTENSIONS} + */ + public static boolean isWindowsExecutable(String fileName) { + if ((fileName == null) || (fileName.length() <= 0)) { + return false; + } + for (String suffix : WINDOWS_EXECUTABLE_EXTENSIONS) { + if (fileName.endsWith(suffix)) { + return true; + } + } + return false; + } + + /** + * If the "posix" view is supported, then it returns + * {@link Files#getPosixFilePermissions(Path, LinkOption...)}, otherwise + * uses the {@link #getPermissionsFromFile(File)} method + * + * @param path The {@link Path} + * @param options The {@link LinkOption}s to use when querying the permissions + * @return A {@link Set} of {@link PosixFilePermission} + * @throws IOException If failed to access the file system in order to + * retrieve the permissions + */ + public static Set<PosixFilePermission> getPermissions(Path path, LinkOption... options) throws IOException { + FileSystem fs = path.getFileSystem(); + Collection<String> views = fs.supportedFileAttributeViews(); + if (views.contains("posix")) { + return Files.getPosixFilePermissions(path, options); + } else { + return getPermissionsFromFile(path.toFile()); + } + } + + /** + * @param f The {@link File} to be checked + * @return A {@link Set} of {@link PosixFilePermission}s based on whether + * the file is readable/writable/executable. If so, then <U>all</U> the + * relevant permissions are set (i.e., owner, group and others) + */ + public static Set<PosixFilePermission> getPermissionsFromFile(File f) { + Set<PosixFilePermission> perms = EnumSet.noneOf(PosixFilePermission.class); + if (f.canRead()) { + perms.add(PosixFilePermission.OWNER_READ); + perms.add(PosixFilePermission.GROUP_READ); + perms.add(PosixFilePermission.OTHERS_READ); + } + + if (f.canWrite()) { + perms.add(PosixFilePermission.OWNER_WRITE); + perms.add(PosixFilePermission.GROUP_WRITE); + perms.add(PosixFilePermission.OTHERS_WRITE); + } + + if (isExecutable(f)) { + perms.add(PosixFilePermission.OWNER_EXECUTE); + perms.add(PosixFilePermission.GROUP_EXECUTE); + perms.add(PosixFilePermission.OTHERS_EXECUTE); + } + + return perms; + } + + public static boolean isExecutable(File f) { + if (f == null) { + return false; + } + + if (OsUtils.isWin32()) { + return isWindowsExecutable(f.getName()); + } else { + return f.canExecute(); + } + } + + /** + * If the "posix" view is supported, then it invokes + * {@link Files#setPosixFilePermissions(Path, Set)}, otherwise + * uses the {@link #setPermissionsToFile(File, Collection)} method + * + * @param path The {@link Path} + * @param perms The {@link Set} of {@link PosixFilePermission}s + * @throws IOException If failed to access the file system + */ + public static void setPermissions(Path path, Set<PosixFilePermission> perms) throws IOException { + FileSystem fs = path.getFileSystem(); + Collection<String> views = fs.supportedFileAttributeViews(); + if (views.contains("posix")) { + Files.setPosixFilePermissions(path, perms); + } else { + setPermissionsToFile(path.toFile(), perms); + } + } + + /** + * @param f The {@link File} + * @param perms A {@link Collection} of {@link PosixFilePermission}s to set on it. + * <B>Note:</B> the file is set to readable/writable/executable not only by the + * owner if <U>any</U> of relevant the owner/group/others permission is set + */ + public static void setPermissionsToFile(File f, Collection<PosixFilePermission> perms) { + boolean readable = perms != null + && (perms.contains(PosixFilePermission.OWNER_READ) + || perms.contains(PosixFilePermission.GROUP_READ) + || perms.contains(PosixFilePermission.OTHERS_READ)); + f.setReadable(readable, false); + + boolean writable = perms != null + && (perms.contains(PosixFilePermission.OWNER_WRITE) + || perms.contains(PosixFilePermission.GROUP_WRITE) + || perms.contains(PosixFilePermission.OTHERS_WRITE)); + f.setWritable(writable, false); + + boolean executable = perms != null + && (perms.contains(PosixFilePermission.OWNER_EXECUTE) + || perms.contains(PosixFilePermission.GROUP_EXECUTE) + || perms.contains(PosixFilePermission.OTHERS_EXECUTE)); + f.setExecutable(executable, false); + } + + /** + * <P>Get file owner.</P> + * + * @param path The {@link Path} + * @param options The {@link LinkOption}s to use when querying the owner + * @return Owner of the file or null if unsupported. <B>Note:</B> for + * <I>Windows</I> it strips any prepended domain or group name + * @throws IOException If failed to access the file system + * @see Files#getOwner(Path, LinkOption...) + */ + public static String getFileOwner(Path path, LinkOption... options) throws IOException { + try { + UserPrincipal principal = Files.getOwner(path, options); + String owner = (principal == null) ? null : principal.getName(); + return OsUtils.getCanonicalUser(owner); + } catch (UnsupportedOperationException e) { + return null; + } + } + + /** + * <P>Checks if a file exists - <B>Note:</B> according to the + * <A HREF="http://docs.oracle.com/javase/tutorial/essential/io/check.html">Java tutorial - Checking a File or Directory</A>: + * </P> + * + * <PRE> + * The methods in the Path class are syntactic, meaning that they operate + * on the Path instance. But eventually you must access the file system + * to verify that a particular Path exists, or does not exist. You can do + * so with the exists(Path, LinkOption...) and the notExists(Path, LinkOption...) + * methods. Note that !Files.exists(path) is not equivalent to Files.notExists(path). + * When you are testing a file's existence, three results are possible: + * + * - The file is verified to exist. + * - The file is verified to not exist. + * - The file's status is unknown. + * + * This result can occur when the program does not have access to the file. + * If both exists and notExists return false, the existence of the file cannot + * be verified. + * </PRE> + * + * @param path The {@link Path} to be tested + * @param options The {@link LinkOption}s to use + * @return {@link Boolean#TRUE}/{@link Boolean#FALSE} or {@code null} + * according to the file status as explained above + */ + public static Boolean checkFileExists(Path path, LinkOption... options) { + if (Files.exists(path, options)) { + return Boolean.TRUE; + } else if (Files.notExists(path, options)) { + return Boolean.FALSE; + } else { + return null; + } + } + + /** + * Read the requested number of bytes or fail if there are not enough left. + * + * @param input where to read input from + * @param buffer destination + * @throws IOException if there is a problem reading the file + * @throws EOFException if the number of bytes read was incorrect + */ + public static void readFully(InputStream input, byte[] buffer) throws IOException { + readFully(input, buffer, 0, buffer.length); + } + + /** + * Read the requested number of bytes or fail if there are not enough left. + * + * @param input where to read input from + * @param buffer destination + * @param offset initial offset into buffer + * @param length length to read, must be ≥ 0 + * @throws IOException if there is a problem reading the file + * @throws EOFException if the number of bytes read was incorrect + */ + public static void readFully(InputStream input, byte[] buffer, int offset, int length) throws IOException { + int actual = read(input, buffer, offset, length); + if (actual != length) { + throw new EOFException("Premature EOF - expected=" + length + ", actual=" + actual); + } + } + + /** + * Read as many bytes as possible until EOF or achieved required length + * + * @param input where to read input from + * @param buffer destination + * @return actual length read; may be less than requested if EOF was reached + * @throws IOException if a read error occurs + */ + public static int read(InputStream input, byte[] buffer) throws IOException { + return read(input, buffer, 0, buffer.length); + } + + /** + * Read as many bytes as possible until EOF or achieved required length + * + * @param input where to read input from + * @param buffer destination + * @param offset initial offset into buffer + * @param length length to read - ignored if non-positive + * @return actual length read; may be less than requested if EOF was reached + * @throws IOException if a read error occurs + */ + public static int read(InputStream input, byte[] buffer, int offset, int length) throws IOException { + for (int remaining = length, curOffset = offset; remaining > 0;) { + int count = input.read(buffer, curOffset, remaining); + if (count == -1) { // EOF before achieved required length + return curOffset - offset; + } + + remaining -= count; + curOffset += count; + } + + return length; + } + + /** + * @param perms The current {@link PosixFilePermission}s - ignored if {@code null}/empty + * @param excluded The permissions <U>not</U> allowed to exist - ignored if {@code null}/empty + * @return The violating {@link PosixFilePermission} - {@code null} + * if no violating permission found + */ + public static PosixFilePermission validateExcludedPermissions(Collection<PosixFilePermission> perms, Collection<PosixFilePermission> excluded) { + if (GenericUtils.isEmpty(perms) || GenericUtils.isEmpty(excluded)) { + return null; + } + + for (PosixFilePermission p : excluded) { + if (perms.contains(p)) { + return p; + } + } + + return null; + } + + /** + * @param path The {@link Path} to check + * @param options The {@link LinkOption}s to use when checking if path is a directory + * @return The same input path if it is a directory + * @throws UnsupportedOperationException if input path not a directory + */ + public static Path ensureDirectory(Path path, LinkOption... options) { + if (!Files.isDirectory(path, options)) { + throw new UnsupportedOperationException("Not a directory: " + path); + } + return path; + } + + /** + * @param options The {@link LinkOption}s - OK if {@code null}/empty + * @return {@code true} if the link options are {@code null}/empty or do + * not contain {@link LinkOption#NOFOLLOW_LINKS}, {@code false} otherwise + * (i.e., the array is not empty and contains the special value) + */ + public static boolean followLinks(LinkOption... options) { + if (GenericUtils.isEmpty(options)) { + return true; + } + + for (LinkOption localLinkOption : options) { + if (localLinkOption == LinkOption.NOFOLLOW_LINKS) { + return false; + } + } + return true; + } + + public static String appendPathComponent(String prefix, String component) { + if (GenericUtils.isEmpty(prefix)) { + return component; + } + + if (GenericUtils.isEmpty(component)) { + return prefix; + } + + StringBuilder sb = new StringBuilder(prefix.length() + component.length() + File.separator.length()).append(prefix); + + if (sb.charAt(prefix.length() - 1) == File.separatorChar) { + if (component.charAt(0) == File.separatorChar) { + sb.append(component.substring(1)); + } else { + sb.append(component); + } + } else { + if (component.charAt(0) != File.separatorChar) { + sb.append(File.separatorChar); + } + sb.append(component); + } + + return sb.toString(); + } + + public static byte[] toByteArray(InputStream inStream) throws IOException { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(DEFAULT_COPY_SIZE)) { + copy(inStream, baos); + return baos.toByteArray(); + } + } + + /** + * Reads all lines until no more available + * + * @param url The {@link URL} to read from + * @return The {@link List} of lines in the same <U>order</U> as it was read + * @throws IOException If failed to read the lines + * @see #readAllLines(InputStream) + */ + public static List<String> readAllLines(URL url) throws IOException { + try (InputStream stream = Objects.requireNonNull(url, "No URL").openStream()) { + return readAllLines(stream); + } + } + + /** + * Reads all lines until no more available + * + * @param stream The {@link InputStream} - <B>Note:</B> assumed to + * contain {@code UTF-8} encoded data + * @return The {@link List} of lines in the same <U>order</U> as it was read + * @throws IOException If failed to read the lines + * @see #readAllLines(Reader) + */ + public static List<String> readAllLines(InputStream stream) throws IOException { + try (Reader reader = new InputStreamReader(Objects.requireNonNull(stream, "No stream instance"), StandardCharsets.UTF_8)) { + return readAllLines(reader); + } + } + + public static List<String> readAllLines(Reader reader) throws IOException { + try (BufferedReader br = new BufferedReader(Objects.requireNonNull(reader, "No reader instance"), DEFAULT_COPY_SIZE)) { + return readAllLines(br); + } + } + + /** + * Reads all lines until no more available + * + * @param reader The {@link BufferedReader} to read all lines + * @return The {@link List} of lines in the same <U>order</U> as it was read + * @throws IOException If failed to read the lines + * @see #readAllLines(BufferedReader, int) + */ + public static List<String> readAllLines(BufferedReader reader) throws IOException { + return readAllLines(reader, -1); + } + + /** + * Reads all lines until no more available + * + * @param reader The {@link BufferedReader} to read all lines + * @param lineCountHint A hint as to the expected number of lines - non-positive + * means unknown - in which case some initial default value will be used to + * initialize the list used to accumulate the lines. + * @return The {@link List} of lines in the same <U>order</U> as it was read + * @throws IOException If failed to read the lines + */ + public static List<String> readAllLines(BufferedReader reader, int lineCountHint) throws IOException { + List<String> result = new ArrayList<>(Math.max(lineCountHint, Short.SIZE)); + for (String line = reader.readLine(); line != null; line = reader.readLine()) { + result.add(line); + } + return result; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/LimitInputStream.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/LimitInputStream.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/LimitInputStream.java new file mode 100644 index 0000000..bbf956a --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/LimitInputStream.java @@ -0,0 +1,113 @@ +/* + * 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.util.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.Channel; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Reads from another {@link InputStream} up to specified max. length + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class LimitInputStream extends FilterInputStream implements Channel { + private final AtomicBoolean open = new AtomicBoolean(true); + private long remaining; + + public LimitInputStream(InputStream in, long length) { + super(in); + remaining = length; + } + + @Override + public boolean isOpen() { + return open.get(); + } + + @Override + public int read() throws IOException { + if (!isOpen()) { + throw new IOException("read() - stream is closed (remaining=" + remaining + ")"); + } + + if (remaining > 0) { + remaining--; + return super.read(); + } else { + return -1; + } + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (!isOpen()) { + throw new IOException("read(len=" + len + ") stream is closed (remaining=" + remaining + ")"); + } + + int nb = len; + if (nb > remaining) { + nb = (int) remaining; + } + if (nb > 0) { + int read = super.read(b, off, nb); + remaining -= read; + return read; + } else { + return -1; + } + } + + @Override + public long skip(long n) throws IOException { + if (!isOpen()) { + throw new IOException("skip(" + n + ") stream is closed (remaining=" + remaining + ")"); + } + + long skipped = super.skip(n); + remaining -= skipped; + return skipped; + } + + @Override + public int available() throws IOException { + if (!isOpen()) { + throw new IOException("available() stream is closed (remaining=" + remaining + ")"); + } + + int av = super.available(); + if (av > remaining) { + return (int) remaining; + } else { + return av; + } + } + + @Override + public void close() throws IOException { + // do not close the original input stream since it serves for ACK(s) + if (open.getAndSet(false)) { + //noinspection UnnecessaryReturnStatement + return; // debug breakpoint + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/LoggingFilterOutputStream.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/LoggingFilterOutputStream.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/LoggingFilterOutputStream.java new file mode 100644 index 0000000..ff4dbb6 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/LoggingFilterOutputStream.java @@ -0,0 +1,67 @@ +/* + * 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.util.io; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.sshd.common.PropertyResolver; +import org.apache.sshd.common.util.buffer.BufferUtils; +import org.apache.sshd.common.util.logging.LoggingUtils; +import org.apache.sshd.common.util.logging.SimplifiedLog; +import org.slf4j.Logger; + +/** + * Dumps everything that is written to the stream to the logger + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class LoggingFilterOutputStream extends FilterOutputStream { + + private final String msg; + private final SimplifiedLog log; + private final int chunkSize; + private final AtomicInteger writeCount = new AtomicInteger(0); + + public LoggingFilterOutputStream(OutputStream out, String msg, Logger log, PropertyResolver resolver) { + this(out, msg, log, resolver.getIntProperty(BufferUtils.HEXDUMP_CHUNK_SIZE, BufferUtils.DEFAULT_HEXDUMP_CHUNK_SIZE)); + } + + public LoggingFilterOutputStream(OutputStream out, String msg, Logger log, int chunkSize) { + super(out); + this.msg = msg; + this.log = LoggingUtils.wrap(log); + this.chunkSize = chunkSize; + } + + @Override + public void write(int b) throws IOException { + byte[] d = new byte[1]; + d[0] = (byte) b; + write(d, 0, 1); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + int count = writeCount.incrementAndGet(); + BufferUtils.dumpHex(log, BufferUtils.DEFAULT_HEXDUMP_LEVEL, msg + "[" + count + "]", BufferUtils.DEFAULT_HEX_SEPARATOR, chunkSize, b, off, len); + out.write(b, off, len); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java new file mode 100644 index 0000000..032260b --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/ModifiableFileWatcher.java @@ -0,0 +1,258 @@ +/* + * 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.util.io; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.PosixFilePermission; +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.OsUtils; +import org.apache.sshd.common.util.logging.AbstractLoggingBean; + +/** + * Watches over changes for a file and re-loads them if file has changed - including + * if file is deleted or (re-)created + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class ModifiableFileWatcher extends AbstractLoggingBean { + + /** + * The {@link Set} of {@link PosixFilePermission} <U>not</U> allowed if strict + * permissions are enforced on key files + */ + public static final Set<PosixFilePermission> STRICTLY_PROHIBITED_FILE_PERMISSION = + Collections.unmodifiableSet( + EnumSet.of(PosixFilePermission.GROUP_WRITE, PosixFilePermission.OTHERS_WRITE)); + + protected final LinkOption[] options; + + private final Path file; + private final AtomicBoolean lastExisted = new AtomicBoolean(false); + private final AtomicLong lastSize = new AtomicLong(Long.MIN_VALUE); + private final AtomicLong lastModified = new AtomicLong(-1L); + + public ModifiableFileWatcher(File file) { + this(Objects.requireNonNull(file, "No file to watch").toPath()); + } + + public ModifiableFileWatcher(Path file) { + this(file, IoUtils.getLinkOptions(true)); + } + + public ModifiableFileWatcher(Path file, LinkOption... options) { + this.file = Objects.requireNonNull(file, "No path to watch"); + // use a clone to avoid being sensitive to changes in the passed array + this.options = (options == null) ? IoUtils.EMPTY_LINK_OPTIONS : options.clone(); + } + + /** + * @return The watched {@link Path} + */ + public final Path getPath() { + return file; + } + + public final boolean exists() throws IOException { + return Files.exists(getPath(), options); + } + + public final long size() throws IOException { + if (exists()) { + return Files.size(getPath()); + } else { + return -1L; + } + } + + public final FileTime lastModified() throws IOException { + if (exists()) { + BasicFileAttributes attrs = Files.readAttributes(getPath(), BasicFileAttributes.class, options); + return attrs.lastModifiedTime(); + } else { + return null; + } + } + + /** + * @return {@code true} if the watched file has probably been changed + * @throws IOException If failed to query file data + */ + public boolean checkReloadRequired() throws IOException { + boolean exists = exists(); + // if existence state changed from last time + if (exists != lastExisted.getAndSet(exists)) { + return true; + } + + if (!exists) { + // file did not exist and still does not exist + resetReloadAttributes(); + return false; + } + + long size = size(); + if (size < 0L) { + // means file no longer exists + resetReloadAttributes(); + return true; + } + + // if size changed then obviously need reload + if (size != lastSize.getAndSet(size)) { + return true; + } + + FileTime modifiedTime = lastModified(); + if (modifiedTime == null) { + // means file no longer exists + resetReloadAttributes(); + return true; + } + + long timestamp = modifiedTime.toMillis(); + return timestamp != lastModified.getAndSet(timestamp); + + } + + /** + * Resets the state attributes used to detect changes to the initial + * construction values - i.e., file assumed not to exist and no known + * size of modify time + */ + public void resetReloadAttributes() { + lastExisted.set(false); + lastSize.set(Long.MIN_VALUE); + lastModified.set(-1L); + } + + /** + * May be called to refresh the state attributes used to detect changes + * e.g., file existence, size and last-modified time once re-loading is + * successfully completed. If the file does not exist then the attributes + * are reset to an "unknown" state. + * + * @throws IOException If failed to access the file (if exists) + * @see #resetReloadAttributes() + */ + public void updateReloadAttributes() throws IOException { + if (exists()) { + long size = size(); + FileTime modifiedTime = lastModified(); + + if ((size >= 0L) && (modifiedTime != null)) { + lastExisted.set(true); + lastSize.set(size); + lastModified.set(modifiedTime.toMillis()); + return; + } + } + + resetReloadAttributes(); + } + + @Override + public String toString() { + return Objects.toString(getPath()); + } + + /** + * <P>Checks if a path has strict permissions</P> + * <UL> + * + * <LI><P> + * (For {@code Unix}) The path may not have group or others write permissions + * </P></LI> + * + * <LI><P> + * The path must be owned by current user. + * </P></LI> + * + * <LI><P> + * (For {@code Unix}) The path may be owned by root. + * </P></LI> + * + * </UL> + * + * @param path The {@link Path} to be checked - ignored if {@code null} + * or does not exist + * @param options The {@link LinkOption}s to use to query the file's permissions + * @return The violated permission as {@link SimpleImmutableEntry} where key + * is a loggable message and value is the offending object + * - e.g., {@link PosixFilePermission} or {@link String} for owner. Return + * value is {@code null} if no violations detected + * @throws IOException If failed to retrieve the permissions + * @see #STRICTLY_PROHIBITED_FILE_PERMISSION + */ + public static SimpleImmutableEntry<String, Object> validateStrictConfigFilePermissions(Path path, LinkOption... options) throws IOException { + if ((path == null) || (!Files.exists(path, options))) { + return null; + } + + Collection<PosixFilePermission> perms = IoUtils.getPermissions(path, options); + if (GenericUtils.isEmpty(perms)) { + return null; + } + + if (OsUtils.isUNIX()) { + PosixFilePermission p = IoUtils.validateExcludedPermissions(perms, STRICTLY_PROHIBITED_FILE_PERMISSION); + if (p != null) { + return new SimpleImmutableEntry<>(String.format("Permissions violation (%s)", p), p); + } + } + + String owner = IoUtils.getFileOwner(path, options); + if (GenericUtils.isEmpty(owner)) { + // we cannot get owner + // general issue: jvm does not support permissions + // security issue: specific filesystem does not support permissions + return null; + } + + String current = OsUtils.getCurrentUser(); + Set<String> expected = new HashSet<>(); + expected.add(current); + if (OsUtils.isUNIX()) { + // Windows "Administrator" was considered however in Windows most likely a group is used. + expected.add(OsUtils.ROOT_USER); + } + + if (!expected.contains(owner)) { + return new SimpleImmutableEntry<>(String.format("Owner violation (%s)", owner), owner); + } + + return null; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseInputStream.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseInputStream.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseInputStream.java new file mode 100644 index 0000000..b9aedee --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseInputStream.java @@ -0,0 +1,47 @@ +/* + * 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.util.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * TODO Add javadoc + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class NoCloseInputStream extends FilterInputStream { + public NoCloseInputStream(InputStream in) { + super(in); + } + + @Override + public void close() throws IOException { + // ignored + } + + public static InputStream resolveInputStream(InputStream input, boolean okToClose) { + if ((input == null) || okToClose) { + return input; + } else { + return new NoCloseInputStream(input); + } + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseOutputStream.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseOutputStream.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseOutputStream.java new file mode 100644 index 0000000..4ba16d3 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseOutputStream.java @@ -0,0 +1,47 @@ +/* + * 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.util.io; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * TODO Add javadoc + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class NoCloseOutputStream extends FilterOutputStream { + public NoCloseOutputStream(OutputStream out) { + super(out); + } + + @Override + public void close() throws IOException { + // ignored + } + + public static OutputStream resolveOutputStream(OutputStream output, boolean okToClose) { + if ((output == null) || okToClose) { + return output; + } else { + return new NoCloseOutputStream(output); + } + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseReader.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseReader.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseReader.java new file mode 100644 index 0000000..9c8b218 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseReader.java @@ -0,0 +1,46 @@ +/* + * 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.util.io; + +import java.io.FilterReader; +import java.io.IOException; +import java.io.Reader; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class NoCloseReader extends FilterReader { + public NoCloseReader(Reader in) { + super(in); + } + + @Override + public void close() throws IOException { + // ignored + } + + public static Reader resolveReader(Reader r, boolean okToClose) { + if ((r == null) || okToClose) { + return r; + } else { + return new NoCloseReader(r); + } + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseWriter.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseWriter.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseWriter.java new file mode 100644 index 0000000..0f92697 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NoCloseWriter.java @@ -0,0 +1,46 @@ +/* + * 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.util.io; + +import java.io.FilterWriter; +import java.io.IOException; +import java.io.Writer; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class NoCloseWriter extends FilterWriter { + public NoCloseWriter(Writer out) { + super(out); + } + + @Override + public void close() throws IOException { + // ignored + } + + public static Writer resolveWriter(Writer r, boolean okToClose) { + if ((r == null) || okToClose) { + return r; + } else { + return new NoCloseWriter(r); + } + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullInputStream.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullInputStream.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullInputStream.java new file mode 100644 index 0000000..eb21383 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullInputStream.java @@ -0,0 +1,90 @@ +/* + * 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.util.io; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.Channel; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A {@code /dev/null} input stream + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class NullInputStream extends InputStream implements Channel { + private final AtomicBoolean open = new AtomicBoolean(true); + + public NullInputStream() { + super(); + } + + @Override + public boolean isOpen() { + return open.get(); + } + + @Override + public int read() throws IOException { + if (!isOpen()) { + throw new EOFException("Stream is closed for reading one value"); + } + return -1; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (!isOpen()) { + throw new EOFException("Stream is closed for reading " + len + " bytes"); + } + return -1; + } + + @Override + public long skip(long n) throws IOException { + if (!isOpen()) { + throw new EOFException("Stream is closed for skipping " + n + " bytes"); + } + return 0L; + } + + @Override + public int available() throws IOException { + if (!isOpen()) { + throw new EOFException("Stream is closed for availability query"); + } + return 0; + } + + @Override + public synchronized void reset() throws IOException { + if (!isOpen()) { + throw new EOFException("Stream is closed for reset"); + } + } + + @Override + public void close() throws IOException { + if (open.getAndSet(false)) { + //noinspection UnnecessaryReturnStatement + return; // debug breakpoint + } + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullOutputStream.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullOutputStream.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullOutputStream.java new file mode 100644 index 0000000..67fa2d0 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/NullOutputStream.java @@ -0,0 +1,72 @@ +/* + * 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.util.io; + +import java.io.EOFException; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.channels.Channel; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A {code /dev/null} output stream + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class NullOutputStream extends OutputStream implements Channel { + private final AtomicBoolean open = new AtomicBoolean(true); + + public NullOutputStream() { + super(); + } + + @Override + public boolean isOpen() { + return open.get(); + } + + @Override + public void write(int b) throws IOException { + if (!isOpen()) { + throw new EOFException("Stream is closed for writing one byte"); + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (!isOpen()) { + throw new EOFException("Stream is closed for writing " + len + " bytes"); + } + } + + @Override + public void flush() throws IOException { + if (!isOpen()) { + throw new EOFException("Stream is closed for flushing"); + } + } + + @Override + public void close() throws IOException { + if (open.getAndSet(false)) { + //noinspection UnnecessaryReturnStatement + return; // debug breakpoint + } + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/OutputStreamWithChannel.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/OutputStreamWithChannel.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/OutputStreamWithChannel.java new file mode 100644 index 0000000..6f81872 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/OutputStreamWithChannel.java @@ -0,0 +1,32 @@ +/* + * 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.util.io; + +import java.io.OutputStream; +import java.nio.channels.Channel; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public abstract class OutputStreamWithChannel extends OutputStream implements Channel { + protected OutputStreamWithChannel() { + super(); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/10de190e/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/ASN1Class.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/ASN1Class.java b/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/ASN1Class.java new file mode 100644 index 0000000..b8351f1 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/io/der/ASN1Class.java @@ -0,0 +1,93 @@ +/* + * 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.util.io.der; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apache.sshd.common.util.GenericUtils; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public enum ASN1Class { + // NOTE: order is crucial, so DON'T change it + UNIVERSAL((byte) 0x00), + APPLICATION((byte) 0x01), + CONTEXT((byte) 0x02), + PRIVATE((byte) 0x03); + + public static final List<ASN1Class> VALUES = + Collections.unmodifiableList(Arrays.asList(values())); + + private final byte byteValue; + + ASN1Class(byte classValue) { + byteValue = classValue; + } + + public byte getClassValue() { + return byteValue; + } + + public static ASN1Class fromName(String s) { + if (GenericUtils.isEmpty(s)) { + return null; + } + + for (ASN1Class c : VALUES) { + if (s.equalsIgnoreCase(c.name())) { + return c; + } + } + + return null; + } + + /** + * <P>The first byte in DER encoding is made of following fields</P> + * <pre> + *------------------------------------------------- + *|Bit 8|Bit 7|Bit 6|Bit 5|Bit 4|Bit 3|Bit 2|Bit 1| + *------------------------------------------------- + *| Class | CF | Type | + *------------------------------------------------- + * </pre> + * @param value The original DER encoded byte + * @return The {@link ASN1Class} value - {@code null} if no match found + * @see #fromTypeValue(int) + */ + public static ASN1Class fromDERValue(int value) { + return fromTypeValue((value >> 6) & 0x03); + } + + /** + * @param value The "pure" value - unshifted and with no extras + * @return The {@link ASN1Class} value - {@code null} if no match found + */ + public static ASN1Class fromTypeValue(int value) { + // all 4 values are defined + if ((value < 0) || (value >= VALUES.size())) { + return null; + } + + return VALUES.get(value); + } +}
