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 * <echo/> 093 * <echo>Listing members of a file:</echo> 094 * 095 * <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 * > 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 */ 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}