You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by se...@apache.org on 2012/01/04 23:10:18 UTC

svn commit: r1227346 - in /commons/proper/net/trunk/src: changes/ main/java/org/apache/commons/net/ftp/ main/java/org/apache/commons/net/ftp/parser/ test/java/org/apache/commons/net/ftp/parser/

Author: sebb
Date: Wed Jan  4 22:10:17 2012
New Revision: 1227346

URL: http://svn.apache.org/viewvc?rev=1227346&view=rev
Log:
NET-436 - Support for SYST "Mac OS" listing

Added:
    commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java   (with props)
    commons/proper/net/trunk/src/test/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParserTest.java   (with props)
Modified:
    commons/proper/net/trunk/src/changes/changes.xml
    commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/FTPClientConfig.java
    commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java
    commons/proper/net/trunk/src/test/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactoryTest.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=1227346&r1=1227345&r2=1227346&view=diff
==============================================================================
--- commons/proper/net/trunk/src/changes/changes.xml (original)
+++ commons/proper/net/trunk/src/changes/changes.xml Wed Jan  4 22:10:17 2012
@@ -59,6 +59,9 @@ The <action> type attribute can be add,u
         <release version="3.1-SNAPSHOT" date="TBA" description="
 TBA
         ">
+            <action issue="NET-436" dev="sebb" type="add">
+            Support for SYST "Mac OS" listing - "MACOS Peter's Server"
+            </action>
             <action issue="NET-425" dev="sebb" type="add">
             _openDataConnection_, __storeFile, and __storeFileStream should be protected and take String for FTP command.
             Likewise for receiveFile and receiveFileStream.

Modified: commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/FTPClientConfig.java
URL: http://svn.apache.org/viewvc/commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/FTPClientConfig.java?rev=1227346&r1=1227345&r2=1227346&view=diff
==============================================================================
--- commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/FTPClientConfig.java (original)
+++ commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/FTPClientConfig.java Wed Jan  4 22:10:17 2012
@@ -205,6 +205,15 @@ public class FTPClientConfig
      */
     public static final String SYST_NETWARE = "NETWARE";
 
+    /**
+     * Identifier by which a Mac pre OS-X -based ftp server is known throughout
+     * the commons-net ftp system.
+     *
+     * @since 3.1
+     */
+    // Full string is "MACOS Peter's Server"; the substring below should be enough
+    public static final String SYST_MACOS_PETER  = "MACOS PETER"; // NET-436
+
     private final String serverSystemKey;
     private String defaultDateFormatStr = null;
     private String recentDateFormatStr = null;

Modified: commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java
URL: http://svn.apache.org/viewvc/commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java?rev=1227346&r1=1227345&r2=1227346&view=diff
==============================================================================
--- commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java (original)
+++ commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java Wed Jan  4 22:10:17 2012
@@ -149,6 +149,10 @@ public class DefaultFTPFileEntryParserFa
             {
                 parser = new NetwareFTPEntryParser(config);
             }
