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.util.Locale; 21 import java.util.regex.Pattern; 22 23 import org.apache.commons.net.ftp.Configurable; 24 import org.apache.commons.net.ftp.FTPClientConfig; 25 import org.apache.commons.net.ftp.FTPFileEntryParser; 26 27 /** 28 * This is the default implementation of the FTPFileEntryParserFactory interface. This is the implementation that will be used by 29 * org.apache.commons.net.ftp.FTPClient.listFiles() if no other implementation has been specified. 30 * 31 * @see org.apache.commons.net.ftp.FTPClient#listFiles 32 * @see org.apache.commons.net.ftp.FTPClient#setParserFactory 33 */ 34 public class DefaultFTPFileEntryParserFactory implements FTPFileEntryParserFactory { 35 36 /** 37 * Match a plain Java Identifier 38 */ 39 private static final String JAVA_IDENTIFIER = "\\p{javaJavaIdentifierStart}(\\p{javaJavaIdentifierPart})*"; 40 41 /** 42 * Match a qualified name, e.g. a.b.c.Name - but don't allow the default package as that would allow "VMS"/"UNIX" etc. 43 */ 44 private static final String JAVA_QUALIFIED_NAME = "(" + JAVA_IDENTIFIER + "\\.)+" + JAVA_IDENTIFIER; 45 46 /** 47 * Create the pattern, as it will be reused many times 48 */ 49 private static final Pattern JAVA_QUALIFIED_NAME_PATTERN = Pattern.compile(JAVA_QUALIFIED_NAME); 50 51 /** 52 * Constructs a new instance. 53 */ 54 public DefaultFTPFileEntryParserFactory() { 55 // empty 56 } 57 58 /** 59 * Implementation extracts a key from the supplied {@link FTPClientConfig FTPClientConfig} parameter and creates an object implementing the interface 60 * FTPFileEntryParser and uses the supplied configuration to configure it. 61 * <p> 62 * Note that this method will generally not be called in scenarios that call for autodetection of parser type but rather, for situations where the user 63 * knows that the server uses a non-default configuration and knows what that configuration is. 64 * </p> 65 * 66 * @param config A {@link FTPClientConfig FTPClientConfig} used to configure the parser created 67 * @return the {@link FTPFileEntryParser} so created. 68 * @throws ParserInitializationException Thrown on any exception in instantiation 69 * @throws NullPointerException if {@code config} is {@code null} 70 * @since 1.4 71 */ 72 @Override 73 public FTPFileEntryParser createFileEntryParser(final FTPClientConfig config) throws ParserInitializationException { 74 return createFileEntryParser(config.getServerSystemKey(), config); 75 } 76 77 /** 78 * This default implementation of the FTPFileEntryParserFactory interface works according to the following logic: First it attempts to interpret the 79 * supplied key as a fully qualified class name (default package is not allowed) of a class implementing the FTPFileEntryParser interface. If that succeeds, 80 * a parser object of this class is instantiated and is returned; otherwise it attempts to interpret the key as an identifier commonly used by the FTP SYST 81 * command to identify systems. 82 * <p> 83 * If {@code key} is not recognized as a fully qualified class name known to the system, this method will then attempt to see whether it 84 * <strong>contains</strong> a string identifying one of the known parsers. This comparison is <strong>case-insensitive</strong>. The intent here is where 85 * possible, to select as keys strings which are returned by the SYST command on the systems which the corresponding parser successfully parses. This 86 * enables this factory to be used in the auto-detection system. 87 * </p> 88 * 89 * @param key should be a fully qualified class name corresponding to a class implementing the FTPFileEntryParser interface<br> 90 * OR<br> 91 * a string containing (case-insensitively) one of the following keywords: 92 * <ul> 93 * <li>{@link FTPClientConfig#SYST_UNIX UNIX}</li> 94 * <li>{@link FTPClientConfig#SYST_NT WINDOWS}</li> 95 * <li>{@link FTPClientConfig#SYST_OS2 OS/2}</li> 96 * <li>{@link FTPClientConfig#SYST_OS400 OS/400}</li> 97 * <li>{@link FTPClientConfig#SYST_AS400 AS/400}</li> 98 * <li>{@link FTPClientConfig#SYST_VMS VMS}</li> 99 * <li>{@link FTPClientConfig#SYST_MVS MVS}</li> 100 * <li>{@link FTPClientConfig#SYST_NETWARE NETWARE}</li> 101 * <li>{@link FTPClientConfig#SYST_L8 TYPE:L8}</li> 102 * </ul> 103 * @return the FTPFileEntryParser corresponding to the supplied key. 104 * @throws ParserInitializationException thrown if for any reason the factory cannot resolve the supplied key into an FTPFileEntryParser. 105 * @see FTPFileEntryParser 106 */ 107 @Override 108 public FTPFileEntryParser createFileEntryParser(final String key) { 109 if (key == null) { 110 throw new ParserInitializationException("Parser key cannot be null"); 111 } 112 return createFileEntryParser(key, null); 113 } 114 115 // Common method to process both key and config parameters. 116 private FTPFileEntryParser createFileEntryParser(final String key, final FTPClientConfig config) { 117 FTPFileEntryParser parser = null; 118 // Is the key a possible class name? 119 if (JAVA_QUALIFIED_NAME_PATTERN.matcher(key).matches()) { 120 try { 121 final Class<?> parserClass = Class.forName(key); 122 try { 123 parser = (FTPFileEntryParser) parserClass.getConstructor().newInstance(); 124 } catch (final ClassCastException e) { 125 throw new ParserInitializationException( 126 parserClass.getName() + " does not implement the interface " + FTPFileEntryParser.class.getCanonicalName(), e); 127 } catch (final Exception | LinkageError e) { 128 throw new ParserInitializationException("Error initializing parser", e); 129 } 130 } catch (final ClassNotFoundException e) { 131 // OK, assume it is an alias 132 } 133 } 134 if (parser == null) { // Now try for aliases 135 final String upperKey = key.toUpperCase(Locale.ENGLISH); 136 if (upperKey.contains(FTPClientConfig.SYST_UNIX_TRIM_LEADING)) { 137 parser = new UnixFTPEntryParser(config, true); 138 // must check this after SYST_UNIX_TRIM_LEADING as it is a substring of it 139 } else if (upperKey.contains(FTPClientConfig.SYST_UNIX)) { 140 parser = new UnixFTPEntryParser(config, false); 141 } else if (upperKey.contains(FTPClientConfig.SYST_VMS)) { 142 parser = new VMSVersioningFTPEntryParser(config); 143 } else if (upperKey.contains(FTPClientConfig.SYST_NT)) { 144 parser = createNTFTPEntryParser(config); 145 } else if (upperKey.contains(FTPClientConfig.SYST_OS2)) { 146 parser = new OS2FTPEntryParser(config); 147 } else if (upperKey.contains(FTPClientConfig.SYST_OS400) || upperKey.contains(FTPClientConfig.SYST_AS400)) { 148 parser = createOS400FTPEntryParser(config); 149 } else if (upperKey.contains(FTPClientConfig.SYST_MVS)) { 150 parser = new MVSFTPEntryParser(); // Does not currently support config parameter 151 } else if (upperKey.contains(FTPClientConfig.SYST_NETWARE)) { 152 parser = new NetwareFTPEntryParser(config); 153 } else if (upperKey.contains(FTPClientConfig.SYST_MACOS_PETER)) { 154 parser = new MacOsPeterFTPEntryParser(config); 155 } else if (upperKey.contains(FTPClientConfig.SYST_L8)) { 156 // L8 normally means Unix, but move it to the end for some L8 systems that aren't. 157 // This check should be last! 158 parser = new UnixFTPEntryParser(config); 159 } else { 160 throw new ParserInitializationException("Unknown parser type: " + key); 161 } 162 } 163 if (parser instanceof Configurable) { 164 ((Configurable) parser).configure(config); 165 } 166 return parser; 167 } 168 169 /** 170 * Creates a new MVSFTPEntryParser. 171 * 172 * @return a new MVSFTPEntryParser. 173 */ 174 public FTPFileEntryParser createMVSEntryParser() { 175 return new MVSFTPEntryParser(); 176 } 177 178 /** 179 * Creates a new NetwareFTPEntryParser. 180 * 181 * @return a new NetwareFTPEntryParser. 182 */ 183 public FTPFileEntryParser createNetwareFTPEntryParser() { 184 return new NetwareFTPEntryParser(); 185 } 186 187 /** 188 * Creates a new FTPFileEntryParser. 189 * 190 * @return a new FTPFileEntryParser. 191 */ 192 public FTPFileEntryParser createNTFTPEntryParser() { 193 return createNTFTPEntryParser(null); 194 } 195 196 /** 197 * Creates an NT FTP parser: if the config exists, and the system key equals {@link FTPClientConfig#SYST_NT} then a plain {@link NTFTPEntryParser} is used, 198 * otherwise a composite of {@link NTFTPEntryParser} and {@link UnixFTPEntryParser} is used. 199 * 200 * @param config the config to use, may be {@code null} 201 * @return the parser 202 */ 203 private FTPFileEntryParser createNTFTPEntryParser(final FTPClientConfig config) { 204 if (config != null && FTPClientConfig.SYST_NT.equals(config.getServerSystemKey())) { 205 return new NTFTPEntryParser(config); 206 } 207 // clone the config as it may be changed by the parsers (NET-602) 208 final FTPClientConfig config2 = config != null ? new FTPClientConfig(config) : null; 209 return new CompositeFileEntryParser(new FTPFileEntryParser[] { new NTFTPEntryParser(config), 210 new UnixFTPEntryParser(config2, config2 != null && FTPClientConfig.SYST_UNIX_TRIM_LEADING.equals(config2.getServerSystemKey()))}); 211 } 212 213 /** 214 * Creates a new OS2FTPEntryParser. 215 * 216 * @return a new OS2FTPEntryParser. 217 */ 218 public FTPFileEntryParser createOS2FTPEntryParser() { 219 return new OS2FTPEntryParser(); 220 } 221 222 /** 223 * Creates a new FTPFileEntryParser. 224 * 225 * @return a new FTPFileEntryParser. 226 */ 227 public FTPFileEntryParser createOS400FTPEntryParser() { 228 return createOS400FTPEntryParser(null); 229 } 230 231 /** 232 * Creates an OS400 FTP parser: if the config exists, and the system key equals {@link FTPClientConfig#SYST_OS400} then a plain {@link OS400FTPEntryParser} 233 * is used, otherwise a composite of {@link OS400FTPEntryParser} and {@link UnixFTPEntryParser} is used. 234 * 235 * @param config the config to use, may be {@code null} 236 * @return the parser 237 */ 238 private FTPFileEntryParser createOS400FTPEntryParser(final FTPClientConfig config) { 239 if (config != null && FTPClientConfig.SYST_OS400.equals(config.getServerSystemKey())) { 240 return new OS400FTPEntryParser(config); 241 } 242 // clone the config as it may be changed by the parsers (NET-602) 243 final FTPClientConfig config2 = config != null ? new FTPClientConfig(config) : null; 244 return new CompositeFileEntryParser(new FTPFileEntryParser[] { new OS400FTPEntryParser(config), 245 new UnixFTPEntryParser(config2, config2 != null && FTPClientConfig.SYST_UNIX_TRIM_LEADING.equals(config2.getServerSystemKey()))}); 246 } 247 248 /** 249 * Creates a new UnixFTPEntryParser. 250 * 251 * @return a new UnixFTPEntryParser. 252 */ 253 public FTPFileEntryParser createUnixFTPEntryParser() { 254 return new UnixFTPEntryParser(); 255 } 256 257 /** 258 * Creates a new VMSVersioningFTPEntryParser. 259 * 260 * @return a new VMSVersioningFTPEntryParser. 261 */ 262 public FTPFileEntryParser createVMSVersioningFTPEntryParser() { 263 return new VMSVersioningFTPEntryParser(); 264 } 265 266 }