    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 *      https://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    private static final String DEFAULT_DATE_FORMAT = "yy/MM/dd HH:mm:ss"; // 01/11/09 12:30:24
    236
    237    private static final String REGEX = "(\\S+)\\s+" // user
    238            + "(?:(\\d+)\\s+)?" // size, empty for members
    239            + "(?:(\\S+)\\s+(\\S+)\\s+)?" // date stuff, empty for members
    240            + "(\\*STMF|\\*DIR|\\*FILE|\\*MEM)\\s+" // *STMF/*DIR/*FILE/*MEM
    241            + "((\\S+\\s*)+)?"; // file name, missing, when CWD is a *FILE
    242
    243    /**
    244     * Constructs a new instance.
    245     *
    246     * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. If it is seen, this is a
    247     *                                  sign that {@code REGEX} is not a valid regular expression.
    248     */
    249    public OS400FTPEntryParser() {
    250        this(null);
    251    }
    252
    253    /**
    254     * Constructs a new instance with something other than the default configuration.
    255     *
    256     * @param config The {@link FTPClientConfig configuration} object used to configure this parser.
    257     * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. If it is seen, this is a
    258     *                                  sign that {@code REGEX} is not a valid regular expression.
    259     * @since 1.4
    260     */
    261    public OS400FTPEntryParser(final FTPClientConfig config) {
    262        super(REGEX);
    263        configure(config);
    264    }
    265
    266    /**
    267     * Gets a new default configuration to be used when this class is instantiated without a {@link FTPClientConfig FTPClientConfig} parameter being specified.
    268     *
    269     * @return the default configuration for this parser.
    270     */
    271    @Override
    272    protected FTPClientConfig getDefaultConfiguration() {
    273        return new FTPClientConfig(FTPClientConfig.SYST_OS400, DEFAULT_DATE_FORMAT, null);
    274    }
    275
    276    /**
    277     *
    278     * @param string String value that is checked for {@code null} or empty.
    279     * @return {@code true} for {@code null} or empty values, else {@code false}.
    280     */
    281    private boolean isNullOrEmpty(final String string) {
    282        return string == null || string.isEmpty();
    283    }
    284
    285    @Override
    286    public FTPFile parseFTPEntry(final String entry) {
    287
    288        final FTPFile file = new FTPFile();
    289        file.setRawListing(entry);
    290        final int type;
    291
    292        if (matches(entry)) {
    293            final String usr = group(1);
    294            final String fileSize = group(2);
    295            String datestr = "";
    296            if (!isNullOrEmpty(group(3)) || !isNullOrEmpty(group(4))) {
    297                datestr = group(3) + " " + group(4);
    298            }
    299            final String typeStr = group(5);
    300            String name = group(6);
    301
    302            boolean mustScanForPathSeparator = true;
    303
    304            try {
    305                file.setTimestamp(super.parseTimestamp(datestr));
    306            } catch (final ParseException e) {
    307                // intentionally do nothing
    308            }
    309
    310            if (typeStr.equalsIgnoreCase("*STMF")) {
    311                type = FTPFile.FILE_TYPE;
    312                if (isNullOrEmpty(fileSize) || isNullOrEmpty(name)) {
    313                    return null;
    314                }
    315            } else if (typeStr.equalsIgnoreCase("*DIR")) {
    316                type = FTPFile.DIRECTORY_TYPE;
    317                if (isNullOrEmpty(fileSize) || isNullOrEmpty(name)) {
    318                    return null;
    319                }
    320            } else if (typeStr.equalsIgnoreCase("*FILE")) {
    321                // File, defines the structure of the data (columns of a row)
    322                // but the data is stored in one or more members. Typically, a
    323                // source file contains multiple members whereas it is
    324                // recommended (but not enforced) to use one member per data
    325                // file.
    326                // Save files are a special type of files which are used
    327                // to save objects, e.g. for backups.
    328                if (name == null || !name.toUpperCase(Locale.ROOT).endsWith(".SAVF")) {
    329                    return null;
    330                }
    331                mustScanForPathSeparator = false;
    332                type = FTPFile.FILE_TYPE;
    333            } else if (typeStr.equalsIgnoreCase("*MEM")) {
    334                mustScanForPathSeparator = false;
    335                type = FTPFile.FILE_TYPE;
    336
    337                if (isNullOrEmpty(name)) {
    338                    return null;
    339                }
    340                if (!(isNullOrEmpty(fileSize) && isNullOrEmpty(datestr))) {
    341                    return null;
    342                }
    343
    344                // Quick and dirty bug fix to make SelectorUtils work.
    345                // Class SelectorUtils uses 'File.separator' to splitt
    346                // a given path into pieces. But actually it had to
    347                // use the separator of the FTP server, which is a forward
    348                // slash in case of an AS/400.
    349                name = name.replace('/', File.separatorChar);
    350            } else {
    351                type = FTPFile.UNKNOWN_TYPE;
    352            }
    353
    354            file.setType(type);
    355
    356            file.setUser(usr);
    357
    358            try {
    359                file.setSize(Long.parseLong(fileSize));
    360            } catch (final NumberFormatException e) {
    361                // intentionally do nothing
    362            }
    363
    364            if (name.endsWith("/")) {
    365                name = name.substring(0, name.length() - 1);
    366            }
    367            if (mustScanForPathSeparator) {
    368                final int pos = name.lastIndexOf('/');
    369                if (pos > -1) {
    370                    name = name.substring(pos + 1);
    371                }
    372            }
    373
    374            file.setName(name);
    375
    376            return file;
    377        }
    378        return null;
    379    }
    380
    381}