Author: sebb
Date: Fri Mar 18 01:45:24 2011
New Revision: 1082785

URL: http://svn.apache.org/viewvc?rev=1082785&view=rev
Log:
NET-378 FTP listing should support MLST and MLSD.

Added:
    
commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/MLSxEntryParser.java
   (with props)
Modified:
    commons/proper/net/trunk/src/changes/changes.xml
    
commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/FTPClient.java

Modified: commons/proper/net/trunk/src/changes/changes.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/net/trunk/src/changes/changes.xml?rev=1082785&r1=1082784&r2=1082785&view=diff
==============================================================================
--- commons/proper/net/trunk/src/changes/changes.xml (original)
+++ commons/proper/net/trunk/src/changes/changes.xml Fri Mar 18 01:45:24 2011
@@ -57,6 +57,9 @@ The <action> type attribute can be add,u
 
     <body>
         <release version="3.0" date="TBA" description="TBA">
+            <action issue="NET-378" dev="sebb" type="add">
+            FTP listing should support MLST and MLSD.
+            </action>
             <action issue="NET-377" dev="sebb" type="update">
             NLST does not take notice of HiddenFiles setting.
             </action>

Modified: 
commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/FTPClient.java
URL: 
http://svn.apache.org/viewvc/commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/FTPClient.java?rev=1082785&r1=1082784&r2=1082785&view=diff
==============================================================================
--- 
commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/FTPClient.java
 (original)
+++ 
commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/FTPClient.java
 Fri Mar 18 01:45:24 2011
@@ -39,6 +39,7 @@ import java.util.Random;
 import org.apache.commons.net.MalformedServerReplyException;
 import org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory;
 import org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory;
+import org.apache.commons.net.ftp.parser.MLSxEntryParser;
 import org.apache.commons.net.ftp.parser.ParserInitializationException;
 import org.apache.commons.net.io.CopyStreamAdapter;
 import org.apache.commons.net.io.CopyStreamEvent;
@@ -754,7 +755,8 @@ implements Configurable
                 }
             }
             // restore the original reply (server greeting)
-            _replyLines = new ArrayList<String> (oldReplyLines);
+            _replyLines.clear();
+            _replyLines.addAll(oldReplyLines);
             _replyCode = oldReplyCode;
         }
     }
@@ -1952,6 +1954,54 @@ implements Configurable
     }
 
 
