1/*
2 * Copyright 2007 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 */
16package org.mockftpserver.stub;
17
18import java.io.ByteArrayInputStream;
19import java.io.ByteArrayOutputStream;
20import java.io.IOException;
21
22
23import org.apache.commons.net.ftp.FTP;
24import org.apache.commons.net.ftp.FTPClient;
25import org.apache.commons.net.ftp.FTPFile;
26import org.apache.log4j.Logger;
27import org.mockftpserver.core.command.CommandHandler;
28import org.mockftpserver.core.command.CommandNames;
29import org.mockftpserver.core.command.InvocationRecord;
30import org.mockftpserver.core.command.SimpleCompositeCommandHandler;
31import org.mockftpserver.core.command.StaticReplyCommandHandler;
32import org.mockftpserver.stub.StubFtpServer;
33import org.mockftpserver.stub.command.AppeCommandHandler;
34import org.mockftpserver.stub.command.CwdCommandHandler;
35import org.mockftpserver.stub.command.HelpCommandHandler;
36import org.mockftpserver.stub.command.ListCommandHandler;
37import org.mockftpserver.stub.command.NlstCommandHandler;
38import org.mockftpserver.stub.command.PwdCommandHandler;
39import org.mockftpserver.stub.command.RetrCommandHandler;
40import org.mockftpserver.stub.command.StatCommandHandler;
41import org.mockftpserver.stub.command.StorCommandHandler;
42import org.mockftpserver.stub.command.StouCommandHandler;
43import org.mockftpserver.test.AbstractTest;
44import org.mockftpserver.test.IntegrationTest;
45import org.mockftpserver.test.PortTestUtil;
46
47/**
48 * Tests for StubFtpServer using the Apache Jakarta Commons Net FTP client.
49 *
50 * @version $Revision$ - $Date$
51 *
52 * @author Chris Mair
53 */
54public final class StubFtpServerIntegrationTest extends AbstractTest implements IntegrationTest {
55
56    private static final Logger LOG = Logger.getLogger(StubFtpServerIntegrationTest.class);
57    private static final String SERVER = "localhost";
58    private static final String USERNAME = "user123";
59    private static final String PASSWORD = "password";
60    private static final String FILENAME = "abc.txt";
61    private static final String ASCII_CONTENTS = "abcdef\tghijklmnopqr";
62    private static final byte[] BINARY_CONTENTS = new byte[256];
63
64    private StubFtpServer stubFtpServer;
65    private FTPClient ftpClient;
66    private RetrCommandHandler retrCommandHandler;
67    private StorCommandHandler storCommandHandler;
68
69    //-------------------------------------------------------------------------
70    // Tests
71    //-------------------------------------------------------------------------
72
73    /**
74     * Test connecting and logging in
75     */
76    public void testLogin() throws Exception {
77        // Connect
78        LOG.info("Conecting to " + SERVER);
79        ftpClientConnect();
80        verifyReplyCode("connect", 220);
81
82        // Login
83        String userAndPassword = USERNAME + "/" + PASSWORD;
84        LOG.info("Logging in as " + userAndPassword);
85        boolean success = ftpClient.login(USERNAME, PASSWORD);
86        assertTrue("Unable to login with " + userAndPassword, success);
87        verifyReplyCode("login with " + userAndPassword, 230);
88
89        // Quit
90        LOG.info("Quit");
91        ftpClient.quit();
92        verifyReplyCode("quit", 221);
93    }
94
95    /**
96     * Test the ACCT command
97     */
98    public void testAcct() throws Exception {
99        ftpClientConnect();
100
101        // ACCT
102        int replyCode = ftpClient.acct("123456");
103        assertEquals("acct", 230, replyCode);
104    }
105
106    /**
107     * Test the stop() method when no session has ever been started
108     */
109    public void testStop_NoSessionEverStarted() throws Exception {
110        LOG.info("Testing a stop() when no session has ever been started");
111    }
112
113    /**
114     * Test help (HELP)
115     */
116    public void testHelp() throws Exception {
117        // Modify HELP CommandHandler to return a predefined help message
118        final String HELP = "help message";
119        HelpCommandHandler helpCommandHandler = (HelpCommandHandler) stubFtpServer.getCommandHandler(CommandNames.HELP);
120        helpCommandHandler.setHelpMessage(HELP);
121
122        ftpClientConnect();
123
124        // HELP
125        String help = ftpClient.listHelp();
126        assertTrue("Wrong response", help.indexOf(HELP) != -1);
127        verifyReplyCode("listHelp", 214);
128    }
129
130    /**
131     * Test the LIST and SYST commands.
132     */
133    public void testList() throws Exception {
134        ftpClientConnect();
135
136        // Set directory listing
137        ListCommandHandler listCommandHandler = (ListCommandHandler) stubFtpServer.getCommandHandler(CommandNames.LIST);
138        listCommandHandler.setDirectoryListing("11-09-01 12:30PM  406348 File2350.log\n"
139                + "11-01-01 1:30PM <DIR> 0 archive");
140
141        // LIST
142        FTPFile[] files = ftpClient.listFiles();
143        assertEquals("number of files", 2, files.length);
144        verifyFTPFile(files[0], FTPFile.FILE_TYPE, "File2350.log", 406348L);
145        verifyFTPFile(files[1], FTPFile.DIRECTORY_TYPE, "archive", 0L);
146        verifyReplyCode("list", 226);
147    }
148
149    /**
150     * Test the LIST, PASV and SYST commands, transferring a directory listing in passive mode
151     */
152    public void testList_PassiveMode() throws Exception {
153        ftpClientConnect();
154
155        ftpClient.enterLocalPassiveMode();
156
157        // Set directory listing
158        ListCommandHandler listCommandHandler = (ListCommandHandler) stubFtpServer.getCommandHandler(CommandNames.LIST);
159        listCommandHandler.setDirectoryListing("11-09-01 12:30PM  406348 File2350.log");
160
161        // LIST
162        FTPFile[] files = ftpClient.listFiles();
163        assertEquals("number of files", 1, files.length);
164        verifyReplyCode("list", 226);
165    }
166
167    /**
168     * Test the NLST command.
169     */
170    public void testNlst() throws Exception {
171        ftpClientConnect();
172
173        // Set directory listing
174        NlstCommandHandler nlstCommandHandler = (NlstCommandHandler) stubFtpServer.getCommandHandler(CommandNames.NLST);
175        nlstCommandHandler.setDirectoryListing("File1.txt\nfile2.data");
176
177        // NLST
178        String[] filenames = ftpClient.listNames();
179        assertEquals("number of files", 2, filenames.length);
180        assertEquals(filenames[0], "File1.txt");
181        assertEquals(filenames[1], "file2.data");
182        verifyReplyCode("listNames", 226);
183    }
184
185    /**
186     * Test printing the current working directory (PWD)
187     */
188    public void testPwd() throws Exception {
189        // Modify PWD CommandHandler to return a predefined directory
190        final String DIR = "some/dir";
191        PwdCommandHandler pwdCommandHandler = (PwdCommandHandler) stubFtpServer.getCommandHandler(CommandNames.PWD);
192        pwdCommandHandler.setDirectory(DIR);
193
194        ftpClientConnect();
195
196        // PWD
197        String dir = ftpClient.printWorkingDirectory();
198        assertEquals("Unable to PWD", DIR, dir);
199        verifyReplyCode("printWorkingDirectory", 257);
200    }
201
202    /**
203     * Test getting the status (STAT)
204     */
205    public void testStat() throws Exception {
206        // Modify Stat CommandHandler to return predefined text
207        final String STATUS = "some information 123";
208        StatCommandHandler statCommandHandler = (StatCommandHandler) stubFtpServer.getCommandHandler(CommandNames.STAT);
209        statCommandHandler.setStatus(STATUS);
210
211        ftpClientConnect();
212
213        // STAT
214        String status = ftpClient.getStatus();
215        assertEquals("STAT reply", "211 " + STATUS + ".", status.trim());
216        verifyReplyCode("getStatus", 211);
217    }
218
219    /**
220     * Test getting the status (STAT), when the reply text contains multiple lines
221     */
222    public void testStat_MultilineReplyText() throws Exception {
223        // Modify Stat CommandHandler to return predefined text
224        final String STATUS = "System name: abc.def\nVersion 3.5.7\nNumber of failed logins: 2";
225        final String FORMATTED_REPLY_STATUS = "211-System name: abc.def\r\nVersion 3.5.7\r\n211 Number of failed logins: 2.";
226        StatCommandHandler statCommandHandler = (StatCommandHandler) stubFtpServer.getCommandHandler(CommandNames.STAT);
227        statCommandHandler.setStatus(STATUS);
228
229        ftpClientConnect();
230
231        // STAT
232        String status = ftpClient.getStatus();
233        assertEquals("STAT reply", FORMATTED_REPLY_STATUS, status.trim());
234        verifyReplyCode("getStatus", 211);
235    }
236
237    /**
238     * Test the System (SYST) command
239     */
240    public void testSyst() throws Exception {
241        ftpClientConnect();
242
243        // SYST
244        assertEquals("getSystemName()", "\"WINDOWS\" system type.", ftpClient.getSystemName());
245        verifyReplyCode("syst", 215);
246    }
247
248    /**
249     * Test changing the current working directory (CWD)
250     */
251    public void testCwd() throws Exception {
252        // Connect
253        LOG.info("Conecting to " + SERVER);
254        ftpClientConnect();
255        verifyReplyCode("connect", 220);
256
257        // CWD
258        boolean success = ftpClient.changeWorkingDirectory("dir1/dir2");
259        assertTrue("Unable to CWD", success);
260        verifyReplyCode("changeWorkingDirectory", 250);
261    }
262
263    /**
264     * Test changing the current working directory (CWD), when it causes a remote error
265     */
266    public void testCwd_Error() throws Exception {
267        // Override CWD CommandHandler to return error reply code
268        final int REPLY_CODE = 500;
269        StaticReplyCommandHandler cwdCommandHandler = new StaticReplyCommandHandler(REPLY_CODE);
270        stubFtpServer.setCommandHandler("CWD", cwdCommandHandler);
271
272        ftpClientConnect();
273
274        // CWD
275        boolean success = ftpClient.changeWorkingDirectory("dir1/dir2");
276        assertFalse("Expected failure", success);
277        verifyReplyCode("changeWorkingDirectory", REPLY_CODE);
278    }
279
280    /**
281     * Test changing to the parent directory (CDUP)
282     */
283    public void testCdup() throws Exception {
284        ftpClientConnect();
285
286        // CDUP
287        boolean success = ftpClient.changeToParentDirectory();
288        assertTrue("Unable to CDUP", success);
289        verifyReplyCode("changeToParentDirectory", 250);
290    }
291
292    /**
293     * Test delete (DELE)
294     */
295    public void testDele() throws Exception {
296        ftpClientConnect();
297
298        // DELE
299        boolean success = ftpClient.deleteFile(FILENAME);
300        assertTrue("Unable to DELE", success);
301        verifyReplyCode("deleteFile", 250);
302    }
303
304    /**
305     * Test make directory (MKD)
306     */
307    public void testMkd() throws Exception {
308        ftpClientConnect();
309
310        // MKD
311        boolean success = ftpClient.makeDirectory("dir1/dir2");
312        assertTrue("Unable to CWD", success);
313        verifyReplyCode("makeDirectory", 257);
314    }
315
316    /**
317     * Test NOOP
318     */
319    public void testNoop() throws Exception {
320        ftpClientConnect();
321
322        // NOOP
323        boolean success = ftpClient.sendNoOp();
324        assertTrue("Unable to NOOP", success);
325        verifyReplyCode("NOOP", 200);
326    }
327
328    /**
329     * Test restart (REST)
330     */
331    public void testRest() throws Exception {
332        ftpClientConnect();
333
334        // REST
335        int replyCode = ftpClient.rest("marker");
336        assertEquals("Unable to REST", 350, replyCode);
337    }
338
339    /**
340     * Test changing the current working directory (RMD)
341     */
342    public void testRmd() throws Exception {
343        ftpClientConnect();
344
345        // RMD
346        boolean success = ftpClient.removeDirectory("dir1/dir2");
347        assertTrue("Unable to RMD", success);
348        verifyReplyCode("removeDirectory", 250);
349    }
350
351    /**
352     * Test rename (RNFR/RNTO)
353     */
354    public void testRename() throws Exception {
355        ftpClientConnect();
356
357        // Rename (RNFR, RNTO)
358        boolean success = ftpClient.rename(FILENAME, "new_" + FILENAME);
359        assertTrue("Unable to RENAME", success);
360        verifyReplyCode("rename", 250);
361    }
362
363    /**
364     * Test the ALLO command
365     */
366    public void testAllo() throws Exception {
367        ftpClientConnect();
368
369        // ALLO
370        assertTrue("ALLO", ftpClient.allocate(1024));
371        assertTrue("ALLO with recordSize", ftpClient.allocate(1024, 64));
372    }
373
374    /**
375     * Test GET and PUT of ASCII files
376     */
377    public void testTransferAsciiFile() throws Exception {
378        retrCommandHandler.setFileContents(ASCII_CONTENTS);
379
380        ftpClientConnect();
381
382        // Get File
383        LOG.info("Get File for remotePath [" + FILENAME + "]");
384        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
385        assertTrue(ftpClient.retrieveFile(FILENAME, outputStream));
386        LOG.info("File contents=[" + outputStream.toString());
387        assertEquals("File contents", ASCII_CONTENTS, outputStream.toString());
388
389        // Put File
390        LOG.info("Put File for local path [" + FILENAME + "]");
391        ByteArrayInputStream inputStream = new ByteArrayInputStream(ASCII_CONTENTS.getBytes());
392        assertTrue(ftpClient.storeFile(FILENAME, inputStream));
393        InvocationRecord invocationRecord = storCommandHandler.getInvocation(0);
394        byte[] contents = (byte[]) invocationRecord.getObject(StorCommandHandler.FILE_CONTENTS_KEY);
395        LOG.info("File contents=[" + contents + "]");
396        assertEquals("File contents", ASCII_CONTENTS.getBytes(), contents);
397    }
398
399    /**
400     * Test GET and PUT of binary files
401     */
402    public void testTransferBinaryFiles() throws Exception {
403        retrCommandHandler.setFileContents(BINARY_CONTENTS);
404
405        ftpClientConnect();
406        ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
407
408        // Get File
409        LOG.info("Get File for remotePath [" + FILENAME + "]");
410        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
411        assertTrue("GET", ftpClient.retrieveFile(FILENAME, outputStream));
412        LOG.info("GET File length=" + outputStream.size());
413        assertEquals("File contents", BINARY_CONTENTS, outputStream.toByteArray());
414
415        // Put File
416        LOG.info("Put File for local path [" + FILENAME + "]");
417        ByteArrayInputStream inputStream = new ByteArrayInputStream(BINARY_CONTENTS);
418        assertTrue("PUT", ftpClient.storeFile(FILENAME, inputStream));
419        InvocationRecord invocationRecord = storCommandHandler.getInvocation(0);
420        byte[] contents = (byte[]) invocationRecord.getObject(StorCommandHandler.FILE_CONTENTS_KEY);
421        LOG.info("PUT File length=" + contents.length);
422        assertEquals("File contents", BINARY_CONTENTS, contents);
423    }
424
425    /**
426     * Test the STOU command
427     */
428    public void testStou() throws Exception {
429        StouCommandHandler stouCommandHandler = (StouCommandHandler) stubFtpServer.getCommandHandler(CommandNames.STOU);
430        stouCommandHandler.setFilename(FILENAME);
431
432        ftpClientConnect();
433
434        // Stor a File (STOU)
435        ByteArrayInputStream inputStream = new ByteArrayInputStream(ASCII_CONTENTS.getBytes());
436        assertTrue(ftpClient.storeUniqueFile(FILENAME, inputStream));
437        InvocationRecord invocationRecord = stouCommandHandler.getInvocation(0);
438        byte[] contents = (byte[]) invocationRecord.getObject(StorCommandHandler.FILE_CONTENTS_KEY);
439        LOG.info("File contents=[" + contents + "]");
440        assertEquals("File contents", ASCII_CONTENTS.getBytes(), contents);
441    }
442
443    /**
444     * Test the APPE command
445     */
446    public void testAppe() throws Exception {
447        AppeCommandHandler appeCommandHandler = (AppeCommandHandler) stubFtpServer.getCommandHandler(CommandNames.APPE);
448
449        ftpClientConnect();
450
451        // Append a File (APPE)
452        ByteArrayInputStream inputStream = new ByteArrayInputStream(ASCII_CONTENTS.getBytes());
453        assertTrue(ftpClient.appendFile(FILENAME, inputStream));
454        InvocationRecord invocationRecord = appeCommandHandler.getInvocation(0);
455        byte[] contents = (byte[]) invocationRecord.getObject(AppeCommandHandler.FILE_CONTENTS_KEY);
456        LOG.info("File contents=[" + contents + "]");
457        assertEquals("File contents", ASCII_CONTENTS.getBytes(), contents);
458    }
459
460    /**
461     * Test the ABOR command
462     */
463    public void testAbor() throws Exception {
464        ftpClientConnect();
465
466        // ABOR
467        assertTrue("ABOR", ftpClient.abort());
468    }
469
470    /**
471     * Test the Passive (PASV) command
472     */
473    public void testPasv() throws Exception {
474        ftpClientConnect();
475
476        // PASV
477        ftpClient.enterLocalPassiveMode();
478        // no reply code; the PASV command is sent only when the data connection is opened
479    }
480
481    /**
482     * Test Mode (MODE)
483    */
484    public void testMode() throws Exception {
485        ftpClientConnect();
486
487        // MODE
488        boolean success = ftpClient.setFileTransferMode(FTP.STREAM_TRANSFER_MODE);
489        assertTrue("Unable to MODE", success);
490        verifyReplyCode("setFileTransferMode", 200);
491    }
492
493    /**
494     * Test file structure (STRU)
495     */
496    public void testStru() throws Exception {
497        ftpClientConnect();
498
499        // STRU
500        boolean success = ftpClient.setFileStructure(FTP.FILE_STRUCTURE);
501        assertTrue("Unable to STRU", success);
502        verifyReplyCode("setFileStructure", 200);
503    }
504
505    /**
506     * Test the SimpleCompositeCommandHandler
507     */
508    public void testSimpleCompositeCommandHandler() throws Exception {
509        // Replace CWD CommandHandler with a SimpleCompositeCommandHandler
510        CommandHandler commandHandler1 = new StaticReplyCommandHandler(500);
511        CommandHandler commandHandler2 = new CwdCommandHandler();
512        SimpleCompositeCommandHandler simpleCompositeCommandHandler = new SimpleCompositeCommandHandler();
513        simpleCompositeCommandHandler.addCommandHandler(commandHandler1);
514        simpleCompositeCommandHandler.addCommandHandler(commandHandler2);
515        stubFtpServer.setCommandHandler("CWD", simpleCompositeCommandHandler);
516
517        // Connect
518        ftpClientConnect();
519
520        // CWD
521        assertFalse("first", ftpClient.changeWorkingDirectory("dir1/dir2"));
522        assertTrue("first", ftpClient.changeWorkingDirectory("dir1/dir2"));
523    }
524
525    /**
526     * Test site parameters (SITE)
527     */
528    public void testSite() throws Exception {
529        ftpClientConnect();
530
531        // SITE
532        int replyCode = ftpClient.site("parameters,1,2,3");
533        assertEquals("SITE", 200, replyCode);
534    }
535
536    /**
537     * Test structure mount (SMNT)
538     */
539    public void testSmnt() throws Exception {
540        ftpClientConnect();
541
542        // SMNT
543        assertTrue("SMNT", ftpClient.structureMount("dir1/dir2"));
544        verifyReplyCode("structureMount", 250);
545    }
546
547    /**
548     * Test reinitialize (REIN)
549     */
550    public void testRein() throws Exception {
551        ftpClientConnect();
552
553        // REIN
554        assertEquals("REIN", 220, ftpClient.rein());
555    }
556
557    /**
558     * Test that command names in lowercase or mixed upper/lower case are accepted
559     */
560    public void testCommandNamesInLowerOrMixedCase() throws Exception {
561        ftpClientConnect();
562
563        assertEquals("rein", 220, ftpClient.sendCommand("rein"));
564        assertEquals("rEIn", 220, ftpClient.sendCommand("rEIn"));
565        assertEquals("reiN", 220, ftpClient.sendCommand("reiN"));
566        assertEquals("Rein", 220, ftpClient.sendCommand("Rein"));
567    }
568
569    // -------------------------------------------------------------------------
570    // Test setup and tear-down
571    // -------------------------------------------------------------------------
572
573    /**
574     * Perform initialization before each test
575     * @see org.mockftpserver.test.AbstractTest#setUp()
576     */
577    protected void setUp() throws Exception {
578        super.setUp();
579
580        for (int i = 0; i < BINARY_CONTENTS.length; i++) {
581            BINARY_CONTENTS[i] = (byte) i;
582        }
583
584        stubFtpServer = new StubFtpServer();
585        stubFtpServer.setServerControlPort(PortTestUtil.getFtpServerControlPort());
586        stubFtpServer.start();
587        ftpClient = new FTPClient();
588        retrCommandHandler = (RetrCommandHandler) stubFtpServer.getCommandHandler(CommandNames.RETR);
589        storCommandHandler = (StorCommandHandler) stubFtpServer.getCommandHandler(CommandNames.STOR);
590    }
591
592    /**
593     * Perform cleanup after each test
594     * @see org.mockftpserver.test.AbstractTest#tearDown()
595     */
596    protected void tearDown() throws Exception {
597        super.tearDown();
598        stubFtpServer.stop();
599    }
600
601    // -------------------------------------------------------------------------
602    // Internal Helper Methods
603    // -------------------------------------------------------------------------
604
605    /**
606     * Connect to the server from the FTPClient
607     */
608    private void ftpClientConnect() throws IOException {
609        ftpClient.connect(SERVER, PortTestUtil.getFtpServerControlPort());
610    }
611
612    /**
613     * Assert that the FtpClient reply code is equal to the expected value
614     *
615     * @param operation - the description of the operation performed; used in the error message
616     * @param expectedReplyCode - the expected FtpClient reply code
617     */
618    private void verifyReplyCode(String operation, int expectedReplyCode) {
619        int replyCode = ftpClient.getReplyCode();
620        LOG.info("Reply: operation=\"" + operation + "\" replyCode=" + replyCode);
621        assertEquals("Unexpected replyCode for " + operation, expectedReplyCode, replyCode);
622    }
623
624    /**
625     * Verify that the FTPFile has the specified properties
626     *
627     * @param ftpFile - the FTPFile to verify
628     * @param type - the expected file type
629     * @param name - the expected file name
630     * @param size - the expected file size (will be zero for a directory)
631     */
632    private void verifyFTPFile(FTPFile ftpFile, int type, String name, long size) {
633        LOG.info(ftpFile);
634        assertEquals("type: " + ftpFile, type, ftpFile.getType());
635        assertEquals("name: " + ftpFile, name, ftpFile.getName());
636        assertEquals("size: " + ftpFile, size, ftpFile.getSize());
637    }
638
639}
640