Home | History | Annotate | Download | only in fake
      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$ - $Date$
    140  */
    141 public class FakeFtpServer extends AbstractFtpServer implements ServerConfiguration {
    142 
    143     private FileSystem fileSystem;
    144     private String systemName = "WINDOWS";
    145     private String systemStatus = "Connected";
    146     private Map helpText = new HashMap();
    147     private Map userAccounts = new HashMap();
    148 
    149     public FileSystem getFileSystem() {
    150         return fileSystem;
    151     }
    152 
    153     public void setFileSystem(FileSystem fileSystem) {
    154         this.fileSystem = fileSystem;
    155     }
    156 
    157     public String getSystemName() {
    158         return systemName;
    159     }
    160 
    161     public void setSystemName(String systemName) {
    162         this.systemName = systemName;
    163     }
    164 
    165     public Map getHelpText() {
    166         return helpText;
    167     }
    168 
    169     public void setHelpText(Map helpText) {
    170         this.helpText = helpText;
    171     }
    172 
    173     public FakeFtpServer() {
    174         setCommandHandler(CommandNames.ACCT, new AcctCommandHandler());
    175         setCommandHandler(CommandNames.ABOR, new AborCommandHandler());
    176         setCommandHandler(CommandNames.ALLO, new AlloCommandHandler());
    177         setCommandHandler(CommandNames.APPE, new AppeCommandHandler());
    178         setCommandHandler(CommandNames.CWD, new CwdCommandHandler());
    179         setCommandHandler(CommandNames.CDUP, new CdupCommandHandler());
    180         setCommandHandler(CommandNames.DELE, new DeleCommandHandler());
    181         setCommandHandler(CommandNames.EPRT, new EprtCommandHandler());
    182         setCommandHandler(CommandNames.EPSV, new EpsvCommandHandler());
    183         setCommandHandler(CommandNames.HELP, new HelpCommandHandler());
    184         setCommandHandler(CommandNames.LIST, new ListCommandHandler());
    185         setCommandHandler(CommandNames.MKD, new MkdCommandHandler());
    186         setCommandHandler(CommandNames.MODE, new ModeCommandHandler());
    187         setCommandHandler(CommandNames.NLST, new NlstCommandHandler());
    188         setCommandHandler(CommandNames.NOOP, new NoopCommandHandler());
    189         setCommandHandler(CommandNames.PASS, new PassCommandHandler());
    190         setCommandHandler(CommandNames.PASV, new PasvCommandHandler());
    191         setCommandHandler(CommandNames.PWD, new PwdCommandHandler());
    192         setCommandHandler(CommandNames.PORT, new PortCommandHandler());
    193         setCommandHandler(CommandNames.QUIT, new QuitCommandHandler());
    194         setCommandHandler(CommandNames.REIN, new ReinCommandHandler());
    195         setCommandHandler(CommandNames.REST, new RestCommandHandler());
    196         setCommandHandler(CommandNames.RETR, new RetrCommandHandler());
    197         setCommandHandler(CommandNames.RMD, new RmdCommandHandler());
    198         setCommandHandler(CommandNames.RNFR, new RnfrCommandHandler());
    199         setCommandHandler(CommandNames.RNTO, new RntoCommandHandler());
    200         setCommandHandler(CommandNames.SITE, new SiteCommandHandler());
    201         setCommandHandler(CommandNames.SMNT, new SmntCommandHandler());
    202         setCommandHandler(CommandNames.STAT, new StatCommandHandler());
    203         setCommandHandler(CommandNames.STOR, new StorCommandHandler());
    204         setCommandHandler(CommandNames.STOU, new StouCommandHandler());
    205         setCommandHandler(CommandNames.STRU, new StruCommandHandler());
    206         setCommandHandler(CommandNames.SYST, new SystCommandHandler());
    207         setCommandHandler(CommandNames.TYPE, new TypeCommandHandler());
    208         setCommandHandler(CommandNames.USER, new UserCommandHandler());
    209         setCommandHandler(CommandNames.XPWD, new PwdCommandHandler());
    210 
    211         // "Special" Command Handlers
    212         setCommandHandler(CommandNames.CONNECT, new ConnectCommandHandler());
    213         setCommandHandler(CommandNames.UNSUPPORTED, new UnsupportedCommandHandler());
    214     }
    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         if (commandHandler instanceof ServerConfigurationAware) {
    229             ServerConfigurationAware sca = (ServerConfigurationAware) commandHandler;
    230             sca.setServerConfiguration(this);
    231         }
    232 
    233         ReplyTextBundleUtil.setReplyTextBundleIfAppropriate(commandHandler, getReplyTextBundle());
    234     }
    235 
    236     /**
    237      * @return the {@link UserAccount}        configured for this server for the specified user name
    238      */
    239     public UserAccount getUserAccount(String username) {
    240         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         String key = name == null ? "" : name;
    251         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         userAccounts.put(userAccount.getUsername(), userAccount);
    262     }
    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         for (int i = 0; i < userAccountList.size(); i++) {
    271             UserAccount userAccount = (UserAccount) userAccountList.get(i);
    272             userAccounts.put(userAccount.getUsername(), userAccount);
    273         }
    274     }
    275 
    276     /**
    277      * Return the system status description
    278      *
    279      * @return the system status
    280      */
    281     public String getSystemStatus() {
    282         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         this.systemStatus = systemStatus;
    292     }
    293 
    294 }