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 * <echo/>
93 * <echo>Listing members of a file:</echo>
94 *
95 * <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 * >
105 * <fileset dir="./i5-downloads-file" casesensitive="false">
106 * <include name="run*.mbr" />
107 * </fileset>
108 * </ftp>
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 * <echo/>
128 * <echo>Listing members of all files of a library:</echo>
129 *
130 * <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 * >
140 * <fileset dir="./i5-downloads-lib" casesensitive="false">
141 * <include name="**\run*.mbr" />
142 * </fileset>
143 * </ftp>
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 * <echo/>
166 * <echo>Downloading members of a file:</echo>
167 *
168 * <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 * >
177 * <fileset dir="./i5-downloads-file" casesensitive="false">
178 * <include name="run*.mbr" />
179 * </fileset>
180 * </ftp>
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 * <echo/>
200 * <echo>Downloading members of all files of a library:</echo>
201 *
202 * <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 * >
211 * <fileset dir="./i5-downloads-lib" casesensitive="false">
212 * <include name="**\run*.mbr" />
213 * </fileset>
214 * </ftp>
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 * <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 * >
245 * <fileset dir="./i5-downloads-savf" casesensitive="false">
246 * <include name="RPGUNIT.SAVF" />
247 * </fileset>
248 * </ftp>
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 }