    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.net.ftp.parser;
    019
    020import java.io.File;
    021import java.text.ParseException;
    022import java.util.Locale;
    023
    024import org.apache.commons.net.ftp.FTPClientConfig;
    025import org.apache.commons.net.ftp.FTPFile;
    026
    027/**
    028 * <pre>
    029 * Example *FILE/*MEM FTP entries, when the current
    030 * working directory is a file of file system QSYS:
    031 * ------------------------------------------------
    032 *
    033 * $ cwd /qsys.lib/rpgunit.lib/rpgunitc1.file
    034 *   250-NAMEFMT set to 1.
    035 *   250 "/QSYS.LIB/RPGUNIT.LIB/RPGUNITC1.FILE" is current directory.
    036 * $ dir
    037 *   227 Entering Passive Mode (10,200,36,33,40,249).
    038 *   125 List started.
    039 *   QPGMR          135168 22.06.13 13:18:19 *FILE
    040 *   QPGMR                                   *MEM       MKCMD.MBR
    041 *   QPGMR                                   *MEM       RUCALLTST.MBR
    042 *   QPGMR                                   *MEM       RUCMDHLP.MBR
    043 *   QPGMR                                   *MEM       RUCRTTST.MBR
    044 *   250 List completed.
    045 *
    046 *
    047 * Example *FILE entry of an OS/400 save file:
    048 * ---------------------------------------------------
    049 *
    050 * $ cwd /qsys.lib/rpgunit.lib
    051 *   250 "/QSYS.LIB/RPGUNIT.LIB" is current library.
    052 * $ dir rpgunit.file
    053 *   227 Entering Passive Mode (10,200,36,33,188,106).
    054 *   125 List started.
    055 *   QPGMR        16347136 29.06.13 15:45:09 *FILE      RPGUNIT.SAVF
    056 *   250 List completed.
    057 *
    058 *
    059 * Example *STMF/*DIR FTP entries, when the
    060 * current working directory is in file system "root":
    061 * ---------------------------------------------------
    062 *
    063 * $ cwd /home/raddatz
    064 *   250 "/home/raddatz" is current directory.
    065 * $ dir test*
    066 *   227 Entering Passive Mode (10,200,36,33,200,189).
    067 *   125 List started.
    068 *   RADDATZ           200 21.05.11 12:31:18 *STMF      TEST_RG_02_CRLF.properties
    069 *   RADDATZ           187 08.05.11 12:31:40 *STMF      TEST_RG_02_LF.properties
    070 *   RADDATZ           187 08.05.11 12:31:52 *STMF      TEST_RG_02_CR.properties
    071 *   RADDATZ          8192 04.07.13 09:04:14 *DIR       testDir1/
    072 *   RADDATZ          8192 04.07.13 09:04:17 *DIR       testDir2/
    073 *   250 List completed.
    074 *
    075 *
    076 * Example 1, using ANT to list specific members of a file:
    077 * --------------------------------------------------------
    078 *
    079 *      &lt;echo/&gt;
    080 *      &lt;echo&gt;Listing members of a file:&lt;/echo&gt;
    081 *
    082 *      &lt;ftp action="list"
    083 *           server="${ftp.server}"
    084 *           userid="${ftp.user}"
    085 *           password="${ftp.password}"
    086 *           binary="false"
    087 *           verbose="true"
    088 *           remotedir="/QSYS.LIB/RPGUNIT.LIB/RPGUNITY1.FILE"
    089 *           systemTypeKey="OS/400"
    090 *           listing="ftp-listing.txt"
    091 *           &gt;
    092 *          &lt;fileset dir="./i5-downloads-file" casesensitive="false"&gt;
    093 *              &lt;include name="run*.mbr" /&gt;
    094 *          &lt;/fileset&gt;
    095 *      &lt;/ftp&gt;
    096 *
    097 * Output:
    098 * -------
    099 *
    100 *   [echo] Listing members of a file:
    101 *    [ftp] listing files
    102 *    [ftp] listing RUN.MBR
    103 *    [ftp] listing RUNNER.MBR
    104 *    [ftp] listing RUNNERBND.MBR
    105 *    [ftp] 3 files listed
    106 *
    107 *
    108 * Example 2, using ANT to list specific members of all files of a library:
    109 * ------------------------------------------------------------------------
    110 *
    111 *      &lt;echo/&gt;
    112 *      &lt;echo&gt;Listing members of all files of a library:&lt;/echo&gt;
    113 *
    114 *      &lt;ftp action="list"
    115 *           server="${ftp.server}"
    116 *           userid="${ftp.user}"
    117 *           password="${ftp.password}"
    118 *           binary="false"
    119 *           verbose="true"
    120 *           remotedir="/QSYS.LIB/RPGUNIT.LIB"
    121 *           systemTypeKey="OS/400"
    122 *           listing="ftp-listing.txt"
    123 *           &gt;
    124 *          &lt;fileset dir="./i5-downloads-lib" casesensitive="false"&gt;
    125 *              &lt;include name="**\run*.mbr" /&gt;
    126 *          &lt;/fileset&gt;
    127 *      &lt;/ftp&gt;
    128 *
    129 * Output:
    130 * -------
    131 *
    132 *   [echo] Listing members of all files of a library:
    133 *    [ftp] listing files
    134 *    [ftp] listing RPGUNIT1.FILE\RUN.MBR
    135 *    [ftp] listing RPGUNIT1.FILE\RUNRMT.MBR
    136 *    [ftp] listing RPGUNITT1.FILE\RUNT.MBR
    137 *    [ftp] listing RPGUNITY1.FILE\RUN.MBR
    138 *    [ftp] listing RPGUNITY1.FILE\RUNNER.MBR
    139 *    [ftp] listing RPGUNITY1.FILE\RUNNERBND.MBR
    140 *    [ftp] 6 files listed
    141 *
    142 *
    143 * Example 3, using ANT to download specific members of a file:
    144 * ------------------------------------------------------------
    145 *
    146 *      &lt;echo/&gt;
    147 *      &lt;echo&gt;Downloading members of a file:&lt;/echo&gt;
    148 *
    149 *      &lt;ftp action="get"
    150 *           server="${ftp.server}"
    151 *           userid="${ftp.user}"
    152 *           password="${ftp.password}"
    153 *           binary="false"
    154 *           verbose="true"
    155 *           remotedir="/QSYS.LIB/RPGUNIT.LIB/RPGUNITY1.FILE"
    156 *           systemTypeKey="OS/400"
    157 *           &gt;
    158 *          &lt;fileset dir="./i5-downloads-file" casesensitive="false"&gt;
    159 *              &lt;include name="run*.mbr" /&gt;
    160 *          &lt;/fileset&gt;
    161 *      &lt;/ftp&gt;
    162 *
    163 * Output:
    164 * -------
    165 *
    166 *   [echo] Downloading members of a file:
    167 *    [ftp] getting files
    168 *    [ftp] transferring RUN.MBR to C:\workspaces\rdp_080\workspace\ANT - FTP\i5-downloads-file\RUN.MBR
    169 *    [ftp] transferring RUNNER.MBR to C:\workspaces\rdp_080\workspace\ANT - FTP\i5-downloads-file\RUNNER.MBR
    170 *    [ftp] transferring RUNNERBND.MBR to C:\workspaces\rdp_080\workspace\ANT - FTP\i5-downloads-file\RUNNERBND.MBR
    171 *    [ftp] 3 files retrieved
    172 *
    173 *
    174 * Example 4, using ANT to download specific members of all files of a library:
    175 * ----------------------------------------------------------------------------
    176 *
    177 *      &lt;echo/&gt;
    178 *      &lt;echo&gt;Downloading members of all files of a library:&lt;/echo&gt;
    179 *
    180 *      &lt;ftp action="get"
    181 *           server="${ftp.server}"
    182 *           userid="${ftp.user}"
    183 *           password="${ftp.password}"
    184 *           binary="false"
    185 *           verbose="true"
    186 *           remotedir="/QSYS.LIB/RPGUNIT.LIB"
    187 *           systemTypeKey="OS/400"
    188 *           &gt;
    189 *          &lt;fileset dir="./i5-downloads-lib" casesensitive="false"&gt;
    190 *              &lt;include name="**\run*.mbr" /&gt;
    191 *          &lt;/fileset&gt;
    192 *      &lt;/ftp&gt;
    193 *
    194 * Output:
    195 * -------
    196 *
    197 *   [echo] Downloading members of all files of a library:
    198 *    [ftp] getting files
    199 *    [ftp] transferring RPGUNIT1.FILE\RUN.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNIT1.FILE\RUN.MBR
    200 *    [ftp] transferring RPGUNIT1.FILE\RUNRMT.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNIT1.FILE\RUNRMT.MBR
    201 *    [ftp] transferring RPGUNITT1.FILE\RUNT.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITT1.FILE\RUNT.MBR
    202 *    [ftp] transferring RPGUNITY1.FILE\RUN.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITY1.FILE\RUN.MBR
    203 *    [ftp] transferring RPGUNITY1.FILE\RUNNER.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITY1.FILE\RUNNER.MBR
    204 *    [ftp] transferring RPGUNITY1.FILE\RUNNERBND.MBR to C:\work\rdp_080\space\ANT - FTP\i5-downloads\RPGUNITY1.FILE\RUNNERBND.MBR
    205 *    [ftp] 6 files retrieved
    206 *
    207 *
    208 * Example 5, using ANT to download a save file of a library:
    209 * ----------------------------------------------------------
    210 *
    211 *      &lt;ftp action="get"
    212 *           server="${ftp.server}"
    213 *           userid="${ftp.user}"
    214 *           password="${ftp.password}"
    215 *           binary="true"
    216 *           verbose="true"
    217 *           remotedir="/QSYS.LIB/RPGUNIT.LIB"
    218 *           systemTypeKey="OS/400"
    219 *           &gt;
    220 *        &lt;fileset dir="./i5-downloads-savf" casesensitive="false"&gt;
    221 *            &lt;include name="RPGUNIT.SAVF" /&gt;
    222 *        &lt;/fileset&gt;
    223 *      &lt;/ftp&gt;
    224 *
    225 * Output:
    226 * -------
    227 *   [echo] Downloading save file:
    228 *    [ftp] getting files
    229 *    [ftp] transferring RPGUNIT.SAVF to C:\workspaces\rdp_080\workspace\net-Test\i5-downloads-lib\RPGUNIT.SAVF
    230 *    [ftp] 1 files retrieved
    231 *
    232 * </pre>
    233 */
    234public class OS400FTPEntryParser extends ConfigurableFTPFileEntryParserImpl
    235{
    236    private static final String DEFAULT_DATE_FORMAT
    237        = "yy/MM/dd HH:mm:ss"; //01/11/09 12:30:24
    238
    239
    240
    241    private static final String REGEX =
    242        "(\\S+)\\s+"                  // user
    243        + "(?:(\\d+)\\s+)?"           // size, empty for members
    244        + "(?:(\\S+)\\s+(\\S+)\\s+)?" // date stuff, empty for members
    245        + "(\\*STMF|\\*DIR|\\*FILE|\\*MEM)\\s+"  // *STMF/*DIR/*FILE/*MEM
    246        + "(?:(\\S+)\\s*)?";          // file name, missing, when CWD is a *FILE
    247
    248
    249    /**
    250     * The default constructor for a OS400FTPEntryParser object.
    251     *
    252     * @throws IllegalArgumentException
    253     * Thrown if the regular expression is unparseable.  Should not be seen
    254     * under normal conditions.  It it is seen, this is a sign that
    255     * <code>REGEX</code> is  not a valid regular expression.
    256     */
    257    public OS400FTPEntryParser()
    258    {
    259        this(null);
    260    }
    261
    262    /**
    263     * This constructor allows the creation of an OS400FTPEntryParser object
    264     * with something other than the default configuration.
    265     *
    266     * @param config The {@link FTPClientConfig configuration} object used to
    267     * configure this parser.
    268     * @throws IllegalArgumentException
    269     * Thrown if the regular expression is unparseable.  Should not be seen
    270     * under normal conditions.  It it is seen, this is a sign that
    271     * <code>REGEX</code> is  not a valid regular expression.
    272     * @since 1.4
    273     */
    274    public OS400FTPEntryParser(final FTPClientConfig config)
    275    {
    276        super(REGEX);
    277        configure(config);
    278    }
    279
    280
    281    @Override
    282    public FTPFile parseFTPEntry(final String entry)
    283    {
    284
    285        final FTPFile file = new FTPFile();
    286        file.setRawListing(entry);
    287        int type;
    288
    289        if (matches(entry))
    290        {
    291            final String usr = group(1);
    292            final String filesize = group(2);
    293            String datestr = "";
    294            if (!isNullOrEmpty(group(3)) || !isNullOrEmpty(group(4)))
    295            {
    296                datestr = group(3)+" "+group(4);
    297            }
    298            final String typeStr = group(5);
    299            String name = group(6);
    300
    301            boolean mustScanForPathSeparator = true;
    302
    303            try
    304            {
    305                file.setTimestamp(super.parseTimestamp(datestr));
    306            }
    307            catch (final ParseException e)
    308            {
    309                // intentionally do nothing
    310            }
    311
    312
    313            if (typeStr.equalsIgnoreCase("*STMF"))
    314            {
    315                type = FTPFile.FILE_TYPE;
    316                if (isNullOrEmpty(filesize) || isNullOrEmpty(name))
    317                {
    318                    return null;
    319                }
    320            }
    321            else if (typeStr.equalsIgnoreCase("*DIR"))
    322            {
    323                type = FTPFile.DIRECTORY_TYPE;
    324                if (isNullOrEmpty(filesize) || isNullOrEmpty(name))
    325                {
    326                    return null;
    327                }
    328            }
    329            else if (typeStr.equalsIgnoreCase("*FILE"))
    330            {
    331                // File, defines the structure of the data (columns of a row)
    332                // but the data is stored in one or more members. Typically a
    333                // source file contains multiple members whereas it is
    334                // recommended (but not enforced) to use one member per data
    335                // file.
    336                // Save files are a special type of files which are used
    337                // to save objects, e.g. for backups.
    338                if (name != null && name.toUpperCase(Locale.ROOT).endsWith(".SAVF"))
    339                {
    340                    mustScanForPathSeparator = false;
    341                    type = FTPFile.FILE_TYPE;
    342                }
    343                else
    344                {
    345                    return null;
    346                }
    347            }
    348            else if (typeStr.equalsIgnoreCase("*MEM"))
    349            {
    350                mustScanForPathSeparator = false;
    351                type = FTPFile.FILE_TYPE;
    352
    353                if (isNullOrEmpty(name))
    354                {
    355                    return null;
    356                }
    357                if (!(isNullOrEmpty(filesize) && isNullOrEmpty(datestr)))
    358                {
    359                    return null;
    360                }
    361
    362                // Quick and dirty bug fix to make SelectorUtils work.
    363                // Class SelectorUtils uses 'File.separator' to splitt
    364                // a given path into pieces. But actually it had to
    365                // use the separator of the FTP server, which is a forward
    366                // slash in case of an AS/400.
    367                name = name.replace('/', File.separatorChar);
    368            }
    369            else
    370            {
    371                type = FTPFile.UNKNOWN_TYPE;
    372            }
    373
    374            file.setType(type);
    375
    376            file.setUser(usr);
    377
    378            try
    379            {
    380                file.setSize(Long.parseLong(filesize));
    381            }
    382            catch (final NumberFormatException e)
    383            {
    384                // intentionally do nothing
    385            }
    386
    387            if (name.endsWith("/"))
    388            {
    389                name = name.substring(0, name.length() - 1);
    390            }
    391            if (mustScanForPathSeparator)
    392            {
    393                final int pos = name.lastIndexOf('/');
    394                if (pos > -1)
    395                {
    396                    name = name.substring(pos + 1);
    397                }
    398            }
    399
    400            file.setName(name);
    401
    402            return file;
    403        }
    404        return null;
    405    }
    406
    407    /**
    408     *
    409     * @param string String value that is checked for <code>null</code>
    410     * or empty.
    411     * @return <code>true</code> for <code>null</code> or empty values,
    412     * else <code>false</code>.
    413     */
    414    private boolean isNullOrEmpty(final String string) {
    415        if (string == null || string.length() == 0) {
    416            return true;
    417        }
    418        return false;
    419    }
    420
    421    /**
    422     * Defines a default configuration to be used when this class is
    423     * instantiated without a {@link  FTPClientConfig  FTPClientConfig}
    424     * parameter being specified.
    425     * @return the default configuration for this parser.
    426     */
    427    @Override
    428    protected FTPClientConfig getDefaultConfiguration() {
    429        return new FTPClientConfig(
    430                FTPClientConfig.SYST_OS400,
    431                DEFAULT_DATE_FORMAT,
    432                null);
    433    }
    434
    435}