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