View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net.ftp.parser;
19  
20  import java.io.File;
21  import java.text.ParseException;
22  import java.util.Locale;
23  
24  import org.apache.commons.net.ftp.FTPClientConfig;
25  import org.apache.commons.net.ftp.FTPFile;
26  
27  /**
28   * OS400 (IBM i) FTP entry parser.
29   *
30   * <p>
31   * Below are examples of the format of FTP entries on an IBM i.
32   * </p>
33   *
34   * <strong>
35   * Example *FILE/*MEM FTP entries, when the current
36   * working directory is a file of file system QSYS:
37   * </strong>
38   *
39   * <pre>
40   * $ cwd /qsys.lib/rpgunit.lib/rpgunitc1.file
41   *   250-NAMEFMT set to 1.
42   *   250 "/QSYS.LIB/RPGUNIT.LIB/RPGUNITC1.FILE" is current directory.
43   * $ dir
44   *   227 Entering Passive Mode (10,200,36,33,40,249).
45   *   125 List started.
46   *   QPGMR          135168 22.06.13 13:18:19 *FILE
47   *   QPGMR                                   *MEM       MKCMD.MBR
48   *   QPGMR                                   *MEM       RUCALLTST.MBR
49   *   QPGMR                                   *MEM       RUCMDHLP.MBR
50   *   QPGMR                                   *MEM       RUCRTTST.MBR
51   *   250 List completed.
52   * </pre>
53   *
54   * <strong>
55   * Example *FILE entry of an OS/400 save file:
56   * </strong>
57   *
58   * <pre>
59   * $ cwd /qsys.lib/rpgunit.lib
60   *   250 "/QSYS.LIB/RPGUNIT.LIB" is current library.
61   * $ dir rpgunit.file
62   *   227 Entering Passive Mode (10,200,36,33,188,106).
63   *   125 List started.
64   *   QPGMR        16347136 29.06.13 15:45:09 *FILE      RPGUNIT.SAVF
65   *   250 List completed.
66   * </pre>
67   *
68   * <strong>
69   * Example *STMF/*DIR FTP entries, when the
70   * current working directory is in file system "root":
71   * </strong>
72   *
73   * <pre>
74   * $ cwd /home/raddatz
75   *   250 "/home/raddatz" is current directory.
76   * $ dir test*
77   *   227 Entering Passive Mode (10,200,36,33,200,189).
78   *   125 List started.
79   *   RADDATZ           200 21.05.11 12:31:18 *STMF      TEST_RG_02_CRLF.properties
80   *   RADDATZ           187 08.05.11 12:31:40 *STMF      TEST_RG_02_LF.properties
81   *   RADDATZ           187 08.05.11 12:31:52 *STMF      TEST_RG_02_CR.properties
82   *   RADDATZ          8192 04.07.13 09:04:14 *DIR       testDir1/
83   *   RADDATZ          8192 04.07.13 09:04:17 *DIR       testDir2/
84   *   250 List completed.
85   * </pre>
86   *
87   * <strong>
88   * Example 1, using ANT to list specific members of a file:
89   * </strong>
90   *
91   * <pre>
92   *      &lt;echo/&gt;
93   *      &lt;echo&gt;Listing members of a file:&lt;/echo&gt;
94   *
95   *      &lt;ftp action="list"
96   *           server="${ftp.server}"
97   *           userid="${ftp.user}"
98   *           password="${ftp.password}"
99   *           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  */
260 public 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 }