+            else if (ukey.indexOf(FTPClientConfig.SYST_MACOS_PETER) >= 0)
+            {
+                parser = new MacOsPeterFTPEntryParser(config);
+            }
             else if (ukey.indexOf(FTPClientConfig.SYST_L8) >= 0)
             {
                 // L8 normally means Unix, but move it to the end for some L8 systems that aren't.

Added: commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java
URL: http://svn.apache.org/viewvc/commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java?rev=1227346&view=auto
==============================================================================
--- commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java (added)
+++ commons/proper/net/trunk/src/main/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java Wed Jan  4 22:10:17 2012
@@ -0,0 +1,278 @@
+/*
+ * 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 org.apache.commons.net.ftp.FTPClientConfig;
+import org.apache.commons.net.ftp.FTPFile;
+
+/**
+ * Implementation FTPFileEntryParser and FTPFileListParser for pre MacOS-X Systems.
+ *
+ * @version $Id$
+ * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions)
+ * @since 3.1
+ */
+public class MacOsPeterFTPEntryParser extends ConfigurableFTPFileEntryParserImpl
+{
+
+    static final String DEFAULT_DATE_FORMAT
+        = "MMM d yyyy"; //Nov 9 2001
+
+    static final String DEFAULT_RECENT_DATE_FORMAT
+        = "MMM d HH:mm"; //Nov 9 20:06
+
+    /**
+     * this is the regular expression used by this parser.
+     *
+     * Permissions:
+     *    r   the file is readable
+     *    w   the file is writable
+     *    x   the file is executable
+     *    -   the indicated permission is not granted
+     *    L   mandatory locking occurs during access (the set-group-ID bit is
+     *        on and the group execution bit is off)
+     *    s   the set-user-ID or set-group-ID bit is on, and the corresponding
+     *        user or group execution bit is also on
+     *    S   undefined bit-state (the set-user-ID bit is on and the user
+     *        execution bit is off)
+     *    t   the 1000 (octal) bit, or sticky bit, is on [see chmod(1)], and
+     *        execution is on
+     *    T   the 1000 bit is turned on, and execution is off (undefined bit-
+     *        state)
+     *    e   z/OS external link bit
+     */
+    private static final String REGEX =
+        "([bcdelfmpSs-])" // type (1)
+        + "(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?\\s+" // permission
+        + "("
+        +   "(folder\\s+)"
+        + "|"
+        +   "((\\d+)\\s+(\\d+)\\s+)" // resource size & data size
+        + ")"
+        + "(\\d+)\\s+"              // size
+        /*
+         * numeric or standard format date:
+         *   yyyy-mm-dd (expecting hh:mm to follow)
+         *   MMM [d]d
+         *   [d]d MMM
+         *   N.B. use non-space for MMM to allow for languages such as German which use
+         *   diacritics (e.g. umlaut) in some abbreviations.
+        */
+        + "((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S{3}\\s+\\d{1,2})|(?:\\d{1,2}\\s+\\S{3}))\\s+"
+        /*
+           year (for non-recent standard format) - yyyy
+           or time (for numeric or recent standard format) [h]h:mm
+        */
+        + "(\\d+(?::\\d+)?)\\s+"
+
+        + "(\\S*)(\\s*.*)"; // the rest
+
+
+    /**
+     * The default constructor for a UnixFTPEntryParser object.
+     *
+     * @exception IllegalArgumentException
+     * Thrown if the regular expression is unparseable.  Should not be seen
+     * under normal conditions.  It it is seen, this is a sign that
+     * <code>REGEX</code> is  not a valid regular expression.
+     */
+    public MacOsPeterFTPEntryParser()
+    {
+        this(null);
+    }
+
+    /**
+     * This constructor allows the creation of a UnixFTPEntryParser object with
+     * something other than the default configuration.
+     *
+     * @param config The {@link FTPClientConfig configuration} object used to
+     * configure this parser.
+     * @exception IllegalArgumentException
+     * Thrown if the regular expression is unparseable.  Should not be seen
+     * under normal conditions.  It it is seen, this is a sign that
+     * <code>REGEX</code> is  not a valid regular expression.
+     * @since 1.4
+     */
+    public MacOsPeterFTPEntryParser(FTPClientConfig config)
+    {
+        super(REGEX);
+        configure(config);
+    }
+
+    /**
+     * Parses a line of a unix (standard) FTP server file listing and converts
+     * it into a usable format in the form of an <code> FTPFile </code>
+     * instance.  If the file listing line doesn't describe a file,
+     * <code> null </code> is returned, otherwise a <code> FTPFile </code>
+     * instance representing the files in the directory is returned.
+     * <p>
+     * @param entry A line of text from the file listing
+     * @return An FTPFile instance corresponding to the supplied entry
+     */
+    public FTPFile parseFTPEntry(String entry) {
+        FTPFile file = new FTPFile();
+        file.setRawListing(entry);
+        int type;
+        boolean isDevice = false;
+
+        if (matches(entry))
+        {
+            String typeStr = group(1);
+            String hardLinkCount = "0";
+            String usr = null;
+            String grp = null;
+            String filesize = group(20);
+            String datestr = group(21) + " " + group(22);
+            String name = group(23);
+            String endtoken = group(24);
+
+            try
+            {
+                file.setTimestamp(super.parseTimestamp(datestr));
+            }
+            catch (ParseException e)
+            {
+                 // intentionally do nothing
+            }
+
+            // A 'whiteout' file is an ARTIFICIAL entry in any of several types of
+            // 'translucent' filesystems, of which a 'union' filesystem is one.
+
+            // bcdelfmpSs-
+            switch (typeStr.charAt(0))
+            {
+            case 'd':
+                type = FTPFile.DIRECTORY_TYPE;
+                break;
+            case 'e': // NET-39 => z/OS external link
+                type = FTPFile.SYMBOLIC_LINK_TYPE;
+                break;
+            case 'l':
+                type = FTPFile.SYMBOLIC_LINK_TYPE;
+                break;
+            case 'b':
+            case 'c':
+                isDevice = true;
+                // break; - fall through
+                //$FALL-THROUGH$ TODO change this if DEVICE_TYPE implemented
+            case 'f':
+            case '-':
+                type = FTPFile.FILE_TYPE;
+                break;
+            default: // e.g. ? and w = whiteout
+                type = FTPFile.UNKNOWN_TYPE;
+            }
+
+            file.setType(type);
+
+            int g = 4;
+            for (int access = 0; access < 3; access++, g += 4)
+            {
+                // Use != '-' to avoid having to check for suid and sticky bits
+                file.setPermission(access, FTPFile.READ_PERMISSION,
+                                   (!group(g).equals("-")));
+                file.setPermission(access, FTPFile.WRITE_PERMISSION,
+                                   (!group(g + 1).equals("-")));
+
+                String execPerm = group(g + 2);
+                if (!execPerm.equals("-") && !Character.isUpperCase(execPerm.charAt(0)))
+                {
+                    file.setPermission(access, FTPFile.EXECUTE_PERMISSION, true);
+                }
+                else
+                {
+                    file.setPermission(access, FTPFile.EXECUTE_PERMISSION, false);
+                }
+            }
+
+            if (!isDevice)
+            {
+                try
+                {
+                    file.setHardLinkCount(Integer.parseInt(hardLinkCount));
+                }
+                catch (NumberFormatException e)
+                {
+                    // intentionally do nothing
+                }
+            }
+
+            file.setUser(usr);
+            file.setGroup(grp);
+
+            try
+            {
+                file.setSize(Long.parseLong(filesize));
+            }
+            catch (NumberFormatException e)
+            {
+                // intentionally do nothing
+            }
+
+            if (null == endtoken)
+            {
+                file.setName(name);
+            }
+            else
+            {
+                // oddball cases like symbolic links, file names
+                // with spaces in them.
+                name += endtoken;
+                if (type == FTPFile.SYMBOLIC_LINK_TYPE)
+                {
+
+                    int end = name.indexOf(" -> ");
+                    // Give up if no link indicator is present
+                    if (end == -1)
+                    {
+                        file.setName(name);
+                    }
+                    else
+                    {
+                        file.setName(name.substring(0, end));
+                        file.setLink(name.substring(end + 4));
+                    }
+
+                }
+                else
+                {
+                    file.setName(name);
+                }
+            }
+            return file;
+        }
+        return null;
+    }
+
+    /**
+     * Defines a default configuration to be used when this class is
+     * instantiated without a {@link  FTPClientConfig  FTPClientConfig}
+     * parameter being specified.
+     * @return the default configuration for this parser.
+     */
+    @Override
+    protected FTPClientConfig getDefaultConfiguration() {
+        return new FTPClientConfig(
+                FTPClientConfig.SYST_UNIX,
+                DEFAULT_DATE_FORMAT,
+                DEFAULT_RECENT_DATE_FORMAT,
+                null, null, null);
+    }
+
+}

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

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

Modified: commons/proper/net/trunk/src/test/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactoryTest.java
URL: http://svn.apache.org/viewvc/commons/proper/net/trunk/src/test/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactoryTest.java?rev=1227346&r1=1227345&r2=1227346&view=diff
==============================================================================
--- commons/proper/net/trunk/src/test/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactoryTest.java (original)
+++ commons/proper/net/trunk/src/test/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactoryTest.java Wed Jan  4 22:10:17 2012
@@ -139,6 +139,7 @@ public class DefaultFTPFileEntryParserFa
         checkParserClass(factory, FTPClientConfig.SYST_OS2, OS2FTPEntryParser.class);
         checkParserClass(factory, FTPClientConfig.SYST_UNIX, UnixFTPEntryParser.class);
         checkParserClass(factory, FTPClientConfig.SYST_VMS, VMSFTPEntryParser.class);
+        checkParserClass(factory, FTPClientConfig.SYST_MACOS_PETER, MacOsPeterFTPEntryParser.class);
 
         checkParserClass(factory, "WINDOWS", NTFTPEntryParser.class); // Same as SYST_NT
         // This is the way it works at present; config matching is exact

Added: commons/proper/net/trunk/src/test/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParserTest.java
URL: http://svn.apache.org/viewvc/commons/proper/net/trunk/src/test/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParserTest.java?rev=1227346&view=auto
==============================================================================
--- commons/proper/net/trunk/src/test/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParserTest.java (added)
+++ commons/proper/net/trunk/src/test/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParserTest.java Wed Jan  4 22:10:17 2012
@@ -0,0 +1,160 @@
+/*
+ * 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.util.Calendar;
+
+import org.apache.commons.net.ftp.FTPFile;
+import org.apache.commons.net.ftp.FTPFileEntryParser;
+
+public class MacOsPeterFTPEntryParserTest extends FTPParseTestFramework {
+
+    private static final String[] badsamples = {
+        "drwxr-xr-x    123       folder        0 Jan  4 14:49 Steak",
+    };
+
+    private static final String[] goodsamples =
+    {
+        "-rw-r--r--    54149       27826    81975 Jul 22  2010 09.jpg",
+        "drwxr-xr-x               folder        0 Jan  4 14:51 Alias_to_Steak",
+        "-rw-r--r--    78440       49231   127671 Jul 22  2010 Filename with whitespace.jpg",
+        "-rw-r--r--    78440       49231   127671 Jul 22 14:51 Filename with whitespace.jpg",
+        "-rw-r--r--        0      108767   108767 Jul 22  2010 presentation03.jpg",
+        "-rw-r--r--    58679       60393   119072 Jul 22  2010 presentation04.jpg",
+        "-rw-r--r--    82543       51433   133976 Jul 22  2010 presentation06.jpg",
+        "-rw-r--r--    83616     1430976  1514592 Jul 22  2010 presentation10.jpg",
+        "-rw-r--r--        0       66990    66990 Jul 22  2010 presentation11.jpg",
+        "drwxr-xr-x               folder        0 Jan  4 14:49 Steak",
+        "-rwx------        0       12713    12713 Jul  8  2009 Twitter_Avatar.png",
+    };
+
+    /**
+     * @see junit.framework.TestCase#TestCase(String)
+     */
+    public MacOsPeterFTPEntryParserTest(String name) {
+        super(name);
+    }
+
+    /**
+     * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#getBadListing()
+     */
+    @Override
+    protected String[] getBadListing() {
+        return (badsamples);
+    }
+
+    /**
+     * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#getGoodListing()
+     */
+    @Override
+    protected String[] getGoodListing() {
+        return (goodsamples);
+    }
+
+    @Override
+    protected FTPFileEntryParser getParser() {
+        return (new MacOsPeterFTPEntryParser());
+    }
+
+    @Override
+    public void testParseFieldsOnDirectory() throws Exception {
+        FTPFile f = getParser().parseFTPEntry(
+                "drwxr-xr-x               folder        0 Mar  2 15:13 Alias_to_Steak");
+        assertNotNull("Could not parse entry.", f);
+        assertTrue("Should have been a directory.", f.isDirectory());
+        checkPermissions(f);
+        assertEquals(0, f.getHardLinkCount());
+        assertNull(f.getUser());
+        assertNull(f.getGroup());
+        assertEquals(0, f.getSize());
+        assertEquals("Alias_to_Steak", f.getName());
+
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.MONTH, Calendar.MARCH);
+
+        cal.set(Calendar.DATE, 1);
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        if (f.getTimestamp().getTime().before(cal.getTime())) {
+            cal.add(Calendar.YEAR, -1);
+        }
+        cal.set(Calendar.DATE, 2);
+        cal.set(Calendar.HOUR_OF_DAY, 15);
+        cal.set(Calendar.MINUTE, 13);
+
+        assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp()
+                .getTime()));
+    }
+
+    @Override
+    public void testParseFieldsOnFile() throws Exception {
+        FTPFile f = getParser().parseFTPEntry(
+            "-rwxr-xr-x    78440       49231   127671 Jul  2 14:51 Filename with whitespace.jpg"
+            );
+        assertNotNull("Could not parse entry.", f);
+        assertTrue("Should have been a file.", f.isFile());
+        checkPermissions(f);
+        assertEquals(0, f.getHardLinkCount());
+        assertNull(f.getUser());
+        assertNull(f.getGroup());
+        assertEquals("Filename with whitespace.jpg", f.getName());
+        assertEquals(127671L, f.getSize());
+
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.MONTH, Calendar.JULY);
+
+        cal.set(Calendar.DATE, 1);
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        if (f.getTimestamp().getTime().before(cal.getTime())) {
+            cal.add(Calendar.YEAR, -1);
+        }
+        cal.set(Calendar.DATE, 2);
+        cal.set(Calendar.HOUR_OF_DAY, 14);
+        cal.set(Calendar.MINUTE, 51);
+        assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp().getTime()));
+    }
+
+    /**
+     * Method checkPermissions.
+     * Verify that the persmissions were properly set.
+     * @param f
+     */
+    private void checkPermissions(FTPFile f) {
+        assertTrue("Should have user read permission.", f.hasPermission(
+                FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION));
+        assertTrue("Should have user write permission.", f.hasPermission(
+                FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION));
+        assertTrue("Should have user execute permission.", f.hasPermission(
+                FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION));
+        assertTrue("Should have group read permission.", f.hasPermission(
+                FTPFile.GROUP_ACCESS, FTPFile.READ_PERMISSION));
+        assertTrue("Should NOT have group write permission.", !f.hasPermission(
+                FTPFile.GROUP_ACCESS, FTPFile.WRITE_PERMISSION));
+        assertTrue("Should have group execute permission.", f.hasPermission(
+                FTPFile.GROUP_ACCESS, FTPFile.EXECUTE_PERMISSION));
+        assertTrue("Should have world read permission.", f.hasPermission(
+                FTPFile.WORLD_ACCESS, FTPFile.READ_PERMISSION));
+        assertTrue("Should NOT have world write permission.", !f.hasPermission(
+                FTPFile.WORLD_ACCESS, FTPFile.WRITE_PERMISSION));
+        assertTrue("Should have world execute permission.", f.hasPermission(
+                FTPFile.WORLD_ACCESS, FTPFile.EXECUTE_PERMISSION));
+    }
+
+}

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

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