+    /**
+     * Get file details using the MLST command
+     * 
+     * @param pathname the file or directory to list, may be {@code} null
+     * @return the file details, may be {@code null}
+     * @throws IOException
+     * @since 3.0
+     */
+    public FTPFile mlistFile(String pathname) throws IOException
+    {
+        boolean success = 
FTPReply.isPositiveCompletion(sendCommand(FTPCommand.MLST, pathname));
+        if (success){
+            String entry = getReplyStrings()[1].substring(1); // skip leading 
space for parser
+            return MLSxEntryParser.parseEntry(entry);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Generate a directory listing using the MSLD command.
+     * 
+     * @param pathname the directory name, may be {@code null}
+     * @return the array of file entries
+     * @throws IOException
+     * @since 3.0
+     */
+    public FTPFile[] mlistDir(String pathname) throws IOException
+    {
+        FTPListParseEngine engine = initiateMListParsing( pathname);
+        return engine.getFiles();
+    }
+
+    /**
+     * Generate a directory listing using the MSLD command.
+     * 
+     * @param pathname the directory name, may be {@code null}
+     * @param filter the filter to apply to the responses
+     * @return the array of file entries
+     * @throws IOException
+     * @since 3.0
+     */
+    public FTPFile[] mlistDir(String pathname, FTPFileFilter filter) throws 
IOException
+    {
+        FTPListParseEngine engine = initiateMListParsing( pathname);
+        return engine.getFiles(filter);
+    }
+
     /***
      * Restart a <code>STREAM_TRANSFER_MODE</code> file transfer starting
      * from the given offset.  This will only work on FTP servers supporting
@@ -2374,6 +2424,7 @@ implements Configurable
         return engine.getFiles();
 
     }
+
     /**
      * Using the default system autodetect mechanism, obtain a
      * list of file information for the current working directory.
@@ -2751,6 +2802,32 @@ implements Configurable
         completePendingCommand();
         return engine;
     }
+    
+    /**
+     * Initiate list parsing for MLSD listings.
+     * 
+     * @param pathname
+     * @return the engine
+     * @throws IOException
+     */
+    private FTPListParseEngine initiateMListParsing(String pathname) throws 
IOException
+    {
+        Socket socket;
+        FTPListParseEngine engine = new 
FTPListParseEngine(MLSxEntryParser.getInstance());
+        if ((socket = _openDataConnection_(FTPCommand.MLSD, pathname)) == null)
+        {
+            return engine;
+        }
+
+        try {
+            engine.readServerList(socket.getInputStream(), 
getControlEncoding());
+        }
+        finally {
+            Util.closeQuietly(socket);
+            completePendingCommand();
+        }
+        return engine;
+    }
 
     /**
      * @since 2.0

Added: 
commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/MLSxEntryParser.java
URL: 
http://svn.apache.org/viewvc/commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/MLSxEntryParser.java?rev=1082785&view=auto
==============================================================================
--- 
commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/MLSxEntryParser.java
 (added)
+++ 
commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/MLSxEntryParser.java
 Fri Mar 18 01:45:24 2011
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.net.ftp.parser;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.commons.net.ftp.FTPFile;
+import org.apache.commons.net.ftp.FTPFileEntryParserImpl;
+
+/**
+ * Parser class for MSLT and MLSD replies.
+ * 
+ * @since 3.0
+ */
+public class MLSxEntryParser extends FTPFileEntryParserImpl
+{
+    //    Format taken from RFC 3659:
+    
+    //    entry            = [ facts ] SP pathname
+    //    facts            = 1*( fact ";" )
+    //    fact             = factname "=" value
+    //    factname         = "Size" / "Modify" / "Create" /
+    //                       "Type" / "Unique" / "Perm" /
+    //                       "Lang" / "Media-Type" / "CharSet" /
+    //                       os-depend-fact / local-fact
+    //    os-depend-fact   = <IANA assigned OS name> "." token
+    //    local-fact       = "X." token
+    //    value            = *SCHAR
+
+    // Sample os-depend-fact:
+    //    UNIX.group=0;UNIX.mode=0755;UNIX.owner=0;
+    
+    // A single control response entry (MLST) is returned with a leading space;
+    // multiple (data) entries are returned without any leading spaces.
+
+   private static final MLSxEntryParser PARSER = new MLSxEntryParser();
+
+    private static final HashMap<String, Integer> TYPE_TO_INT = new 
HashMap<String, Integer>();
+    static {
+        TYPE_TO_INT.put("file", Integer.valueOf(FTPFile.FILE_TYPE));
+        TYPE_TO_INT.put("cdir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // 
listed directory
+        TYPE_TO_INT.put("pdir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // a 
parent dir
+        TYPE_TO_INT.put("dir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // 
dir or sub-dir
+    }
+
+    private static int UNIX_GROUPS[] = { // Groups in order of mode digits 
+        FTPFile.USER_ACCESS,
+        FTPFile.GROUP_ACCESS,
+        FTPFile.WORLD_ACCESS,
+    };
+
+    private static int UNIX_PERMS[][] = { // perm bits, broken down
+/* 0 */  {},
+/* 1 */  {FTPFile.EXECUTE_PERMISSION},
+/* 2 */  {FTPFile.WRITE_PERMISSION},
+/* 3 */  {FTPFile.EXECUTE_PERMISSION, FTPFile.WRITE_PERMISSION},
+/* 4 */  {FTPFile.READ_PERMISSION},
+/* 5 */  {FTPFile.READ_PERMISSION, FTPFile.EXECUTE_PERMISSION},
+/* 6 */  {FTPFile.READ_PERMISSION, FTPFile.WRITE_PERMISSION},
+/* 7 */  {FTPFile.READ_PERMISSION, FTPFile.WRITE_PERMISSION, 
FTPFile.EXECUTE_PERMISSION},
+    };
+
+    public MLSxEntryParser()
+    {
+        super();
+    }
+
+    public FTPFile parseFTPEntry(String entry) {
+        String parts[] = entry.split(" ",2); // Path may contain space
+        if (parts.length != 2) {
+            return null;
+        }
+        FTPFile file = new FTPFile();
+        file.setRawListing(entry);
+        file.setName(parts[1]);
+        String[] facts = parts[0].split(";");
+        boolean hasUnixMode = 
parts[0].toLowerCase(Locale.ENGLISH).contains("unix.mode=");
+        for(String fact : facts) {
+            String []factparts = fact.split("=");
+            String factname = factparts[0].toLowerCase(Locale.ENGLISH);
+            String factvalue = factparts[1];
+            if ("size".equals(factname)) {
+                file.setSize(Long.parseLong(factvalue));
+            }
+            else if ("modify".equals(factname)) {
+                // YYYYMMDDHHMMSS[.sss]
+                SimpleDateFormat sdf; // Not thread-safe
+                if (factvalue.contains(".")){
+                    sdf = new SimpleDateFormat("yyyyMMddHHmmss.SSS");          
          
+                } else { 
+                    sdf = new SimpleDateFormat("yyyyMMddHHmmss");
+                }
+                
+                GregorianCalendar gc = new 
GregorianCalendar(TimeZone.getTimeZone("GMT")); // TODO are these thread-safe?
+                try {
+                    gc.setTime(sdf.parse(factvalue));
+                } catch (ParseException e) {
+                    // TODO ??
+                }
+                file.setTimestamp(gc);
+            }
+            else if ("type".equals(factname)) {
+                
file.setType(TYPE_TO_INT.get(factvalue.toLowerCase(Locale.ENGLISH)).intValue());
+            }
+            else if (factname.startsWith("unix.")) {
+                String unixfact = 
factname.substring("unix.".length()).toLowerCase(Locale.ENGLISH);
+                if ("group".equals(unixfact)){
+                    file.setGroup(factvalue);
+                } else if ("owner".equals(unixfact)){
+                    file.setUser(factvalue);
+                } else if ("mode".equals(unixfact)){ // e.g. 0755
+                    for(int i=1; i<=3; i++){
+                        int ch = factvalue.charAt(i)-'0';
+                        for(int p : UNIX_PERMS[ch]) {
+                            file.setPermission(UNIX_GROUPS[i-1], p, true);     
                       
+                        }
+                    }
+                    file.setUser(factvalue);
+                }
+            } 
+            else if (!hasUnixMode && "perm".equals(factname)) { // skip if we 
have the UNIX.mode
+                //              perm-fact    = "Perm" "=" *pvals
+                //              pvals        = "a" / "c" / "d" / "e" / "f" /
+                //                             "l" / "m" / "p" / "r" / "w"
+                for(char c : 
factvalue.toLowerCase(Locale.ENGLISH).toCharArray()) {
+                    // TODO these are mostly just guesses at present
+                    switch (c) {
+                        case 'a':     // (file) may APPEnd
+                            file.setPermission(FTPFile.USER_ACCESS, 
FTPFile.WRITE_PERMISSION, true);
+                            break;
+                        case 'c':     // (dir) files may be created in the dir
+                            file.setPermission(FTPFile.USER_ACCESS, 
FTPFile.WRITE_PERMISSION, true);
+                            break;
+                        case 'd':     // deletable
+                            file.setPermission(FTPFile.USER_ACCESS, 
FTPFile.WRITE_PERMISSION, true);
+                            break;
+                        case 'e':     // (dir) can change to this dir
+                            file.setPermission(FTPFile.USER_ACCESS, 
FTPFile.READ_PERMISSION, true);
+                            break;
+                        case 'f':     // (file) renamable
+                            // ?? file.setPermission(FTPFile.USER_ACCESS, 
FTPFile.WRITE_PERMISSION, true);
+                            break;
+                        case 'l':     // (dir) can be listed
+                            file.setPermission(FTPFile.USER_ACCESS, 
FTPFile.EXECUTE_PERMISSION, true);
+                            break;
+                        case 'm':     // (dir) can create directory here
+                            file.setPermission(FTPFile.USER_ACCESS, 
FTPFile.WRITE_PERMISSION, true);
+                            break;
+                        case 'p':     // (dir) entries may be deleted
+                            file.setPermission(FTPFile.USER_ACCESS, 
FTPFile.WRITE_PERMISSION, true);
+                            break;
+                        case 'r':     // (files) file may be RETRieved
+                            file.setPermission(FTPFile.USER_ACCESS, 
FTPFile.READ_PERMISSION, true);
+                            break;
+                        case 'w':     // (files) file may be STORed
+                            file.setPermission(FTPFile.USER_ACCESS, 
FTPFile.WRITE_PERMISSION, true);
+                            break;
+                        default:
+                            return null; // TODO?
+                    }
+                }
+                
file.setType(TYPE_TO_INT.get(factvalue.toLowerCase(Locale.ENGLISH)).intValue());
+            }
+        }
+        return file;
+    }
+
+    public static FTPFile parseEntry(String entry) {
+        return PARSER.parseFTPEntry(entry);
+    }
+
+    public static  MLSxEntryParser getInstance() {
+        return PARSER;
+    }
+}

Propchange: 
commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/MLSxEntryParser.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/MLSxEntryParser.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision


Reply via email to