Author: gnodet
Date: Thu Apr 29 12:29:35 2010
New Revision: 939298
URL: http://svn.apache.org/viewvc?rev=939298&view=rev
Log:
SSHD-56: SCPCommand can't handle wildcards
Added:
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/DirectoryScanner.java
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/SelectorUtils.java
Modified:
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/ScpTest.java
Added:
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/DirectoryScanner.java
URL:
http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/DirectoryScanner.java?rev=939298&view=auto
==============================================================================
---
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/DirectoryScanner.java
(added)
+++
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/DirectoryScanner.java
Thu Apr 29 12:29:35 2010
@@ -0,0 +1,415 @@
+/*
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2000-2003 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ * any, must include the following acknowlegement:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.codehaus.org/)."
+ * Alternately, this acknowlegement may appear in the software itself,
+ * if and wherever such third-party acknowlegements normally appear.
+ *
+ * 4. The names "Ant" and "Apache Software
+ * Foundation" must not be used to endorse or promote products derived
+ * from this software without prior written permission. For written
+ * permission, please contact [email protected].
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ * nor may "Apache" appear in their names without prior written
+ * permission of the Apache Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.codehaus.org/>.
+ */
+
+package org.apache.sshd.common.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class for scanning a directory for files/directories which match certain
+ * criteria.
+ * <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/>
+ * 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/>
+ * 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/>
+ * 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/>
+ * 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/>
+ * 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/>
+ * 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/>
+ * Examples:
+ * <p/>
+ * "**\*.class" matches all .class files/dirs in a directory tree.
+ * <p/>
+ * "test\a??.java" matches all files/dirs which start with an 'a', then two
+ * more characters and then ".java", in a directory called test.
+ * <p/>
+ * "**" matches everything in a directory tree.
+ * <p/>
+ * "**\test\**\XYZ*" matches all files/dirs which start with "XYZ" and where
+ * there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123").
+ * <p/>
+ * Case sensitivity may be turned off if necessary. By default, it is
+ * turned on.
+ * <p/>
+ * Example of usage:
+ * <pre>
+ * String[] includes = {"**\\*.class"};
+ * String[] excludes = {"modules\\*\\**"};
+ * ds.setIncludes(includes);
+ * ds.setExcludes(excludes);
+ * ds.setBasedir(new File("test"));
+ * ds.setCaseSensitive(true);
+ * ds.scan();
+ * <p/>
+ * System.out.println("FILES:");
+ * String[] files = ds.getIncludedFiles();
+ * for (int i = 0; i < files.length; i++) {
+ * System.out.println(files[i]);
+ * }
+ * </pre>
+ * This will scan a directory called test for .class files, but excludes all
+ * files in all proper subdirectories of a directory called "modules"
+ *
+ * @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() {
+ }
+
+ 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</code>.
+ */
+ 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</code>.
+ */
+ 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;
+ }
+
+ /**
+ * 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/>
+ * When a pattern ends with a '/' or '\', "**" is appended.
+ *
+ * @param includes A list of include patterns.
+ * May be <code>null</code>, indicating that all files
+ * should be included. If a non-<code>null</code>
+ * list is given, all elements must be
+ * non-<code>null</code>.
+ */
+ 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.
+ *
+ * @throws IllegalStateException if the base directory was set
+ * incorrectly (i.e. if it is
<code>null</code>, 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</code>.
+ * @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</code>.
+ * @throws IOException
+ */
+ protected void scandir(File dir, String vpath) {
+ String[] newfiles = dir.list();
+ if (newfiles == null) {
+ newfiles = new String[0];
+ }
+
+ for (int i = 0; i < newfiles.length; i++) {
+ String name = vpath + newfiles[i];
+ File file = new File(dir, newfiles[i]);
+ 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()];
+ filesIncluded.toArray(files);
+ return 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</code>.
+ * @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 (int i = 0; i < includes.length; i++) {
+ if (SelectorUtils.matchPath(includes[i], 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</code>.
+ * @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 (int i = 0; i < includes.length; i++) {
+ if (SelectorUtils.matchPatternStart(includes[i], 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</code>.
+ * @return The normalized pattern, never <code>null</code>.
+ */
+ 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/>
+ * <p>A <code>null</code> 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
+ */
+ public static String replace(String text, String repl, String with, int
max) {
+ if ((text == null) || (repl == null) || (with == null) ||
(repl.length() == 0)) {
+ return text;
+ }
+
+ StringBuffer buf = new StringBuffer(text.length());
+ int start = 0, end = 0;
+ while ((end = text.indexOf(repl, start)) != -1) {
+ buf.append(text.substring(start, end)).append(with);
+ start = end + repl.length();
+
+ if (--max == 0) {
+ break;
+ }
+ }
+ buf.append(text.substring(start));
+ return buf.toString();
+ }
+
+}
\ No newline at end of file
Added:
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/SelectorUtils.java
URL:
http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/SelectorUtils.java?rev=939298&view=auto
==============================================================================
---
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/SelectorUtils.java
(added)
+++
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/SelectorUtils.java
Thu Apr 29 12:29:35 2010
@@ -0,0 +1,612 @@
+/*
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2002-2003 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ * any, must include the following acknowlegement:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.codehaus.org/)."
+ * Alternately, this acknowlegement may appear in the software itself,
+ * if and wherever such third-party acknowlegements normally appear.
+ *
+ * 4. The names "Ant" and "Apache Software
+ * Foundation" must not be used to endorse or promote products derived
+ * from this software without prior written permission. For written
+ * permission, please contact [email protected].
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ * nor may "Apache" appear in their names without prior written
+ * permission of the Apache Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.codehaus.org/>.
+ */
+
+package org.apache.sshd.common.util;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * <p>This is a utility class used by selectors and DirectoryScanner. The
+ * functionality more properly belongs just to selectors, but unfortunately
+ * DirectoryScanner exposed these as protected methods. Thus we have to
+ * support any subclasses of DirectoryScanner that may access these methods.
+ * </p>
+ * <p>This is a Singleton.</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>
+ * @version $Id$
+ * @since 1.5
+ */
+public final class SelectorUtils {
+
+ public static final String PATTERN_HANDLER_PREFIX = "[";
+
+ public static final String PATTERN_HANDLER_SUFFIX = "]";
+
+ public static final String REGEX_HANDLER_PREFIX = "%regex" +
PATTERN_HANDLER_PREFIX;
+
+ public static final String ANT_HANDLER_PREFIX = "%ant" +
PATTERN_HANDLER_PREFIX;
+
+ /**
+ * Private Constructor
+ */
+ private SelectorUtils() {
+ }
+
+ /**
+ * Tests whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ * <p/>
+ * This is not a general purpose test and should only be used if you
+ * can live with false positives. For example, <code>pattern=**\a</code>
+ * and <code>str=b</code> will yield <code>true</code>.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * <code>null</code>.
+ * @param str The path to match, as a String. Must not be
+ * <code>null</code>.
+ * @return whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ */
+ public static boolean matchPatternStart(String pattern, String str) {
+ return matchPatternStart(pattern, str, true);
+ }
+
+ /**
+ * Tests whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ * <p/>
+ * This is not a general purpose test and should only be used if you
+ * can live with false positives. For example, <code>pattern=**\a</code>
+ * and <code>str=b</code> will yield <code>true</code>.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * <code>null</code>.
+ * @param str The path to match, as a String. Must not be
+ * <code>null</code>.
+ * @param isCaseSensitive Whether or not matching should be performed
+ * case sensitively.
+ * @return whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ */
+ public static boolean matchPatternStart(String pattern, String str,
+ boolean isCaseSensitive) {
+ if (pattern.length() > (REGEX_HANDLER_PREFIX.length() +
PATTERN_HANDLER_SUFFIX.length() + 1)
+ && pattern.startsWith(REGEX_HANDLER_PREFIX) &&
pattern.endsWith(PATTERN_HANDLER_SUFFIX)) {
+ // FIXME: ICK! But we can't do partial matches for regex, so we
have to reserve judgement until we have
+ // a file to deal with, or we can definitely say this is an
exclusion...
+ return true;
+ } else {
+ if (pattern.length() > (ANT_HANDLER_PREFIX.length() +
PATTERN_HANDLER_SUFFIX.length() + 1)
+ && pattern.startsWith(ANT_HANDLER_PREFIX) &&
pattern.endsWith(PATTERN_HANDLER_SUFFIX)) {
+ pattern =
+ pattern.substring(ANT_HANDLER_PREFIX.length(),
pattern.length() - PATTERN_HANDLER_SUFFIX.length());
+ }
+
+ String altStr = str.replace('\\', '/');
+
+ return matchAntPathPatternStart(pattern, str, File.separator,
isCaseSensitive)
+ || matchAntPathPatternStart(pattern, altStr, "/",
isCaseSensitive);
+ }
+ }
+
+ private static boolean matchAntPathPatternStart(String pattern, String
str, String separator, boolean isCaseSensitive) {
+ // When str starts with a File.separator, pattern has to start with a
+ // File.separator.
+ // When pattern starts with a File.separator, str has to start with a
+ // File.separator.
+ if (str.startsWith(separator) !=
+ pattern.startsWith(separator)) {
+ return false;
+ }
+
+ List<String> patDirs = tokenizePath(pattern, separator);
+ List<String> strDirs = tokenizePath(str, separator);
+
+ int patIdxStart = 0;
+ int patIdxEnd = patDirs.size() - 1;
+ int strIdxStart = 0;
+ int strIdxEnd = strDirs.size() - 1;
+
+ // up to first '**'
+ while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
+ String patDir = patDirs.get(patIdxStart);
+ if (patDir.equals("**")) {
+ break;
+ }
+ if (!match(patDir, strDirs.get(strIdxStart),
+ isCaseSensitive)) {
+ return false;
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+
+ if (strIdxStart > strIdxEnd) {
+ // String is exhausted
+ return true;
+ } else if (patIdxStart > patIdxEnd) {
+ // String not exhausted, but pattern is. Failure.
+ return false;
+ } else {
+ // pattern now holds ** while string is not exhausted
+ // this will generate false positives but we can live with that.
+ return true;
+ }
+ }
+
+ /**
+ * Tests whether or not a given path matches a given pattern.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * <code>null</code>.
+ * @param str The path to match, as a String. Must not be
+ * <code>null</code>.
+ * @return <code>true</code> if the pattern matches against the string,
+ * or <code>false</code> otherwise.
+ */
+ public static boolean matchPath(String pattern, String str) {
+ return matchPath(pattern, str, true);
+ }
+
+ /**
+ * Tests whether or not a given path matches a given pattern.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * <code>null</code>.
+ * @param str The path to match, as a String. Must not be
+ * <code>null</code>.
+ * @param isCaseSensitive Whether or not matching should be performed
+ * case sensitively.
+ * @return <code>true</code> if the pattern matches against the string,
+ * or <code>false</code> otherwise.
+ */
+ public static boolean matchPath(String pattern, String str,
+ boolean isCaseSensitive) {
+ if (pattern.length() > (REGEX_HANDLER_PREFIX.length() +
PATTERN_HANDLER_SUFFIX.length() + 1)
+ && pattern.startsWith(REGEX_HANDLER_PREFIX) &&
pattern.endsWith(PATTERN_HANDLER_SUFFIX)) {
+ pattern = pattern.substring(REGEX_HANDLER_PREFIX.length(),
pattern.length()
+ - PATTERN_HANDLER_SUFFIX.length());
+
+ return str.matches(pattern);
+ } else {
+ if (pattern.length() > (ANT_HANDLER_PREFIX.length() +
PATTERN_HANDLER_SUFFIX.length() + 1)
+ && pattern.startsWith(ANT_HANDLER_PREFIX) &&
pattern.endsWith(PATTERN_HANDLER_SUFFIX)) {
+ pattern =
+ pattern.substring(ANT_HANDLER_PREFIX.length(),
pattern.length() - PATTERN_HANDLER_SUFFIX.length());
+ }
+
+ return matchAntPathPattern(pattern, str, isCaseSensitive);
+ }
+ }
+
+ private static boolean matchAntPathPattern(String pattern, String str,
boolean isCaseSensitive) {
+ // When str starts with a File.separator, pattern has to start with a
+ // File.separator.
+ // When pattern starts with a File.separator, str has to start with a
+ // File.separator.
+ if (str.startsWith(File.separator) !=
+ pattern.startsWith(File.separator)) {
+ return false;
+ }
+
+ List<String> patDirs = tokenizePath(pattern, File.separator);
+ List<String> strDirs = tokenizePath(str, File.separator);
+
+ int patIdxStart = 0;
+ int patIdxEnd = patDirs.size() - 1;
+ int strIdxStart = 0;
+ int strIdxEnd = strDirs.size() - 1;
+
+ // up to first '**'
+ while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
+ String patDir = (String) patDirs.get(patIdxStart);
+ if (patDir.equals("**")) {
+ break;
+ }
+ if (!match(patDir, (String) strDirs.get(strIdxStart),
+ isCaseSensitive)) {
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+ if (strIdxStart > strIdxEnd) {
+ // String is exhausted
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (!patDirs.get(i).equals("**")) {
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+ }
+ return true;
+ } else {
+ if (patIdxStart > patIdxEnd) {
+ // String not exhausted, but pattern is. Failure.
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+ }
+
+ // up to last '**'
+ while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
+ String patDir = (String) patDirs.get(patIdxEnd);
+ if (patDir.equals("**")) {
+ break;
+ }
+ if (!match(patDir, (String) strDirs.get(strIdxEnd),
+ isCaseSensitive)) {
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+ patIdxEnd--;
+ strIdxEnd--;
+ }
+ if (strIdxStart > strIdxEnd) {
+ // String is exhausted
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (!patDirs.get(i).equals("**")) {
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+ }
+ return true;
+ }
+
+ while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
+ int patIdxTmp = -1;
+ for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
+ if (patDirs.get(i).equals("**")) {
+ patIdxTmp = i;
+ break;
+ }
+ }
+ if (patIdxTmp == patIdxStart + 1) {
+ // '**/**' situation, so skip one
+ patIdxStart++;
+ continue;
+ }
+ // Find the pattern between padIdxStart & padIdxTmp in str between
+ // strIdxStart & strIdxEnd
+ int patLength = (patIdxTmp - patIdxStart - 1);
+ int strLength = (strIdxEnd - strIdxStart + 1);
+ int foundIdx = -1;
+ strLoop:
+ for (int i = 0; i <= strLength - patLength; i++) {
+ for (int j = 0; j < patLength; j++) {
+ String subPat = (String) patDirs.get(patIdxStart + j + 1);
+ String subStr = (String) strDirs.get(strIdxStart + i + j);
+ if (!match(subPat, subStr, isCaseSensitive)) {
+ continue strLoop;
+ }
+ }
+
+ foundIdx = strIdxStart + i;
+ break;
+ }
+
+ if (foundIdx == -1) {
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+
+ patIdxStart = patIdxTmp;
+ strIdxStart = foundIdx + patLength;
+ }
+
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (!patDirs.get(i).equals("**")) {
+ patDirs = null;
+ strDirs = null;
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Tests whether or not a string matches against a pattern.
+ * The pattern may contain two special characters:<br>
+ * '*' means zero or more characters<br>
+ * '?' means one and only one character
+ *
+ * @param pattern The pattern to match against.
+ * Must not be <code>null</code>.
+ * @param str The string which must be matched against the pattern.
+ * Must not be <code>null</code>.
+ * @return <code>true</code> if the string matches against the pattern,
+ * or <code>false</code> otherwise.
+ */
+ public static boolean match(String pattern, String str) {
+ return match(pattern, str, true);
+ }
+
+ /**
+ * Tests whether or not a string matches against a pattern.
+ * The pattern may contain two special characters:<br>
+ * '*' means zero or more characters<br>
+ * '?' means one and only one character
+ *
+ * @param pattern The pattern to match against.
+ * Must not be <code>null</code>.
+ * @param str The string which must be matched against the
pattern.
+ * Must not be <code>null</code>.
+ * @param isCaseSensitive Whether or not matching should be performed
+ * case sensitively.
+ * @return <code>true</code> if the string matches against the pattern,
+ * or <code>false</code> otherwise.
+ */
+ public static boolean match(String pattern, String str,
+ boolean isCaseSensitive) {
+ char[] patArr = pattern.toCharArray();
+ char[] strArr = str.toCharArray();
+ int patIdxStart = 0;
+ int patIdxEnd = patArr.length - 1;
+ int strIdxStart = 0;
+ int strIdxEnd = strArr.length - 1;
+ char ch;
+
+ boolean containsStar = false;
+ for (int i = 0; i < patArr.length; i++) {
+ if (patArr[i] == '*') {
+ containsStar = true;
+ break;
+ }
+ }
+
+ if (!containsStar) {
+ // No '*'s, so we make a shortcut
+ if (patIdxEnd != strIdxEnd) {
+ return false; // Pattern and string do not have the same size
+ }
+ for (int i = 0; i <= patIdxEnd; i++) {
+ ch = patArr[i];
+ if (ch != '?' && !equals(ch, strArr[i], isCaseSensitive)) {
+ return false; // Character mismatch
+ }
+ }
+ return true; // String matches against pattern
+ }
+
+ if (patIdxEnd == 0) {
+ return true; // Pattern contains only '*', which matches anything
+ }
+
+ // Process characters before first star
+ while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
+ if (ch != '?' && !equals(ch, strArr[strIdxStart],
isCaseSensitive)) {
+ return false; // Character mismatch
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+ if (strIdxStart > strIdxEnd) {
+ // All characters in the string are used. Check if only '*'s are
+ // left in the pattern. If so, we succeeded. Otherwise failure.
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Process characters after last star
+ while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
+ if (ch != '?' && !equals(ch, strArr[strIdxEnd], isCaseSensitive)) {
+ return false; // Character mismatch
+ }
+ patIdxEnd--;
+ strIdxEnd--;
+ }
+ if (strIdxStart > strIdxEnd) {
+ // All characters in the string are used. Check if only '*'s are
+ // left in the pattern. If so, we succeeded. Otherwise failure.
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // process pattern between stars. padIdxStart and patIdxEnd point
+ // always to a '*'.
+ while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
+ int patIdxTmp = -1;
+ for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
+ if (patArr[i] == '*') {
+ patIdxTmp = i;
+ break;
+ }
+ }
+ if (patIdxTmp == patIdxStart + 1) {
+ // Two stars next to each other, skip the first one.
+ patIdxStart++;
+ continue;
+ }
+ // Find the pattern between padIdxStart & padIdxTmp in str between
+ // strIdxStart & strIdxEnd
+ int patLength = (patIdxTmp - patIdxStart - 1);
+ int strLength = (strIdxEnd - strIdxStart + 1);
+ int foundIdx = -1;
+ strLoop:
+ for (int i = 0; i <= strLength - patLength; i++) {
+ for (int j = 0; j < patLength; j++) {
+ ch = patArr[patIdxStart + j + 1];
+ if (ch != '?' && !equals(ch, strArr[strIdxStart + i + j],
isCaseSensitive)) {
+ continue strLoop;
+ }
+ }
+
+ foundIdx = strIdxStart + i;
+ break;
+ }
+
+ if (foundIdx == -1) {
+ return false;
+ }
+
+ patIdxStart = patIdxTmp;
+ strIdxStart = foundIdx + patLength;
+ }
+
+ // All characters in the string are used. Check if only '*'s are left
+ // in the pattern. If so, we succeeded. Otherwise failure.
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Tests whether two characters are equal.
+ */
+ private static boolean equals(char c1, char c2, boolean isCaseSensitive) {
+ if (c1 == c2) {
+ return true;
+ }
+ if (!isCaseSensitive) {
+ // NOTE: Try both upper case and lower case as done by
String.equalsIgnoreCase()
+ if (Character.toUpperCase(c1) == Character.toUpperCase(c2) ||
+ Character.toLowerCase(c1) == Character.toLowerCase(c2)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Breaks a path up into a Vector of path elements, tokenizing on
+ * <code>File.separator</code>.
+ *
+ * @param path Path to tokenize. Must not be <code>null</code>.
+ * @return a List of path elements from the tokenized path
+ */
+ public static List<String> tokenizePath(String path) {
+ return tokenizePath(path, File.separator);
+ }
+
+ public static List<String> tokenizePath(String path, String separator) {
+ List<String> ret = new ArrayList<String>();
+ StringTokenizer st = new StringTokenizer(path, separator);
+ while (st.hasMoreTokens()) {
+ ret.add(st.nextToken());
+ }
+ return ret;
+ }
+
+
+ /**
+ * Returns dependency information on these two files. If src has been
+ * modified later than target, it returns true. If target doesn't exist,
+ * it likewise returns true. Otherwise, target is newer than src and
+ * is not out of date, thus the method returns false. It also returns
+ * false if the src file doesn't even exist, since how could the
+ * target then be out of date.
+ *
+ * @param src the original file
+ * @param target the file being compared against
+ * @param granularity the amount in seconds of slack we will give in
+ * determining out of dateness
+ * @return whether the target is out of date
+ */
+ public static boolean isOutOfDate(File src, File target, int granularity) {
+ if (!src.exists()) {
+ return false;
+ }
+ if (!target.exists()) {
+ return true;
+ }
+ if ((src.lastModified() - granularity) > target.lastModified()) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * "Flattens" a string by removing all whitespace (space, tab, linefeed,
+ * carriage return, and formfeed). This uses StringTokenizer and the
+ * default set of tokens as documented in the single arguement constructor.
+ *
+ * @param input a String to remove all whitespace.
+ * @return a String that has had all whitespace removed.
+ */
+ public static String removeWhitespace(String input) {
+ StringBuffer result = new StringBuffer();
+ if (input != null) {
+ StringTokenizer st = new StringTokenizer(input);
+ while (st.hasMoreTokens()) {
+ result.append(st.nextToken());
+ }
+ }
+ return result.toString();
+ }
+}
\ No newline at end of file
Modified:
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
URL:
http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java?rev=939298&r1=939297&r2=939298&view=diff
==============================================================================
---
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
(original)
+++
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java
Thu Apr 29 12:29:35 2010
@@ -19,6 +19,7 @@
package org.apache.sshd.server.command;
import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -27,6 +28,8 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
+import org.apache.sshd.common.util.DirectoryScanner;
+import org.apache.sshd.common.util.SelectorUtils;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.CommandFactory;
import org.apache.sshd.server.Environment;
@@ -45,6 +48,7 @@ public class ScpCommand implements Comma
protected static final Logger log =
LoggerFactory.getLogger(ScpCommand.class);
protected static final int OK = 0;
+ protected static final int WARNING = 1;
protected static final int ERROR = 2;
protected String name;
@@ -52,8 +56,9 @@ public class ScpCommand implements Comma
protected boolean optT;
protected boolean optF;
protected boolean optV;
+ protected boolean optD;
protected boolean optP;
- protected File root;
+ protected String root;
protected InputStream in;
protected OutputStream out;
protected OutputStream err;
@@ -65,7 +70,7 @@ public class ScpCommand implements Comma
if (log.isDebugEnabled()) {
log.debug("Executing command {}", name);
}
- root = new File(".");
+ root = ".";
for (int i = 1; i < args.length; i++) {
if (args[i].charAt(0) == '-') {
for (int j = 1; j < args[i].length(); j++) {
@@ -85,13 +90,16 @@ public class ScpCommand implements Comma
case 'v':
optV = true;
break;
+ case 'd':
+ optD = true;
+ break;
// default:
// error = new IOException("Unsupported option: " +
args[i].charAt(j));
// return;
}
}
} else if (i == args.length - 1) {
- root = new File(args[args.length - 1]);
+ root = args[args.length - 1];
}
}
if (!optF && !optT) {
@@ -130,26 +138,87 @@ public class ScpCommand implements Comma
String exitMessage = null;
try {
- if (optT && !optR) {
- ack();
- writeFile(readLine(), root);
- } else if (optT && optR) {
+ if (optT)
+ {
ack();
- writeDir(readLine(), root);
- } else if (optF) {
- if (!root.exists()) {
- throw new IOException(root + ": no such file or
directory");
+ for (; ;)
+ {
+ String line;
+ boolean isDir = false;
+ int c = readAck(true);
+ switch (c)
+ {
+ case -1:
+ return;
+ case 'D':
+ isDir = true;
+ case 'C':
+ case 'E':
+ line = ((char) c) + readLine();
+ break;
+ default:
+ //a real ack that has been acted upon already
+ continue;
+ }
+
+ if (optR && isDir)
+ {
+ writeDir(line, new File(root));
+ }
+ else
+ {
+ writeFile(line, new File(root));
+ }
}
- if (root.isFile()) {
- readFile(root);
- } else if (root.isDirectory()) {
- if (!optR) {
- throw new IOException(root + " not a regular file");
- } else {
- readDir(root);
+ } else if (optF) {
+ String pattern = root;
+ int idx = pattern.indexOf('*');
+ if (idx >= 0) {
+ String basedir = "";
+ int lastSep = pattern.substring(0, idx).lastIndexOf('/');
+ if (lastSep >= 0) {
+ basedir = pattern.substring(0, lastSep);
+ pattern = pattern.substring(lastSep + 1);
+ }
+ String[] included = new DirectoryScanner(basedir,
pattern).scan();
+ for (String path : included) {
+ File file = new File(basedir, path);
+ if (file.isFile()) {
+ readFile(file);
+ } else if (file.isDirectory()) {
+ if (!optR) {
+ out.write(WARNING);
+ out.write((path + " not a regular
file\n").getBytes());
+ } else {
+ readDir(file);
+ }
+ } else {
+ out.write(WARNING);
+ out.write((path + " unknown file
type\n").getBytes());
+ }
}
} else {
- throw new IOException(root + ": unknown file type");
+ String basedir = "";
+ int lastSep = pattern.lastIndexOf('/');
+ if (lastSep >= 0) {
+ basedir = pattern.substring(0, lastSep);
+ pattern = pattern.substring(lastSep + 1);
+ }
+ File file = new File(basedir, pattern);
+ if (!file.exists()) {
+ throw new IOException(file + ": no such file or
directory");
+ }
+ if (file.isFile()) {
+ readFile(file);
+ } else if (file.isDirectory()) {
+ if (!optR) {
+ throw new IOException(file + " not a regular
file");
+ } else {
+ readDir(file);
+ }
+ } else {
+ throw new IOException(file + ": unknown file type");
+ }
}
} else {
throw new IOException("Unsupported mode");
@@ -264,7 +333,7 @@ public class ScpCommand implements Comma
}
ack();
- readAck();
+ readAck(false);
}
protected String readLine() throws IOException {
@@ -295,7 +364,7 @@ public class ScpCommand implements Comma
buf.append("\n");
out.write(buf.toString().getBytes());
out.flush();
- readAck();
+ readAck(false);
InputStream is = new FileInputStream(path);
try {
@@ -311,7 +380,7 @@ public class ScpCommand implements Comma
is.close();
}
ack();
- readAck();
+ readAck(false);
}
protected void readDir(File path) throws IOException {
@@ -328,7 +397,7 @@ public class ScpCommand implements Comma
buf.append("\n");
out.write(buf.toString().getBytes());
out.flush();
- readAck();
+ readAck(false);
for (File child : path.listFiles()) {
if (child.isFile()) {
@@ -340,7 +409,7 @@ public class ScpCommand implements Comma
out.write("E\n".getBytes());
out.flush();
- readAck();
+ readAck(false);
}
protected void ack() throws IOException {
@@ -348,17 +417,25 @@ public class ScpCommand implements Comma
out.flush();
}
- protected void readAck() throws IOException {
+ protected int readAck(boolean canEof) throws IOException {
int c = in.read();
switch (c) {
- case 0:
+ case -1:
+ if (!canEof) {
+ throw new EOFException();
+ }
break;
- case 1:
- System.out.println("Received warning: " + readLine());
+ case OK:
break;
- case 2:
+ case WARNING:
+ log.warn("Received warning: " + readLine());
+ break;
+ case ERROR:
throw new IOException("Received nack: " + readLine());
+ default:
+ break;
}
+ return c;
}
}
Modified: mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/ScpTest.java
URL:
http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/ScpTest.java?rev=939298&r1=939297&r2=939298&view=diff
==============================================================================
--- mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/ScpTest.java
(original)
+++ mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/ScpTest.java Thu
Apr 29 12:29:35 2010
@@ -27,6 +27,7 @@ import java.io.ByteArrayOutputStream;
import org.junit.Before;
import org.junit.After;
+import org.junit.Ignore;
import org.junit.Test;
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
import org.apache.sshd.util.EchoShellFactory;
@@ -105,6 +106,13 @@ public class ScpTest {
}
@Test
+ @Ignore
+ public void testExternal() throws Exception {
+ System.out.println("Scp available on port " + port);
+ Thread.sleep(5 * 60000);
+ }
+
+ @Test
public void testScp() throws Exception {
String data = "0123456789\n";