You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2018/09/06 16:03:47 UTC
[37/51] [abbrv] mina-sshd git commit: [SSHD-842] Split common
utilities code from sshd-core into sshd-common (new artifact)
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:ajkuiper@wxs.nl">ajkuiper@wxs.nl</a>
+ * @author Magesh Umasankar
+ * @author <a href="mailto:bruce@callenish.com">Bruce Atherton</a>
+ * @author <a href="mailto:levylambert@tiscali-dsl.de">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:dev@mina.apache.org">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:dev@mina.apache.org">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:dev@mina.apache.org">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:dev@mina.apache.org">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:dev@mina.apache.org">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:dev@mina.apache.org">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:dev@mina.apache.org">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:dev@mina.apache.org">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:dev@mina.apache.org">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:dev@mina.apache.org">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:dev@mina.apache.org">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:dev@mina.apache.org">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:dev@mina.apache.org">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:dev@mina.apache.org">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:dev@mina.apache.org">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);
+ }
+}