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";
 


Reply via email to