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.util.Locale; 021import java.util.regex.Pattern; 022 023import org.apache.commons.net.ftp.Configurable; 024import org.apache.commons.net.ftp.FTPClientConfig; 025import org.apache.commons.net.ftp.FTPFileEntryParser; 026 027/** 028 * This is the default implementation of the FTPFileEntryParserFactory interface. This is the implementation that will be used by 029 * org.apache.commons.net.ftp.FTPClient.listFiles() if no other implementation has been specified. 030 * 031 * @see org.apache.commons.net.ftp.FTPClient#listFiles 032 * @see org.apache.commons.net.ftp.FTPClient#setParserFactory 033 */ 034public class DefaultFTPFileEntryParserFactory implements FTPFileEntryParserFactory { 035 036 /** 037 * Match a plain Java Identifier 038 */ 039 private static final String JAVA_IDENTIFIER = "\\p{javaJavaIdentifierStart}(\\p{javaJavaIdentifierPart})*"; 040 041 /** 042 * Match a qualified name, e.g. a.b.c.Name - but don't allow the default package as that would allow "VMS"/"UNIX" etc. 043 */ 044 private static final String JAVA_QUALIFIED_NAME = "(" + JAVA_IDENTIFIER + "\\.)+" + JAVA_IDENTIFIER; 045 046 /** 047 * Create the pattern, as it will be reused many times 048 */ 049 private static final Pattern JAVA_QUALIFIED_NAME_PATTERN = Pattern.compile(JAVA_QUALIFIED_NAME); 050 051 /** 052 * Constructs a new instance. 053 */ 054 public DefaultFTPFileEntryParserFactory() { 055 // empty 056 } 057 058 /** 059 * Implementation extracts a key from the supplied {@link FTPClientConfig FTPClientConfig} parameter and creates an object implementing the interface 060 * FTPFileEntryParser and uses the supplied configuration to configure it. 061 * <p> 062 * 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 063 * knows that the server uses a non-default configuration and knows what that configuration is. 064 * </p> 065 * 066 * @param config A {@link FTPClientConfig FTPClientConfig} used to configure the parser created 067 * @return the {@link FTPFileEntryParser} so created. 068 * @throws ParserInitializationException Thrown on any exception in instantiation 069 * @throws NullPointerException if {@code config} is {@code null} 070 * @since 1.4 071 */ 072 @Override 073 public FTPFileEntryParser createFileEntryParser(final FTPClientConfig config) throws ParserInitializationException { 074 return createFileEntryParser(config.getServerSystemKey(), config); 075 } 076 077 /** 078 * This default implementation of the FTPFileEntryParserFactory interface works according to the following logic: First it attempts to interpret the 079 * supplied key as a fully qualified class name (default package is not allowed) of a class implementing the FTPFileEntryParser interface. If that succeeds, 080 * 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 081 * command to identify systems. 082 * <p> 083 * 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 084 * <strong>contains</strong> a string identifying one of the known parsers. This comparison is <strong>case-insensitive</strong>. The intent here is where 085 * possible, to select as keys strings which are returned by the SYST command on the systems which the corresponding parser successfully parses. This 086 * enables this factory to be used in the auto-detection system. 087 * </p> 088 * 089 * @param key should be a fully qualified class name corresponding to a class implementing the FTPFileEntryParser interface<br> 090 * OR<br> 091 * a string containing (case-insensitively) one of the following keywords: 092 * <ul> 093 * <li>{@link FTPClientConfig#SYST_UNIX UNIX}</li> 094 * <li>{@link FTPClientConfig#SYST_NT WINDOWS}</li> 095 * <li>{@link FTPClientConfig#SYST_OS2 OS/2}</li> 096 * <li>{@link FTPClientConfig#SYST_OS400 OS/400}</li> 097 * <li>{@link FTPClientConfig#SYST_AS400 AS/400}</li> 098 * <li>{@link FTPClientConfig#SYST_VMS VMS}</li> 099 * <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}