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