Coverage Report - org.mockftpserver.fake.FakeFtpServer
 
Classes in this File Line Coverage Branch Coverage Complexity
FakeFtpServer
98%
69/70
83%
5/6
1.214
 
 1  
 /*
 2  
  * Copyright 2008 the original author or authors.
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *      http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package org.mockftpserver.fake;
 17  
 
 18  
 import org.mockftpserver.core.command.CommandHandler;
 19  
 import org.mockftpserver.core.command.CommandNames;
 20  
 import org.mockftpserver.core.command.ConnectCommandHandler;
 21  
 import org.mockftpserver.core.command.ReplyTextBundleUtil;
 22  
 import org.mockftpserver.core.command.UnsupportedCommandHandler;
 23  
 import org.mockftpserver.core.server.AbstractFtpServer;
 24  
 import org.mockftpserver.fake.command.*;
 25  
 import org.mockftpserver.fake.filesystem.FileSystem;
 26  
 
 27  
 import java.util.HashMap;
 28  
 import java.util.List;
 29  
 import java.util.Map;
 30  
 
 31  
 /**
 32  
  * <b>FakeFtpServer</b> is the top-level class for a "fake" implementation of an FTP Server,
 33  
  * suitable for testing FTP client code or standing in for a live FTP server.
 34  
  * <p/>
 35  
  * <b>FakeFtpServer</b> provides a high-level abstraction for an FTP Server and is suitable
 36  
  * for most testing and simulation scenarios. You define a filesystem (internal, in-memory) containing
 37  
  * an arbitrary set of files and directories. These files and directories can (optionally) have
 38  
  * associated access permissions. You also configure a set of one or more user accounts that
 39  
  * control which users can login to the FTP server, and their home (default) directories. The
 40  
  * user account is also used when assigning file and directory ownership for new files.
 41  
  * <p> <b>FakeFtpServer</b> processes FTP client requests and responds with reply codes and
 42  
  * reply messages consistent with its configuration and the contents of its internal filesystem,
 43  
  * including file and directory permissions, if they have been configured.
 44  
  * <p/>
 45  
  * <b>FakeFtpServer</b> can be fully configured programmatically or within the
 46  
  * <a href="http://www.springframework.org/">Spring Framework</a> or other dependency-injection container.
 47  
  * <p/>
 48  
  * In general the steps for setting up and starting the <b>FakeFtpServer</b> are:
 49  
  * <ol>
 50  
  * <li>Create a new <b>FakeFtpServer</b> instance, and optionally set the server control port.</li>
 51  
  * <li>Create and configure a <b>FileSystem</b>, and attach to the <b>FakeFtpServer</b> instance.</li>
 52  
  * <li>Create and configure one or more <b>UserAccount</b> objects and attach to the <b>FakeFtpServer</b> instance.</li>
 53  
  * <li>Start the <b>FakeFtpServer</b> instance.</li>
 54  
  * </ol>
 55  
  * <h4>Example Code</h4>
 56  
  * <pre><code>
 57  
  * FakeFtpServer fakeFtpServer = new FakeFtpServer();
 58  
  *
 59  
  * FileSystem fileSystem = new WindowsFakeFileSystem();
 60  
  * fileSystem.add(new DirectoryEntry("c:\\"));
 61  
  * fileSystem.add(new DirectoryEntry("c:\\data"));
 62  
  * fileSystem.add(new FileEntry("c:\\data\\file1.txt", "abcdef 1234567890"));
 63  
  * fileSystem.add(new FileEntry("c:\\data\\run.exe"));
 64  
  * fakeFtpServer.setFileSystem(fileSystem);
 65  
  *
 66  
  * // Create UserAccount with username, password, home-directory
 67  
  * UserAccount userAccount = new UserAccount("joe", "joe123", "c:\\");
 68  
  * fakeFtpServer.addUserAccounts(userAccount);
 69  
  *
 70  
  * fakeFtpServer.start();
 71  
  * </code></pre>
 72  
  *
 73  
  * <h4>Example Code with Permissions</h4>
 74  
  * You can optionally set the permissions and owner/group for each file and directory, as in the following example.
 75  
  * <pre><code>
 76  
  * FileSystem fileSystem = new UnixFakeFileSystem();
 77  
  * DirectoryEntry directoryEntry1 = new DirectoryEntry("/");
 78  
  * directoryEntry1.setPermissions(new Permissions("rwxrwx---"));
 79  
  * directoryEntry1.setOwner("joe");
 80  
  * directoryEntry1.setGroup("dev");
 81  
  *
 82  
  * DirectoryEntry directoryEntry2 = new DirectoryEntry("/data");
 83  
  * directoryEntry2.setPermissions(Permissions.ALL);
 84  
  * directoryEntry2.setOwner("joe");
 85  
  * directoryEntry2.setGroup("dev");
 86  
  *
 87  
  * FileEntry fileEntry1 = new FileEntry("/data/file1.txt", "abcdef 1234567890");
 88  
  * fileEntry1.setPermissionsFromString("rw-rw-rw-");
 89  
  * fileEntry1.setOwner("joe");
 90  
  * fileEntry1.setGroup("dev");
 91  
  *
 92  
  * FileEntry fileEntry2 = new FileEntry("/data/run.exe");
 93  
  * fileEntry2.setPermissionsFromString("rwxrwx---");
 94  
  * fileEntry2.setOwner("mary");
 95  
  * fileEntry2.setGroup("dev");
 96  
  *
 97  
  * fileSystem.add(directoryEntry1);
 98  
  * fileSystem.add(directoryEntry2);
 99  
  * fileSystem.add(fileEntry1);
 100  
  * fileSystem.add(fileEntry2);
 101  
  *
 102  
  * FakeFtpServer fakeFtpServer = new FakeFtpServer();
 103  
  * fakeFtpServer.setFileSystem(fileSystem);
 104  
  *
 105  
  * // Create UserAccount with username, password, home-directory
 106  
  * UserAccount userAccount = new UserAccount("joe", "joe123", "/");
 107  
  * fakeFtpServer.addUserAccounts(userAccount);
 108  
  *
 109  
  * fakeFtpServer.start();
 110  
  * </code></pre>
 111  
  *
 112  
  * <h4>FTP Server Control Port</h4>
 113  
  * By default, <b>FakeFtpServer</b> binds to the server control port of 21. You can use a different server control
 114  
  * port by setting the <code>serverControlPort</code> property. If you specify a value of <code>0</code>,
 115  
  * then a free port number will be chosen automatically; call <code>getServerControlPort()</code> AFTER
 116  
  * <code>start()</code> has been called to determine the actual port number being used. Using a non-default
 117  
  * port number is usually necessary when running on Unix or some other system where that port number is
 118  
  * already in use or cannot be bound from a user process.
 119  
  *
 120  
  * <h4>Other Configuration</h4>
 121  
  * The <code>systemName</code> property specifies the value returned by the <code>SYST</code>
 122  
  * command. Note that this is typically used by an FTP client to determine how to parse
 123  
  * system-dependent reply text, such as directory listings. This value defaults to <code>"WINDOWS"</code>.
 124  
  * <p/>
 125  
  * The <code>helpText</code> property specifies a <i>Map</i> of help text replies sent by the
 126  
  * <code>HELP</code> command. The keys in that <i>Map</i> correspond to the command names passed as
 127  
  * parameters to the <code>HELP</code> command. An entry with the key of an empty string ("") indicates the
 128  
  * text used as the default help text when no command name parameter is specified for the <code>HELP</code> command.
 129  
  *
 130  
  * <h4>FTP Command Reply Text ResourceBundle</h4>
 131  
  * The default text asociated with each FTP command reply code is contained within the
 132  
  * "ReplyText.properties" ResourceBundle file. You can customize these messages by providing a
 133  
  * locale-specific ResourceBundle file on the CLASSPATH, according to the normal lookup rules of
 134  
  * the ResourceBundle class (e.g., "ReplyText_de.properties"). Alternatively, you can
 135  
  * completely replace the ResourceBundle file by calling the calling the
 136  
  * {@link #setReplyTextBaseName(String)} method.
 137  
  *
 138  
  * @author Chris Mair
 139  
  * @version $Revision: 255 $ - $Date: 2011-06-05 21:23:55 -0400 (Sun, 05 Jun 2011) $
 140  
  */
 141  
 public class FakeFtpServer extends AbstractFtpServer implements ServerConfiguration {
 142  
 
 143  
     private FileSystem fileSystem;
 144  70
     private String systemName = "WINDOWS";
 145  70
     private String systemStatus = "Connected";
 146  70
     private Map helpText = new HashMap();
 147  70
     private Map userAccounts = new HashMap();
 148  
 
 149  
     public FileSystem getFileSystem() {
 150  220
         return fileSystem;
 151  
     }
 152  
 
 153  
     public void setFileSystem(FileSystem fileSystem) {
 154  48
         this.fileSystem = fileSystem;
 155  48
     }
 156  
 
 157  
     public String getSystemName() {
 158  7
         return systemName;
 159  
     }
 160  
 
 161  
     public void setSystemName(String systemName) {
 162  43
         this.systemName = systemName;
 163  43
     }
 164  
 
 165  
     public Map getHelpText() {
 166  0
         return helpText;
 167  
     }
 168  
 
 169  
     public void setHelpText(Map helpText) {
 170  2
         this.helpText = helpText;
 171  2
     }
 172  
 
 173  70
     public FakeFtpServer() {
 174  70
         setCommandHandler(CommandNames.ACCT, new AcctCommandHandler());
 175  70
         setCommandHandler(CommandNames.ABOR, new AborCommandHandler());
 176  70
         setCommandHandler(CommandNames.ALLO, new AlloCommandHandler());
 177  70
         setCommandHandler(CommandNames.APPE, new AppeCommandHandler());
 178  70
         setCommandHandler(CommandNames.CWD, new CwdCommandHandler());
 179  70
         setCommandHandler(CommandNames.CDUP, new CdupCommandHandler());
 180  70
         setCommandHandler(CommandNames.DELE, new DeleCommandHandler());
 181  70
         setCommandHandler(CommandNames.EPRT, new EprtCommandHandler());
 182  70
         setCommandHandler(CommandNames.EPSV, new EpsvCommandHandler());
 183  70
         setCommandHandler(CommandNames.HELP, new HelpCommandHandler());
 184  70
         setCommandHandler(CommandNames.LIST, new ListCommandHandler());
 185  70
         setCommandHandler(CommandNames.MKD, new MkdCommandHandler());
 186  70
         setCommandHandler(CommandNames.MODE, new ModeCommandHandler());
 187  70
         setCommandHandler(CommandNames.NLST, new NlstCommandHandler());
 188  70
         setCommandHandler(CommandNames.NOOP, new NoopCommandHandler());
 189  70
         setCommandHandler(CommandNames.PASS, new PassCommandHandler());
 190  70
         setCommandHandler(CommandNames.PASV, new PasvCommandHandler());
 191  70
         setCommandHandler(CommandNames.PWD, new PwdCommandHandler());
 192  70
         setCommandHandler(CommandNames.PORT, new PortCommandHandler());
 193  70
         setCommandHandler(CommandNames.QUIT, new QuitCommandHandler());
 194  70
         setCommandHandler(CommandNames.REIN, new ReinCommandHandler());
 195  70
         setCommandHandler(CommandNames.REST, new RestCommandHandler());
 196  70
         setCommandHandler(CommandNames.RETR, new RetrCommandHandler());
 197  70
         setCommandHandler(CommandNames.RMD, new RmdCommandHandler());
 198  70
         setCommandHandler(CommandNames.RNFR, new RnfrCommandHandler());
 199  70
         setCommandHandler(CommandNames.RNTO, new RntoCommandHandler());
 200  70
         setCommandHandler(CommandNames.SITE, new SiteCommandHandler());
 201  70
         setCommandHandler(CommandNames.SMNT, new SmntCommandHandler());
 202  70
         setCommandHandler(CommandNames.STAT, new StatCommandHandler());
 203  70
         setCommandHandler(CommandNames.STOR, new StorCommandHandler());
 204  70
         setCommandHandler(CommandNames.STOU, new StouCommandHandler());
 205  70
         setCommandHandler(CommandNames.STRU, new StruCommandHandler());
 206  70
         setCommandHandler(CommandNames.SYST, new SystCommandHandler());
 207  70
         setCommandHandler(CommandNames.TYPE, new TypeCommandHandler());
 208  70
         setCommandHandler(CommandNames.USER, new UserCommandHandler());
 209  70
         setCommandHandler(CommandNames.XPWD, new PwdCommandHandler());
 210  
 
 211  
         // "Special" Command Handlers
 212  70
         setCommandHandler(CommandNames.CONNECT, new ConnectCommandHandler());
 213  70
         setCommandHandler(CommandNames.UNSUPPORTED, new UnsupportedCommandHandler());
 214  70
     }
 215  
 
 216  
     /**
 217  
      * Initialize a CommandHandler that has been registered to this server.
 218  
      *
 219  
      * If the CommandHandler implements the <code>ServerConfigurationAware</code> interface, then set its
 220  
      * <code>ServerConfiguration</code> property to <code>this</code>.
 221  
      *
 222  
      * If the CommandHandler implements the <code>ReplyTextBundleAware</code> interface, then set its
 223  
      * <code>replyTextBundle</code> property using the reply text bundle for this server.
 224  
      *
 225  
      * @param commandHandler - the CommandHandler to initialize
 226  
      */
 227  
     protected void initializeCommandHandler(CommandHandler commandHandler) {
 228  2672
         if (commandHandler instanceof ServerConfigurationAware) {
 229  2527
             ServerConfigurationAware sca = (ServerConfigurationAware) commandHandler;
 230  2527
             sca.setServerConfiguration(this);
 231  
         }
 232  
 
 233  2672
         ReplyTextBundleUtil.setReplyTextBundleIfAppropriate(commandHandler, getReplyTextBundle());
 234  2672
     }
 235  
 
 236  
     /**
 237  
      * @return the {@link UserAccount}        configured for this server for the specified user name
 238  
      */
 239  
     public UserAccount getUserAccount(String username) {
 240  166
         return (UserAccount) userAccounts.get(username);
 241  
     }
 242  
 
 243  
     /**
 244  
      * Return the help text for a command or the default help text if no command name is specified
 245  
      *
 246  
      * @param name - the command name; may be empty or null to indicate  a request for the default help text
 247  
      * @return the help text for the named command or the default help text if no name is supplied
 248  
      */
 249  
     public String getHelpText(String name) {
 250  7
         String key = name == null ? "" : name;
 251  7
         return (String) helpText.get(key);
 252  
     }
 253  
 
 254  
     /**
 255  
      * Add a single UserAccount. If an account with the same <code>username</code> already exists,
 256  
      * it will be replaced.
 257  
      *
 258  
      * @param userAccount - the UserAccount to add
 259  
      */
 260  
     public void addUserAccount(UserAccount userAccount) {
 261  45
         userAccounts.put(userAccount.getUsername(), userAccount);
 262  45
     }
 263  
 
 264  
     /**
 265  
      * Add the UserAccount objects in the <code>userAccountList</code> to the set of UserAccounts.
 266  
      *
 267  
      * @param userAccountList - the List of UserAccount objects to add
 268  
      */
 269  
     public void setUserAccounts(List userAccountList) {
 270  6
         for (int i = 0; i < userAccountList.size(); i++) {
 271  3
             UserAccount userAccount = (UserAccount) userAccountList.get(i);
 272  3
             userAccounts.put(userAccount.getUsername(), userAccount);
 273  
         }
 274  3
     }
 275  
 
 276  
     /**
 277  
      * Return the system status description
 278  
      *
 279  
      * @return the system status
 280  
      */
 281  
     public String getSystemStatus() {
 282  3
         return systemStatus;
 283  
     }
 284  
 
 285  
     /**
 286  
      * Set the system status description text, used by the STAT command handler.
 287  
      *
 288  
      * @param systemStatus - the system status description text
 289  
      */
 290  
     public void setSystemStatus(String systemStatus) {
 291  1
         this.systemStatus = systemStatus;
 292  1
     }
 293  
 
 294  
 }