summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorchrismair <chrismair@531de8e6-9941-0410-b38b-9a92acbe0330>2014-05-11 21:21:28 +0000
committerchrismair <chrismair@531de8e6-9941-0410-b38b-9a92acbe0330>2014-05-11 21:21:28 +0000
commit00dc7bdcf1df9e86789d963984dfc6912a8854c6 (patch)
tree789468103a54b69b90528e8f18ae976545ccacd2
parent0ef70e71163dc2b7870b23282663ac09dbc98a52 (diff)
downloadmockftpserver-marshmallow-dr-release.tar.gz
v2.5android-wear-5.1.1_r1android-wear-5.1.0_r1android-cts-6.0_r9android-cts-6.0_r8android-cts-6.0_r7android-cts-6.0_r6android-cts-6.0_r5android-cts-6.0_r4android-cts-6.0_r32android-cts-6.0_r31android-cts-6.0_r30android-cts-6.0_r3android-cts-6.0_r29android-cts-6.0_r28android-cts-6.0_r27android-cts-6.0_r26android-cts-6.0_r25android-cts-6.0_r24android-cts-6.0_r23android-cts-6.0_r22android-cts-6.0_r21android-cts-6.0_r20android-cts-6.0_r2android-cts-6.0_r19android-cts-6.0_r18android-cts-6.0_r17android-cts-6.0_r16android-cts-6.0_r15android-cts-6.0_r14android-cts-6.0_r13android-cts-6.0_r12android-cts-6.0_r1android-6.0.1_r9android-6.0.1_r81android-6.0.1_r80android-6.0.1_r8android-6.0.1_r79android-6.0.1_r78android-6.0.1_r77android-6.0.1_r74android-6.0.1_r73android-6.0.1_r72android-6.0.1_r70android-6.0.1_r7android-6.0.1_r69android-6.0.1_r68android-6.0.1_r67android-6.0.1_r66android-6.0.1_r65android-6.0.1_r63android-6.0.1_r62android-6.0.1_r61android-6.0.1_r60android-6.0.1_r59android-6.0.1_r58android-6.0.1_r57android-6.0.1_r56android-6.0.1_r55android-6.0.1_r54android-6.0.1_r53android-6.0.1_r52android-6.0.1_r51android-6.0.1_r50android-6.0.1_r5android-6.0.1_r49android-6.0.1_r48android-6.0.1_r47android-6.0.1_r46android-6.0.1_r45android-6.0.1_r43android-6.0.1_r42android-6.0.1_r41android-6.0.1_r40android-6.0.1_r4android-6.0.1_r33android-6.0.1_r32android-6.0.1_r31android-6.0.1_r30android-6.0.1_r3android-6.0.1_r28android-6.0.1_r27android-6.0.1_r26android-6.0.1_r25android-6.0.1_r24android-6.0.1_r22android-6.0.1_r21android-6.0.1_r20android-6.0.1_r18android-6.0.1_r17android-6.0.1_r16android-6.0.1_r13android-6.0.1_r12android-6.0.1_r11android-6.0.1_r10android-6.0.1_r1android-6.0.0_r7android-6.0.0_r6android-6.0.0_r5android-6.0.0_r41android-6.0.0_r4android-6.0.0_r3android-6.0.0_r26android-6.0.0_r25android-6.0.0_r24android-6.0.0_r23android-6.0.0_r2android-6.0.0_r13android-6.0.0_r12android-6.0.0_r11android-6.0.0_r1master-soongmarshmallow-releasemarshmallow-mr3-releasemarshmallow-mr2-releasemarshmallow-mr1-releasemarshmallow-mr1-devmarshmallow-dr1.6-releasemarshmallow-dr1.5-releasemarshmallow-dr1.5-devmarshmallow-dr-releasemarshmallow-dr-dragon-releasemarshmallow-dr-devmarshmallow-devmarshmallow-cts-release
git-svn-id: svn://svn.code.sf.net/p/mockftpserver/code@274 531de8e6-9941-0410-b38b-9a92acbe0330
-rw-r--r--tags/2.5/.classpath36
-rw-r--r--tags/2.5/.project23
-rw-r--r--tags/2.5/.settings/org.codehaus.groovy.eclipse.preferences.prefs3
-rw-r--r--tags/2.5/.settings/org.eclipse.jdt.core.prefs268
-rw-r--r--tags/2.5/.settings/org.eclipse.jdt.ui.prefs5
-rw-r--r--tags/2.5/CHANGELOG.txt171
-rw-r--r--tags/2.5/LICENSE.txt201
-rw-r--r--tags/2.5/MockFtpServer.eml44
-rw-r--r--tags/2.5/MockFtpServer.iml9
-rw-r--r--tags/2.5/MockFtpServer.ipr522
-rw-r--r--tags/2.5/README.txt30
-rw-r--r--tags/2.5/pom.xml325
-rw-r--r--tags/2.5/src/assembly/assembly.xml76
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/CommandSyntaxException.java49
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/IllegalStateException.java49
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/MockFtpServerException.java52
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/NotLoggedInException.java49
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/command/AbstractCommandHandler.java89
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/command/AbstractStaticReplyCommandHandler.java104
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/command/AbstractTrackingCommandHandler.java194
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/command/Command.java171
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/command/CommandHandler.java42
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/command/CommandNames.java74
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/command/ConnectCommandHandler.java48
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/command/InvocationHistory.java50
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/command/InvocationRecord.java182
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/command/ReplyCodes.java83
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/command/ReplyTextBundleAware.java43
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/command/ReplyTextBundleUtil.java53
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/command/SimpleCompositeCommandHandler.java129
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/command/StaticReplyCommandHandler.java69
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/command/UnsupportedCommandHandler.java49
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/server/AbstractFtpServer.java375
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/session/DefaultSession.java483
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/session/Session.java135
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/session/SessionKeys.java30
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/socket/DefaultServerSocketFactory.java41
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/socket/DefaultSocketFactory.java43
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/socket/ServerSocketFactory.java38
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/socket/SocketFactory.java40
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/util/Assert.java138
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/util/AssertFailedException.java36
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/util/HostAndPort.java42
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/util/IoUtil.java63
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/util/PatternUtil.java85
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/util/PortParser.java164
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/core/util/StringUtil.java96
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/FakeFtpServer.java294
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/ServerConfiguration.java57
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/ServerConfigurationAware.java26
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/UserAccount.java294
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/AborCommandHandler.java39
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/AbstractFakeCommandHandler.java442
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/AbstractStoreFileCommandHandler.java121
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/AcctCommandHandler.java44
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/AlloCommandHandler.java39
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/AppeCommandHandler.java53
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/CdupCommandHandler.java53
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/CwdCommandHandler.java54
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/DeleCommandHandler.java52
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/EprtCommandHandler.java49
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/EpsvCommandHandler.java47
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/HelpCommandHandler.java55
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/ListCommandHandler.java80
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/MkdCommandHandler.java62
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/ModeCommandHandler.java39
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/NlstCommandHandler.java70
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/NoopCommandHandler.java37
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/PassCommandHandler.java55
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/PasvCommandHandler.java52
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/PortCommandHandler.java46
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/PwdCommandHandler.java42
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/QuitCommandHandler.java35
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/ReinCommandHandler.java40
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/RestCommandHandler.java39
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/RetrCommandHandler.java120
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/RmdCommandHandler.java54
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/RnfrCommandHandler.java53
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/RntoCommandHandler.java61
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/SiteCommandHandler.java38
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/SmntCommandHandler.java39
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/StatCommandHandler.java41
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/StorCommandHandler.java45
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/StouCommandHandler.java55
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/StruCommandHandler.java39
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/SystCommandHandler.java42
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/TypeCommandHandler.java43
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/command/UserCommandHandler.java58
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/AbstractFakeFileSystem.java653
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/AbstractFileSystemEntry.java129
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/DirectoryEntry.java82
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/DirectoryListingFormatter.java35
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/FileEntry.java187
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/FileSystem.java161
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/FileSystemEntry.java99
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/FileSystemException.java77
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/InvalidFilenameException.java45
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/Permissions.java157
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/UnixDirectoryListingFormatter.java81
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/UnixFakeFileSystem.java87
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/WindowsDirectoryListingFormatter.java50
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/WindowsFakeFileSystem.java102
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/StubFtpServer.java148
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/AborCommandHandler.java45
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/AbstractStorCommandHandler.java49
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/AbstractStubCommandHandler.java31
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/AbstractStubDataCommandHandler.java231
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/AcctCommandHandler.java54
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/AlloCommandHandler.java73
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/AppeCommandHandler.java45
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/CdupCommandHandler.java45
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/CwdCommandHandler.java54
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/DeleCommandHandler.java54
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/EprtCommandHandler.java71
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/EpsvCommandHandler.java61
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/FileRetrCommandHandler.java111
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/HelpCommandHandler.java68
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/ListCommandHandler.java91
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/MkdCommandHandler.java55
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/ModeCommandHandler.java54
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/NlstCommandHandler.java68
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/NoopCommandHandler.java45
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/PassCommandHandler.java54
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/PasvCommandHandler.java68
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/PortCommandHandler.java68
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/PwdCommandHandler.java58
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/QuitCommandHandler.java46
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/ReinCommandHandler.java46
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/RestCommandHandler.java55
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/RetrCommandHandler.java113
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/RmdCommandHandler.java54
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/RnfrCommandHandler.java54
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/RntoCommandHandler.java54
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/SiteCommandHandler.java54
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/SmntCommandHandler.java54
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/StatCommandHandler.java75
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/StorCommandHandler.java45
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/StouCommandHandler.java60
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/StruCommandHandler.java54
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/SystCommandHandler.java63
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/TypeCommandHandler.java58
-rw-r--r--tags/2.5/src/main/java/org/mockftpserver/stub/command/UserCommandHandler.java84
-rw-r--r--tags/2.5/src/main/resources/ReplyText.properties131
-rw-r--r--tags/2.5/src/site/apt/fakeftpserver-features.apt113
-rw-r--r--tags/2.5/src/site/apt/fakeftpserver-filesystems.apt235
-rw-r--r--tags/2.5/src/site/apt/fakeftpserver-getting-started.apt449
-rw-r--r--tags/2.5/src/site/apt/fakeftpserver-versus-stubftpserver.apt61
-rw-r--r--tags/2.5/src/site/apt/index.apt60
-rw-r--r--tags/2.5/src/site/apt/stubftpserver-commandhandlers.apt98
-rw-r--r--tags/2.5/src/site/apt/stubftpserver-features.apt35
-rw-r--r--tags/2.5/src/site/apt/stubftpserver-getting-started.apt341
-rw-r--r--tags/2.5/src/site/fml/faq.fml51
-rw-r--r--tags/2.5/src/site/resources/css/site.css4
-rw-r--r--tags/2.5/src/site/resources/images/mockftpserver-logo.pngbin0 -> 15713 bytes
-rw-r--r--tags/2.5/src/site/site.xml51
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/core/server/AbstractFtpServer_MultipleStartAndStopTest.groovy69
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/core/session/StubSession.groovy185
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/core/util/IoUtilTest.groovy45
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/core/util/PatternUtilTest.groovy51
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/core/util/PortParserTest.groovy106
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/core/util/StringUtilTest.groovy61
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/CustomUserAccount.groovy27
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/FakeFtpServerIntegrationTest.groovy510
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/FakeFtpServerTest.groovy132
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/FakeFtpServer_AlreadyStartedTest.groovy43
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/FakeFtpServer_StartTest.groovy34
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/RunFakeFtpServer.groovy53
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/StubServerConfiguration.groovy46
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/TestCommandHandler.groovy34
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/TestCommandHandlerNotServerConfigurationAware.groovy34
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/UserAccountTest.groovy206
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AborCommandHandlerTest.groovy53
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AbstractFakeCommandHandlerTestCase.groovy306
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AbstractStoreFileCommandHandlerTestCase.groovy109
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AcctCommandHandlerTest.groovy85
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AlloCommandHandlerTest.groovy53
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AppeCommandHandlerTest.groovy88
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/CdupCommandHandlerTest.groovy77
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/CwdCommandHandlerTest.groovy94
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/DeleCommandHandlerTest.groovy97
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/EprtCommandHandlerTest.groovy96
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/EpsvCommandHandlerTest.groovy56
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/HelpCommandHandlerTest.groovy76
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/ListCommandHandlerTest.groovy132
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/MkdCommandHandlerTest.groovy110
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/ModeCommandHandlerTest.groovy53
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/NlstCommandHandlerTest.groovy111
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/NoopCommandHandlerTest.groovy55
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/PassCommandHandlerTest.groovy156
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/PasvCommandHandlerTest.groovy61
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/PortCommandHandlerTest.groovy66
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/PwdCommandHandlerTest.groovy60
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/QuitCommandHandlerTest.groovy53
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/ReinCommandHandlerTest.groovy72
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RestCommandHandlerTest.groovy53
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RetrCommandHandlerTest.groovy135
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RmdCommandHandlerTest.groovy113
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RnfrCommandHandlerTest.groovy88
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RntoCommandHandlerTest.groovy170
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/SiteCommandHandlerTest.groovy53
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/SmntCommandHandlerTest.groovy50
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/StatCommandHandlerText.groovy52
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/StorCommandHandlerTest.groovy79
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/StouCommandHandlerTest.groovy77
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/StruCommandHandlerTest.groovy53
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/SystCommandHandlerTest.groovy58
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/TypeCommandHandlerTest.groovy66
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/UserCommandHandlerTest.groovy130
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/command/_AbstractFakeCommandHandlerTest.groovy208
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/example/FakeFtpServerSpringConfigurationTest.groovy100
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/AbstractFakeFileSystemTestCase.groovy171
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/AbstractFileSystemEntryTestCase.groovy92
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/AbstractFileSystemTestCase.groovy372
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/DirectoryEntryTest.groovy65
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/FileEntryTest.groovy216
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/PermissionsTest.groovy102
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/TestUnixFakeFileSystem.groovy68
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/UnixDirectoryListingFormatterTest.groovy114
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/UnixFakeFileSystemTest.groovy143
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/WindowsDirectoryListingFormatterTest.groovy81
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/WindowsFakeFileSystemTest.groovy191
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/stub/RunStubFtpServer.groovy43
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/stub/StubFtpServer_RestartTest.groovy58
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/test/AbstractGroovyTestCase.groovy121
-rw-r--r--tags/2.5/src/test/groovy/org/mockftpserver/test/StubResourceBundle.groovy44
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/command/AbstractCommandHandlerTestCase.java230
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/command/CommandTest.java186
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/command/ConnectCommandHandlerTest.java48
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/command/InvocationRecordTest.java226
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/command/ReplyTextBundleUtilTest.java104
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/command/SimpleCompositeCommandHandlerTest.java269
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/command/StaticReplyCommandHandlerTest.java140
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/command/UnsupportedCommandHandlerTest.java48
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/command/_AbstractCommandHandlerTest.java126
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/command/_AbstractStaticReplyCommandHandlerTest.java158
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/command/_AbstractTrackingCommandHandlerTest.java222
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/server/AbstractFtpServerTestCase.java175
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/server/AbstractFtpServer_StartTestCase.java87
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/session/DefaultSessionTest.java482
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/session/DefaultSession_RunTest.java244
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/socket/StubServerSocket.java74
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/socket/StubServerSocketFactory.java48
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/socket/StubSocket.java101
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/socket/StubSocketFactory.java53
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/core/util/AssertTest.java229
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/fake/example/RemoteFileTest.java81
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/fake/example/SimpleUnixFakeFtpServerTest.java49
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/fake/example/SimpleWindowsFakeFtpServerTest.java49
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/fake/example/WindowsFakeFileSystemPermissionsTest.java76
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/StubFtpServerIntegrationTest.java591
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/StubFtpServerTest.java122
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/StubFtpServer_MultipleClientsIntegrationTest.java137
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/StubFtpServer_StartTest.java37
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/AborCommandHandlerTest.java59
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/AcctCommandHandlerTest.java75
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/AlloCommandHandlerTest.java123
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/AppeCommandHandlerTest.java72
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/CdupCommandHandlerTest.java65
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/CwdCommandHandlerTest.java72
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/DeleCommandHandlerTest.java72
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/EprtCommandHandlerTest.java89
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/EpsvCommandHandlerTest.java67
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/FileRetrCommandHandlerTest.java179
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/HelpCommandHandlerTest.java69
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/ListCommandHandlerTest.java75
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/MkdCommandHandlerTest.java73
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/ModeCommandHandlerTest.java75
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/NlstCommandHandlerTest.java75
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/NoopCommandHandlerTest.java61
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/PassCommandHandlerTest.java76
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/PasvCommandHandlerTest.java73
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/PortCommandHandlerTest.java78
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/PwdCommandHandlerTest.java65
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/QuitCommandHandlerTest.java60
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/ReinCommandHandlerTest.java65
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/RestCommandHandlerTest.java78
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/RetrCommandHandlerTest.java134
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/RmdCommandHandlerTest.java72
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/RnfrCommandHandlerTest.java72
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/RntoCommandHandlerTest.java72
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/SiteCommandHandlerTest.java73
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/SmntCommandHandlerTest.java70
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/StatCommandHandlerTest.java101
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/StorCommandHandlerTest.java72
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/StouCommandHandlerTest.java68
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/StruCommandHandlerTest.java75
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/SystCommandHandlerTest.java80
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/TypeCommandHandlerTest.java75
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/UserCommandHandlerTest.java85
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/command/_AbstractStubDataCommandHandlerTest.java177
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/example/FtpWorkingDirectory.java63
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/example/FtpWorkingDirectoryTest.java70
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/example/RemoteFile.java72
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/example/RemoteFileTest.java104
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/stub/example/SpringConfigurationTest.java89
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/test/AbstractTestCase.java369
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/test/IntegrationTest.java27
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/test/LoggingUtil.java147
-rw-r--r--tags/2.5/src/test/java/org/mockftpserver/test/PortTestUtil.java43
-rw-r--r--tags/2.5/src/test/resources/Sample.jpg1
-rw-r--r--tags/2.5/src/test/resources/SampleReplyText.properties17
-rw-r--r--tags/2.5/src/test/resources/fakeftpserver-beans.xml59
-rw-r--r--tags/2.5/src/test/resources/fakeftpserver-permissions-beans.xml75
-rw-r--r--tags/2.5/src/test/resources/log4j.properties6
-rw-r--r--tags/2.5/src/test/resources/stubftpserver-beans.xml66
305 files changed, 30092 insertions, 0 deletions
diff --git a/tags/2.5/.classpath b/tags/2.5/.classpath
new file mode 100644
index 0000000..61fc388
--- /dev/null
+++ b/tags/2.5/.classpath
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src/main/java"/>
+ <classpathentry excluding="**/*.java" kind="src" path="src/main/resources"/>
+ <classpathentry kind="src" path="src/test/java"/>
+ <classpathentry kind="src" path="src/test/groovy"/>
+ <classpathentry excluding="**/*.java" kind="src" path="src/test/resources"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="var" path="M2_REPO/junit-addons/junit-addons/1.4/junit-addons-1.4.jar"/>
+ <classpathentry kind="var" path="M2_REPO/log4j/log4j/1.2.13/log4j-1.2.13.jar"/>
+ <classpathentry kind="var" path="M2_REPO/commons-net/commons-net/1.4.1/commons-net-1.4.1.jar"/>
+ <classpathentry kind="var" path="M2_REPO/junit/junit/3.8.1/junit-3.8.1.jar"/>
+ <classpathentry kind="var" path="M2_REPO/commons-logging/commons-logging/1.1/commons-logging-1.1.jar"/>
+ <classpathentry kind="var" path="M2_REPO/oro/oro/2.0.8/oro-2.0.8.jar"/>
+ <classpathentry kind="var" path="M2_REPO/org/springframework/spring/2.0.7/spring-2.0.7.jar"/>
+ <classpathentry kind="var" path="M2_REPO/easymock/easymock/1.2_Java1.3/easymock-1.2_Java1.3.jar"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3.8.1"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Groovy 1.7.10"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Maven: org.slf4j:slf4j-api:1.6.6"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Maven: org.codehaus.groovy:groovy-all:1.7.10"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Maven: commons-net:commons-net:1.4.1"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Maven: oro:oro:2.0.8"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Maven: easymock:easymock:1.2_Java1.3"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Maven: junit-addons:junit-addons:1.4"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Maven: junit:junit:3.8.1"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Maven: xerces:xercesImpl:2.6.2"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Maven: xerces:xmlParserAPIs:2.6.2"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Maven: org.springframework:spring:2.0.7"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Maven: commons-logging:commons-logging:1.1"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Maven: log4j:log4j:1.2.17"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Maven: logkit:logkit:1.0.1"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Maven: avalon-framework:avalon-framework:4.1.3"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Maven: javax.servlet:servlet-api:2.3"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Maven: org.slf4j:slf4j-log4j12:1.6.6"/>
+ <classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/tags/2.5/.project b/tags/2.5/.project
new file mode 100644
index 0000000..59ef6fc
--- /dev/null
+++ b/tags/2.5/.project
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>MockFtpServer</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.codehaus.groovy.eclipse.groovyBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.codehaus.groovy.eclipse.groovyNature</nature>
+ </natures>
+</projectDescription>
diff --git a/tags/2.5/.settings/org.codehaus.groovy.eclipse.preferences.prefs b/tags/2.5/.settings/org.codehaus.groovy.eclipse.preferences.prefs
new file mode 100644
index 0000000..c422985
--- /dev/null
+++ b/tags/2.5/.settings/org.codehaus.groovy.eclipse.preferences.prefs
@@ -0,0 +1,3 @@
+#Tue Mar 18 19:49:13 EDT 2008
+eclipse.preferences.version=1
+groovy.compiler.output.path=bin-groovy
diff --git a/tags/2.5/.settings/org.eclipse.jdt.core.prefs b/tags/2.5/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..900b691
--- /dev/null
+++ b/tags/2.5/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,268 @@
+#Thu Jan 31 20:14:07 EST 2008
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.4
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=warning
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning
+org.eclipse.jdt.core.compiler.source=1.3
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
+org.eclipse.jdt.core.formatter.comment.line_length=100
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=120
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/tags/2.5/.settings/org.eclipse.jdt.ui.prefs b/tags/2.5/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..538a82b
--- /dev/null
+++ b/tags/2.5/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,5 @@
+#Thu Jan 31 20:14:06 EST 2008
+eclipse.preferences.version=1
+formatter_profile=_Chris Profile
+formatter_settings_version=11
+internal.default.compliance=default
diff --git a/tags/2.5/CHANGELOG.txt b/tags/2.5/CHANGELOG.txt
new file mode 100644
index 0000000..b9bc465
--- /dev/null
+++ b/tags/2.5/CHANGELOG.txt
@@ -0,0 +1,171 @@
+MockFtpServer Change Log
+-------------------------------------------------------------------------------
+
+Changes in version 2.5 (May 2014)
+------------------------------------------
+- Fix #23 PWD response should have commentary: "{0}" is current directory.
+ Also adjusted reply text for MKD to adhere to RFC959: "{0}" created.
+- Removed deprecation of assertSessionReply(int,Object)
+- Fix broken internal links on the web site pages.
+- Update “Log4J Configuration Required to See Log Output” section on “FakeFtpServer – Getting Started” with info for SLF4J.
+- Added MockFtpServer logo image. Thanks to cooltext.com.
+
+
+Changes in version 2.4 (15 Jul 2012)
+------------------------------------------
+- FEATURE #2466395: Remove log4j dependency. Switch to using SLF4J (http://www.slf4j.org/).
+- FEATURE #3544349: Return MockFtpServer information as part of connect 220 response.
+- Upgrade to Groovy 1.7.10; fix Maven site plugin incompatibility.
+- Change “pom.xml” to use SFTP to deploy to Maven repo.
+
+
+Changes in version 2.3 (05 Jun 2011)
+------------------------------------------
+- FEATURE #2996739: Use a dynamically chosen free port number ("ephemeral")for the server control port
+ if you specify 0 for the serverControlPort property of FakeFtpServer or StubFtpServer. Then call
+ getServerControlPort() AFTER start() has been called to determine the actual port number being used.
+ This is useful if you are running on a system where the default port (21) is already in use or cannot
+ be bound from a user process (such as Unix).
+- FEATURE #3304849: Add a new readData(int numBytes) to Session
+- BUG #3103132: shutting down takes too long.
+
+
+Changes in version 2.2 (23 Mar 2010)
+------------------------------------------
+- FakeFtpServer: Support renaming of directories.
+ * BUG FIX: #2823519 "The RnfrCommandHandler is currently set to only support renaming of files": https://sourceforge.net/tracker/?func=detail&atid=1006533&aid=2823519&group_id=208647.
+ * Change fake RNTO and RNFR CommandHandlers to allow renaming directories.
+- BUG FIX: #2828362: "Unit tests using FakeFtpServer are slow" https://sourceforge.net/tracker/?func=detail&atid=1006533&aid=2828362&group_id=208647. DefaultSession.readCommand().
+ * Reduce default socket read interval time to 20ms.
+- BUG FIX: #2953392: "AbstractFtpServer waits endless if binding to port fails" https://sourceforge.net/tracker/?func=detail&atid=1006533&aid=2953392&group_id=208647.
+- FakeFtpServer (AbstractFakeFileSystem): Change rename() to fail if the TO file already exists.
+- Add sample directory listing(s) to online docs for StubFtpServer ListCommandHandler. Update online docs/javadoc describing that multiple directory entries in a file listing can be simulated.
+- PatternUtil: Support plus sign ('+') within wildcard strings. See convertStringWithWildcardsToRegex().
+- TESTS: Rename AbstractTest to AbstractTestCase and AbstractGroovyTest to AbstractGroovyTestCase.
+
+
+Changes in version 2.1 (16 Jun 2009)
+------------------------------------------
+- Added support for IPv6 (EPRT and EPSV commands) to FakeFtpServer and StubFtpServer. Thanks to Fernando Martinez for testing.
+- BUG FIX: #2696898: �WindowsFakeFilesystem DirectoryEntry case sensitivity� (https://sourceforge.net/tracker/?func=detail&atid=1006533&aid=2696898&group_id=208647).
+- BUG FIX: #2797980: �UnixFakeFileSystem IsValidName Regex incorrect� (https://sourceforge.net/tracker/?func=detail&atid=1006533&aid=2797980&group_id=208647).
+- Add getServerControlPort() to AbstractFtpServer.
+- Create HostAndPort class. Refactor both PortCommandHandler(s) and the PortParser classes to use HostAndPort.
+- TESTS: Convert PortParserTest to Groovy.
+
+
+Changes in version 2.0.2 (09 Mar 2009)
+------------------------------------------
+- BUG FIX: #2654577: 'month' in UnixDirectoryListingFormatter is Locale specific. http://sourceforge.net/tracker/index.php?func=detail&aid=2654577&group_id=208647&atid=1006533.
+- BUG FIX: #2653626: Cannot start() server after calling stop(). https://sourceforge.net/tracker2/index.php?func=detail&aid=2653626&group_id=208647&atid=1006533.
+
+
+Changes in version 2.0.1 (09 Feb 2009)
+------------------------------------------
+- BUG FIX: #2543193 �"cd .." and "pwd" don't work properly together� (https://sourceforge.net/tracker2/?func=detail&aid=2543193&group_id=208647&atid=1006533).
+- BUG FIX: #2540548 �Missing new line on directory listing� (https://sourceforge.net/tracker2/?func=detail&aid=2540548&group_id=208647&atid=1006533).
+- BUG FIX: #2540366 �FileEntry.setContents( byte [] contents ) change the content� (https://sourceforge.net/tracker2/?func=detail&aid=2540366&group_id=208647&atid=1006533).
+ - AbstractFtpServer: Use entrySet() to iterate through sessions (From Rijk van Haaften).
+
+
+Changes in version 2.0 (03 Jan 2009)
+------------------------------------------
+- BUG FIX: #2462794 filesystem.pathDoesNotExist key is missing from the ReplyText resource bundle. See https://sourceforge.net/tracker2/?func=detail&aid=2462794&group_id=208647&atid=1006533
+- BUG FIX: #2462973 FileEntry.cloneWithNewPath doesn't clone out field. See https://sourceforge.net/tracker/index.php?func=detail&aid=2462973&group_id=208647&atid=1006533
+- Add note to online doc about requiring Log4J configuration file if you want to see log output.
+
+
+Changes in version 2.0-rc3 (14 Dec 2008)
+------------------------------------------
+- BUG FIX: ClassCastException in AbstractFtpServer during server cleanup.
+- Reorganize sample code and include in online doc.
+
+
+Changes in version 2.0-rc2 (12 Dec 2008)
+------------------------------------------
+- BUG FIX: AbstractFtpServer: Fix bug when iterating through sessions.
+- [BREAKING CHANGE] Move ConnectCommandHandler into core package.
+- [BREAKING CHANGE] Unify Fake and Stub CommandHandlers. Change ServerConfiguration to remove getReplyTextBundle(); make AbstractFakeCommandHandler implement ReplyTextBundleAware instead. Change FakeFtpServer to check for ReplyTextBundleAware and set replyTextBundle. Pull common from stub/fake into AbstractCommandHandler.
+- [BREAKING CHANGE] Rename AbstractCommandHandler to AbstractTrackingCommandHandler.
+- Create AbstractStaticReplyCommandHandler, and make both AbstractStubCommandHandler and StaticReplyCommandHandler subclasses.
+- Create new UnrecognizedCommandHandler, and use to return 502 reply from FakeFtpServer and StubFtpServer when a requested command is not supported.
+- Add support for STAT command; Add systemStatus property to FakeFtpServer.
+- Add support for SMNT command to FakeFtpServer;
+- AbstractFtpServer: Add createSession() method. Make some attributes protected.
+- StubFtpServer: Introduce AbstractStorCommandHandler. Remove final from stub CommandHandler classes.
+- Cleanup code and javadoc
+- DOCS: Add �Requirements� section to main (index) page. Also �Maven� section.
+- DOCS: Add "Configuring CommandHandler for New (Unsupported) Command" and �Creating Your Own Custom CommandHandler Class� sections to StubFtpServer Getting Started Guide.
+- DOCS: Add "Configuring Custom CommandHandlers" section to Getting Started Guide (FakeFtpServer).
+- TESTS: Move AbstractCommandHandlerTest into core package.
+- TESTS: Create sample test of FakeFtpServer with StaticReplyCommandHandler command handler(s).
+- Create source jar during package and include within assemblies.
+- Change "assembly.xml" to include "fakeftpserver*.xml" files.
+
+
+Changes in version 2.0-rc1 (23 Nov 2008)
+------------------------------------------
+NEW FakeFtpServer.
+ This is an alternative "mock" FTP server implementation. FakeFtpServer provides a higher-level abstraction
+ than StubFtpServer. You define a virtual file system, including directories and files, as well as a set of
+ valid user accounts and credentials. The FakeFtpServer then responds with appropriate replies and reply
+ codes based on that configuration. See online documentation for more information.
+StubFtpServer
+- StubFtpServer: Refactored to inherit from common AbstractFtpServer superclass.
+- Change default org.mockftpserver.stub.command.CdupCommandHandler CDUP reply code from 250 to 200.
+- Rename ReplyCodes.SEND_DATA_INITIAL_OK and SEND_DATA_FINAL_OK to TRANSFER_DATA_.. indicate bi-directionality.
+- Rename Command.getRequiredString(int) to getRequiredParameter(int).
+- Change StubFtpServer CommandHandlers to reply with 501 if required command parameters are missing. Changed AbstractCommandHandler and AbstractCommandHandlerTest.
+- Refactor (Stub)PortCommandHandler - pull out common logic into PortParser util class.
+
+
+Changes in version 1.2.4 (01 Sep 2008)
+------------------------------------------
+- BUG FIX: StubFtpServer: Only execute serverSocket.close() if serverSocket != null.
+- BUG FIX: Terminate replies with <CRLF> (\r\n).
+- DOCS: Fix Getting Started Guide code example: setOverrideFinalReplyCode() to setFinalReplyCode().
+- DOCS: Add note to Getting Started Guide about calling setServerControlPort() if on Unix system.
+
+
+Changes in version 1.2.3 (13 Aug 2008)
+------------------------------------------
+- BUG FIX: Tracker item #2047355. Parse host IP numbers as unsigned bytes.
+ See https://sourceforge.net/tracker/index.php?func=detail&aid=2047355&group_id=208647&atid=1006533
+
+
+Changes in version 1.2.2 (27 May 2008)
+------------------------------------------
+- BUG FIX: Move serverThread.start() into synchronized block to avoid server hang if
+ server thread runs faster than main thread.
+ See https://sourceforge.net/tracker/?func=detail&atid=1006533&aid=1925590&group_id=208647
+
+
+Changes in version 1.2.1 (10 Mar 2008)
+------------------------------------------
+- Change Maven POM (pom.xml) to enable sync-ing with central Maven repository (ibiblio).
+
+Changes in version 1.2 (29 Feb 2008)
+------------------------------------------
+- BUG FIX: StubFtpServer: Add wait/notify to ensure that the server starts up and opens the server
+ control port before the start() method returns. This fixes a potential race condition, which
+ shows up on some Linux systems. (Thanks to Aasman Bajaj for identifying the problem and providing the fix)
+- Modify tests to make server port configurable (through "ftp.server.port" system property), allowing
+ tests to run on non-Windows systems.
+
+
+Changes in version 1.1 (20 Feb 2008)
+------------------------------------------
+- StubFtpServer: Allow configuring server control connection port other than the default (21).
+- AbstractTest: Add some test convenience methods.
+
+
+Changes in version 1.0 final (11 Dec 2007)
+------------------------------------------
+- Implement default CommandHandlers for NLST, REIN, SMNT, SITE, ABOR and ALLO commands.
+- Handle command names in any case.
+- CwdCommandHandler: Fix PATHNAME_KEY constant value; change to "pathname".
+
+
+Changes in version 1.0-RC1 (1 Nov 2007)
+---------------------------------------
+Initial release. \ No newline at end of file
diff --git a/tags/2.5/LICENSE.txt b/tags/2.5/LICENSE.txt
new file mode 100644
index 0000000..29f81d8
--- /dev/null
+++ b/tags/2.5/LICENSE.txt
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/tags/2.5/MockFtpServer.eml b/tags/2.5/MockFtpServer.eml
new file mode 100644
index 0000000..762ff2c
--- /dev/null
+++ b/tags/2.5/MockFtpServer.eml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<component LANGUAGE_LEVEL="JDK_1_4" inheritJdk="true">
+ <output-test url="file://$MODULE_DIR$/target/test-classes"/>
+ <contentEntry url="file://$MODULE_DIR$">
+ <testFolder url="file://$MODULE_DIR$/src/test/java"/>
+ <testFolder url="file://$MODULE_DIR$/src/test/groovy"/>
+ <testFolder url="file://$MODULE_DIR$/src/test/resources"/>
+ <excludeFolder url="file://$MODULE_DIR$/target"/>
+ </contentEntry>
+ <lib name="Maven: org.codehaus.groovy:groovy-all:1.7.10" scope="TEST"/>
+ <lib name="Maven: commons-net:commons-net:1.4.1" scope="TEST"/>
+ <lib name="Maven: oro:oro:2.0.8" scope="TEST"/>
+ <lib name="Maven: easymock:easymock:1.2_Java1.3" scope="TEST"/>
+ <lib name="Maven: junit-addons:junit-addons:1.4" scope="TEST"/>
+ <lib name="Maven: junit:junit:3.8.1" scope="TEST"/>
+ <lib name="Maven: xerces:xercesImpl:2.6.2" scope="TEST"/>
+ <lib name="Maven: xerces:xmlParserAPIs:2.6.2" scope="TEST"/>
+ <lib name="Maven: org.springframework:spring:2.0.7" scope="TEST"/>
+ <lib name="Maven: commons-logging:commons-logging:1.1" scope="TEST"/>
+ <lib name="Maven: log4j:log4j:1.2.17" scope="TEST"/>
+ <lib name="Maven: logkit:logkit:1.0.1" scope="TEST"/>
+ <lib name="Maven: avalon-framework:avalon-framework:4.1.3" scope="TEST"/>
+ <lib name="Maven: javax.servlet:servlet-api:2.3" scope="TEST"/>
+ <lib name="Maven: org.slf4j:slf4j-log4j12:1.6.6" scope="TEST"/>
+ <levels>
+ <level name="Groovy 1.7.10" value="project"/>
+ <level name="Maven: org.slf4j:slf4j-api:1.6.6" value="project"/>
+ <level name="Maven: org.codehaus.groovy:groovy-all:1.7.10" value="project"/>
+ <level name="Maven: commons-net:commons-net:1.4.1" value="project"/>
+ <level name="Maven: oro:oro:2.0.8" value="project"/>
+ <level name="Maven: easymock:easymock:1.2_Java1.3" value="project"/>
+ <level name="Maven: junit-addons:junit-addons:1.4" value="project"/>
+ <level name="Maven: junit:junit:3.8.1" value="project"/>
+ <level name="Maven: xerces:xercesImpl:2.6.2" value="project"/>
+ <level name="Maven: xerces:xmlParserAPIs:2.6.2" value="project"/>
+ <level name="Maven: org.springframework:spring:2.0.7" value="project"/>
+ <level name="Maven: commons-logging:commons-logging:1.1" value="project"/>
+ <level name="Maven: log4j:log4j:1.2.17" value="project"/>
+ <level name="Maven: logkit:logkit:1.0.1" value="project"/>
+ <level name="Maven: avalon-framework:avalon-framework:4.1.3" value="project"/>
+ <level name="Maven: javax.servlet:servlet-api:2.3" value="project"/>
+ <level name="Maven: org.slf4j:slf4j-log4j12:1.6.6" value="project"/>
+ </levels>
+</component>
diff --git a/tags/2.5/MockFtpServer.iml b/tags/2.5/MockFtpServer.iml
new file mode 100644
index 0000000..565b9a1
--- /dev/null
+++ b/tags/2.5/MockFtpServer.iml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module classpath="eclipse" org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" relativePaths="true" type="JAVA_MODULE" version="4">
+ <component name="FacetManager">
+ <facet type="Spring" name="Spring">
+ <configuration />
+ </facet>
+ </component>
+</module>
+
diff --git a/tags/2.5/MockFtpServer.ipr b/tags/2.5/MockFtpServer.ipr
new file mode 100644
index 0000000..f3377e5
--- /dev/null
+++ b/tags/2.5/MockFtpServer.ipr
@@ -0,0 +1,522 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="BuildJarProjectSettings">
+ <option name="BUILD_JARS_ON_MAKE" value="false" />
+ </component>
+ <component name="CodeStyleProjectProfileManger">
+ <option name="PROJECT_PROFILE" />
+ <option name="USE_PROJECT_LEVEL_SETTINGS" value="false" />
+ </component>
+ <component name="CodeStyleSettingsManager">
+ <option name="PER_PROJECT_SETTINGS">
+ <value>
+ <ADDITIONAL_INDENT_OPTIONS fileType="java">
+ <option name="INDENT_SIZE" value="4" />
+ <option name="CONTINUATION_INDENT_SIZE" value="8" />
+ <option name="TAB_SIZE" value="4" />
+ <option name="USE_TAB_CHARACTER" value="false" />
+ <option name="SMART_TABS" value="false" />
+ <option name="LABEL_INDENT_SIZE" value="0" />
+ <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+ <option name="USE_RELATIVE_INDENTS" value="false" />
+ </ADDITIONAL_INDENT_OPTIONS>
+ <ADDITIONAL_INDENT_OPTIONS fileType="js">
+ <option name="INDENT_SIZE" value="4" />
+ <option name="CONTINUATION_INDENT_SIZE" value="8" />
+ <option name="TAB_SIZE" value="4" />
+ <option name="USE_TAB_CHARACTER" value="false" />
+ <option name="SMART_TABS" value="false" />
+ <option name="LABEL_INDENT_SIZE" value="0" />
+ <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+ <option name="USE_RELATIVE_INDENTS" value="false" />
+ </ADDITIONAL_INDENT_OPTIONS>
+ <ADDITIONAL_INDENT_OPTIONS fileType="jsp">
+ <option name="INDENT_SIZE" value="4" />
+ <option name="CONTINUATION_INDENT_SIZE" value="8" />
+ <option name="TAB_SIZE" value="4" />
+ <option name="USE_TAB_CHARACTER" value="false" />
+ <option name="SMART_TABS" value="false" />
+ <option name="LABEL_INDENT_SIZE" value="0" />
+ <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+ <option name="USE_RELATIVE_INDENTS" value="false" />
+ </ADDITIONAL_INDENT_OPTIONS>
+ <ADDITIONAL_INDENT_OPTIONS fileType="xml">
+ <option name="INDENT_SIZE" value="4" />
+ <option name="CONTINUATION_INDENT_SIZE" value="8" />
+ <option name="TAB_SIZE" value="4" />
+ <option name="USE_TAB_CHARACTER" value="false" />
+ <option name="SMART_TABS" value="false" />
+ <option name="LABEL_INDENT_SIZE" value="0" />
+ <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+ <option name="USE_RELATIVE_INDENTS" value="false" />
+ </ADDITIONAL_INDENT_OPTIONS>
+ </value>
+ </option>
+ </component>
+ <component name="CompilerConfiguration">
+ <option name="DEFAULT_COMPILER" value="Javac" />
+ <resourceExtensions>
+ <entry name=".+\.(properties|xml|html|dtd|tld)" />
+ <entry name=".+\.(gif|png|jpeg|jpg)" />
+ </resourceExtensions>
+ <wildcardResourcePatterns>
+ <entry name="?*.properties" />
+ <entry name="?*.xml" />
+ <entry name="?*.gif" />
+ <entry name="?*.png" />
+ <entry name="?*.jpeg" />
+ <entry name="?*.jpg" />
+ <entry name="?*.html" />
+ <entry name="?*.dtd" />
+ <entry name="?*.tld" />
+ </wildcardResourcePatterns>
+ <annotationProcessing>
+ <profile default="true" name="Default" enabled="false">
+ <processorPath useClasspath="true" />
+ </profile>
+ <profile default="false" name="Maven default annotation processors profile" enabled="true">
+ <sourceOutputDir name="target/generated-sources/annotations" />
+ <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
+ <outputRelativeToContentRoot value="true" />
+ <processorPath useClasspath="true" />
+ <module name="MockFtpServer" />
+ </profile>
+ </annotationProcessing>
+ <bytecodeTargetLevel>
+ <module name="MockFtpServer" target="1.4" />
+ </bytecodeTargetLevel>
+ </component>
+ <component name="CopyrightManager" default="">
+ <module2copyright />
+ </component>
+ <component name="DependenciesAnalyzeManager">
+ <option name="myForwardDirection" value="false" />
+ </component>
+ <component name="DependencyValidationManager">
+ <option name="SKIP_IMPORT_STATEMENTS" value="false" />
+ </component>
+ <component name="EclipseCompilerSettings">
+ <option name="GENERATE_NO_WARNINGS" value="true" />
+ <option name="DEPRECATION" value="false" />
+ </component>
+ <component name="EclipseEmbeddedCompilerSettings">
+ <option name="DEBUGGING_INFO" value="true" />
+ <option name="GENERATE_NO_WARNINGS" value="true" />
+ <option name="DEPRECATION" value="false" />
+ <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+ <option name="MAXIMUM_HEAP_SIZE" value="128" />
+ </component>
+ <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
+ <component name="EntryPointsManager">
+ <entry_points version="2.0" />
+ </component>
+ <component name="IdProvider" IDEtalkID="8A6189ED5C30FC95107AD305B649986C" />
+ <component name="JavadocGenerationManager">
+ <option name="OUTPUT_DIRECTORY" />
+ <option name="OPTION_SCOPE" value="protected" />
+ <option name="OPTION_HIERARCHY" value="true" />
+ <option name="OPTION_NAVIGATOR" value="true" />
+ <option name="OPTION_INDEX" value="true" />
+ <option name="OPTION_SEPARATE_INDEX" value="true" />
+ <option name="OPTION_DOCUMENT_TAG_USE" value="false" />
+ <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false" />
+ <option name="OPTION_DOCUMENT_TAG_VERSION" value="false" />
+ <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true" />
+ <option name="OPTION_DEPRECATED_LIST" value="true" />
+ <option name="OTHER_OPTIONS" value="" />
+ <option name="HEAP_SIZE" />
+ <option name="LOCALE" />
+ <option name="OPEN_IN_BROWSER" value="true" />
+ </component>
+ <component name="MavenProjectsManager">
+ <option name="originalFiles">
+ <list>
+ <option value="$PROJECT_DIR$/pom.xml" />
+ </list>
+ </option>
+ </component>
+ <component name="Palette2">
+ <group name="Swing">
+ <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
+ </item>
+ <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
+ </item>
+ <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
+ </item>
+ <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
+ <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
+ </item>
+ <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
+ <initial-values>
+ <property name="text" value="Button" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+ <initial-values>
+ <property name="text" value="RadioButton" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+ <initial-values>
+ <property name="text" value="CheckBox" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
+ <initial-values>
+ <property name="text" value="Label" />
+ </initial-values>
+ </item>
+ <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+ <preferred-size width="150" height="-1" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+ <preferred-size width="150" height="-1" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+ <preferred-size width="150" height="-1" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
+ </item>
+ <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+ <preferred-size width="150" height="50" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+ <preferred-size width="200" height="200" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+ <preferred-size width="200" height="200" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+ </item>
+ <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+ </item>
+ <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
+ </item>
+ <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
+ </item>
+ <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
+ <preferred-size width="-1" height="20" />
+ </default-constraints>
+ </item>
+ <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+ <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
+ </item>
+ <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+ <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
+ </item>
+ </group>
+ </component>
+ <component name="ProjectCodeStyleSettingsManager">
+ <option name="PER_PROJECT_SETTINGS">
+ <value>
+ <XML>
+ <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
+ </XML>
+ <codeStyleSettings language="JavaScript">
+ <indentOptions>
+ <option name="CONTINUATION_INDENT_SIZE" value="8" />
+ </indentOptions>
+ </codeStyleSettings>
+ </value>
+ </option>
+ </component>
+ <component name="ProjectDetails">
+ <option name="projectName" value="MockFtpServer" />
+ </component>
+ <component name="ProjectDictionaryState">
+ <dictionary name="Chris">
+ <words>
+ <w>inet</w>
+ <w>mockftpserver</w>
+ <w>pathname</w>
+ <w>programmatically</w>
+ <w>rnfr</w>
+ <w>rnto</w>
+ <w>subdirectory</w>
+ </words>
+ </dictionary>
+ </component>
+ <component name="ProjectKey">
+ <option name="state" value="https://mockftpserver.svn.sourceforge.net/svnroot/mockftpserver/MockFtpServer/MockFtpServer.ipr" />
+ </component>
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/MockFtpServer.iml" filepath="$PROJECT_DIR$/MockFtpServer.iml" />
+ </modules>
+ </component>
+ <component name="ProjectResources">
+ <default-html-doctype>http://www.w3.org/1999/xhtml</default-html-doctype>
+ </component>
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_1_4" assert-keyword="true" jdk-15="false" project-jdk-name="$project_jdk_name$" project-jdk-type="JavaSDK">
+ <output url="file://$PROJECT_DIR$/out" />
+ </component>
+ <component name="ResourceManagerContainer">
+ <option name="myResourceBundles">
+ <value>
+ <list size="0" />
+ </value>
+ </option>
+ </component>
+ <component name="SvnBranchConfigurationManager">
+ <option name="myConfigurationMap">
+ <map>
+ <entry key="$PROJECT_DIR$">
+ <value>
+ <SvnBranchConfiguration>
+ <option name="branchUrls">
+ <list>
+ <option value="https://mockftpserver.svn.sourceforge.net/svnroot/mockftpserver/branches/1.x_Branch" />
+ <option value="https://mockftpserver.svn.sourceforge.net/svnroot/mockftpserver/tags" />
+ <option value="https://mockftpserver.svn.sourceforge.net/svnroot/mockftpserver/tags/1.2.4" />
+ </list>
+ </option>
+ <option name="trunkUrl" value="https://mockftpserver.svn.sourceforge.net/svnroot/mockftpserver/MockFtpServer" />
+ </SvnBranchConfiguration>
+ </value>
+ </entry>
+ </map>
+ </option>
+ <option name="myVersion" value="124" />
+ <option name="mySupportsUserInfoFilter" value="true" />
+ </component>
+ <component name="VcsDirectoryMappings">
+ <mapping directory="" vcs="svn" />
+ </component>
+ <component name="WebServicesPlugin" addRequiredLibraries="true" />
+ <component name="libraryTable">
+ <library name="Groovy 1.7.10">
+ <CLASSES>
+ <root url="jar://$M2_REPO$/org/codehaus/groovy/groovy-all/1.7.10/groovy-all-1.7.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ <library name="Maven: avalon-framework:avalon-framework:4.1.3">
+ <CLASSES>
+ <root url="jar://$M2_REPO$/avalon-framework/avalon-framework/4.1.3/avalon-framework-4.1.3.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$M2_REPO$/avalon-framework/avalon-framework/4.1.3/avalon-framework-4.1.3-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$M2_REPO$/avalon-framework/avalon-framework/4.1.3/avalon-framework-4.1.3-sources.jar!/" />
+ </SOURCES>
+ </library>
+ <library name="Maven: commons-logging:commons-logging:1.1">
+ <CLASSES>
+ <root url="jar://$M2_REPO$/commons-logging/commons-logging/1.1/commons-logging-1.1.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$M2_REPO$/commons-logging/commons-logging/1.1/commons-logging-1.1-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$M2_REPO$/commons-logging/commons-logging/1.1/commons-logging-1.1-sources.jar!/" />
+ </SOURCES>
+ </library>
+ <library name="Maven: commons-net:commons-net:1.4.1">
+ <CLASSES>
+ <root url="jar://$M2_REPO$/commons-net/commons-net/1.4.1/commons-net-1.4.1.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$M2_REPO$/commons-net/commons-net/1.4.1/commons-net-1.4.1-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$M2_REPO$/commons-net/commons-net/1.4.1/commons-net-1.4.1-sources.jar!/" />
+ </SOURCES>
+ </library>
+ <library name="Maven: easymock:easymock:1.2_Java1.3">
+ <CLASSES>
+ <root url="jar://$M2_REPO$/easymock/easymock/1.2_Java1.3/easymock-1.2_Java1.3.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$M2_REPO$/easymock/easymock/1.2_Java1.3/easymock-1.2_Java1.3-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$M2_REPO$/easymock/easymock/1.2_Java1.3/easymock-1.2_Java1.3-sources.jar!/" />
+ </SOURCES>
+ </library>
+ <library name="Maven: javax.servlet:servlet-api:2.3">
+ <CLASSES>
+ <root url="jar://$M2_REPO$/javax/servlet/servlet-api/2.3/servlet-api-2.3.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$M2_REPO$/javax/servlet/servlet-api/2.3/servlet-api-2.3-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$M2_REPO$/javax/servlet/servlet-api/2.3/servlet-api-2.3-sources.jar!/" />
+ </SOURCES>
+ </library>
+ <library name="Maven: junit-addons:junit-addons:1.4">
+ <CLASSES>
+ <root url="jar://$M2_REPO$/junit-addons/junit-addons/1.4/junit-addons-1.4.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$M2_REPO$/junit-addons/junit-addons/1.4/junit-addons-1.4-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$M2_REPO$/junit-addons/junit-addons/1.4/junit-addons-1.4-sources.jar!/" />
+ </SOURCES>
+ </library>
+ <library name="Maven: junit:junit:3.8.1">
+ <CLASSES>
+ <root url="jar://$M2_REPO$/junit/junit/3.8.1/junit-3.8.1.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$M2_REPO$/junit/junit/3.8.1/junit-3.8.1-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$M2_REPO$/junit/junit/3.8.1/junit-3.8.1-sources.jar!/" />
+ </SOURCES>
+ </library>
+ <library name="Maven: log4j:log4j:1.2.17">
+ <CLASSES>
+ <root url="jar://$M2_REPO$/log4j/log4j/1.2.17/log4j-1.2.17.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$M2_REPO$/log4j/log4j/1.2.17/log4j-1.2.17-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$M2_REPO$/log4j/log4j/1.2.17/log4j-1.2.17-sources.jar!/" />
+ </SOURCES>
+ </library>
+ <library name="Maven: logkit:logkit:1.0.1">
+ <CLASSES>
+ <root url="jar://$M2_REPO$/logkit/logkit/1.0.1/logkit-1.0.1.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$M2_REPO$/logkit/logkit/1.0.1/logkit-1.0.1-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$M2_REPO$/logkit/logkit/1.0.1/logkit-1.0.1-sources.jar!/" />
+ </SOURCES>
+ </library>
+ <library name="Maven: org.codehaus.groovy:groovy-all:1.7.10">
+ <CLASSES>
+ <root url="jar://$M2_REPO$/org/codehaus/groovy/groovy-all/1.7.10/groovy-all-1.7.10.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$M2_REPO$/org/codehaus/groovy/groovy-all/1.7.10/groovy-all-1.7.10-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$M2_REPO$/org/codehaus/groovy/groovy-all/1.7.10/groovy-all-1.7.10-sources.jar!/" />
+ </SOURCES>
+ </library>
+ <library name="Maven: org.slf4j:slf4j-api:1.6.6">
+ <CLASSES>
+ <root url="jar://$M2_REPO$/org/slf4j/slf4j-api/1.6.6/slf4j-api-1.6.6.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$M2_REPO$/org/slf4j/slf4j-api/1.6.6/slf4j-api-1.6.6-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$M2_REPO$/org/slf4j/slf4j-api/1.6.6/slf4j-api-1.6.6-sources.jar!/" />
+ </SOURCES>
+ </library>
+ <library name="Maven: org.slf4j:slf4j-log4j12:1.6.6">
+ <CLASSES>
+ <root url="jar://$M2_REPO$/org/slf4j/slf4j-log4j12/1.6.6/slf4j-log4j12-1.6.6.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$M2_REPO$/org/slf4j/slf4j-log4j12/1.6.6/slf4j-log4j12-1.6.6-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$M2_REPO$/org/slf4j/slf4j-log4j12/1.6.6/slf4j-log4j12-1.6.6-sources.jar!/" />
+ </SOURCES>
+ </library>
+ <library name="Maven: org.springframework:spring:2.0.7">
+ <CLASSES>
+ <root url="jar://$M2_REPO$/org/springframework/spring/2.0.7/spring-2.0.7.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$M2_REPO$/org/springframework/spring/2.0.7/spring-2.0.7-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$M2_REPO$/org/springframework/spring/2.0.7/spring-2.0.7-sources.jar!/" />
+ </SOURCES>
+ </library>
+ <library name="Maven: oro:oro:2.0.8">
+ <CLASSES>
+ <root url="jar://$M2_REPO$/oro/oro/2.0.8/oro-2.0.8.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$M2_REPO$/oro/oro/2.0.8/oro-2.0.8-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$M2_REPO$/oro/oro/2.0.8/oro-2.0.8-sources.jar!/" />
+ </SOURCES>
+ </library>
+ <library name="Maven: xerces:xercesImpl:2.6.2">
+ <CLASSES>
+ <root url="jar://$M2_REPO$/xerces/xercesImpl/2.6.2/xercesImpl-2.6.2.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$M2_REPO$/xerces/xercesImpl/2.6.2/xercesImpl-2.6.2-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$M2_REPO$/xerces/xercesImpl/2.6.2/xercesImpl-2.6.2-sources.jar!/" />
+ </SOURCES>
+ </library>
+ <library name="Maven: xerces:xmlParserAPIs:2.6.2">
+ <CLASSES>
+ <root url="jar://$M2_REPO$/xerces/xmlParserAPIs/2.6.2/xmlParserAPIs-2.6.2.jar!/" />
+ </CLASSES>
+ <JAVADOC>
+ <root url="jar://$M2_REPO$/xerces/xmlParserAPIs/2.6.2/xmlParserAPIs-2.6.2-javadoc.jar!/" />
+ </JAVADOC>
+ <SOURCES>
+ <root url="jar://$M2_REPO$/xerces/xmlParserAPIs/2.6.2/xmlParserAPIs-2.6.2-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </component>
+</project>
+
diff --git a/tags/2.5/README.txt b/tags/2.5/README.txt
new file mode 100644
index 0000000..582fcc4
--- /dev/null
+++ b/tags/2.5/README.txt
@@ -0,0 +1,30 @@
+MockFtpServer version ${project.version}
+-------------------------------------------------------------------------------
+${project.url}
+
+The MockFtpServer project provides mock/dummy FTP server implementations for testing FTP client
+code. Two FTP Server implementations are provided, each at a different level of abstraction.
+
+FakeFtpServer provides a higher-level abstraction. You define a virtual file system, including
+directories and files, as well as a set of valid user accounts and credentials. The FakeFtpServer
+then responds with appropriate replies and reply codes based on that configuration.
+
+StubFtpServer, on the other hand, is a lower-level "stub" implementation. You configure the
+individual FTP server commands to return custom data or reply codes, allowing simulation of
+either success or failure scenarios. You can also verify expected command invocations.
+
+MockFtpServer is written in Java, and is ideally suited to testing Java code. But because
+communication with the FTP server is across sockets and TCP/IP, it can be used to test FTP client
+code written in any language.
+
+See the online documentation for more information.
+
+See the FTP Protocol Spec (http://www.ietf.org/rfc/rfc0959.txt) for information about
+FTP, commands, reply codes, etc..
+
+DEPENDENCIES
+
+MockFtpServer requires
+ - Java (JDK) version 1.4 or later
+ - The Log4J jar, version 1.2.13 or later, accessible on the CLASSPATH
+ (http://logging.apache.org/log4j/index.html).
diff --git a/tags/2.5/pom.xml b/tags/2.5/pom.xml
new file mode 100644
index 0000000..d13fe08
--- /dev/null
+++ b/tags/2.5/pom.xml
@@ -0,0 +1,325 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.mockftpserver</groupId>
+ <artifactId>MockFtpServer</artifactId>
+ <name>MockFtpServer</name>
+ <description>
+ The MockFtpServer project provides mock/dummy FTP server implementations for testing FTP client
+ code. Two FTP Server implementations are provided, each at a different level of abstraction.
+ FakeFtpServer provides a higher-level abstraction. You define a virtual file system, including
+ directories and files, as well as a set of valid user accounts and credentials. The FakeFtpServer
+ then responds with appropriate replies and reply codes based on that configuration.
+ StubFtpServer, on the other hand, is a lower-level "stub" implementation. You configure the
+ individual FTP server commands to return custom data or reply codes, allowing simulation of
+ either success or failure scenarios. You can also verify expected command invocations.
+ </description>
+ <packaging>jar</packaging>
+ <version>2.5</version>
+ <url>http://mockftpserver.sourceforge.net/</url>
+
+ <scm>
+ <connection>scm:svn:https://mockftpserver.svn.sourceforge.net/svnroot/mockftpserver/MockFtpServer</connection>
+ <developerConnection>scm:svn:https://mockftpserver.svn.sourceforge.net/svnroot/mockftpserver/MockFtpServer
+ </developerConnection>
+ <url>https://mockftpserver.svn.sourceforge.net/svnroot/mockftpserver</url>
+ </scm>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>1.6.6</version>
+ </dependency>
+
+ <!-- TESTING ONLY -->
+
+ <dependency>
+ <groupId>org.codehaus.groovy</groupId>
+ <artifactId>groovy-all</artifactId>
+ <version>1.7.10</version>
+ <scope>test</scope>
+ </dependency>
+
+
+ <dependency>
+ <groupId>commons-net</groupId>
+ <artifactId>commons-net</artifactId>
+ <version>1.4.1</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <version>1.2_Java1.3</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit-addons</groupId>
+ <artifactId>junit-addons</artifactId>
+ <version>1.4</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring</artifactId>
+ <version>2.0.7</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <version>1.6.6</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- Transitive dependency.
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ -->
+
+ </dependencies>
+
+ <distributionManagement>
+ <snapshotRepository>
+ <id>ossrh</id>
+ <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+ </snapshotRepository>
+ <repository>
+ <id>ossrh</id>
+ <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+ </repository>
+ </distributionManagement>
+
+ <build>
+ <plugins>
+
+ <plugin>
+ <groupId>org.codehaus.gmaven</groupId>
+ <artifactId>gmaven-plugin</artifactId>
+ <version>1.3</version>
+ <configuration>
+ <providerSelection>1.7</providerSelection>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>generateStubs</goal>
+ <goal>compile</goal>
+ <goal>generateTestStubs</goal>
+ <goal>testCompile</goal>
+ <!--if you want joint compilation, add stub generation goals here-->
+ </goals>
+ </execution>
+ </executions>
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.gmaven.runtime</groupId>
+ <artifactId>gmaven-runtime-1.7</artifactId>
+ <version>1.3</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.codehaus.groovy</groupId>
+ <artifactId>groovy-all</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.groovy</groupId>
+ <artifactId>groovy-all</artifactId>
+ <version>1.7.10</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <verbose>true</verbose>
+ <source>1.4</source>
+ <target>1.4</target>
+ <fork>true</fork>
+ </configuration>
+ </plugin>
+
+ <!-- clean coverage data before collecting -->
+ <plugin>
+ <artifactId>cobertura-maven-plugin</artifactId>
+ <groupId>org.codehaus.mojo</groupId>
+ <version>2.0</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>clean</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifestEntries>
+ <MockFtpServer-Version>${pom.version}</MockFtpServer-Version>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </plugin>
+
+ <!--<plugin>-->
+ <!--<groupId>org.apache.maven.plugins</groupId>-->
+ <!--<artifactId>maven-source-plugin</artifactId>-->
+ <!--<executions>-->
+ <!--<execution>-->
+ <!--<id>attach-sources</id>-->
+ <!--<phase>package</phase>-->
+ <!--<goals>-->
+ <!--<goal>jar</goal>-->
+ <!--</goals>-->
+ <!--</execution>-->
+ <!--</executions>-->
+ <!--</plugin>-->
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>2.2.1</version>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <goals>
+ <goal>jar-no-fork</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>2.9.1</version>
+ <executions>
+ <execution>
+ <id>attach-javadocs</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <descriptors>
+ <descriptor>src/assembly/assembly.xml</descriptor>
+ </descriptors>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-release-plugin</artifactId>
+ <configuration>
+ <preparationGoals>clean site assembly:assembly</preparationGoals>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-site-plugin</artifactId>
+ <version>2.1</version>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <version>1.5</version>
+ <executions>
+ <execution>
+ <id>sign-artifacts</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>sign</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ </plugins>
+ </build>
+
+ <reporting>
+ <plugins>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-project-info-reports-plugin</artifactId>
+ <version>2.2</version>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>dependencies</report>
+ <!-- <report>project-team</report> -->
+ <!-- <report>mailing-list</report> -->
+ <!-- <report>cim</report> -->
+ <!-- <report>issue-tracking</report> -->
+ <report>license</report>
+ <!-- <report>scm</report> -->
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>cobertura-maven-plugin</artifactId>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ </plugin>
+
+ </plugins>
+ </reporting>
+
+ <developers>
+ <developer>
+ <id>chrismair</id>
+ <name>Chris Mair</name>
+ <email>chrismair@users.sourceforge.net</email>
+ <url>https://sourceforge.net/users/chrismair</url>
+ <roles>
+ <role>developer</role>
+ </roles>
+ <timezone>-4</timezone>
+ </developer>
+ </developers>
+
+ <licenses>
+ <license>
+ <name>Apache 2</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+
+</project> \ No newline at end of file
diff --git a/tags/2.5/src/assembly/assembly.xml b/tags/2.5/src/assembly/assembly.xml
new file mode 100644
index 0000000..b0d1dd1
--- /dev/null
+++ b/tags/2.5/src/assembly/assembly.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0"?>
+<assembly>
+ <id>bin</id>
+ <formats>
+ <format>tar.gz</format>
+ <format>zip</format>
+ </formats>
+ <includeSiteDirectory>false</includeSiteDirectory>
+
+ <files>
+ <file>
+ <source>README.txt</source>
+ <outputDirectory>/</outputDirectory>
+ <filtered>true</filtered>
+ </file>
+ </files>
+
+ <fileSets>
+ <fileSet>
+ <includes>
+ <include>CHANGELOG*</include>
+ <include>LICENSE*</include>
+ <include>NOTICE*</include>
+ <include>pom.xml</include>
+ </includes>
+ </fileSet>
+
+ <fileSet>
+ <directory>target</directory>
+ <outputDirectory></outputDirectory>
+ <includes>
+ <include>*.jar</include>
+ </includes>
+ </fileSet>
+
+ <fileSet>
+ <directory>src</directory>
+ <useDefaultExcludes>true</useDefaultExcludes>
+ </fileSet>
+
+ <fileSet>
+ <directory>target/site</directory>
+ <outputDirectory>docs</outputDirectory>
+ <includes>
+ <include>apidocs/**</include>
+ <include>css/**</include>
+ <include>images/**</include>
+ <include>*.html</include>
+ </includes>
+ </fileSet>
+
+ <fileSet>
+ <directory>samples</directory>
+ <outputDirectory>samples</outputDirectory>
+ <includes>
+ <include>rulesets/**</include>
+ <include>src/**</include>
+ <include>*.bat</include>
+ <include>*.groovy</include>
+ <include>*.xml</include>
+ </includes>
+ </fileSet>
+ </fileSets>
+
+ <dependencySets>
+ <dependencySet>
+ <unpack>false</unpack>
+ <scope>runtime</scope>
+ <outputDirectory>lib</outputDirectory>
+ <includes>
+ <include>*:log4j</include>
+ </includes>
+ </dependencySet>
+ </dependencySets>
+
+</assembly>
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/CommandSyntaxException.java b/tags/2.5/src/main/java/org/mockftpserver/core/CommandSyntaxException.java
new file mode 100644
index 0000000..688eb9e
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/CommandSyntaxException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core;
+
+/**
+ * Represents an error indicating that a server command has been received that
+ * has invalid syntax. For instance, the command may be missing a required parameter.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class CommandSyntaxException extends MockFtpServerException {
+
+ /**
+ * @param message
+ */
+ public CommandSyntaxException(String message) {
+ super(message);
+ }
+
+ /**
+ * @param cause
+ */
+ public CommandSyntaxException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public CommandSyntaxException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/IllegalStateException.java b/tags/2.5/src/main/java/org/mockftpserver/core/IllegalStateException.java
new file mode 100644
index 0000000..64d8d1e
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/IllegalStateException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core;
+
+/**
+ * Represents an error indicating that the server is in an illegal state, or
+ * that a server command is invoked when its preconditions have not been met.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class IllegalStateException extends MockFtpServerException {
+
+ /**
+ * @param message
+ */
+ public IllegalStateException(String message) {
+ super(message);
+ }
+
+ /**
+ * @param cause
+ */
+ public IllegalStateException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public IllegalStateException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/MockFtpServerException.java b/tags/2.5/src/main/java/org/mockftpserver/core/MockFtpServerException.java
new file mode 100644
index 0000000..d412ecd
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/MockFtpServerException.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core;
+
+/**
+ * Represents an error specific to the MockFtpServer project.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public class MockFtpServerException extends RuntimeException {
+
+ /**
+ * Create a new instance with the specified detail message and no cause
+ * @param message - the exception detail message
+ */
+ public MockFtpServerException(String message) {
+ super(message);
+ }
+
+ /**
+ * Create a new instance with the specified detail message and no cause
+ * @param cause - the Throwable cause
+ */
+ public MockFtpServerException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Create a new instance with the specified detail message and cause
+ * @param message - the exception detail message
+ * @param cause - the Throwable cause
+ */
+ public MockFtpServerException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/NotLoggedInException.java b/tags/2.5/src/main/java/org/mockftpserver/core/NotLoggedInException.java
new file mode 100644
index 0000000..e779904
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/NotLoggedInException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core;
+
+/**
+ * Represents an error indicating that the current user has not yet logged in, but
+ * is required to in order to invoke the requested command.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class NotLoggedInException extends MockFtpServerException {
+
+ /**
+ * @param message
+ */
+ public NotLoggedInException(String message) {
+ super(message);
+ }
+
+ /**
+ * @param cause
+ */
+ public NotLoggedInException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public NotLoggedInException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/command/AbstractCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/core/command/AbstractCommandHandler.java
new file mode 100644
index 0000000..a384bb0
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/command/AbstractCommandHandler.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.util.Assert;
+
+import java.util.ResourceBundle;
+
+/**
+ * The abstract superclass for CommandHandler classes.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public abstract class AbstractCommandHandler implements CommandHandler, ReplyTextBundleAware {
+
+ protected final Logger LOG = LoggerFactory.getLogger(getClass());
+
+ private ResourceBundle replyTextBundle;
+
+ //-------------------------------------------------------------------------
+ // Support for reply text ResourceBundle
+ //-------------------------------------------------------------------------
+
+ /**
+ * Return the ResourceBundle containing the reply text messages
+ *
+ * @return the replyTextBundle
+ * @see ReplyTextBundleAware#getReplyTextBundle()
+ */
+ public ResourceBundle getReplyTextBundle() {
+ return replyTextBundle;
+ }
+
+ /**
+ * Set the ResourceBundle containing the reply text messages
+ *
+ * @param replyTextBundle - the replyTextBundle to set
+ * @see ReplyTextBundleAware#setReplyTextBundle(java.util.ResourceBundle)
+ */
+ public void setReplyTextBundle(ResourceBundle replyTextBundle) {
+ this.replyTextBundle = replyTextBundle;
+ }
+
+ // -------------------------------------------------------------------------
+ // Utility methods for subclasses
+ // -------------------------------------------------------------------------
+
+ /**
+ * Return the specified text surrounded with double quotes
+ *
+ * @param text - the text to surround with quotes
+ * @return the text with leading and trailing double quotes
+ * @throws org.mockftpserver.core.util.AssertFailedException
+ * - if text is null
+ */
+ protected static String quotes(String text) {
+ Assert.notNull(text, "text");
+ final String QUOTES = "\"";
+ return QUOTES + text + QUOTES;
+ }
+
+ /**
+ * Assert that the specified number is a valid reply code
+ *
+ * @param replyCode - the reply code to check
+ * @throws org.mockftpserver.core.util.AssertFailedException
+ * - if the replyCode is invalid
+ */
+ protected void assertValidReplyCode(int replyCode) {
+ Assert.isTrue(replyCode > 0, "The number [" + replyCode + "] is not a valid reply code");
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/command/AbstractStaticReplyCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/core/command/AbstractStaticReplyCommandHandler.java
new file mode 100644
index 0000000..efe8494
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/command/AbstractStaticReplyCommandHandler.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.mockftpserver.core.session.Session;
+
+/**
+ * The abstract superclass for CommandHandler classes that default to sending
+ * back a configured reply code and text. You can customize the returned reply
+ * code by setting the required <code>replyCode</code> property. If only the
+ * <code>replyCode</code> property is set, then the default reply text corresponding to that
+ * reply code is used in the response. You can optionally configure the reply text by setting
+ * the <code>replyMessageKey</code> or <code>replyText</code> property.
+ * <p>
+ * Subclasses can optionally override the reply code and/or text for the reply by calling
+ * {@link #setReplyCode(int)}, {@link #setReplyMessageKey(String)} and {@link #setReplyText(String)}.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public abstract class AbstractStaticReplyCommandHandler extends AbstractTrackingCommandHandler {
+
+ // Defaults to zero; must be set to non-zero
+ protected int replyCode = 0;
+
+ // Defaults to null; if set to non-null, this value will override the default reply text associated with
+ // the replyCode.
+ protected String replyText = null;
+
+ // The message key for the reply text. Defaults to null. If null, use the default message associated
+ // with the reply code
+ protected String replyMessageKey = null;
+
+ /**
+ * Set the reply code.
+ *
+ * @param replyCode - the replyCode
+ *
+ * @throws org.mockftpserver.core.util.AssertFailedException - if the replyCode is not valid
+ */
+ public void setReplyCode(int replyCode) {
+ assertValidReplyCode(replyCode);
+ this.replyCode = replyCode;
+ }
+
+ /**
+ * Set the reply text. If null, then use the (default) message key for the replyCode.
+ *
+ * @param replyText - the replyText
+ */
+ public void setReplyText(String replyText) {
+ this.replyText = replyText;
+ }
+
+ /**
+ * Set the message key for the reply text. If null, then use the default message key.
+ *
+ * @param replyMessageKey - the replyMessageKey to set
+ */
+ public void setReplyMessageKey(String replyMessageKey) {
+ this.replyMessageKey = replyMessageKey;
+ }
+
+ // -------------------------------------------------------------------------
+ // Utility methods for subclasses
+ // -------------------------------------------------------------------------
+
+ /**
+ * Send the reply using the replyCode and message key/text configured for this command handler.
+ * @param session - the Session
+ *
+ * @throws org.mockftpserver.core.util.AssertFailedException if the replyCode is not valid
+ */
+ protected void sendReply(Session session) {
+ sendReply(session, null);
+ }
+
+ /**
+ * Send the reply using the replyCode and message key/text configured for this command handler.
+ * @param session - the Session
+ * @param messageParameter - message parameter; may be null
+ *
+ * @throws org.mockftpserver.core.util.AssertFailedException if the replyCode is not valid
+ */
+ protected void sendReply(Session session, Object messageParameter) {
+ Object[] parameters = (messageParameter == null) ? null : new Object[] { messageParameter };
+ sendReply(session, replyCode, replyMessageKey, replyText, parameters);
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/command/AbstractTrackingCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/core/command/AbstractTrackingCommandHandler.java
new file mode 100644
index 0000000..bb817ab
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/command/AbstractTrackingCommandHandler.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.mockftpserver.core.CommandSyntaxException;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.Assert;
+import org.mockftpserver.core.util.AssertFailedException;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.MissingResourceException;
+
+/**
+ * The abstract superclass for CommandHandler classes that manage the List of InvocationRecord
+ * objects corresponding to each invocation of the command handler, and provide helper methods for subclasses.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public abstract class AbstractTrackingCommandHandler extends AbstractCommandHandler implements InvocationHistory {
+
+ private List invocations = new ArrayList();
+
+ // -------------------------------------------------------------------------
+ // Template Method
+ // -------------------------------------------------------------------------
+
+ /**
+ * Handle the specified command for the session. This method is declared to throw Exception,
+ * allowing CommandHandler implementations to avoid unnecessary exception-handling. All checked
+ * exceptions are expected to be wrapped and handled by the caller.
+ *
+ * @param command - the Command to be handled
+ * @param session - the session on which the Command was submitted
+ * @throws Exception
+ * @throws AssertFailedException - if the command or session is null
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command,
+ * org.mockftpserver.core.session.Session)
+ */
+ public final void handleCommand(Command command, Session session) throws Exception {
+ Assert.notNull(command, "command");
+ Assert.notNull(session, "session");
+ InvocationRecord invocationRecord = new InvocationRecord(command, session.getClientHost());
+ invocations.add(invocationRecord);
+ try {
+ handleCommand(command, session, invocationRecord);
+ }
+ catch (CommandSyntaxException e) {
+ sendReply(session, ReplyCodes.COMMAND_SYNTAX_ERROR, null, null, null);
+ }
+ invocationRecord.lock();
+ }
+
+ /**
+ * Handle the specified command for the session. This method is declared to throw Exception,
+ * allowing CommandHandler implementations to avoid unnecessary exception-handling. All checked
+ * exceptions are expected to be wrapped and handled by the caller.
+ *
+ * @param command - the Command to be handled
+ * @param session - the session on which the Command was submitted
+ * @param invocationRecord - the InvocationRecord; CommandHandlers are expected to add
+ * handler-specific data to the InvocationRecord, as appropriate
+ * @throws Exception
+ */
+ protected abstract void handleCommand(Command command, Session session, InvocationRecord invocationRecord)
+ throws Exception;
+
+ // -------------------------------------------------------------------------
+ // Utility methods for subclasses
+ // -------------------------------------------------------------------------
+
+ /**
+ * Send a reply for this command on the control connection.
+ * <p/>
+ * The reply code is designated by the <code>replyCode</code> property, and the reply text
+ * is determined by the following rules:
+ * <ol>
+ * <li>If the <code>replyText</code> property is non-null, then use that.</li>
+ * <li>Otherwise, if <code>replyMessageKey</code> is non-null, the use that to retrieve a
+ * localized message from the <code>replyText</code> ResourceBundle.</li>
+ * <li>Otherwise, retrieve the reply text from the <code>replyText</code> ResourceBundle,
+ * using the reply code as the key.</li>
+ * </ol>
+ * If the arguments Object[] is not null, then these arguments are substituted within the
+ * reply text using the {@link MessageFormat} class.
+ *
+ * @param session - the Session
+ * @param replyCode - the reply code
+ * @param replyMessageKey - if not null (and replyText is null), this is used as the ResourceBundle
+ * message key instead of the reply code.
+ * @param replyText - if non-null, this is used as the reply text
+ * @param arguments - the array of arguments to be formatted and substituted within the reply
+ * text; may be null
+ * @throws AssertFailedException - if session is null
+ * @see MessageFormat
+ */
+ protected void sendReply(Session session, int replyCode, String replyMessageKey, String replyText,
+ Object[] arguments) {
+
+ Assert.notNull(session, "session");
+ assertValidReplyCode(replyCode);
+
+ String key = (replyMessageKey != null) ? replyMessageKey : Integer.toString(replyCode);
+ String text = getTextForReplyCode(replyCode, key, replyText, arguments);
+ String replyTextToLog = (text == null) ? "" : " " + text;
+ LOG.info("Sending reply [" + replyCode + replyTextToLog + "]");
+ session.sendReply(replyCode, text);
+ }
+
+ // -------------------------------------------------------------------------
+ // InvocationHistory - Support for command history
+ // -------------------------------------------------------------------------
+
+ /**
+ * @return the number of invocation records stored for this command handler instance
+ * @see org.mockftpserver.core.command.InvocationHistory#numberOfInvocations()
+ */
+ public int numberOfInvocations() {
+ return invocations.size();
+ }
+
+ /**
+ * Return the InvocationRecord representing the command invoction data for the nth invocation
+ * for this command handler instance. One InvocationRecord should be stored for each invocation
+ * of the CommandHandler.
+ *
+ * @param index - the index of the invocation record to return. The first record is at index zero.
+ * @return the InvocationRecord for the specified index
+ * @throws AssertFailedException - if there is no invocation record corresponding to the specified index
+ * @see org.mockftpserver.core.command.InvocationHistory#getInvocation(int)
+ */
+ public InvocationRecord getInvocation(int index) {
+ return (InvocationRecord) invocations.get(index);
+ }
+
+ /**
+ * Clear out the invocation history for this CommandHandler. After invoking this method, the
+ * <code>numberOfInvocations()</code> method will return zero.
+ *
+ * @see org.mockftpserver.core.command.InvocationHistory#clearInvocations()
+ */
+ public void clearInvocations() {
+ invocations.clear();
+ }
+
+ // -------------------------------------------------------------------------
+ // Internal Helper Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Return the text for the specified reply code, formatted using the message arguments, if
+ * supplied. If overrideText is not null, then return that. Otherwise, return the text mapped to
+ * the code from the replyText ResourceBundle. If the ResourceBundle contains no mapping, then
+ * return null.
+ * <p/>
+ * If arguments is not null, then the returned reply text if formatted using the
+ * {@link MessageFormat} class.
+ *
+ * @param code - the reply code
+ * @param messageKey - the key used to retrieve the reply text from the replyTextBundle
+ * @param overrideText - if not null, this is used instead of the text from the replyTextBundle.
+ * @param arguments - the array of arguments to be formatted and substituted within the reply
+ * text; may be null
+ * @return the text for the reply code; may be null
+ */
+ private String getTextForReplyCode(int code, String messageKey, String overrideText, Object[] arguments) {
+ try {
+ String t = (overrideText == null) ? getReplyTextBundle().getString(messageKey) : overrideText;
+ String formattedMessage = MessageFormat.format(t, arguments);
+ return (formattedMessage == null) ? null : formattedMessage.trim();
+ }
+ catch (MissingResourceException e) {
+ // No reply text is mapped for the specified key
+ LOG.warn("No reply text defined for reply code [" + code + "]");
+ return null;
+ }
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/command/Command.java b/tags/2.5/src/main/java/org/mockftpserver/core/command/Command.java
new file mode 100644
index 0000000..935a407
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/command/Command.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 20078 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.mockftpserver.core.CommandSyntaxException;
+import org.mockftpserver.core.util.Assert;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a command received from an FTP client, containing a command name and parameters.
+ * Objects of this class are immutable.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class Command {
+
+ private String name;
+ private String[] parameters;
+
+ /**
+ * Construct a new immutable instance with the specified command name and parameters
+ *
+ * @param name - the command name; may not be null
+ * @param parameters - the command parameters; may be empty; may not be null
+ */
+ public Command(String name, String[] parameters) {
+ Assert.notNull(name, "name");
+ Assert.notNull(parameters, "parameters");
+ this.name = name;
+ this.parameters = copy(parameters);
+ }
+
+ /**
+ * Construct a new immutable instance with the specified command name and parameters
+ *
+ * @param name - the command name; may not be null
+ * @param parameters - the command parameters; may be empty; may not be null
+ */
+ public Command(String name, List parameters) {
+ this(name, (String[]) parameters.toArray(new String[parameters.size()]));
+ }
+
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return the parameters
+ */
+ public String[] getParameters() {
+ return copy(parameters);
+ }
+
+ /**
+ * Get the String value of the parameter at the specified index
+ *
+ * @param index - the index
+ * @return the parameter value as a String
+ * @throws org.mockftpserver.core.util.AssertFailedException
+ * if the parameter index is invalid or the value is not a valid String
+ */
+ public String getRequiredParameter(int index) {
+ assertValidIndex(index);
+ return parameters[index];
+ }
+
+ /**
+ * Get the String value of the parameter at the specified index; return null if no parameter exists for the index
+ *
+ * @param index - the index
+ * @return the parameter value as a String, or null if this Command does not have a parameter for that index
+ */
+ public String getParameter(int index) {
+ return (parameters.length > index) ? parameters[index] : null;
+ }
+
+ /**
+ * Get the String value of the parameter at the specified index; return null if no
+ * parameter exists for the index. This is an alias for {@link #getParameter(int)}.
+ *
+ * @param index - the index
+ * @return the parameter value as a String, or null if this Command does not have a parameter for that index
+ */
+ public String getOptionalString(int index) {
+ return getParameter(index);
+ }
+
+ /**
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || !(obj instanceof Command)) {
+ return false;
+ }
+ return this.hashCode() == obj.hashCode();
+ }
+
+ /**
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ String str = name + Arrays.asList(parameters);
+ return str.hashCode();
+ }
+
+ /**
+ * Return the String representation of this object
+ *
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return "Command[" + name + ":" + Arrays.asList(parameters) + "]";
+ }
+
+ /**
+ * Return the name, normalized to a common format - convert to upper case.
+ *
+ * @return the name converted to upper case
+ */
+ public static String normalizeName(String name) {
+ return name.toUpperCase();
+ }
+
+ /**
+ * Construct a shallow copy of the specified array
+ *
+ * @param array - the array to copy
+ * @return a new array with the same contents
+ */
+ private static String[] copy(String[] array) {
+ String[] newArray = new String[array.length];
+ System.arraycopy(array, 0, newArray, 0, array.length);
+ return newArray;
+ }
+
+ /**
+ * Assert that the index is valid
+ *
+ * @param index - the index
+ * @throws org.mockftpserver.core.CommandSyntaxException
+ * - if the parameter index is invalid
+ */
+ private void assertValidIndex(int index) {
+ if (index < 0 || index >= parameters.length) {
+ throw new CommandSyntaxException("The parameter index " + index + " is not valid for " + this);
+ }
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/command/CommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/core/command/CommandHandler.java
new file mode 100644
index 0000000..e004b5b
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/command/CommandHandler.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.mockftpserver.core.session.Session;
+
+/**
+ * Interface for classes that can handle an FTP command.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public interface CommandHandler {
+
+ /**
+ * Handle the specified command for the session. This method is declared to throw
+ * Exception, allowing CommandHandler implementations to avoid unnecessary
+ * exception-handling. All checked exceptions are expected to be wrapped and handled
+ * by the caller.
+ *
+ * @param command - the Command to be handled
+ * @param session - the session on which the Command was submitted
+ *
+ * @throws Exception
+ */
+ public void handleCommand(Command command, Session session) throws Exception;
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/command/CommandNames.java b/tags/2.5/src/main/java/org/mockftpserver/core/command/CommandNames.java
new file mode 100644
index 0000000..83f29f5
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/command/CommandNames.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+/**
+ * FTP command name constants.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class CommandNames {
+
+ public static final String ABOR = "ABOR";
+ public static final String ACCT = "ACCT";
+ public static final String ALLO = "ALLO";
+ public static final String APPE = "APPE";
+ public static final String CDUP = "CDUP";
+ public static final String CWD = "CWD";
+ public static final String DELE = "DELE";
+ public static final String EPRT = "EPRT";
+ public static final String EPSV = "EPSV";
+ public static final String HELP = "HELP";
+ public static final String LIST = "LIST";
+ public static final String MKD = "MKD";
+ public static final String MODE = "MODE";
+ public static final String NLST = "NLST";
+ public static final String NOOP = "NOOP";
+ public static final String PASS = "PASS";
+ public static final String PASV = "PASV";
+ public static final String PORT = "PORT";
+ public static final String PWD = "PWD";
+ public static final String QUIT = "QUIT";
+ public static final String REIN = "REIN";
+ public static final String REST = "REST";
+ public static final String RETR = "RETR";
+ public static final String RMD = "RMD";
+ public static final String RNFR = "RNFR";
+ public static final String RNTO = "RNTO";
+ public static final String SITE = "SITE";
+ public static final String SMNT = "SMNT";
+ public static final String STAT = "STAT";
+ public static final String STOR = "STOR";
+ public static final String STOU = "STOU";
+ public static final String STRU = "STRU";
+ public static final String SYST = "SYST";
+ public static final String TYPE = "TYPE";
+ public static final String USER = "USER";
+
+ public static final String XPWD = "XPWD";
+
+ // Special commands - not "real" FTP commands
+ public static final String CONNECT = "CONNECT";
+ public static final String UNSUPPORTED = "UNSUPPORTED";
+
+ /**
+ * Private constructor. This class should not be instantiated.
+ */
+ private CommandNames() {
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/command/ConnectCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/core/command/ConnectCommandHandler.java
new file mode 100644
index 0000000..a54557b
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/command/ConnectCommandHandler.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler that encapsulates the sending of the reply for the initial connection from
+ * the FTP client to the server. Send back a reply code of 220, indicating a successful connection.
+ * <p>
+ * Note that this is a "special" CommandHandler, in that it handles the initial connection from the
+ * client, rather than an explicit FTP command.
+ * <p>
+ * Each invocation record stored by this CommandHandler contains no data elements.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class ConnectCommandHandler extends AbstractStaticReplyCommandHandler implements CommandHandler {
+
+ /**
+ * Constructor. Initiate the replyCode.
+ */
+ public ConnectCommandHandler() {
+ setReplyCode(ReplyCodes.CONNECT_OK);
+ }
+
+ /**
+ * @see AbstractTrackingCommandHandler#handleCommand(Command, org.mockftpserver.core.session.Session, InvocationRecord)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ sendReply(session);
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/command/InvocationHistory.java b/tags/2.5/src/main/java/org/mockftpserver/core/command/InvocationHistory.java
new file mode 100644
index 0000000..71c66c2
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/command/InvocationHistory.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+/**
+ * Interface for an object that can retrieve and clear the history of InvocationRecords
+ * for a command handler.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public interface InvocationHistory {
+
+ /**
+ * @return the number of invocation records stored for this command handler instance
+ */
+ public int numberOfInvocations();
+
+ /**
+ * Return the InvocationRecord representing the command invoction data for the nth invocation
+ * for this command handler instance. One InvocationRecord should be stored for each invocation
+ * of the CommandHandler.
+ *
+ * @param index - the index of the invocation record to return. The first record is at index zero.
+ * @return the InvocationRecord for the specified index
+ *
+ * @throws AssertFailedException - if there is no invocation record corresponding to the specified index */
+ public InvocationRecord getInvocation(int index);
+
+ /**
+ * Clear out the invocation history for this CommandHandler. After invoking this method, the
+ * <code>numberOfInvocations()</code> method will return zero.
+ */
+ public void clearInvocations();
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/command/InvocationRecord.java b/tags/2.5/src/main/java/org/mockftpserver/core/command/InvocationRecord.java
new file mode 100644
index 0000000..4f92d78
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/command/InvocationRecord.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.mockftpserver.core.util.Assert;
+import org.mockftpserver.core.util.AssertFailedException;
+
+import java.net.InetAddress;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Represents information about a single FTP Command invocation. Manages and provides access to
+ * the Command, the host address (<code>InetAddress</code>) of the client that submitted the
+ * Command and the timestamp of the Command submission.
+ * <p>
+ * This class also supports storing zero or more arbitrary mappings of <i>key</i> to value, where <i>key</i> is
+ * a String and <i>value</i> is any Object. Convenience methods are provided that enable retrieving
+ * type-specific data by its <i>key</i>. The data stored in an {@link InvocationRecord} is CommandHandler-specific.
+ * <p>
+ * The {@link #lock()} method makes an instance of this class immutable. After an instance is locked,
+ * calling the {@link #set(String, Object)} method will throw an <code>AssertFailedException</code>.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class InvocationRecord {
+
+ private Command command;
+ private Date time;
+ private InetAddress clientHost;
+ private Map data = new HashMap();
+ private boolean locked = false;
+
+ /**
+ * Create a new instance
+ *
+ * @param command - the Command
+ * @param clientHost - the client host
+ */
+ public InvocationRecord(Command command, InetAddress clientHost) {
+ this.command = command;
+ this.time = new Date();
+ this.clientHost = clientHost;
+ }
+
+ /**
+ * Lock this instance, making it immutable. After an instance is locked,
+ * calling the {@link #set(String, Object)} method will throw an
+ * <code>AssertFailedException</code>.
+ */
+ public void lock() {
+ locked = true;
+ }
+
+ /**
+ * Return true if this object has been locked, false otherwise. See {@link #lock()}.
+ *
+ * @return true if this object has been locked, false otherwise.
+ */
+ public boolean isLocked() {
+ return locked;
+ }
+
+ /**
+ * @return the client host that submitted the command, as an InetAddress
+ */
+ public InetAddress getClientHost() {
+ return clientHost;
+ }
+
+ /**
+ * @return the Command
+ */
+ public Command getCommand() {
+ return command;
+ }
+
+ /**
+ * @return the time that the command was processed; this may differ slightly from when the command was received.
+ */
+ public Date getTime() {
+ // Return a copy of the Date object to preserve immutability
+ return new Date(time.getTime());
+ }
+
+ /**
+ * Store the value for the specified key. If this object already contained a mapping
+ * for this key, the old value is replaced by the specified value. This method throws
+ * an <code>AssertFailedException</code> if this object has been locked. See {@link #lock()}.
+ *
+ * @param key - the key; must not be null
+ * @param value - the value to store for the specified key
+ * @throws AssertFailedException - if the key is null or this object has been locked.
+ */
+ public void set(String key, Object value) {
+ Assert.notNull(key, "key");
+ Assert.isFalse(locked, "The InvocationRecord is locked!");
+ data.put(key, value);
+ }
+
+ /**
+ * Returns <code>true</code> if this object contains a mapping for the specified key.
+ *
+ * @param key - the key; must not be null
+ * @return <code>true</code> if there is a mapping for the key
+ * @throws AssertFailedException - if the key is null
+ */
+ public boolean containsKey(String key) {
+ Assert.notNull(key, "key");
+ return data.containsKey(key);
+ }
+
+ /**
+ * Returns a Set view of the keys for the data stored in this object.
+ * Changes to the returned Set have no effect on the data stored within this object
+ * .
+ *
+ * @return the Set of keys for the data stored within this object
+ */
+ public Set keySet() {
+ return Collections.unmodifiableSet(data.keySet());
+ }
+
+ /**
+ * Get the String value associated with the specified key. Returns null if there is
+ * no mapping for this key. A return value of null does not necessarily indicate that
+ * this object contains no mapping for the key; it's also possible that the value was
+ * explicitly set to null for the key. The containsKey operation may be used to
+ * distinguish these two cases.
+ *
+ * @param key - the key; must not be null
+ * @return the String data stored at the specified key; may be null
+ * @throws ClassCastException - if the object for the specified key is not a String
+ * @throws AssertFailedException - if the key is null
+ */
+ public String getString(String key) {
+ Assert.notNull(key, "key");
+ return (String) data.get(key);
+ }
+
+ /**
+ * Get the Object value associated with the specified key. Returns null if there is
+ * no mapping for this key. A return value of null does not necessarily indicate that
+ * this object contains no mapping for the key; it's also possible that the value was
+ * explicitly set to null for the key. The containsKey operation may be used to
+ * distinguish these two cases.
+ *
+ * @param key - the key; must not be null
+ * @return the data stored at the specified key, as an Object; may be null
+ * @throws AssertFailedException - if the key is null
+ */
+ public Object getObject(String key) {
+ Assert.notNull(key, "key");
+ return data.get(key);
+ }
+
+ /**
+ * Return the String representation of this object
+ *
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return "InvocationRecord[time=" + time + " client-host=" + clientHost + " command=" + command + " data=" + data + "]";
+ }
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/command/ReplyCodes.java b/tags/2.5/src/main/java/org/mockftpserver/core/command/ReplyCodes.java
new file mode 100644
index 0000000..98c8b62
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/command/ReplyCodes.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+/**
+ * Reply Code constants.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class ReplyCodes {
+
+ public static final int ABOR_OK = 226;
+ public static final int ACCT_OK = 230;
+ public static final int ALLO_OK = 200;
+ public static final int CDUP_OK = 200;
+ public static final int CWD_OK = 250;
+ public static final int DELE_OK = 250;
+ public static final int EPRT_OK = 200;
+ public static final int EPSV_OK = 229;
+ public static final int HELP_OK = 214;
+ public static final int MKD_OK = 257;
+ public static final int MODE_OK = 200;
+ public static final int NOOP_OK = 200;
+ public static final int PASS_OK = 230;
+ public static final int PASS_NEED_ACCOUNT = 332;
+ public static final int PASS_LOG_IN_FAILED = 530;
+ public static final int PASV_OK = 227;
+ public static final int PORT_OK = 200;
+ public static final int PWD_OK = 257;
+ public static final int QUIT_OK = 221;
+ public static final int REIN_OK = 220;
+ public static final int REST_OK = 350;
+ public static final int RMD_OK = 250;
+ public static final int RNFR_OK = 350;
+ public static final int RNTO_OK = 250;
+ public static final int SITE_OK = 200;
+ public static final int SMNT_OK = 250;
+ public static final int STAT_SYSTEM_OK = 211;
+ public static final int STAT_FILE_OK = 213;
+ public static final int STRU_OK = 200;
+ public static final int SYST_OK = 215;
+ public static final int TYPE_OK = 200;
+ public static final int USER_LOGGED_IN_OK = 230;
+ public static final int USER_NEED_PASSWORD_OK = 331;
+ public static final int USER_NO_SUCH_USER = 530;
+ public static final int USER_ACCOUNT_NOT_VALID = 530;
+
+ public static final int TRANSFER_DATA_INITIAL_OK = 150;
+ public static final int TRANSFER_DATA_FINAL_OK = 226;
+
+ public static final int CONNECT_OK = 220;
+
+ // GENERIC
+ public static final int SYSTEM_ERROR = 451;
+ public static final int COMMAND_SYNTAX_ERROR = 501;
+ public static final int COMMAND_NOT_SUPPORTED = 502;
+ public static final int ILLEGAL_STATE = 503; // Bad sequence
+ public static final int NOT_LOGGED_IN = 530;
+ public static final int READ_FILE_ERROR = 550;
+ public static final int WRITE_FILE_ERROR = 553;
+ public static final int FILENAME_NOT_VALID = 553;
+
+ /**
+ * Private constructor. This class should not be instantiated.
+ */
+ private ReplyCodes() {
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/command/ReplyTextBundleAware.java b/tags/2.5/src/main/java/org/mockftpserver/core/command/ReplyTextBundleAware.java
new file mode 100644
index 0000000..42b5955
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/command/ReplyTextBundleAware.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import java.util.ResourceBundle;
+
+/**
+ * Interface for objects that allow getting and setting a reply text ResourceBundle. This
+ * interface is implemented by CommandHandlers so that the StubFtpServer can automatically
+ * set the default reply text ResourceBundle for the CommandHandler.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public interface ReplyTextBundleAware {
+
+ /**
+ * Return the ResourceBundle containing the reply text messages
+ * @return the replyTextBundle
+ */
+ public ResourceBundle getReplyTextBundle();
+
+ /**
+ * Set the ResourceBundle containing the reply text messages
+ * @param replyTextBundle - the replyTextBundle to set
+ */
+ public void setReplyTextBundle(ResourceBundle replyTextBundle);
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/command/ReplyTextBundleUtil.java b/tags/2.5/src/main/java/org/mockftpserver/core/command/ReplyTextBundleUtil.java
new file mode 100644
index 0000000..b3fa0e4
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/command/ReplyTextBundleUtil.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.mockftpserver.core.util.Assert;
+
+import java.util.ResourceBundle;
+
+/**
+ * Contains common utility method to conditionally set the reply text ResourceBundle on a
+ * CommandHandler instance.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class ReplyTextBundleUtil {
+
+ /**
+ * Set the <code>replyTextBundle</code> property of the specified CommandHandler to the
+ * <code>ResourceBundle</code> if and only if the <code>commandHandler</code> implements the
+ * {@link ReplyTextBundleAware} interface AND its <code>replyTextBundle</code> property
+ * has not been set (is null).
+ *
+ * @param commandHandler - the CommandHandler instance
+ * @param replyTextBundle - the ResourceBundle to use for localizing reply text
+ *
+ * @throws org.mockftpserver.core.util.AssertFailedException - if the commandHandler is null
+ */
+ public static void setReplyTextBundleIfAppropriate(CommandHandler commandHandler, ResourceBundle replyTextBundle) {
+ Assert.notNull(commandHandler, "commandHandler");
+ if (commandHandler instanceof ReplyTextBundleAware) {
+ ReplyTextBundleAware replyTextBundleAware = (ReplyTextBundleAware) commandHandler;
+ if (replyTextBundleAware.getReplyTextBundle() == null) {
+ replyTextBundleAware.setReplyTextBundle(replyTextBundle);
+ }
+ }
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/command/SimpleCompositeCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/core/command/SimpleCompositeCommandHandler.java
new file mode 100644
index 0000000..520867f
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/command/SimpleCompositeCommandHandler.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.Assert;
+import org.mockftpserver.core.util.AssertFailedException;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ResourceBundle;
+
+/**
+ * Composite CommandHandler that manages an internal list of CommandHandlers to which it delegates.
+ * The internal CommandHandlers are maintained in an ordered list. Starting with the first
+ * CommandHandler in the list, each invocation of this composite handler will invoke (delegate to)
+ * the current internal CommandHander. Then it moves on the next CommandHandler in the internal list.
+ * <p>
+ * The following example replaces the CWD CommandHandler with a <code>SimpleCompositeCommandHandler</code>.
+ * The first invocation of the CWD command will fail (reply code 500). The seconds will succeed.
+ * <pre><code>
+ *
+ * StubFtpServer stubFtpServer = new StubFtpServer();
+ *
+ * CommandHandler commandHandler1 = new StaticReplyCommandHandler(500);
+ * CommandHandler commandHandler2 = new CwdCommandHandler();
+ *
+ * SimpleCompositeCommandHandler simpleCompositeCommandHandler = new SimpleCompositeCommandHandler();
+ * simpleCompositeCommandHandler.addCommandHandler(commandHandler1);
+ * simpleCompositeCommandHandler.addCommandHandler(commandHandler2);
+ *
+ * stubFtpServer.setCommandHandler("CWD", simpleCompositeCommandHandler);
+ * </code></pre>
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class SimpleCompositeCommandHandler implements CommandHandler, ReplyTextBundleAware {
+
+ private List commandHandlers = new ArrayList();
+ private int invocationIndex = 0;
+
+ /**
+ * Add a CommandHandler to the internal list of handlers.
+ *
+ * @param commandHandler - the CommandHandler
+ *
+ * @throws AssertFailedException - if the commandHandler is null
+ */
+ public void addCommandHandler(CommandHandler commandHandler) {
+ Assert.notNull(commandHandler, "commandHandler");
+ commandHandlers.add(commandHandler);
+ }
+
+ /**
+ * Set the List of CommandHandlers to which to delegate. This replaces any CommandHandlers that
+ * have been defined previously.
+ * @param commandHandlers - the complete List of CommandHandlers to which invocations are delegated
+ */
+ public void setCommandHandlers(List commandHandlers) {
+ Assert.notNull(commandHandlers, "commandHandlers");
+ this.commandHandlers = new ArrayList(commandHandlers);
+ }
+
+ /**
+ * Return the CommandHandler corresponding to the specified invocation index. In other words, return
+ * the CommandHandler instance to which the Nth {@link #handleCommand(Command, Session)} has been or will
+ * be delegated (where N=index).
+ * @param index - the index of the desired invocation (zero-based).
+ * @return the CommandHandler
+ *
+ * @throws AssertFailedException - if no CommandHandler is defined for the index or the index is not valid
+ */
+ public CommandHandler getCommandHandler(int index) {
+ Assert.isTrue(index < commandHandlers.size(), "No CommandHandler defined for index " + index);
+ Assert.isTrue(index >= 0, "The index cannot be less than zero: " + index);
+ return (CommandHandler) commandHandlers.get(index);
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session) throws Exception {
+ Assert.notNull(command, "command");
+ Assert.notNull(session, "session");
+ Assert.isTrue(commandHandlers.size() > invocationIndex, "No CommandHandler defined for invocation #" + invocationIndex);
+
+ CommandHandler commandHandler = (CommandHandler) commandHandlers.get(invocationIndex);
+ invocationIndex++;
+ commandHandler.handleCommand(command, session);
+ }
+
+ /**
+ * Returns null. This is a composite, and has no reply text bundle.
+ *
+ * @see org.mockftpserver.core.command.ReplyTextBundleAware#getReplyTextBundle()
+ */
+ public ResourceBundle getReplyTextBundle() {
+ return null;
+ }
+
+ /**
+ * Call <code>setReplyTextBundle()</code> on each of the command handlers within the internal list.
+ *
+ * @see org.mockftpserver.core.command.ReplyTextBundleAware#setReplyTextBundle(java.util.ResourceBundle)
+ */
+ public void setReplyTextBundle(ResourceBundle replyTextBundle) {
+ for (Iterator iter = commandHandlers.iterator(); iter.hasNext();) {
+ CommandHandler commandHandler = (CommandHandler) iter.next();
+ ReplyTextBundleUtil.setReplyTextBundleIfAppropriate(commandHandler, replyTextBundle);
+ }
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/command/StaticReplyCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/core/command/StaticReplyCommandHandler.java
new file mode 100644
index 0000000..135039f
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/command/StaticReplyCommandHandler.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.AssertFailedException;
+
+/**
+ * CommandHandler that sends back the configured reply code and text. You can customize the
+ * returned reply code by setting the required <code>replyCode</code> property. If only the
+ * <code>replyCode</code> property is set, then the default reply text corresponding to that
+ * reply code is used in the response. You can optionally configure the reply text by setting
+ * the <code>replyMessageKey</code> or <code>replyText</code> property.
+ * <p>
+ * Each invocation record stored by this CommandHandler contains no data elements.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class StaticReplyCommandHandler extends AbstractStaticReplyCommandHandler {
+
+ /**
+ * Create a new uninitialized instance
+ */
+ public StaticReplyCommandHandler() {
+ }
+
+ /**
+ * Create a new instance with the specified replyCode
+ * @param replyCode - the replyCode to use
+ * @throws AssertFailedException - if the replyCode is null
+ */
+ public StaticReplyCommandHandler(int replyCode) {
+ setReplyCode(replyCode);
+ }
+
+ /**
+ * Create a new instance with the specified replyCode and replyText
+ * @param replyCode - the replyCode to use
+ * @param replyText - the replyText
+ * @throws AssertFailedException - if the replyCode is null
+ */
+ public StaticReplyCommandHandler(int replyCode, String replyText) {
+ setReplyCode(replyCode);
+ setReplyText(replyText);
+ }
+
+ /**
+ * @see AbstractTrackingCommandHandler#handleCommand(Command, org.mockftpserver.core.session.Session, InvocationRecord)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/command/UnsupportedCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/core/command/UnsupportedCommandHandler.java
new file mode 100644
index 0000000..d452327
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/command/UnsupportedCommandHandler.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler that encapsulates the sending of the reply when a requested command is not
+ * recognized/supported. Send back a reply code of 502, indicating command not implemented.
+ * <p>
+ * Note that this is a "special" CommandHandler, in that it handles any unrecognized command,
+ * rather than an explicit FTP command.
+ * <p>
+ * Each invocation record stored by this CommandHandler contains no data elements.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class UnsupportedCommandHandler extends AbstractStaticReplyCommandHandler implements CommandHandler {
+
+ /**
+ * Constructor. Initiate the replyCode.
+ */
+ public UnsupportedCommandHandler() {
+ setReplyCode(ReplyCodes.COMMAND_NOT_SUPPORTED);
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.AbstractTrackingCommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session, org.mockftpserver.core.command.InvocationRecord)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ LOG.warn("No CommandHandler is defined for command [" + command.getName() + "]");
+ sendReply(session, command.getName());
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/server/AbstractFtpServer.java b/tags/2.5/src/main/java/org/mockftpserver/core/server/AbstractFtpServer.java
new file mode 100644
index 0000000..149d652
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/server/AbstractFtpServer.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.server;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.MockFtpServerException;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.session.DefaultSession;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.socket.DefaultServerSocketFactory;
+import org.mockftpserver.core.socket.ServerSocketFactory;
+import org.mockftpserver.core.util.Assert;
+
+import java.io.IOException;
+import java.net.*;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+/**
+ * This is the abstract superclass for "mock" implementations of an FTP Server,
+ * suitable for testing FTP client code or standing in for a live FTP server. It supports
+ * the main FTP commands by defining handlers for each of the corresponding low-level FTP
+ * server commands (e.g. RETR, DELE, LIST). These handlers implement the {@link org.mockftpserver.core.command.CommandHandler}
+ * interface.
+ * <p/>
+ * By default, mock FTP Servers bind to the server control port of 21. You can use a different server control
+ * port by setting the <code>serverControlPort</code> property. If you specify a value of <code>0</code>,
+ * then a free port number will be chosen automatically; call <code>getServerControlPort()</code> AFTER
+ * <code>start()</code> has been called to determine the actual port number being used. Using a non-default
+ * port number is usually necessary when running on Unix or some other system where that port number is
+ * already in use or cannot be bound from a user process.
+ * <p/>
+ * <h4>Command Handlers</h4>
+ * You can set the existing {@link CommandHandler} defined for an FTP server command
+ * by calling the {@link #setCommandHandler(String, CommandHandler)} method, passing
+ * in the FTP server command name and {@link CommandHandler} instance.
+ * You can also replace multiple command handlers at once by using the {@link #setCommandHandlers(Map)}
+ * method. That is especially useful when configuring the server through the <b>Spring Framework</b>.
+ * <p/>
+ * You can retrieve the existing {@link CommandHandler} defined for an FTP server command by
+ * calling the {@link #getCommandHandler(String)} method, passing in the FTP server command name.
+ * <p/>
+ * <h4>FTP Command Reply Text ResourceBundle</h4>
+ * The default text asociated with each FTP command reply code is contained within the
+ * "ReplyText.properties" ResourceBundle file. You can customize these messages by providing a
+ * locale-specific ResourceBundle file on the CLASSPATH, according to the normal lookup rules of
+ * the ResourceBundle class (e.g., "ReplyText_de.properties"). Alternatively, you can
+ * completely replace the ResourceBundle file by calling the calling the
+ * {@link #setReplyTextBaseName(String)} method.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ * @see org.mockftpserver.fake.FakeFtpServer
+ * @see org.mockftpserver.stub.StubFtpServer
+ */
+public abstract class AbstractFtpServer implements Runnable {
+
+ /**
+ * Default basename for reply text ResourceBundle
+ */
+ public static final String REPLY_TEXT_BASENAME = "ReplyText";
+ private static final int DEFAULT_SERVER_CONTROL_PORT = 21;
+
+ protected Logger LOG = LoggerFactory.getLogger(getClass());
+
+ // Simple value object that holds the socket and thread for a single session
+ private static class SessionInfo {
+ Socket socket;
+ Thread thread;
+ }
+
+ protected ServerSocketFactory serverSocketFactory = new DefaultServerSocketFactory();
+ private ServerSocket serverSocket = null;
+ private ResourceBundle replyTextBundle;
+ private volatile boolean terminate = false;
+ private Map commandHandlers;
+ private Thread serverThread;
+ private int serverControlPort = DEFAULT_SERVER_CONTROL_PORT;
+ private final Object startLock = new Object();
+
+ // Map of Session -> SessionInfo
+ private Map sessions = new HashMap();
+
+ /**
+ * Create a new instance. Initialize the default command handlers and
+ * reply text ResourceBundle.
+ */
+ public AbstractFtpServer() {
+ replyTextBundle = ResourceBundle.getBundle(REPLY_TEXT_BASENAME);
+ commandHandlers = new HashMap();
+ }
+
+ /**
+ * Start a new Thread for this server instance
+ */
+ public void start() {
+ serverThread = new Thread(this);
+
+ synchronized (startLock) {
+ try {
+ // Start here in case server thread runs faster than main thread.
+ // See https://sourceforge.net/tracker/?func=detail&atid=1006533&aid=1925590&group_id=208647
+ serverThread.start();
+
+ // Wait until the server thread is initialized
+ startLock.wait();
+ }
+ catch (InterruptedException e) {
+ e.printStackTrace();
+ throw new MockFtpServerException(e);
+ }
+ }
+ }
+
+ /**
+ * The logic for the server thread
+ *
+ * @see Runnable#run()
+ */
+ public void run() {
+ try {
+ LOG.info("Starting the server on port " + serverControlPort);
+ serverSocket = serverSocketFactory.createServerSocket(serverControlPort);
+ if (serverControlPort == 0) {
+ this.serverControlPort = serverSocket.getLocalPort();
+ LOG.info("Actual server port is " + this.serverControlPort);
+ }
+
+ // Notify to allow the start() method to finish and return
+ synchronized (startLock) {
+ startLock.notify();
+ }
+
+ while (!terminate) {
+ try {
+ Socket clientSocket = serverSocket.accept();
+ LOG.info("Connection accepted from host " + clientSocket.getInetAddress());
+
+ Session session = createSession(clientSocket);
+ Thread sessionThread = new Thread(session);
+ sessionThread.start();
+
+ SessionInfo sessionInfo = new SessionInfo();
+ sessionInfo.socket = clientSocket;
+ sessionInfo.thread = sessionThread;
+ sessions.put(session, sessionInfo);
+ }
+ catch (SocketException e) {
+ LOG.trace("Socket exception: " + e.toString());
+ }
+ }
+ }
+ catch (IOException e) {
+ LOG.error("Error", e);
+ }
+ finally {
+
+ LOG.debug("Cleaning up server...");
+
+ // Ensure that the start() method is not still blocked
+ synchronized (startLock) {
+ startLock.notifyAll();
+ }
+
+ try {
+ if (serverSocket != null) {
+ serverSocket.close();
+ }
+ closeSessions();
+ }
+ catch (IOException e) {
+ LOG.error("Error cleaning up server", e);
+ }
+ catch (InterruptedException e) {
+ LOG.error("Error cleaning up server", e);
+ }
+ LOG.info("Server stopped.");
+ terminate = false;
+ }
+ }
+
+ /**
+ * Stop this server instance and wait for it to terminate.
+ */
+ public void stop() {
+
+ LOG.trace("Stopping the server...");
+ terminate = true;
+
+ if (serverSocket != null) {
+ try {
+ serverSocket.close();
+ } catch (IOException e) {
+ throw new MockFtpServerException(e);
+ }
+ }
+
+ try {
+ if (serverThread != null) {
+ serverThread.join();
+ }
+ }
+ catch (InterruptedException e) {
+ e.printStackTrace();
+ throw new MockFtpServerException(e);
+ }
+ }
+
+ /**
+ * Return the CommandHandler defined for the specified command name
+ *
+ * @param name - the command name
+ * @return the CommandHandler defined for name
+ */
+ public CommandHandler getCommandHandler(String name) {
+ return (CommandHandler) commandHandlers.get(Command.normalizeName(name));
+ }
+
+ /**
+ * Override the default CommandHandlers with those in the specified Map of
+ * commandName>>CommandHandler. This will only override the default CommandHandlers
+ * for the keys in <code>commandHandlerMapping</code>. All other default CommandHandler
+ * mappings remain unchanged.
+ *
+ * @param commandHandlerMapping - the Map of commandName->CommandHandler; these override the defaults
+ * @throws org.mockftpserver.core.util.AssertFailedException
+ * - if the commandHandlerMapping is null
+ */
+ public void setCommandHandlers(Map commandHandlerMapping) {
+ Assert.notNull(commandHandlerMapping, "commandHandlers");
+ for (Iterator iter = commandHandlerMapping.keySet().iterator(); iter.hasNext();) {
+ String commandName = (String) iter.next();
+ setCommandHandler(commandName, (CommandHandler) commandHandlerMapping.get(commandName));
+ }
+ }
+
+ /**
+ * Set the CommandHandler for the specified command name. If the CommandHandler implements
+ * the {@link org.mockftpserver.core.command.ReplyTextBundleAware} interface and its <code>replyTextBundle</code> attribute
+ * is null, then set its <code>replyTextBundle</code> to the <code>replyTextBundle</code> of
+ * this StubFtpServer.
+ *
+ * @param commandName - the command name to which the CommandHandler will be associated
+ * @param commandHandler - the CommandHandler
+ * @throws org.mockftpserver.core.util.AssertFailedException
+ * - if the commandName or commandHandler is null
+ */
+ public void setCommandHandler(String commandName, CommandHandler commandHandler) {
+ Assert.notNull(commandName, "commandName");
+ Assert.notNull(commandHandler, "commandHandler");
+ commandHandlers.put(Command.normalizeName(commandName), commandHandler);
+ initializeCommandHandler(commandHandler);
+ }
+
+ /**
+ * Set the reply text ResourceBundle to a new ResourceBundle with the specified base name,
+ * accessible on the CLASSPATH. See {@link java.util.ResourceBundle#getBundle(String)}.
+ *
+ * @param baseName - the base name of the resource bundle, a fully qualified class name
+ */
+ public void setReplyTextBaseName(String baseName) {
+ replyTextBundle = ResourceBundle.getBundle(baseName);
+ }
+
+ /**
+ * Return the ReplyText ResourceBundle. Set the bundle through the {@link #setReplyTextBaseName(String)} method.
+ *
+ * @return the reply text ResourceBundle
+ */
+ public ResourceBundle getReplyTextBundle() {
+ return replyTextBundle;
+ }
+
+ /**
+ * Set the port number to which the server control connection socket will bind. The default value is 21.
+ *
+ * @param serverControlPort - the port number for the server control connection ServerSocket
+ */
+ public void setServerControlPort(int serverControlPort) {
+ this.serverControlPort = serverControlPort;
+ }
+
+ /**
+ * Return the port number to which the server control connection socket will bind. The default value is 21.
+ *
+ * @return the port number for the server control connection ServerSocket
+ */
+ public int getServerControlPort() {
+ return serverControlPort;
+ }
+
+ /**
+ * Return true if this server is fully shutdown -- i.e., there is no active (alive) threads and
+ * all sockets are closed. This method is intended for testing only.
+ *
+ * @return true if this server is fully shutdown
+ */
+ public boolean isShutdown() {
+ boolean shutdown = !serverThread.isAlive() && serverSocket.isClosed();
+
+ for (Iterator iter = sessions.values().iterator(); iter.hasNext();) {
+ SessionInfo sessionInfo = (SessionInfo) iter.next();
+ shutdown = shutdown && sessionInfo.socket.isClosed() && !sessionInfo.thread.isAlive();
+ }
+ return shutdown;
+ }
+
+ /**
+ * Return true if this server has started -- i.e., there is an active (alive) server threads
+ * and non-null server socket. This method is intended for testing only.
+ *
+ * @return true if this server has started
+ */
+ public boolean isStarted() {
+ return serverThread != null && serverThread.isAlive() && serverSocket != null;
+ }
+
+ //-------------------------------------------------------------------------
+ // Internal Helper Methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * Create a new Session instance for the specified client Socket
+ *
+ * @param clientSocket - the Socket associated with the client
+ * @return a Session
+ */
+ protected Session createSession(Socket clientSocket) {
+ return new DefaultSession(clientSocket, commandHandlers);
+ }
+
+ private void closeSessions() throws InterruptedException, IOException {
+ for (Iterator iter = sessions.entrySet().iterator(); iter.hasNext();) {
+ Map.Entry entry = (Map.Entry) iter.next();
+ Session session = (Session) entry.getKey();
+ SessionInfo sessionInfo = (SessionInfo) entry.getValue();
+ session.close();
+ sessionInfo.thread.join(500L);
+ Socket sessionSocket = sessionInfo.socket;
+ if (sessionSocket != null) {
+ sessionSocket.close();
+ }
+ }
+ }
+
+ //------------------------------------------------------------------------------------
+ // Abstract method declarations
+ //------------------------------------------------------------------------------------
+
+ /**
+ * Initialize a CommandHandler that has been registered to this server. What "initialization"
+ * means is dependent on the subclass implementation.
+ *
+ * @param commandHandler - the CommandHandler to initialize
+ */
+ protected abstract void initializeCommandHandler(CommandHandler commandHandler);
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/session/DefaultSession.java b/tags/2.5/src/main/java/org/mockftpserver/core/session/DefaultSession.java
new file mode 100644
index 0000000..3a8ada6
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/session/DefaultSession.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.session;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.MockFtpServerException;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.socket.DefaultServerSocketFactory;
+import org.mockftpserver.core.socket.DefaultSocketFactory;
+import org.mockftpserver.core.socket.ServerSocketFactory;
+import org.mockftpserver.core.socket.SocketFactory;
+import org.mockftpserver.core.util.Assert;
+import org.mockftpserver.core.util.AssertFailedException;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+/**
+ * Default implementation of the {@link Session} interface.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class DefaultSession implements Session {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultSession.class);
+ private static final String END_OF_LINE = "\r\n";
+ protected static final int DEFAULT_CLIENT_DATA_PORT = 21;
+
+ protected SocketFactory socketFactory = new DefaultSocketFactory();
+ protected ServerSocketFactory serverSocketFactory = new DefaultServerSocketFactory();
+
+ private BufferedReader controlConnectionReader;
+ private Writer controlConnectionWriter;
+ private Socket controlSocket;
+ private Socket dataSocket;
+ ServerSocket passiveModeDataSocket; // non-private for testing
+ private InputStream dataInputStream;
+ private OutputStream dataOutputStream;
+ private Map commandHandlers;
+ private int clientDataPort = DEFAULT_CLIENT_DATA_PORT;
+ private InetAddress clientHost;
+ private InetAddress serverHost;
+ private Map attributes = new HashMap();
+ private volatile boolean terminate = false;
+
+ /**
+ * Create a new initialized instance
+ *
+ * @param controlSocket - the control connection socket
+ * @param commandHandlers - the Map of command name -> CommandHandler. It is assumed that the
+ * command names are all normalized to upper case. See {@link Command#normalizeName(String)}.
+ */
+ public DefaultSession(Socket controlSocket, Map commandHandlers) {
+ Assert.notNull(controlSocket, "controlSocket");
+ Assert.notNull(commandHandlers, "commandHandlers");
+
+ this.controlSocket = controlSocket;
+ this.commandHandlers = commandHandlers;
+ this.serverHost = controlSocket.getLocalAddress();
+ }
+
+ /**
+ * Return the InetAddress representing the client host for this session
+ *
+ * @return the client host
+ * @see org.mockftpserver.core.session.Session#getClientHost()
+ */
+ public InetAddress getClientHost() {
+ return controlSocket.getInetAddress();
+ }
+
+ /**
+ * Return the InetAddress representing the server host for this session
+ *
+ * @return the server host
+ * @see org.mockftpserver.core.session.Session#getServerHost()
+ */
+ public InetAddress getServerHost() {
+ return serverHost;
+ }
+
+ /**
+ * Send the specified reply code and text across the control connection.
+ * The reply text is trimmed before being sent.
+ *
+ * @param code - the reply code
+ * @param text - the reply text to send; may be null
+ */
+ public void sendReply(int code, String text) {
+ assertValidReplyCode(code);
+
+ StringBuffer buffer = new StringBuffer(Integer.toString(code));
+
+ if (text != null && text.length() > 0) {
+ String replyText = text.trim();
+ if (replyText.indexOf("\n") != -1) {
+ int lastIndex = replyText.lastIndexOf("\n");
+ buffer.append("-");
+ for (int i = 0; i < replyText.length(); i++) {
+ char c = replyText.charAt(i);
+ buffer.append(c);
+ if (i == lastIndex) {
+ buffer.append(Integer.toString(code));
+ buffer.append(" ");
+ }
+ }
+ } else {
+ buffer.append(" ");
+ buffer.append(replyText);
+ }
+ }
+ LOG.debug("Sending Reply [" + buffer.toString() + "]");
+ writeLineToControlConnection(buffer.toString());
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#openDataConnection()
+ */
+ public void openDataConnection() {
+ try {
+ if (passiveModeDataSocket != null) {
+ LOG.debug("Waiting for (passive mode) client connection from client host [" + clientHost
+ + "] on port " + passiveModeDataSocket.getLocalPort());
+ // TODO set socket timeout
+ try {
+ dataSocket = passiveModeDataSocket.accept();
+ LOG.debug("Successful (passive mode) client connection to port "
+ + passiveModeDataSocket.getLocalPort());
+ }
+ catch (SocketTimeoutException e) {
+ throw new MockFtpServerException(e);
+ }
+ } else {
+ Assert.notNull(clientHost, "clientHost");
+ LOG.debug("Connecting to client host [" + clientHost + "] on data port [" + clientDataPort
+ + "]");
+ dataSocket = socketFactory.createSocket(clientHost, clientDataPort);
+ }
+ dataOutputStream = dataSocket.getOutputStream();
+ dataInputStream = dataSocket.getInputStream();
+ }
+ catch (IOException e) {
+ throw new MockFtpServerException(e);
+ }
+ }
+
+ /**
+ * Switch to passive mode
+ *
+ * @return the local port to be connected to by clients for data transfers
+ * @see org.mockftpserver.core.session.Session#switchToPassiveMode()
+ */
+ public int switchToPassiveMode() {
+ try {
+ passiveModeDataSocket = serverSocketFactory.createServerSocket(0);
+ return passiveModeDataSocket.getLocalPort();
+ }
+ catch (IOException e) {
+ throw new MockFtpServerException("Error opening passive mode server data socket", e);
+ }
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#closeDataConnection()
+ */
+ public void closeDataConnection() {
+ try {
+ LOG.debug("Flushing and closing client data socket");
+ dataOutputStream.flush();
+ dataOutputStream.close();
+ dataInputStream.close();
+ dataSocket.close();
+ }
+ catch (IOException e) {
+ LOG.error("Error closing client data socket", e);
+ }
+ }
+
+ /**
+ * Write a single line to the control connection, appending a newline
+ *
+ * @param line - the line to write
+ */
+ private void writeLineToControlConnection(String line) {
+ try {
+ controlConnectionWriter.write(line + END_OF_LINE);
+ controlConnectionWriter.flush();
+ }
+ catch (IOException e) {
+ LOG.error("Error writing to control connection", e);
+ throw new MockFtpServerException("Error writing to control connection", e);
+ }
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#close()
+ */
+ public void close() {
+ LOG.trace("close()");
+ terminate = true;
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#sendData(byte[], int)
+ */
+ public void sendData(byte[] data, int numBytes) {
+ Assert.notNull(data, "data");
+ try {
+ dataOutputStream.write(data, 0, numBytes);
+ }
+ catch (IOException e) {
+ throw new MockFtpServerException(e);
+ }
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#readData()
+ */
+ public byte[] readData() {
+ return readData(Integer.MAX_VALUE);
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#readData()
+ */
+ public byte[] readData(int numBytes) {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ int numBytesRead = 0;
+ try {
+ while (numBytesRead < numBytes) {
+ int b = dataInputStream.read();
+ if (b == -1) {
+ break;
+ }
+ bytes.write(b);
+ numBytesRead++;
+ }
+ return bytes.toByteArray();
+ }
+ catch (IOException e) {
+ throw new MockFtpServerException(e);
+ }
+ }
+
+ /**
+ * Wait for and read the command sent from the client on the control connection.
+ *
+ * @return the Command sent from the client; may be null if the session has been closed
+ * <p/>
+ * Package-private to enable testing
+ */
+ Command readCommand() {
+
+ final long socketReadIntervalMilliseconds = 20L;
+
+ try {
+ while (true) {
+ if (terminate) {
+ return null;
+ }
+ // Don't block; only read command when it is available
+ if (controlConnectionReader.ready()) {
+ String command = controlConnectionReader.readLine();
+ LOG.info("Received command: [" + command + "]");
+ return parseCommand(command);
+ }
+ try {
+ Thread.sleep(socketReadIntervalMilliseconds);
+ }
+ catch (InterruptedException e) {
+ throw new MockFtpServerException(e);
+ }
+ }
+ }
+ catch (IOException e) {
+ LOG.error("Read failed", e);
+ throw new MockFtpServerException(e);
+ }
+ }
+
+ /**
+ * Parse the command String into a Command object
+ *
+ * @param commandString - the command String
+ * @return the Command object parsed from the command String
+ */
+ Command parseCommand(String commandString) {
+ Assert.notNullOrEmpty(commandString, "commandString");
+
+ List parameters = new ArrayList();
+ String name;
+
+ int indexOfFirstSpace = commandString.indexOf(" ");
+ if (indexOfFirstSpace != -1) {
+ name = commandString.substring(0, indexOfFirstSpace);
+ StringTokenizer tokenizer = new StringTokenizer(commandString.substring(indexOfFirstSpace + 1),
+ ",");
+ while (tokenizer.hasMoreTokens()) {
+ parameters.add(tokenizer.nextToken());
+ }
+ } else {
+ name = commandString;
+ }
+
+ String[] parametersArray = new String[parameters.size()];
+ return new Command(name, (String[]) parameters.toArray(parametersArray));
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#setClientDataHost(java.net.InetAddress)
+ */
+ public void setClientDataHost(InetAddress clientHost) {
+ this.clientHost = clientHost;
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#setClientDataPort(int)
+ */
+ public void setClientDataPort(int dataPort) {
+ this.clientDataPort = dataPort;
+
+ // Clear out any passive data connection mode information
+ if (passiveModeDataSocket != null) {
+ try {
+ this.passiveModeDataSocket.close();
+ }
+ catch (IOException e) {
+ throw new MockFtpServerException(e);
+ }
+ passiveModeDataSocket = null;
+ }
+ }
+
+ /**
+ * @see java.lang.Runnable#run()
+ */
+ public void run() {
+ try {
+
+ InputStream inputStream = controlSocket.getInputStream();
+ OutputStream outputStream = controlSocket.getOutputStream();
+ controlConnectionReader = new BufferedReader(new InputStreamReader(inputStream));
+ controlConnectionWriter = new PrintWriter(outputStream, true);
+
+ LOG.debug("Starting the session...");
+
+ CommandHandler connectCommandHandler = (CommandHandler) commandHandlers.get(CommandNames.CONNECT);
+ connectCommandHandler.handleCommand(new Command(CommandNames.CONNECT, new String[0]), this);
+
+ while (!terminate) {
+ readAndProcessCommand();
+ }
+ }
+ catch (Exception e) {
+ LOG.error("Error:", e);
+ throw new MockFtpServerException(e);
+ }
+ finally {
+ LOG.debug("Cleaning up the session");
+ try {
+ controlConnectionReader.close();
+ controlConnectionWriter.close();
+ }
+ catch (IOException e) {
+ LOG.error("Error:", e);
+ }
+ LOG.debug("Session stopped.");
+ }
+ }
+
+ /**
+ * Read and process the next command from the control connection
+ *
+ * @throws Exception - if any error occurs
+ */
+ private void readAndProcessCommand() throws Exception {
+
+ Command command = readCommand();
+ if (command != null) {
+ String normalizedCommandName = Command.normalizeName(command.getName());
+ CommandHandler commandHandler = (CommandHandler) commandHandlers.get(normalizedCommandName);
+
+ if (commandHandler == null) {
+ commandHandler = (CommandHandler) commandHandlers.get(CommandNames.UNSUPPORTED);
+ }
+
+ Assert.notNull(commandHandler, "CommandHandler for command [" + normalizedCommandName + "]");
+ commandHandler.handleCommand(command, this);
+ }
+ }
+
+ /**
+ * Assert that the specified number is a valid reply code
+ *
+ * @param replyCode - the reply code to check
+ */
+ private void assertValidReplyCode(int replyCode) {
+ Assert.isTrue(replyCode > 0, "The number [" + replyCode + "] is not a valid reply code");
+ }
+
+ /**
+ * Return the attribute value for the specified name. Return null if no attribute value
+ * exists for that name or if the attribute value is null.
+ *
+ * @param name - the attribute name; may not be null
+ * @return the value of the attribute stored under name; may be null
+ * @see org.mockftpserver.core.session.Session#getAttribute(java.lang.String)
+ */
+ public Object getAttribute(String name) {
+ Assert.notNull(name, "name");
+ return attributes.get(name);
+ }
+
+ /**
+ * Store the value under the specified attribute name.
+ *
+ * @param name - the attribute name; may not be null
+ * @param value - the attribute value; may be null
+ * @see org.mockftpserver.core.session.Session#setAttribute(java.lang.String, java.lang.Object)
+ */
+ public void setAttribute(String name, Object value) {
+ Assert.notNull(name, "name");
+ attributes.put(name, value);
+ }
+
+ /**
+ * Return the Set of names under which attributes have been stored on this session.
+ * Returns an empty Set if no attribute values are stored.
+ *
+ * @return the Set of attribute names
+ * @see org.mockftpserver.core.session.Session#getAttributeNames()
+ */
+ public Set getAttributeNames() {
+ return attributes.keySet();
+ }
+
+ /**
+ * Remove the attribute value for the specified name. Do nothing if no attribute
+ * value is stored for the specified name.
+ *
+ * @param name - the attribute name; may not be null
+ * @throws AssertFailedException - if name is null
+ * @see org.mockftpserver.core.session.Session#removeAttribute(java.lang.String)
+ */
+ public void removeAttribute(String name) {
+ Assert.notNull(name, "name");
+ attributes.remove(name);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/session/Session.java b/tags/2.5/src/main/java/org/mockftpserver/core/session/Session.java
new file mode 100644
index 0000000..9143f77
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/session/Session.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.session;
+
+import java.net.InetAddress;
+import java.util.Set;
+
+/**
+ * Represents an FTP session state and behavior
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public interface Session extends Runnable {
+
+ /**
+ * Close the session, closing the underlying sockets
+ */
+ public void close();
+
+ /**
+ * Send the specified reply code and text across the control connection.
+ *
+ * @param replyCode - the reply code
+ * @param replyText - the reply text to send; may be null
+ */
+ public void sendReply(int replyCode, String replyText);
+
+ /**
+ * Open the data connection, attaching to the predefined port number on the client
+ */
+ public void openDataConnection();
+
+ /**
+ * Close the data connection
+ */
+ public void closeDataConnection();
+
+ /**
+ * Switch to passive mode
+ * @return the local port to be connected to by clients for data transfers
+ */
+ public int switchToPassiveMode();
+
+ /**
+ * Write the specified data using the data connection
+ *
+ * @param data - the data to write
+ * @param numBytes - the number of bytes from data to send
+ */
+ public void sendData(byte[] data, int numBytes);
+
+ /**
+ * Read data from the client across the data connection
+ *
+ * @return the data that was read
+ */
+ public byte[] readData();
+
+ /**
+ * Read and return (up to) numBytes of data from the client across the data connection
+ *
+ * @return the data that was read; the byte[] will be up to numBytes bytes long
+ */
+ public byte[] readData(int numBytes);
+
+ /**
+ * Return the InetAddress representing the client host for this session
+ * @return the client host
+ */
+ public InetAddress getClientHost();
+
+ /**
+ * Return the InetAddress representing the server host for this session
+ * @return the server host
+ */
+ public InetAddress getServerHost();
+
+ /**
+ * @param clientHost - the client host for the data connection
+ */
+ public void setClientDataHost(InetAddress clientHost);
+
+ /**
+ * @param clientDataPort - the port number on the client side for the data connection
+ */
+ public void setClientDataPort(int clientDataPort);
+
+ /**
+ * Return the attribute value for the specified name. Return null if no attribute value
+ * exists for that name or if the attribute value is null.
+ * @param name - the attribute name; may not be null
+ * @return the value of the attribute stored under name; may be null
+ * @throws AssertFailedException - if name is null
+ */
+ public Object getAttribute(String name);
+
+ /**
+ * Store the value under the specified attribute name.
+ * @param name - the attribute name; may not be null
+ * @param value - the attribute value; may be null
+ * @throws AssertFailedException - if name is null
+ */
+ public void setAttribute(String name, Object value);
+
+ /**
+ * Remove the attribute value for the specified name. Do nothing if no attribute
+ * value is stored for the specified name.
+ * @param name - the attribute name; may not be null
+ * @throws AssertFailedException - if name is null
+ */
+ public void removeAttribute(String name);
+
+ /**
+ * Return the Set of names under which attributes have been stored on this session.
+ * Returns an empty Set if no attribute values are stored.
+ * @return the Set of attribute names
+ */
+ public Set getAttributeNames();
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/session/SessionKeys.java b/tags/2.5/src/main/java/org/mockftpserver/core/session/SessionKeys.java
new file mode 100644
index 0000000..7a8b1cf
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/session/SessionKeys.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.session;
+
+/**
+ * Constants for names of properties (attributes) stored in the session.
+ */
+public class SessionKeys {
+
+ public static final String USERNAME = "username";
+ public static final String USER_ACCOUNT = "userAccount";
+ public static final String CURRENT_DIRECTORY = "currentDirectory";
+ public static final String RENAME_FROM = "renameFrom";
+ public static final String ACCOUNT_NAME = "accountName";
+ public static final String ASCII_TYPE = "asciiType";
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/socket/DefaultServerSocketFactory.java b/tags/2.5/src/main/java/org/mockftpserver/core/socket/DefaultServerSocketFactory.java
new file mode 100644
index 0000000..ac6cd0f
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/socket/DefaultServerSocketFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.socket;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+
+/**
+ * Default implementation of the {@link ServerSocketFactory}; creates standard {@link ServerSocket} instances.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public class DefaultServerSocketFactory implements ServerSocketFactory {
+
+ /**
+ * Create a new ServerSocket for the specified port.
+ * @param port - the port
+ * @return a new ServerSocket
+ * @throws IOException
+
+ * @see org.mockftpserver.core.socket.ServerSocketFactory#createServerSocket(int)
+ */
+ public ServerSocket createServerSocket(int port) throws IOException {
+ return new ServerSocket(port);
+ }
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/socket/DefaultSocketFactory.java b/tags/2.5/src/main/java/org/mockftpserver/core/socket/DefaultSocketFactory.java
new file mode 100644
index 0000000..0a365e7
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/socket/DefaultSocketFactory.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.socket;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+
+/**
+ * Default implementation of the {@link SocketFactory}; creates standard {@link Socket} instances.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public class DefaultSocketFactory implements SocketFactory {
+
+ /**
+ * Create a new Socket instance for the specified host and port.
+ * @param host - the IP address of the host endpoint to which the socket is connect
+ * @param port - the port number of the enpoint to which the socket is connected
+ * @return a new Socket
+ * @throws IOException
+ *
+ * @see org.mockftpserver.core.socket.SocketFactory#createSocket(java.net.InetAddress, int)
+ */
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ return new Socket(host, port);
+ }
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/socket/ServerSocketFactory.java b/tags/2.5/src/main/java/org/mockftpserver/core/socket/ServerSocketFactory.java
new file mode 100644
index 0000000..af810a1
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/socket/ServerSocketFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.socket;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+
+/**
+ * Interface for factory that creates new {@link ServerSocket} instances.
+ * Using this abstraction enables unit testing.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public interface ServerSocketFactory {
+
+ /**
+ * Create a new ServerSocket for the specified port
+ * @param port - the port
+ * @return a new ServerSocket
+ * @throws IOException
+ */
+ public ServerSocket createServerSocket(int port) throws IOException;
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/socket/SocketFactory.java b/tags/2.5/src/main/java/org/mockftpserver/core/socket/SocketFactory.java
new file mode 100644
index 0000000..57d9778
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/socket/SocketFactory.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.socket;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+
+/**
+ * Interface for factory that create new {@link Socket} instances.
+ * Using this abstraction enables unit testing.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public interface SocketFactory {
+
+ /**
+ * Create a new Socket instance for the specified host and port.
+ * @param host - the IP address of the host endpoint to which the socket is connect
+ * @param port - the port number of the enpoint to which the socket is connected
+ * @return a new Socket
+ * @throws IOException
+ */
+ public Socket createSocket(InetAddress host, int port) throws IOException;
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/util/Assert.java b/tags/2.5/src/main/java/org/mockftpserver/core/util/Assert.java
new file mode 100644
index 0000000..de4a8cd
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/util/Assert.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.util;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Provides static helper methods to make runtime assertions. Throws an
+ * <code>AssertFailedException</code> when the assertion fails. All methods are static.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class Assert {
+
+ /**
+ * Verify that arg is null. Throw an AssertFailedException if it is not null.
+ * @param arg - the method parameter value
+ * @param argName - the name of the parameter; used in the exception message
+ * @throws AssertFailedException - if arg is not null
+ */
+ public static void isNull(Object arg, String argName) {
+ if (arg != null) {
+ throw new AssertFailedException("The value for \"" + argName + "\" must be null");
+ }
+ }
+
+ /**
+ * Verify that arg is not null. Throw an AssertFailedException if it is null.
+ * @param arg - the method parameter value
+ * @param argName - the name of the parameter; used in the exception message
+ * @throws AssertFailedException - if arg is null
+ */
+ public static void notNull(Object arg, String argName) {
+ if (arg == null) {
+ throw new AssertFailedException("The value of \"" + argName + "\" is null");
+ }
+ }
+
+ /**
+ * Verify that condition is true. Throw an AssertFailedException if it is false.
+ * @param condition - the condition that should be true
+ * @throws AssertFailedException - if condition is false
+ */
+ public static void isTrue(boolean condition, String message) {
+ if (!condition) {
+ throw new AssertFailedException(message);
+ }
+ }
+
+ /**
+ * Verify that condition is false. Throw an AssertFailedException if it is true.
+ * @param condition - the condition that should be false
+ * @throws AssertFailedException - if condition is true
+ */
+ public static void isFalse(boolean condition, String message) {
+ if (condition) {
+ throw new AssertFailedException(message);
+ }
+ }
+
+ /**
+ * Verify that the collection is not null or empty. Throw an
+ * AssertFailedException if it is null or empty.
+ * @param collection - the Collection
+ * @param argName - the name of the parameter; used in the exception message
+ * @throws AssertFailedException - if collection is null or empty
+ */
+ public static void notNullOrEmpty(Collection collection, String argName) {
+ notNull(collection, argName);
+ if (collection.isEmpty()) {
+ throw new AssertFailedException("The \"" + argName + "\" Collection is empty");
+ }
+ }
+
+ /**
+ * Verify that the Map is not null or empty. Throw an AssertFailedException
+ * if it is null or empty.
+ * @param map - the Map
+ * @param argName - the name of the parameter; used in the exception message
+ * @throws AssertFailedException - if map is null or empty
+ */
+ public static void notNullOrEmpty(Map map, String argName) {
+ notNull(map, argName);
+ if (map.isEmpty()) {
+ throw new AssertFailedException("The \"" + argName + "\" Map is empty");
+ }
+ }
+
+ /**
+ * Verify that the array is not null or empty. Throw an
+ * AssertFailedException if it is null or empty.
+ * @param array - the array
+ * @param argName - the name of the parameter; used in the exception message
+ * @throws AssertFailedException - if array is null or empty
+ */
+ public static void notNullOrEmpty(Object[] array, String argName) {
+ notNull(array, argName);
+ if (array.length == 0) {
+ throw new AssertFailedException("The \"" + argName + "\" array is empty");
+ }
+ }
+
+ /**
+ * Verify that the String is not null or empty. Throw an
+ * AssertFailedException if it is null or empty.
+ * @param string - the String
+ * @param argName - the name of the parameter; used in the exception message
+ * @throws AssertFailedException - if string is null or empty
+ */
+ public static void notNullOrEmpty(String string, String argName) {
+ notNull(string, argName);
+ if (string.trim().length() == 0) {
+ throw new AssertFailedException("The \"" + argName + "\" String is empty");
+ }
+ }
+
+ /**
+ * Private constructor. All methods are static
+ */
+ private Assert() {
+ }
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/util/AssertFailedException.java b/tags/2.5/src/main/java/org/mockftpserver/core/util/AssertFailedException.java
new file mode 100644
index 0000000..a0b190e
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/util/AssertFailedException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.util;
+
+/**
+ * Exception that indicates that a runtime assertion from the
+ * {@link org.mockftpserver.core.util.Assert} has failed.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class AssertFailedException extends RuntimeException {
+
+ /**
+ * Create a new instance for the specified message
+ * @param message - the exception message
+ */
+ public AssertFailedException(final String message) {
+ super(message);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/util/HostAndPort.java b/tags/2.5/src/main/java/org/mockftpserver/core/util/HostAndPort.java
new file mode 100644
index 0000000..8fa32d4
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/util/HostAndPort.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.util;
+
+import java.net.InetAddress;
+
+/**
+ * A data-only (transfer) object representing a host (InetAddress) and port number
+ * that together uniquely identify an endpoint for a socket connection.
+ *
+ * This class contains two public properties: host (java.net.InetAddress) and port (int).
+ *
+ * @author Chris Mair
+ * @version : $ - : $
+ */
+public class HostAndPort {
+ public InetAddress host;
+ public int port;
+
+ /**
+ * Construct a new instance with the specified host and port
+ * @param host - the InetAddress host
+ * @param port - the port number
+ */
+ public HostAndPort(InetAddress host, int port) {
+ this.host = host;
+ this.port = port;
+ }
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/util/IoUtil.java b/tags/2.5/src/main/java/org/mockftpserver/core/util/IoUtil.java
new file mode 100644
index 0000000..c6f2055
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/util/IoUtil.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Contains static I/O-related utility methods.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class IoUtil {
+
+ /**
+ * Read the contents of the InputStream and return as a byte[].
+ *
+ * @param input - the InputStream to read
+ * @return the contents of the InputStream as a byte[]
+ * @throws AssertFailedException - if the InputStream is null
+ * @throws java.io.IOException - if an error occurs reading the bytes
+ */
+ public static byte[] readBytes(InputStream input) throws IOException {
+ Assert.notNull(input, "input");
+ ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
+
+ try {
+ while (true) {
+ int b = input.read();
+ if (b == -1) {
+ break;
+ }
+ outBytes.write(b);
+ }
+ }
+ finally {
+ input.close();
+ }
+ return outBytes.toByteArray();
+ }
+
+ /**
+ * Private constructor to prevent instantiation. All members are static.
+ */
+ private IoUtil() {
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/util/PatternUtil.java b/tags/2.5/src/main/java/org/mockftpserver/core/util/PatternUtil.java
new file mode 100644
index 0000000..02f4484
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/util/PatternUtil.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.util;
+
+/**
+ * Contains static utility methods related to pattern-matching and regular expressions.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class PatternUtil {
+
+ /**
+ * Return true if the specified String contains one or more wildcard characters ('?' or '*')
+ *
+ * @param string - the String to check
+ * @return true if the String contains wildcards
+ */
+ public static boolean containsWildcards(String string) {
+ return string.indexOf("*") != -1 || string.indexOf("?") != -1;
+ }
+
+ /**
+ * Convert the specified String, optionally containing wildcards (? or *), to a regular expression String
+ *
+ * @param stringWithWildcards - the String to convert, optionally containing wildcards (? or *)
+ * @return an equivalent regex String
+ * @throws AssertionError - if the stringWithWildcards is null
+ */
+ public static String convertStringWithWildcardsToRegex(String stringWithWildcards) {
+ Assert.notNull(stringWithWildcards, "stringWithWildcards");
+
+ StringBuffer result = new StringBuffer();
+ for (int i = 0; i < stringWithWildcards.length(); i++) {
+ char ch = stringWithWildcards.charAt(i);
+ switch (ch) {
+ case '*':
+ result.append(".*");
+ break;
+ case '?':
+ result.append('.');
+ break;
+ case '$':
+ case '|':
+ case '[':
+ case ']':
+ case '(':
+ case ')':
+ case '.':
+ case ':':
+ case '{':
+ case '}':
+ case '\\':
+ case '^':
+ case '+':
+ result.append('\\');
+ result.append(ch);
+ break;
+ default:
+ result.append(ch);
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * Private constructor to prevent instantiation. All members are static.
+ */
+ private PatternUtil() {
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/util/PortParser.java b/tags/2.5/src/main/java/org/mockftpserver/core/util/PortParser.java
new file mode 100644
index 0000000..487711b
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/util/PortParser.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.util;
+
+import org.mockftpserver.core.CommandSyntaxException;
+import org.mockftpserver.core.MockFtpServerException;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Utility class for parsing host and port values from command arguments.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class PortParser {
+
+ /**
+ * Parse the host address and port number of an extended address. This encoded format is used by
+ * the EPRT FTP command, and supports IPv6.
+ * <p/>
+ * The client network address can be in IPv4 format (e.g., "132.235.1.2") or
+ * IPv6 format (e.g., "1080::8:800:200C:417A"). See RFC2428 for more information.
+ *
+ * @param parameter - the single parameter String containing the encoded host and port number
+ * @return the populated HostAndPort object
+ */
+ public static HostAndPort parseExtendedAddressHostAndPort(String parameter) {
+ if (parameter == null || parameter.length() == 0) {
+ throw new CommandSyntaxException("The parameter string must not be empty or null");
+ }
+
+ String delimiter = parameter.substring(0,1);
+ String[] tokens = parameter.split("\\" + delimiter);
+
+ if (tokens.length < 4) {
+ throw new CommandSyntaxException("Error parsing host and port number [" + parameter + "]");
+ }
+
+ int port = Integer.parseInt(tokens[3]);
+
+ InetAddress host;
+ try {
+ host = InetAddress.getByName(tokens[2]);
+ }
+ catch (UnknownHostException e) {
+ throw new CommandSyntaxException("Error parsing host [" + tokens[2] + "]", e);
+ }
+
+ return new HostAndPort(host, port);
+ }
+
+ /**
+ * Parse a 32-bit IP address and 16-bit port number from the String[] of FTP command parameters.
+ * This is used by the FTP "PORT" command.
+ *
+ * @param parameters - the String[] of command parameters. It is the concatenation
+ * of a 32-bit internet host address and a 16-bit TCP port address. This address
+ * information is broken into 8-bit fields and the value of each field is encoded
+ * as a separate parameter whose value is a decimal number (in character string
+ * representation). Thus, the six parameters for the port command would be:
+ * h1,h2,h3,h4,p1,p2
+ * where h1 is the high order 8 bits of the internet host address, and p1 is the
+ * high order 8 bits of the port number.
+ * @return the HostAndPort object with the host InetAddres and int port parsed from the parameters
+ * @throws org.mockftpserver.core.util.AssertFailedException
+ * - if parameters is null or contains an insufficient number of elements
+ * @throws NumberFormatException - if one of the parameters does not contain a parsable integer
+ */
+ public static HostAndPort parseHostAndPort(String[] parameters) {
+ verifySufficientParameters(parameters);
+
+ byte host1 = parseByte(parameters[0]);
+ byte host2 = parseByte(parameters[1]);
+ byte host3 = parseByte(parameters[2]);
+ byte host4 = parseByte(parameters[3]);
+
+ byte[] address = {host1, host2, host3, host4};
+ InetAddress inetAddress = null;
+ try {
+ inetAddress = InetAddress.getByAddress(address);
+ }
+ catch (UnknownHostException e) {
+ throw new MockFtpServerException("Error parsing host", e);
+ }
+
+ int port1 = Integer.parseInt(parameters[4]);
+ int port2 = Integer.parseInt(parameters[5]);
+ int port = (port1 << 8) + port2;
+
+ return new HostAndPort(inetAddress, port);
+ }
+
+ /**
+ * Convert the InetAddess and port number to a comma-delimited list of byte values,
+ * suitable for the response String from the PASV command.
+ *
+ * @param host - the InetAddress
+ * @param port - the port number
+ * @return the comma-delimited list of byte values, e.g., "196,168,44,55,23,77"
+ */
+ public static String convertHostAndPortToCommaDelimitedBytes(InetAddress host, int port) {
+ StringBuffer buffer = new StringBuffer();
+ byte[] address = host.getAddress();
+ for (int i = 0; i < address.length; i++) {
+ int positiveValue = (address[i] >= 0) ? address[i] : 256 + address[i];
+ buffer.append(positiveValue);
+ buffer.append(",");
+ }
+ int p1 = port >> 8;
+ int p2 = port % 256;
+ buffer.append(String.valueOf(p1));
+ buffer.append(",");
+ buffer.append(String.valueOf(p2));
+ return buffer.toString();
+ }
+
+ /**
+ * Verify that the parameters is not null and contains the required number of elements
+ *
+ * @param parameters - the String[] of command parameters
+ * @throws CommandSyntaxException - if parameters is null or contains an insufficient number of elements
+ */
+ private static void verifySufficientParameters(String[] parameters) {
+ if (parameters == null || parameters.length < 6) {
+ List parms = parameters == null ? null : Arrays.asList(parameters);
+ throw new CommandSyntaxException("The PORT command must contain least be 6 parameters: " + parms);
+ }
+ }
+
+ /**
+ * Parse the specified String as an unsigned decimal byte value (i.e., 0..255). We can't just use
+ * Byte.parseByte(string) because that parses the string as a signed byte.
+ *
+ * @param string - the String containing the decimal byte representation to be parsed
+ * @return the byte value
+ */
+ private static byte parseByte(String string) {
+ return (byte) (0xFF & Short.parseShort(string));
+ }
+
+ /**
+ * Private constructor. All methods are static.
+ */
+ private PortParser() {
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/core/util/StringUtil.java b/tags/2.5/src/main/java/org/mockftpserver/core/util/StringUtil.java
new file mode 100644
index 0000000..090a53c
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/core/util/StringUtil.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.util;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * Contains static String-related utility methods.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class StringUtil {
+
+ /**
+ * Pad the specified String with spaces to the right to the specified width. If the length
+ * of string is already equal to or greater than width, then just return string.
+ *
+ * @param string - the String to pad
+ * @param width - the target width
+ * @return a String of at least width characters, padded on the right with spaces as necessary
+ */
+ public static String padRight(String string, int width) {
+ int numSpaces = width - string.length();
+ return (numSpaces > 0) ? string + spaces(numSpaces) : string;
+ }
+
+ /**
+ * Pad the specified String with spaces to the left to the specified width. If the length
+ * of string is already equal to or greater than width, then just return string.
+ *
+ * @param string - the String to pad
+ * @param width - the target width
+ * @return a String of at least width characters, padded on the left with spaces as necessary
+ */
+ public static String padLeft(String string, int width) {
+ int numSpaces = width - string.length();
+ return (numSpaces > 0) ? spaces(numSpaces) + string : string;
+ }
+
+ /**
+ * Join the Strings within the parts Collection, inserting the delimiter in between elements
+ *
+ * @param parts - the Collection of Strings to join
+ * @param delimiter - the delimiter String to insert between the parts
+ * @return the Strings within the parts collection joined together using the specified delimiter
+ */
+ public static String join(Collection parts, String delimiter) {
+ Assert.notNull(parts, "parts");
+ Assert.notNull(delimiter, "delimiter");
+
+ StringBuffer buf = new StringBuffer();
+ Iterator iter = parts.iterator();
+ while (iter.hasNext()) {
+ String component = (String) iter.next();
+ buf.append(component);
+ if (iter.hasNext()) {
+ buf.append(delimiter);
+ }
+ }
+ return buf.toString();
+ }
+
+ //--------------------------------------------------------------------------
+ // Internal Helper Methods
+ //--------------------------------------------------------------------------
+
+ private static String spaces(int numSpaces) {
+ StringBuffer buf = new StringBuffer();
+ for (int i = 0; i < numSpaces; i++) {
+ buf.append(" ");
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Private constructor to prevent instantiation. All members are static.
+ */
+ private StringUtil() {
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/FakeFtpServer.java b/tags/2.5/src/main/java/org/mockftpserver/fake/FakeFtpServer.java
new file mode 100644
index 0000000..70f447c
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/FakeFtpServer.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake;
+
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ConnectCommandHandler;
+import org.mockftpserver.core.command.ReplyTextBundleUtil;
+import org.mockftpserver.core.command.UnsupportedCommandHandler;
+import org.mockftpserver.core.server.AbstractFtpServer;
+import org.mockftpserver.fake.command.*;
+import org.mockftpserver.fake.filesystem.FileSystem;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <b>FakeFtpServer</b> is the top-level class for a "fake" implementation of an FTP Server,
+ * suitable for testing FTP client code or standing in for a live FTP server.
+ * <p/>
+ * <b>FakeFtpServer</b> provides a high-level abstraction for an FTP Server and is suitable
+ * for most testing and simulation scenarios. You define a filesystem (internal, in-memory) containing
+ * an arbitrary set of files and directories. These files and directories can (optionally) have
+ * associated access permissions. You also configure a set of one or more user accounts that
+ * control which users can login to the FTP server, and their home (default) directories. The
+ * user account is also used when assigning file and directory ownership for new files.
+ * <p> <b>FakeFtpServer</b> processes FTP client requests and responds with reply codes and
+ * reply messages consistent with its configuration and the contents of its internal filesystem,
+ * including file and directory permissions, if they have been configured.
+ * <p/>
+ * <b>FakeFtpServer</b> can be fully configured programmatically or within the
+ * <a href="http://www.springframework.org/">Spring Framework</a> or other dependency-injection container.
+ * <p/>
+ * In general the steps for setting up and starting the <b>FakeFtpServer</b> are:
+ * <ol>
+ * <li>Create a new <b>FakeFtpServer</b> instance, and optionally set the server control port.</li>
+ * <li>Create and configure a <b>FileSystem</b>, and attach to the <b>FakeFtpServer</b> instance.</li>
+ * <li>Create and configure one or more <b>UserAccount</b> objects and attach to the <b>FakeFtpServer</b> instance.</li>
+ * <li>Start the <b>FakeFtpServer</b> instance.</li>
+ * </ol>
+ * <h4>Example Code</h4>
+ * <pre><code>
+ * FakeFtpServer fakeFtpServer = new FakeFtpServer();
+ *
+ * FileSystem fileSystem = new WindowsFakeFileSystem();
+ * fileSystem.add(new DirectoryEntry("c:\\"));
+ * fileSystem.add(new DirectoryEntry("c:\\data"));
+ * fileSystem.add(new FileEntry("c:\\data\\file1.txt", "abcdef 1234567890"));
+ * fileSystem.add(new FileEntry("c:\\data\\run.exe"));
+ * fakeFtpServer.setFileSystem(fileSystem);
+ *
+ * // Create UserAccount with username, password, home-directory
+ * UserAccount userAccount = new UserAccount("joe", "joe123", "c:\\");
+ * fakeFtpServer.addUserAccounts(userAccount);
+ *
+ * fakeFtpServer.start();
+ * </code></pre>
+ *
+ * <h4>Example Code with Permissions</h4>
+ * You can optionally set the permissions and owner/group for each file and directory, as in the following example.
+ * <pre><code>
+ * FileSystem fileSystem = new UnixFakeFileSystem();
+ * DirectoryEntry directoryEntry1 = new DirectoryEntry("/");
+ * directoryEntry1.setPermissions(new Permissions("rwxrwx---"));
+ * directoryEntry1.setOwner("joe");
+ * directoryEntry1.setGroup("dev");
+ *
+ * DirectoryEntry directoryEntry2 = new DirectoryEntry("/data");
+ * directoryEntry2.setPermissions(Permissions.ALL);
+ * directoryEntry2.setOwner("joe");
+ * directoryEntry2.setGroup("dev");
+ *
+ * FileEntry fileEntry1 = new FileEntry("/data/file1.txt", "abcdef 1234567890");
+ * fileEntry1.setPermissionsFromString("rw-rw-rw-");
+ * fileEntry1.setOwner("joe");
+ * fileEntry1.setGroup("dev");
+ *
+ * FileEntry fileEntry2 = new FileEntry("/data/run.exe");
+ * fileEntry2.setPermissionsFromString("rwxrwx---");
+ * fileEntry2.setOwner("mary");
+ * fileEntry2.setGroup("dev");
+ *
+ * fileSystem.add(directoryEntry1);
+ * fileSystem.add(directoryEntry2);
+ * fileSystem.add(fileEntry1);
+ * fileSystem.add(fileEntry2);
+ *
+ * FakeFtpServer fakeFtpServer = new FakeFtpServer();
+ * fakeFtpServer.setFileSystem(fileSystem);
+ *
+ * // Create UserAccount with username, password, home-directory
+ * UserAccount userAccount = new UserAccount("joe", "joe123", "/");
+ * fakeFtpServer.addUserAccounts(userAccount);
+ *
+ * fakeFtpServer.start();
+ * </code></pre>
+ *
+ * <h4>FTP Server Control Port</h4>
+ * By default, <b>FakeFtpServer</b> binds to the server control port of 21. You can use a different server control
+ * port by setting the <code>serverControlPort</code> property. If you specify a value of <code>0</code>,
+ * then a free port number will be chosen automatically; call <code>getServerControlPort()</code> AFTER
+ * <code>start()</code> has been called to determine the actual port number being used. Using a non-default
+ * port number is usually necessary when running on Unix or some other system where that port number is
+ * already in use or cannot be bound from a user process.
+ *
+ * <h4>Other Configuration</h4>
+ * The <code>systemName</code> property specifies the value returned by the <code>SYST</code>
+ * command. Note that this is typically used by an FTP client to determine how to parse
+ * system-dependent reply text, such as directory listings. This value defaults to <code>"WINDOWS"</code>.
+ * <p/>
+ * The <code>helpText</code> property specifies a <i>Map</i> of help text replies sent by the
+ * <code>HELP</code> command. The keys in that <i>Map</i> correspond to the command names passed as
+ * parameters to the <code>HELP</code> command. An entry with the key of an empty string ("") indicates the
+ * text used as the default help text when no command name parameter is specified for the <code>HELP</code> command.
+ *
+ * <h4>FTP Command Reply Text ResourceBundle</h4>
+ * The default text asociated with each FTP command reply code is contained within the
+ * "ReplyText.properties" ResourceBundle file. You can customize these messages by providing a
+ * locale-specific ResourceBundle file on the CLASSPATH, according to the normal lookup rules of
+ * the ResourceBundle class (e.g., "ReplyText_de.properties"). Alternatively, you can
+ * completely replace the ResourceBundle file by calling the calling the
+ * {@link #setReplyTextBaseName(String)} method.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class FakeFtpServer extends AbstractFtpServer implements ServerConfiguration {
+
+ private FileSystem fileSystem;
+ private String systemName = "WINDOWS";
+ private String systemStatus = "Connected";
+ private Map helpText = new HashMap();
+ private Map userAccounts = new HashMap();
+
+ public FileSystem getFileSystem() {
+ return fileSystem;
+ }
+
+ public void setFileSystem(FileSystem fileSystem) {
+ this.fileSystem = fileSystem;
+ }
+
+ public String getSystemName() {
+ return systemName;
+ }
+
+ public void setSystemName(String systemName) {
+ this.systemName = systemName;
+ }
+
+ public Map getHelpText() {
+ return helpText;
+ }
+
+ public void setHelpText(Map helpText) {
+ this.helpText = helpText;
+ }
+
+ public FakeFtpServer() {
+ setCommandHandler(CommandNames.ACCT, new AcctCommandHandler());
+ setCommandHandler(CommandNames.ABOR, new AborCommandHandler());
+ setCommandHandler(CommandNames.ALLO, new AlloCommandHandler());
+ setCommandHandler(CommandNames.APPE, new AppeCommandHandler());
+ setCommandHandler(CommandNames.CWD, new CwdCommandHandler());
+ setCommandHandler(CommandNames.CDUP, new CdupCommandHandler());
+ setCommandHandler(CommandNames.DELE, new DeleCommandHandler());
+ setCommandHandler(CommandNames.EPRT, new EprtCommandHandler());
+ setCommandHandler(CommandNames.EPSV, new EpsvCommandHandler());
+ setCommandHandler(CommandNames.HELP, new HelpCommandHandler());
+ setCommandHandler(CommandNames.LIST, new ListCommandHandler());
+ setCommandHandler(CommandNames.MKD, new MkdCommandHandler());
+ setCommandHandler(CommandNames.MODE, new ModeCommandHandler());
+ setCommandHandler(CommandNames.NLST, new NlstCommandHandler());
+ setCommandHandler(CommandNames.NOOP, new NoopCommandHandler());
+ setCommandHandler(CommandNames.PASS, new PassCommandHandler());
+ setCommandHandler(CommandNames.PASV, new PasvCommandHandler());
+ setCommandHandler(CommandNames.PWD, new PwdCommandHandler());
+ setCommandHandler(CommandNames.PORT, new PortCommandHandler());
+ setCommandHandler(CommandNames.QUIT, new QuitCommandHandler());
+ setCommandHandler(CommandNames.REIN, new ReinCommandHandler());
+ setCommandHandler(CommandNames.REST, new RestCommandHandler());
+ setCommandHandler(CommandNames.RETR, new RetrCommandHandler());
+ setCommandHandler(CommandNames.RMD, new RmdCommandHandler());
+ setCommandHandler(CommandNames.RNFR, new RnfrCommandHandler());
+ setCommandHandler(CommandNames.RNTO, new RntoCommandHandler());
+ setCommandHandler(CommandNames.SITE, new SiteCommandHandler());
+ setCommandHandler(CommandNames.SMNT, new SmntCommandHandler());
+ setCommandHandler(CommandNames.STAT, new StatCommandHandler());
+ setCommandHandler(CommandNames.STOR, new StorCommandHandler());
+ setCommandHandler(CommandNames.STOU, new StouCommandHandler());
+ setCommandHandler(CommandNames.STRU, new StruCommandHandler());
+ setCommandHandler(CommandNames.SYST, new SystCommandHandler());
+ setCommandHandler(CommandNames.TYPE, new TypeCommandHandler());
+ setCommandHandler(CommandNames.USER, new UserCommandHandler());
+ setCommandHandler(CommandNames.XPWD, new PwdCommandHandler());
+
+ // "Special" Command Handlers
+ setCommandHandler(CommandNames.CONNECT, new ConnectCommandHandler());
+ setCommandHandler(CommandNames.UNSUPPORTED, new UnsupportedCommandHandler());
+ }
+
+ /**
+ * Initialize a CommandHandler that has been registered to this server.
+ *
+ * If the CommandHandler implements the <code>ServerConfigurationAware</code> interface, then set its
+ * <code>ServerConfiguration</code> property to <code>this</code>.
+ *
+ * If the CommandHandler implements the <code>ReplyTextBundleAware</code> interface, then set its
+ * <code>replyTextBundle</code> property using the reply text bundle for this server.
+ *
+ * @param commandHandler - the CommandHandler to initialize
+ */
+ protected void initializeCommandHandler(CommandHandler commandHandler) {
+ if (commandHandler instanceof ServerConfigurationAware) {
+ ServerConfigurationAware sca = (ServerConfigurationAware) commandHandler;
+ sca.setServerConfiguration(this);
+ }
+
+ ReplyTextBundleUtil.setReplyTextBundleIfAppropriate(commandHandler, getReplyTextBundle());
+ }
+
+ /**
+ * @return the {@link UserAccount} configured for this server for the specified user name
+ */
+ public UserAccount getUserAccount(String username) {
+ return (UserAccount) userAccounts.get(username);
+ }
+
+ /**
+ * Return the help text for a command or the default help text if no command name is specified
+ *
+ * @param name - the command name; may be empty or null to indicate a request for the default help text
+ * @return the help text for the named command or the default help text if no name is supplied
+ */
+ public String getHelpText(String name) {
+ String key = name == null ? "" : name;
+ return (String) helpText.get(key);
+ }
+
+ /**
+ * Add a single UserAccount. If an account with the same <code>username</code> already exists,
+ * it will be replaced.
+ *
+ * @param userAccount - the UserAccount to add
+ */
+ public void addUserAccount(UserAccount userAccount) {
+ userAccounts.put(userAccount.getUsername(), userAccount);
+ }
+
+ /**
+ * Add the UserAccount objects in the <code>userAccountList</code> to the set of UserAccounts.
+ *
+ * @param userAccountList - the List of UserAccount objects to add
+ */
+ public void setUserAccounts(List userAccountList) {
+ for (int i = 0; i < userAccountList.size(); i++) {
+ UserAccount userAccount = (UserAccount) userAccountList.get(i);
+ userAccounts.put(userAccount.getUsername(), userAccount);
+ }
+ }
+
+ /**
+ * Return the system status description
+ *
+ * @return the system status
+ */
+ public String getSystemStatus() {
+ return systemStatus;
+ }
+
+ /**
+ * Set the system status description text, used by the STAT command handler.
+ *
+ * @param systemStatus - the system status description text
+ */
+ public void setSystemStatus(String systemStatus) {
+ this.systemStatus = systemStatus;
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/ServerConfiguration.java b/tags/2.5/src/main/java/org/mockftpserver/fake/ServerConfiguration.java
new file mode 100644
index 0000000..dbfdb4b
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/ServerConfiguration.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake;
+
+import org.mockftpserver.fake.filesystem.FileSystem;
+
+/**
+ * Interface for objects that provide access to server-specific information.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public interface ServerConfiguration {
+
+ /**
+ * @return the {@link FileSystem} for this server
+ */
+ public FileSystem getFileSystem();
+
+ /**
+ * @param username - the user name
+ * @return the {@link UserAccount} configured for this server for the specified user name
+ */
+ public UserAccount getUserAccount(String username);
+
+ /**
+ * @return the System Name for this server (used by the SYST command)
+ */
+ public String getSystemName();
+
+ /**
+ * @return the System Status text for this server (used by the STAT command)
+ */
+ public String getSystemStatus();
+
+ /**
+ * Return the help text for a command or the default help text if no command name is specified
+ *
+ * @param name - the command name; may be empty or null to indicate a request for the default help text
+ * @return the help text for the named command or the default help text if no name is supplied
+ */
+ public String getHelpText(String name);
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/ServerConfigurationAware.java b/tags/2.5/src/main/java/org/mockftpserver/fake/ServerConfigurationAware.java
new file mode 100644
index 0000000..d89aa36
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/ServerConfigurationAware.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake;
+
+/**
+ * Interface for classes that provide setter and getter to access a ServerConfiguration instance.
+ */
+public interface ServerConfigurationAware {
+
+ public ServerConfiguration getServerConfiguration();
+
+ public void setServerConfiguration(ServerConfiguration serverConfiguration);
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/UserAccount.java b/tags/2.5/src/main/java/org/mockftpserver/fake/UserAccount.java
new file mode 100644
index 0000000..746351a
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/UserAccount.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake;
+
+import org.mockftpserver.core.util.Assert;
+import org.mockftpserver.fake.filesystem.FileSystemEntry;
+import org.mockftpserver.fake.filesystem.Permissions;
+
+import java.util.List;
+
+/**
+ * Represents a single user account on the server, including the username, password, home
+ * directory, list of groups to which this user belongs, and default permissions applied to
+ * newly-created files and directories.
+ * <p/>
+ * The <code>username</code> and <code>homeDirectory</code> property must be non-null
+ * and non-empty. The <code>homeDirectory</code> property must also match the name of an existing
+ * directory within the file system configured for the <code>FakeFtpServer</code>.
+ * <p/>
+ * The group name applied to newly created files/directories is determined by the <code>groups</code> property.
+ * If null or empty, then the default group name ("users") is used. Otherwise, the first value in the
+ * <code>groups</code> List is used. The <code>groups</code> property defaults to an empty List.
+ * <p/>
+ * The default value for <code>defaultPermissionsForNewFile</code> is read and write permissions for
+ * all (user/group/world). The default value for <code>defaultPermissionsForNewDirectory</code> is read,
+ * write and execute permissions for all (user/group/world).
+ * <p/>
+ * The <code>isValidPassword()</code> method returns true if the specified password matches
+ * the password value configured for this user account. This implementation uses the
+ * <code>isEquals()</code> method to compare passwords.
+ * <p/>
+ * If you want to provide a custom comparison, for instance using encrypted passwords, you can
+ * subclass this class and override the <code>comparePassword()</code> method to provide your own
+ * custom implementation.
+ * <p/>
+ * If the <code>passwordCheckedDuringValidation</code> property is set to false, then the password
+ * value is ignored, and the <code>isValidPassword()</code> method just returns <code<true</code>.
+ * <p/>
+ * The <code>accountRequiredForLogin</code> property defaults to false. If it is set to true, then
+ * it is expected that the login for this account will require an ACCOUNT (ACCT) command after the
+ * PASSWORD (PASS) command is completed.
+ */
+public class UserAccount {
+
+ public static final String DEFAULT_USER = "system";
+ public static final String DEFAULT_GROUP = "users";
+ public static final Permissions DEFAULT_PERMISSIONS_FOR_NEW_FILE = new Permissions("rw-rw-rw-");
+ public static final Permissions DEFAULT_PERMISSIONS_FOR_NEW_DIRECTORY = Permissions.ALL;
+
+ private String username;
+ private String password;
+ private String homeDirectory;
+ private List groups;
+ private boolean passwordRequiredForLogin = true;
+ private boolean passwordCheckedDuringValidation = true;
+ private boolean accountRequiredForLogin = false;
+ private Permissions defaultPermissionsForNewFile = DEFAULT_PERMISSIONS_FOR_NEW_FILE;
+ private Permissions defaultPermissionsForNewDirectory = DEFAULT_PERMISSIONS_FOR_NEW_DIRECTORY;
+
+
+ /**
+ * Construct a new uninitialized instance.
+ */
+ public UserAccount() {
+ }
+
+ /**
+ * Construct a new initialized instance.
+ *
+ * @param username - the user name
+ * @param password - the password
+ * @param homeDirectory - the home directory
+ */
+ public UserAccount(String username, String password, String homeDirectory) {
+ setUsername(username);
+ setPassword(password);
+ setHomeDirectory(homeDirectory);
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getHomeDirectory() {
+ return homeDirectory;
+ }
+
+ public void setHomeDirectory(String homeDirectory) {
+ this.homeDirectory = homeDirectory;
+ }
+
+ public List getGroups() {
+ return groups;
+ }
+
+ public void setGroups(List groups) {
+ this.groups = groups;
+ }
+
+ public boolean isPasswordRequiredForLogin() {
+ return passwordRequiredForLogin;
+ }
+
+ public void setPasswordRequiredForLogin(boolean passwordRequiredForLogin) {
+ this.passwordRequiredForLogin = passwordRequiredForLogin;
+ }
+
+ public boolean isPasswordCheckedDuringValidation() {
+ return passwordCheckedDuringValidation;
+ }
+
+ public void setPasswordCheckedDuringValidation(boolean passwordCheckedDuringValidation) {
+ this.passwordCheckedDuringValidation = passwordCheckedDuringValidation;
+ }
+
+ public boolean isAccountRequiredForLogin() {
+ return accountRequiredForLogin;
+ }
+
+ public void setAccountRequiredForLogin(boolean accountRequiredForLogin) {
+ this.accountRequiredForLogin = accountRequiredForLogin;
+ }
+
+ public Permissions getDefaultPermissionsForNewFile() {
+ return defaultPermissionsForNewFile;
+ }
+
+ public void setDefaultPermissionsForNewFile(Permissions defaultPermissionsForNewFile) {
+ this.defaultPermissionsForNewFile = defaultPermissionsForNewFile;
+ }
+
+ public Permissions getDefaultPermissionsForNewDirectory() {
+ return defaultPermissionsForNewDirectory;
+ }
+
+ public void setDefaultPermissionsForNewDirectory(Permissions defaultPermissionsForNewDirectory) {
+ this.defaultPermissionsForNewDirectory = defaultPermissionsForNewDirectory;
+ }
+
+ /**
+ * Return the name of the primary group to which this user belongs. If this account has no associated
+ * groups set, then this method returns the <code>DEFAULT_GROUP</code>. Otherwise, this method
+ * returns the first group name in the <code>groups</code> list.
+ *
+ * @return the name of the primary group for this user
+ */
+ public String getPrimaryGroup() {
+ return (groups == null || groups.isEmpty()) ? DEFAULT_GROUP : (String) groups.get(0);
+ }
+
+ /**
+ * Return true if the specified password is the correct, valid password for this user account.
+ * This implementation uses standard (case-sensitive) String comparison. Subclasses can provide
+ * custom comparison behavior, for instance using encrypted password values, by overriding this
+ * method.
+ *
+ * @param password - the password to compare against the configured value
+ * @return true if the password is correct and valid
+ * @throws org.mockftpserver.core.util.AssertFailedException
+ * - if the username property is null
+ */
+ public boolean isValidPassword(String password) {
+ Assert.notNullOrEmpty(username, "username");
+ return !passwordCheckedDuringValidation || comparePassword(password);
+ }
+
+ /**
+ * @return true if this UserAccount object is valid; i.e. if the homeDirectory is non-null and non-empty.
+ */
+ public boolean isValid() {
+ return homeDirectory != null && homeDirectory.length() > 0;
+ }
+
+ /**
+ * @return the String representation of this object
+ */
+ public String toString() {
+ return "UserAccount[username=" + username + "; password=" + password + "; homeDirectory="
+ + homeDirectory + "; passwordRequiredForLogin=" + passwordRequiredForLogin + "]";
+ }
+
+ /**
+ * Return true if this user has read access to the file/directory represented by the specified FileSystemEntry object.
+ *
+ * @param entry - the FileSystemEntry representing the file or directory
+ * @return true if this use has read access
+ */
+ public boolean canRead(FileSystemEntry entry) {
+ Permissions permissions = entry.getPermissions();
+ if (permissions == null) {
+ return true;
+ }
+
+ if (equalOrBothNull(username, entry.getOwner())) {
+ return permissions.canUserRead();
+ }
+ if (groups != null && groups.contains(entry.getGroup())) {
+ return permissions.canGroupRead();
+ }
+ return permissions.canWorldRead();
+ }
+
+ /**
+ * Return true if this user has write access to the file/directory represented by the specified FileSystemEntry object.
+ *
+ * @param entry - the FileSystemEntry representing the file or directory
+ * @return true if this use has write access
+ */
+ public boolean canWrite(FileSystemEntry entry) {
+ Permissions permissions = entry.getPermissions();
+ if (permissions == null) {
+ return true;
+ }
+
+ if (equalOrBothNull(username, entry.getOwner())) {
+ return permissions.canUserWrite();
+ }
+ if (groups != null && groups.contains(entry.getGroup())) {
+ return permissions.canGroupWrite();
+ }
+ return permissions.canWorldWrite();
+ }
+
+ /**
+ * Return true if this user has execute access to the file/directory represented by the specified FileSystemEntry object.
+ *
+ * @param entry - the FileSystemEntry representing the file or directory
+ * @return true if this use has execute access
+ */
+ public boolean canExecute(FileSystemEntry entry) {
+ Permissions permissions = entry.getPermissions();
+ if (permissions == null) {
+ return true;
+ }
+
+ if (equalOrBothNull(username, entry.getOwner())) {
+ return permissions.canUserExecute();
+ }
+ if (groups != null && groups.contains(entry.getGroup())) {
+ return permissions.canGroupExecute();
+ }
+ return permissions.canWorldExecute();
+ }
+
+ /**
+ * Return true if the specified password matches the password configured for this user account.
+ * This implementation uses standard (case-sensitive) String comparison. Subclasses can provide
+ * custom comparison behavior, for instance using encrypted password values, by overriding this
+ * method.
+ *
+ * @param password - the password to compare against the configured value
+ * @return true if the passwords match
+ */
+ protected boolean comparePassword(String password) {
+ return password != null && password.equals(this.password);
+ }
+
+ /**
+ * Return true only if both Strings are null or they are equal (have the same contents).
+ *
+ * @param string1 - the first String
+ * @param string2 - the second String
+ * @return true if both are null or both are equal
+ */
+ protected boolean equalOrBothNull(String string1, String string2) {
+ return (string1 == null && string2 == null) || (string1 != null && string1.equals(string2));
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/AborCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/AborCommandHandler.java
new file mode 100644
index 0000000..cae876c
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/AborCommandHandler.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the ABOR command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530</li>
+ * <li>Otherwise, reply with 226</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class AborCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ sendReply(session, ReplyCodes.ABOR_OK, "abor");
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/AbstractFakeCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/AbstractFakeCommandHandler.java
new file mode 100644
index 0000000..25bb23c
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/AbstractFakeCommandHandler.java
@@ -0,0 +1,442 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.CommandSyntaxException;
+import org.mockftpserver.core.IllegalStateException;
+import org.mockftpserver.core.NotLoggedInException;
+import org.mockftpserver.core.command.AbstractCommandHandler;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.session.SessionKeys;
+import org.mockftpserver.core.util.Assert;
+import org.mockftpserver.fake.ServerConfiguration;
+import org.mockftpserver.fake.ServerConfigurationAware;
+import org.mockftpserver.fake.UserAccount;
+import org.mockftpserver.fake.filesystem.FileSystem;
+import org.mockftpserver.fake.filesystem.FileSystemEntry;
+import org.mockftpserver.fake.filesystem.FileSystemException;
+import org.mockftpserver.fake.filesystem.InvalidFilenameException;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.MissingResourceException;
+
+/**
+ * Abstract superclass for CommandHandler classes for the "Fake" server.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public abstract class AbstractFakeCommandHandler extends AbstractCommandHandler implements ServerConfigurationAware {
+
+ protected static final String INTERNAL_ERROR_KEY = "internalError";
+
+ private ServerConfiguration serverConfiguration;
+
+ /**
+ * Reply code sent back when a FileSystemException is caught by the {@link #handleCommand(Command, Session)}
+ * This defaults to ReplyCodes.EXISTING_FILE_ERROR (550).
+ */
+ protected int replyCodeForFileSystemException = ReplyCodes.READ_FILE_ERROR;
+
+ public ServerConfiguration getServerConfiguration() {
+ return serverConfiguration;
+ }
+
+ public void setServerConfiguration(ServerConfiguration serverConfiguration) {
+ this.serverConfiguration = serverConfiguration;
+ }
+
+ /**
+ * Use template method to centralize and ensure common validation
+ */
+ public void handleCommand(Command command, Session session) {
+ Assert.notNull(serverConfiguration, "serverConfiguration");
+ Assert.notNull(command, "command");
+ Assert.notNull(session, "session");
+
+ try {
+ handle(command, session);
+ }
+ catch (CommandSyntaxException e) {
+ handleException(command, session, e, ReplyCodes.COMMAND_SYNTAX_ERROR);
+ }
+ catch (IllegalStateException e) {
+ handleException(command, session, e, ReplyCodes.ILLEGAL_STATE);
+ }
+ catch (NotLoggedInException e) {
+ handleException(command, session, e, ReplyCodes.NOT_LOGGED_IN);
+ }
+ catch (InvalidFilenameException e) {
+ handleFileSystemException(command, session, e, ReplyCodes.FILENAME_NOT_VALID, e.getPath());
+ }
+ catch (FileSystemException e) {
+ handleFileSystemException(command, session, e, replyCodeForFileSystemException, e.getPath());
+ }
+ }
+
+ /**
+ * Convenience method to return the FileSystem stored in the ServerConfiguration
+ *
+ * @return the FileSystem
+ */
+ protected FileSystem getFileSystem() {
+ return serverConfiguration.getFileSystem();
+ }
+
+ /**
+ * Handle the specified command for the session. All checked exceptions are expected to be wrapped or handled
+ * by the caller.
+ *
+ * @param command - the Command to be handled
+ * @param session - the session on which the Command was submitted
+ */
+ protected abstract void handle(Command command, Session session);
+
+ // -------------------------------------------------------------------------
+ // Utility methods for subclasses
+ // -------------------------------------------------------------------------
+
+ /**
+ * Send a reply for this command on the control connection.
+ * <p/>
+ * The reply code is designated by the <code>replyCode</code> property, and the reply text
+ * is retrieved from the <code>replyText</code> ResourceBundle, using the specified messageKey.
+ *
+ * @param session - the Session
+ * @param replyCode - the reply code
+ * @param messageKey - the resource bundle key for the reply text
+ * @throws AssertionError - if session is null
+ * @see MessageFormat
+ */
+ protected void sendReply(Session session, int replyCode, String messageKey) {
+ sendReply(session, replyCode, messageKey, Collections.EMPTY_LIST);
+ }
+
+ /**
+ * Send a reply for this command on the control connection.
+ * <p/>
+ * The reply code is designated by the <code>replyCode</code> property, and the reply text
+ * is retrieved from the <code>replyText</code> ResourceBundle, using the specified messageKey.
+ *
+ * @param session - the Session
+ * @param replyCode - the reply code
+ * @param messageKey - the resource bundle key for the reply text
+ * @param args - the optional message arguments; defaults to []
+ * @throws AssertionError - if session is null
+ * @see MessageFormat
+ */
+ protected void sendReply(Session session, int replyCode, String messageKey, List args) {
+ Assert.notNull(session, "session");
+ assertValidReplyCode(replyCode);
+
+ String text = getTextForKey(messageKey);
+ String replyText = (args != null && !args.isEmpty()) ? MessageFormat.format(text, args.toArray()) : text;
+
+ String replyTextToLog = (replyText == null) ? "" : " " + replyText;
+ String argsToLog = (args != null && !args.isEmpty()) ? (" args=" + args) : "";
+ LOG.info("Sending reply [" + replyCode + replyTextToLog + "]" + argsToLog);
+ session.sendReply(replyCode, replyText);
+ }
+
+ /**
+ * Send a reply for this command on the control connection.
+ * <p/>
+ * The reply code is designated by the <code>replyCode</code> property, and the reply text
+ * is retrieved from the <code>replyText</code> ResourceBundle, using the reply code as the key.
+ *
+ * @param session - the Session
+ * @param replyCode - the reply code
+ * @throws AssertionError - if session is null
+ * @see MessageFormat
+ */
+ protected void sendReply(Session session, int replyCode) {
+ sendReply(session, replyCode, Collections.EMPTY_LIST);
+ }
+
+ /**
+ * Send a reply for this command on the control connection.
+ * <p/>
+ * The reply code is designated by the <code>replyCode</code> property, and the reply text
+ * is retrieved from the <code>replyText</code> ResourceBundle, using the reply code as the key.
+ *
+ * @param session - the Session
+ * @param replyCode - the reply code
+ * @param args - the optional message arguments; defaults to []
+ * @throws AssertionError - if session is null
+ * @see MessageFormat
+ */
+ protected void sendReply(Session session, int replyCode, List args) {
+ sendReply(session, replyCode, Integer.toString(replyCode), args);
+ }
+
+ /**
+ * Handle the exception caught during handleCommand()
+ *
+ * @param command - the Command
+ * @param session - the Session
+ * @param exception - the caught exception
+ * @param replyCode - the reply code that should be sent back
+ */
+ private void handleException(Command command, Session session, Throwable exception, int replyCode) {
+ LOG.warn("Error handling command: " + command + "; " + exception, exception);
+ sendReply(session, replyCode);
+ }
+
+ /**
+ * Handle the exception caught during handleCommand()
+ *
+ * @param command - the Command
+ * @param session - the Session
+ * @param exception - the caught exception
+ * @param replyCode - the reply code that should be sent back
+ * @param arg - the arg for the reply (message)
+ */
+ private void handleFileSystemException(Command command, Session session, FileSystemException exception, int replyCode, Object arg) {
+ LOG.warn("Error handling command: " + command + "; " + exception, exception);
+ sendReply(session, replyCode, exception.getMessageKey(), Collections.singletonList(arg));
+ }
+
+ /**
+ * Return the value of the named attribute within the session.
+ *
+ * @param session - the Session
+ * @param name - the name of the session attribute to retrieve
+ * @return the value of the named session attribute
+ * @throws IllegalStateException - if the Session does not contain the named attribute
+ */
+ protected Object getRequiredSessionAttribute(Session session, String name) {
+ Object value = session.getAttribute(name);
+ if (value == null) {
+ throw new IllegalStateException("Session missing required attribute [" + name + "]");
+ }
+ return value;
+ }
+
+ /**
+ * Verify that the current user (if any) has already logged in successfully.
+ *
+ * @param session - the Session
+ */
+ protected void verifyLoggedIn(Session session) {
+ if (getUserAccount(session) == null) {
+ throw new NotLoggedInException("User has not logged in");
+ }
+ }
+
+ /**
+ * @param session - the Session
+ * @return the UserAccount stored in the specified session; may be null
+ */
+ protected UserAccount getUserAccount(Session session) {
+ return (UserAccount) session.getAttribute(SessionKeys.USER_ACCOUNT);
+ }
+
+ /**
+ * Verify that the specified condition related to the file system is true,
+ * otherwise throw a FileSystemException.
+ *
+ * @param condition - the condition that must be true
+ * @param path - the path involved in the operation; this will be included in the
+ * error message if the condition is not true.
+ * @param messageKey - the message key for the exception message
+ * @throws FileSystemException - if the condition is not true
+ */
+ protected void verifyFileSystemCondition(boolean condition, String path, String messageKey) {
+ if (!condition) {
+ throw new FileSystemException(path, messageKey);
+ }
+ }
+
+ /**
+ * Verify that the current user has execute permission to the specified path
+ *
+ * @param session - the Session
+ * @param path - the file system path
+ * @throws FileSystemException - if the condition is not true
+ */
+ protected void verifyExecutePermission(Session session, String path) {
+ UserAccount userAccount = getUserAccount(session);
+ FileSystemEntry entry = getFileSystem().getEntry(path);
+ verifyFileSystemCondition(userAccount.canExecute(entry), path, "filesystem.cannotExecute");
+ }
+
+ /**
+ * Verify that the current user has write permission to the specified path
+ *
+ * @param session - the Session
+ * @param path - the file system path
+ * @throws FileSystemException - if the condition is not true
+ */
+ protected void verifyWritePermission(Session session, String path) {
+ UserAccount userAccount = getUserAccount(session);
+ FileSystemEntry entry = getFileSystem().getEntry(path);
+ verifyFileSystemCondition(userAccount.canWrite(entry), path, "filesystem.cannotWrite");
+ }
+
+ /**
+ * Verify that the current user has read permission to the specified path
+ *
+ * @param session - the Session
+ * @param path - the file system path
+ * @throws FileSystemException - if the condition is not true
+ */
+ protected void verifyReadPermission(Session session, String path) {
+ UserAccount userAccount = getUserAccount(session);
+ FileSystemEntry entry = getFileSystem().getEntry(path);
+ verifyFileSystemCondition(userAccount.canRead(entry), path, "filesystem.cannotRead");
+ }
+
+ /**
+ * Return the full, absolute path for the specified abstract pathname.
+ * If path is null, return the current directory (stored in the session). If
+ * path represents an absolute path, then return path as is. Otherwise, path
+ * is relative, so assemble the full path from the current directory
+ * and the specified relative path.
+ *
+ * @param session - the Session
+ * @param path - the abstract pathname; may be null
+ * @return the resulting full, absolute path
+ */
+ protected String getRealPath(Session session, String path) {
+ String currentDirectory = (String) session.getAttribute(SessionKeys.CURRENT_DIRECTORY);
+ if (path == null) {
+ return currentDirectory;
+ }
+ if (getFileSystem().isAbsolute(path)) {
+ return path;
+ }
+ return getFileSystem().path(currentDirectory, path);
+ }
+
+ /**
+ * Return the end-of-line character(s) used when building multi-line responses
+ *
+ * @return "\r\n"
+ */
+ protected String endOfLine() {
+ return "\r\n";
+ }
+
+ private String getTextForKey(String key) {
+ String msgKey = (key != null) ? key : INTERNAL_ERROR_KEY;
+ try {
+ return getReplyTextBundle().getString(msgKey);
+ }
+ catch (MissingResourceException e) {
+ // No reply text is mapped for the specified key
+ LOG.warn("No reply text defined for key [" + msgKey + "]");
+ return null;
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Login Support (used by USER and PASS commands)
+ // -------------------------------------------------------------------------
+
+ /**
+ * Validate the UserAccount for the specified username. If valid, return true. If the UserAccount does
+ * not exist or is invalid, log an error message, send back a reply code of 530 with an appropriate
+ * error message, and return false. A UserAccount is considered invalid if the homeDirectory property
+ * is not set or is set to a non-existent directory.
+ *
+ * @param username - the username
+ * @param session - the session; used to send back an error reply if necessary
+ * @return true only if the UserAccount for the named user is valid
+ */
+ protected boolean validateUserAccount(String username, Session session) {
+ UserAccount userAccount = serverConfiguration.getUserAccount(username);
+ if (userAccount == null || !userAccount.isValid()) {
+ LOG.error("UserAccount missing or not valid for username [" + username + "]: " + userAccount);
+ sendReply(session, ReplyCodes.USER_ACCOUNT_NOT_VALID, "login.userAccountNotValid", list(username));
+ return false;
+ }
+
+ String home = userAccount.getHomeDirectory();
+ if (!getFileSystem().isDirectory(home)) {
+ LOG.error("Home directory configured for username [" + username + "] is not valid: " + home);
+ sendReply(session, ReplyCodes.USER_ACCOUNT_NOT_VALID, "login.homeDirectoryNotValid", list(username, home));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Log in the specified user for the current session. Send back a reply of 230 with a message indicated
+ * by the replyMessageKey and set the UserAccount and current directory (homeDirectory) in the session.
+ *
+ * @param userAccount - the userAccount for the user to be logged in
+ * @param session - the session
+ * @param replyCode - the reply code to send
+ * @param replyMessageKey - the message key for the reply text
+ */
+ protected void login(UserAccount userAccount, Session session, int replyCode, String replyMessageKey) {
+ sendReply(session, replyCode, replyMessageKey);
+ session.setAttribute(SessionKeys.USER_ACCOUNT, userAccount);
+ session.setAttribute(SessionKeys.CURRENT_DIRECTORY, userAccount.getHomeDirectory());
+ }
+
+ /**
+ * Convenience method to return a List with the specified single item
+ *
+ * @param item - the single item in the returned List
+ * @return a new List with that single item
+ */
+ protected List list(Object item) {
+ return Collections.singletonList(item);
+ }
+
+ /**
+ * Convenience method to return a List with the specified two items
+ *
+ * @param item1 - the first item in the returned List
+ * @param item2 - the second item in the returned List
+ * @return a new List with the specified items
+ */
+ protected List list(Object item1, Object item2) {
+ List list = new ArrayList(2);
+ list.add(item1);
+ list.add(item2);
+ return list;
+ }
+
+ /**
+ * Return true if the specified string is null or empty
+ *
+ * @param string - the String to check; may be null
+ * @return true only if the specified String is null or empyt
+ */
+ protected boolean notNullOrEmpty(String string) {
+ return string != null && string.length() > 0;
+ }
+
+ /**
+ * Return the string unless it is null or empty, in which case return the defaultString.
+ *
+ * @param string - the String to check; may be null
+ * @param defaultString - the value to return if string is null or empty
+ * @return string if not null and not empty; otherwise return defaultString
+ */
+ protected String defaultIfNullOrEmpty(String string, String defaultString) {
+ return (notNullOrEmpty(string) ? string : defaultString);
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/AbstractStoreFileCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/AbstractStoreFileCommandHandler.java
new file mode 100644
index 0000000..f13e780
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/AbstractStoreFileCommandHandler.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.fake.filesystem.FileEntry;
+import org.mockftpserver.fake.filesystem.FileSystemException;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Abstract superclass for CommandHandlers that that store a file (STOR, STOU, APPE). Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530 and terminate</li>
+ * <li>If the pathname parameter is required but missing, then reply with 501 and terminate</li>
+ * <li>If the required pathname parameter does not specify a valid filename, then reply with 553 and terminate</li>
+ * <li>If the current user does not have write access to the named file, if it already exists, or else to its
+ * parent directory, then reply with 553 and terminate</li>
+ * <li>If the current user does not have execute access to the parent directory, then reply with 553 and terminate</li>
+ * <li>Send an initial reply of 150</li>
+ * <li>Read all available bytes from the data connection and store/append to the named file in the server file system</li>
+ * <li>If file write/store fails, then reply with 553 and terminate</li>
+ * <li>Send a final reply with 226</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public abstract class AbstractStoreFileCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ this.replyCodeForFileSystemException = ReplyCodes.WRITE_FILE_ERROR;
+
+ String filename = getOutputFile(command);
+ String path = getRealPath(session, filename);
+ verifyFileSystemCondition(!getFileSystem().isDirectory(path), path, "filesystem.isDirectory");
+ String parentPath = getFileSystem().getParent(path);
+ verifyFileSystemCondition(getFileSystem().isDirectory(parentPath), parentPath, "filesystem.isNotADirectory");
+
+ // User must have write permission to the file, if an existing file, or else to the directory if a new file
+ String pathMustBeWritable = getFileSystem().exists(path) ? path : parentPath;
+ verifyWritePermission(session, pathMustBeWritable);
+
+ // User must have execute permission to the parent directory
+ verifyExecutePermission(session, parentPath);
+
+ sendReply(session, ReplyCodes.TRANSFER_DATA_INITIAL_OK);
+
+ session.openDataConnection();
+ byte[] contents = session.readData();
+ session.closeDataConnection();
+
+ FileEntry file = (FileEntry) getFileSystem().getEntry(path);
+ if (file == null) {
+ file = new FileEntry(path);
+ getFileSystem().add(file);
+ }
+ file.setPermissions(getUserAccount(session).getDefaultPermissionsForNewFile());
+
+ if (contents != null && contents.length > 0) {
+ OutputStream out = file.createOutputStream(appendToOutputFile());
+ try {
+ out.write(contents);
+ }
+ catch (IOException e) {
+ LOG.error("Error writing to file [" + file.getPath() + "]", e);
+ throw new FileSystemException(file.getPath(), null, e);
+ }
+ finally {
+ try {
+ out.close();
+ } catch (IOException e) {
+ LOG.error("Error closing OutputStream for file [" + file.getPath() + "]", e);
+ }
+ }
+ }
+ sendReply(session, ReplyCodes.TRANSFER_DATA_FINAL_OK, getMessageKey(), list(filename));
+ }
+
+ /**
+ * Return the path (absolute or relative) for the output file. The default behavior is to return
+ * the required first parameter for the specified Command. Subclasses may override the default behavior.
+ *
+ * @param command - the Command
+ * @return the output file name
+ */
+ protected String getOutputFile(Command command) {
+ return command.getRequiredParameter(0);
+ }
+
+ /**
+ * @return true if this command should append the transferred contents to the output file; false means
+ * overwrite an existing file. This default implentation returns false.
+ */
+ protected boolean appendToOutputFile() {
+ return false;
+ }
+
+ /**
+ * @return the message key for the reply message sent with the final (226) reply
+ */
+ protected abstract String getMessageKey();
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/AcctCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/AcctCommandHandler.java
new file mode 100644
index 0000000..2048396
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/AcctCommandHandler.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.session.SessionKeys;
+
+/**
+ * CommandHandler for the ACCT command. Handler logic:
+ * <ol>
+ * <li>If the required account parameter is missing, then reply with 501</li>
+ * <li>If this command was not preceded by a valid USER command, then reply with 503</li>
+ * <li>Store the account name in the session and reply with 230</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class AcctCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ String accountName = command.getRequiredParameter(0);
+ String username = (String) getRequiredSessionAttribute(session, SessionKeys.USERNAME);
+
+ session.setAttribute(SessionKeys.ACCOUNT_NAME, accountName);
+ sendReply(session, ReplyCodes.ACCT_OK, "acct", list(username));
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/AlloCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/AlloCommandHandler.java
new file mode 100644
index 0000000..46d3e37
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/AlloCommandHandler.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the ALLO command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530</li>
+ * <li>Otherwise, reply with 200</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class AlloCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ sendReply(session, ReplyCodes.ALLO_OK, "allo");
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/AppeCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/AppeCommandHandler.java
new file mode 100644
index 0000000..b5a7a7e
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/AppeCommandHandler.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+/**
+ * CommandHandler for the APPE command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530 and terminate</li>
+ * <li>If the required pathname parameter is missing, then reply with 501 and terminate</li>
+ * <li>If the pathname parameter does not specify a valid filename, then reply with 553 and terminate</li>
+ * <li>If the current user does not have write access to the named file, if it already exists, or else to its
+ * parent directory, then reply with 553 and terminate</li>
+ * <li>If the current user does not have execute access to the parent directory, then reply with 553 and terminate</li>
+ * <li>Send an initial reply of 150</li>
+ * <li>Read all available bytes from the data connection and append to the named file in the server file system</li>
+ * <li>If file write/store fails, then reply with 553 and terminate</li>
+ * <li>Send a final reply with 226</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class AppeCommandHandler extends AbstractStoreFileCommandHandler {
+
+ /**
+ * @return the message key for the reply message sent with the final (226) reply
+ */
+ protected String getMessageKey() {
+ return "appe";
+ }
+
+ /**
+ * @return true if this command should append the transferred contents to the output file; false means
+ * overwrite an existing file.
+ */
+ protected boolean appendToOutputFile() {
+ return true;
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/CdupCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/CdupCommandHandler.java
new file mode 100644
index 0000000..44db478
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/CdupCommandHandler.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.session.SessionKeys;
+
+/**
+ * CommandHandler for the CDUP command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530</li>
+ * <li>If the current directory has no parent or if the current directory cannot be changed, then reply with 550 and terminate</li>
+ * <li>If the current user does not have execute access to the parent directory, then reply with 550 and terminate</li>
+ * <li>Otherwise, reply with 200 and change the current directory stored in the session to the parent directory</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class CdupCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ String currentDirectory = (String) getRequiredSessionAttribute(session, SessionKeys.CURRENT_DIRECTORY);
+ String path = getFileSystem().getParent(currentDirectory);
+
+ this.replyCodeForFileSystemException = ReplyCodes.READ_FILE_ERROR;
+ verifyFileSystemCondition(notNullOrEmpty(path), currentDirectory, "filesystem.parentDirectoryDoesNotExist");
+ verifyFileSystemCondition(getFileSystem().isDirectory(path), path, "filesystem.isNotADirectory");
+
+ // User must have execute permission to the parent directory
+ verifyExecutePermission(session, path);
+
+ session.setAttribute(SessionKeys.CURRENT_DIRECTORY, path);
+ sendReply(session, ReplyCodes.CDUP_OK, "cdup", list(path));
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/CwdCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/CwdCommandHandler.java
new file mode 100644
index 0000000..a441565
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/CwdCommandHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.session.SessionKeys;
+
+/**
+ * CommandHandler for the CWD command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530</li>
+ * <li>If the required pathname parameter is missing, then reply with 501 and terminate</li>
+ * <li>If the pathname parameter does not specify an existing directory, then reply with 550 and terminate</li>
+ * <li>If the current user does not have execute access to the directory, then reply with 550 and terminate</li>
+ * <li>Otherwise, reply with 250 and change the current directory stored in the session</li>
+ * </ol>
+ * The supplied pathname may be absolute or relative to the current directory.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class CwdCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ String path = getRealPath(session, command.getRequiredParameter(0));
+
+ this.replyCodeForFileSystemException = ReplyCodes.READ_FILE_ERROR;
+ verifyFileSystemCondition(getFileSystem().exists(path), path, "filesystem.doesNotExist");
+ verifyFileSystemCondition(getFileSystem().isDirectory(path), path, "filesystem.isNotADirectory");
+
+ // User must have execute permission to the directory
+ verifyExecutePermission(session, path);
+
+ session.setAttribute(SessionKeys.CURRENT_DIRECTORY, path);
+ sendReply(session, ReplyCodes.CWD_OK, "cwd", list(path));
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/DeleCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/DeleCommandHandler.java
new file mode 100644
index 0000000..a59a6af
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/DeleCommandHandler.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the DELE command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530</li>
+ * <li>If the required pathname parameter is missing, then reply with 501</li>
+ * <li>If the pathname parameter does not specify an existing file then reply with 550</li>
+ * <li>If the current user does not have write access to the parent directory, then reply with 550</li>
+ * <li>Otherwise, delete the named file and reply with 250</li>
+ * </ol>
+ * The supplied pathname may be absolute or relative to the current directory.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class DeleCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ String path = getRealPath(session, command.getRequiredParameter(0));
+
+ this.replyCodeForFileSystemException = ReplyCodes.READ_FILE_ERROR;
+ verifyFileSystemCondition(getFileSystem().isFile(path), path, "filesystem.isNotAFile");
+
+ // User must have write permission to the parent directory
+ verifyWritePermission(session, getFileSystem().getParent(path));
+
+ getFileSystem().delete(path);
+ sendReply(session, ReplyCodes.DELE_OK, "dele", list(path));
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/EprtCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/EprtCommandHandler.java
new file mode 100644
index 0000000..0f24f10
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/EprtCommandHandler.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.HostAndPort;
+import org.mockftpserver.core.util.PortParser;
+
+/**
+ * CommandHandler for the EPRT command. Handler logic:
+ * <ol>
+ * <li>Parse the client network address (InetAddress) and port number from the (single)
+ * parameter string of the form: "EPRT<space><d><net-prt><d><net-addr><d><tcp-port><d>".
+ * The client network address can be in IPv4 format (e.g., "132.235.1.2") or
+ * IPv6 format (e.g., "1080::8:800:200C:417A")
+ * <li>Send back a reply of 200</li>
+ * </ol>
+ * See RFC2428 for more information.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class EprtCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ String parameter = command.getRequiredParameter(0);
+ HostAndPort client = PortParser.parseExtendedAddressHostAndPort(parameter);
+ LOG.debug("host=" + client.host + " port=" + client.port);
+ session.setClientDataHost(client.host);
+ session.setClientDataPort(client.port);
+ sendReply(session, ReplyCodes.EPRT_OK, "eprt");
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/EpsvCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/EpsvCommandHandler.java
new file mode 100644
index 0000000..a228a9c
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/EpsvCommandHandler.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+import java.net.InetAddress;
+
+/**
+ * CommandHandler for the EPSV command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530</li>
+ * <li>Otherwise, request the Session to switch to passive data connection mode. Return a reply code
+ * of 229, along with response text including: "<i>(|||PORT|)</i>", where <i>PORT</i> is the 16-bit
+ * TCP port address of the data connection on the server to which the client must connect.</li>
+ * </ol>
+ * See RFC2428 for more information.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class EpsvCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ int port = session.switchToPassiveMode();
+ InetAddress server = session.getServerHost();
+ LOG.debug("server=" + server + " port=" + port);
+ sendReply(session, ReplyCodes.EPSV_OK, "epsv", list(Integer.toString(port)));
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/HelpCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/HelpCommandHandler.java
new file mode 100644
index 0000000..afaa552
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/HelpCommandHandler.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.StringUtil;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * CommandHandler for the HELP command. Handler logic:
+ * <ol>
+ * <li>If the optional command-name parameter is specified, then reply with 214 along with the
+ * help text configured for that command (or empty if none)</li>
+ * <li>Otherwise, reply with 214 along with the configured default help text that has been configured
+ * (or empty if none)</li>
+ * </ol>
+ * <p/>
+ * The help text is configured within the {@link org.mockftpserver.fake.FakeFtpServer}.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ * @see org.mockftpserver.fake.ServerConfiguration
+ * @see org.mockftpserver.fake.FakeFtpServer
+ */
+public class HelpCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ List parameters = Arrays.asList(command.getParameters());
+ String key = StringUtil.join(parameters, " ");
+ String help = getServerConfiguration().getHelpText(key);
+ if (help == null) {
+ sendReply(session, ReplyCodes.HELP_OK, "help.noHelpTextDefined", list(key));
+ } else {
+ sendReply(session, ReplyCodes.HELP_OK, "help", list(help));
+ }
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/ListCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/ListCommandHandler.java
new file mode 100644
index 0000000..b8e89e8
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/ListCommandHandler.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.StringUtil;
+import org.mockftpserver.fake.filesystem.FileSystemEntry;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * CommandHandler for the LIST command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530 and terminate</li>
+ * <li>Send an initial reply of 150</li>
+ * <li>If the current user does not have read access to the file or directory to be listed, then reply with 550 and terminate</li>
+ * <li>If an error occurs during processing, then send a reply of 451 and terminate</li>
+ * <li>If the optional pathname parameter is missing, then send a directory listing for
+ * the current directory across the data connection</li>
+ * <li>Otherwise, if the optional pathname parameter specifies a directory or group of files,
+ * then send a directory listing for the specified directory across the data connection</li>
+ * <li>Otherwise, if the optional pathname parameter specifies a filename, then send information
+ * for the specified file across the data connection</li>
+ * <li>Send a final reply with 226</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class ListCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ sendReply(session, ReplyCodes.TRANSFER_DATA_INITIAL_OK);
+
+ String path = getRealPath(session, command.getParameter(0));
+
+ // User must have read permission to the path
+ if (getFileSystem().exists(path)) {
+ this.replyCodeForFileSystemException = ReplyCodes.READ_FILE_ERROR;
+ verifyReadPermission(session, path);
+ }
+
+ this.replyCodeForFileSystemException = ReplyCodes.SYSTEM_ERROR;
+ List fileEntries = getFileSystem().listFiles(path);
+ Iterator iter = fileEntries.iterator();
+ List lines = new ArrayList();
+ while (iter.hasNext()) {
+ FileSystemEntry entry = (FileSystemEntry) iter.next();
+ lines.add(getFileSystem().formatDirectoryListing(entry));
+ }
+ String result = StringUtil.join(lines, endOfLine());
+ result += result.length() > 0 ? endOfLine() : "";
+
+ session.openDataConnection();
+ LOG.info("Sending [" + result + "]");
+ session.sendData(result.getBytes(), result.length());
+ session.closeDataConnection();
+
+ sendReply(session, ReplyCodes.TRANSFER_DATA_FINAL_OK);
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/MkdCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/MkdCommandHandler.java
new file mode 100644
index 0000000..3087254
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/MkdCommandHandler.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.fake.filesystem.DirectoryEntry;
+
+/**
+ * CommandHandler for the MKD command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530</li>
+ * <li>If the required pathname parameter is missing, then reply with 501</li>
+ * <li>If the parent directory of the specified pathname does not exist, then reply with 550</li>
+ * <li>If the pathname parameter specifies an existing file or directory, or if the create directory fails, then reply with 550</li>
+ * <li>If the current user does not have write and execute access to the parent directory, then reply with 550</li>
+ * <li>Otherwise, reply with 257</li>
+ * </ol>
+ * The supplied pathname may be absolute or relative to the current directory.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class MkdCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ String path = getRealPath(session, command.getRequiredParameter(0));
+ String parent = getFileSystem().getParent(path);
+
+ this.replyCodeForFileSystemException = ReplyCodes.READ_FILE_ERROR;
+ verifyFileSystemCondition(getFileSystem().exists(parent), parent, "filesystem.doesNotExist");
+ verifyFileSystemCondition(!getFileSystem().exists(path), path, "filesystem.alreadyExists");
+
+ // User must have write permission to the parent directory
+ verifyWritePermission(session, parent);
+
+ // User must have execute permission to the parent directory
+ verifyExecutePermission(session, parent);
+
+ DirectoryEntry dirEntry = new DirectoryEntry(path);
+ getFileSystem().add(dirEntry);
+ dirEntry.setPermissions(getUserAccount(session).getDefaultPermissionsForNewDirectory());
+
+ sendReply(session, ReplyCodes.MKD_OK, "mkd", list(path));
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/ModeCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/ModeCommandHandler.java
new file mode 100644
index 0000000..5ff8bb8
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/ModeCommandHandler.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the MODE command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530</li>
+ * <li>Otherwise, reply with 200</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class ModeCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ sendReply(session, ReplyCodes.MODE_OK, "mode");
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/NlstCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/NlstCommandHandler.java
new file mode 100644
index 0000000..dd11065
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/NlstCommandHandler.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.StringUtil;
+
+import java.util.List;
+
+/**
+ * CommandHandler for the NLST command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530 and terminate</li>
+ * <li>Send an initial reply of 150</li>
+ * <li>If the current user does not have read access to the file or directory to be listed, then reply with 550 and terminate</li>
+ * <li>If an error occurs during processing, then send a reply of 451 and terminate</li>
+ * <li>If the optional pathname parameter is missing, then send a directory listing for
+ * the current directory across the data connection</li>
+ * <li>Otherwise, if the optional pathname parameter specifies a directory or group of files,
+ * then send a directory listing for the specified directory across the data connection</li>
+ * <li>Otherwise, if the pathname parameter does not specify an existing directory or group of files,
+ * then send an empty response across the data connection</li>
+ * <li>Send a final reply with 226</li>
+ * </ol>
+ * The directory listing sent includes filenames only, separated by end-of-line characters.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class NlstCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ sendReply(session, ReplyCodes.TRANSFER_DATA_INITIAL_OK);
+ String path = getRealPath(session, command.getParameter(0));
+
+ // User must have read permission to the path
+ if (getFileSystem().exists(path)) {
+ this.replyCodeForFileSystemException = ReplyCodes.READ_FILE_ERROR;
+ verifyReadPermission(session, path);
+ }
+
+ this.replyCodeForFileSystemException = ReplyCodes.SYSTEM_ERROR;
+ List names = getFileSystem().listNames(path);
+ String directoryListing = StringUtil.join(names, endOfLine());
+ directoryListing += directoryListing.length() > 0 ? endOfLine() : "";
+
+ session.openDataConnection();
+ session.sendData(directoryListing.getBytes(), directoryListing.length());
+ session.closeDataConnection();
+
+ sendReply(session, ReplyCodes.TRANSFER_DATA_FINAL_OK);
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/NoopCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/NoopCommandHandler.java
new file mode 100644
index 0000000..9cbb29d
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/NoopCommandHandler.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the NOOP command. Handler logic:
+ * <ol>
+ * <li>Reply with 200</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class NoopCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ sendReply(session, ReplyCodes.NOOP_OK, "noop");
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/PassCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/PassCommandHandler.java
new file mode 100644
index 0000000..d446696
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/PassCommandHandler.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.session.SessionKeys;
+import org.mockftpserver.fake.UserAccount;
+
+/**
+ * CommandHandler for the PASS command. Handler logic:
+ * <ol>
+ * <li>If the required password parameter is missing, then reply with 501</li>
+ * <li>If this command was not preceded by a valid USER command, then reply with 503</li>
+ * <li>If the user account configured for the named user does not exist or is not valid, then reply with 530</li>
+ * <li>If the specified password is not correct, then reply with 530</li>
+ * <li>Otherwise, reply with 230</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class PassCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ String password = command.getRequiredParameter(0);
+ String username = (String) getRequiredSessionAttribute(session, SessionKeys.USERNAME);
+
+ if (validateUserAccount(username, session)) {
+ UserAccount userAccount = getServerConfiguration().getUserAccount(username);
+ if (userAccount.isValidPassword(password)) {
+ int replyCode = (userAccount.isAccountRequiredForLogin()) ? ReplyCodes.PASS_NEED_ACCOUNT : ReplyCodes.PASS_OK;
+ String replyMessageKey = (userAccount.isAccountRequiredForLogin()) ? "pass.needAccount" : "pass";
+ login(userAccount, session, replyCode, replyMessageKey);
+ } else {
+ sendReply(session, ReplyCodes.PASS_LOG_IN_FAILED, "pass.loginFailed");
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/PasvCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/PasvCommandHandler.java
new file mode 100644
index 0000000..d4fbf95
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/PasvCommandHandler.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.PortParser;
+
+import java.net.InetAddress;
+
+/**
+ * CommandHandler for the PASV command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530</li>
+ * <li>Otherwise, request the Session to switch to passive data connection mode. Return a reply code of 227,
+ * along with response text of the form: "<i>(h1,h2,h3,h4,p1,p2)</i>", where <i>h1..h4</i> are the
+ * 4 bytes of the 32-bit internet host address of the server, and <i>p1..p2</i> are the 2
+ * bytes of the 16-bit TCP port address of the data connection on the server to which
+ * the client must connect. See RFC959 for more information.</i>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class PasvCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+
+ int port = session.switchToPassiveMode();
+ InetAddress server = session.getServerHost();
+ LOG.debug("server=" + server + " port=" + port);
+ String hostAndPort = PortParser.convertHostAndPortToCommaDelimitedBytes(server, port);
+
+ sendReply(session, ReplyCodes.PASV_OK, "pasv", list(hostAndPort));
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/PortCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/PortCommandHandler.java
new file mode 100644
index 0000000..7e7deb6
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/PortCommandHandler.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.HostAndPort;
+import org.mockftpserver.core.util.PortParser;
+
+/**
+ * CommandHandler for the PORT command. Handler logic:
+ * <ol>
+ * <li>Parse the client data host (InetAddress) submitted from parameters 1-4
+ * <li>Parse the port number submitted on the invocation from parameter 5-6
+ * <li>Send backa a reply of 200</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ * @see org.mockftpserver.core.util.PortParser
+ */
+public class PortCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ HostAndPort client = PortParser.parseHostAndPort(command.getParameters());
+ LOG.debug("host=" + client.host + " port=" + client.port);
+ session.setClientDataHost(client.host);
+ session.setClientDataPort(client.port);
+ sendReply(session, ReplyCodes.PORT_OK, "port");
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/PwdCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/PwdCommandHandler.java
new file mode 100644
index 0000000..31b657b
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/PwdCommandHandler.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.session.SessionKeys;
+
+/**
+ * CommandHandler for the PWD command. Handler logic:
+ * <ol>
+ * <li>If the required "current directory" property is missing from the session, then reply with 550</li>
+ * <li>Otherwise, reply with 257 and the current directory</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class PwdCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ String currentDirectory = (String) session.getAttribute(SessionKeys.CURRENT_DIRECTORY);
+ this.replyCodeForFileSystemException = ReplyCodes.READ_FILE_ERROR;
+ verifyFileSystemCondition(notNullOrEmpty(currentDirectory), currentDirectory, "filesystem.currentDirectoryNotSet");
+ sendReply(session, ReplyCodes.PWD_OK, "pwd", list(currentDirectory));
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/QuitCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/QuitCommandHandler.java
new file mode 100644
index 0000000..d30f6bd
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/QuitCommandHandler.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the QUIT command. Return a reply code of 221 and close the current session.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class QuitCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ sendReply(session, ReplyCodes.QUIT_OK, "quit");
+ session.close();
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/ReinCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/ReinCommandHandler.java
new file mode 100644
index 0000000..cca6c9b
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/ReinCommandHandler.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.session.SessionKeys;
+
+/**
+ * CommandHandler for the REIN command. Handler logic:
+ * <ol>
+ * <li>Terminates (logs out) the current user, if there is one</li>
+ * <li>Reply with 220</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class ReinCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ session.removeAttribute(SessionKeys.USER_ACCOUNT);
+ sendReply(session, ReplyCodes.REIN_OK, "rein");
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/RestCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/RestCommandHandler.java
new file mode 100644
index 0000000..3e4265f
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/RestCommandHandler.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the REST command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530</li>
+ * <li>Otherwise, reply with 350</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class RestCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ sendReply(session, ReplyCodes.REST_OK, "rest");
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/RetrCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/RetrCommandHandler.java
new file mode 100644
index 0000000..739e5a4
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/RetrCommandHandler.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.session.SessionKeys;
+import org.mockftpserver.core.util.IoUtil;
+import org.mockftpserver.fake.filesystem.FileEntry;
+import org.mockftpserver.fake.filesystem.FileSystemEntry;
+import org.mockftpserver.fake.filesystem.FileSystemException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * CommandHandler for the RETR command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530 and terminate</li>
+ * <li>If the required pathname parameter is missing, then reply with 501 and terminate</li>
+ * <li>If the pathname parameter does not specify a valid, existing filename, then reply with 550 and terminate</li>
+ * <li>If the current user does not have read access to the file at the specified path or execute permission to its directory, then reply with 550 and terminate</li>
+ * <li>Send an initial reply of 150</li>
+ * <li>Send the contents of the named file across the data connection</li>
+ * <li>If there is an error reading the file, then reply with 550 and terminate</li>
+ * <li>Send a final reply with 226</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class RetrCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ this.replyCodeForFileSystemException = ReplyCodes.READ_FILE_ERROR;
+
+ String path = getRealPath(session, command.getRequiredParameter(0));
+ FileSystemEntry entry = getFileSystem().getEntry(path);
+ verifyFileSystemCondition(entry != null, path, "filesystem.doesNotExist");
+ verifyFileSystemCondition(!entry.isDirectory(), path, "filesystem.isNotAFile");
+ FileEntry fileEntry = (FileEntry) entry;
+
+ // User must have read permission to the file
+ verifyReadPermission(session, path);
+
+ // User must have execute permission to the parent directory
+ verifyExecutePermission(session, getFileSystem().getParent(path));
+
+ sendReply(session, ReplyCodes.TRANSFER_DATA_INITIAL_OK);
+ InputStream input = fileEntry.createInputStream();
+ session.openDataConnection();
+ byte[] bytes = null;
+ try {
+ bytes = IoUtil.readBytes(input);
+ }
+ catch (IOException e) {
+ LOG.error("Error reading from file [" + fileEntry.getPath() + "]", e);
+ throw new FileSystemException(fileEntry.getPath(), null, e);
+ }
+ finally {
+ try {
+ input.close();
+ }
+ catch (IOException e) {
+ LOG.error("Error closing InputStream for file [" + fileEntry.getPath() + "]", e);
+ }
+ }
+
+ if (isAsciiMode(session)) {
+ bytes = convertLfToCrLf(bytes);
+ }
+ session.sendData(bytes, bytes.length);
+ session.closeDataConnection();
+ sendReply(session, ReplyCodes.TRANSFER_DATA_FINAL_OK);
+ }
+
+ /**
+ * Within the specified byte array, replace all LF (\n) that are NOT preceded by a CR (\r) into CRLF (\r\n).
+ *
+ * @param bytes - the bytes to be converted
+ * @return the result of converting LF to CRLF
+ */
+ protected byte[] convertLfToCrLf(byte[] bytes) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ char lastChar = ' ';
+ for (int i = 0; i < bytes.length; i++) {
+ char ch = (char) bytes[i];
+ if (ch == '\n' && lastChar != '\r') {
+ out.write('\r');
+ out.write('\n');
+ } else {
+ out.write(bytes[i]);
+ }
+ lastChar = ch;
+ }
+ return out.toByteArray();
+ }
+
+ private boolean isAsciiMode(Session session) {
+ // Defaults to true
+ return session.getAttribute(SessionKeys.ASCII_TYPE) != Boolean.FALSE;
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/RmdCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/RmdCommandHandler.java
new file mode 100644
index 0000000..db5b537
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/RmdCommandHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the RMD command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530</li>
+ * <li>If the required pathname parameter is missing, then reply with 501</li>
+ * <li>If the pathname parameter does not specify an existing, empty directory, then reply with 550</li>
+ * <li>If the current user does not have write access to the parent directory, then reply with 550</li>
+ * <li>Otherwise, delete the named directory and reply with 250</li>
+ * </ol>
+ * The supplied pathname may be absolute or relative to the current directory.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class RmdCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ String path = getRealPath(session, command.getRequiredParameter(0));
+
+ this.replyCodeForFileSystemException = ReplyCodes.READ_FILE_ERROR;
+ verifyFileSystemCondition(getFileSystem().exists(path), path, "filesystem.doesNotExist");
+ verifyFileSystemCondition(getFileSystem().isDirectory(path), path, "filesystem.isNotADirectory");
+ verifyFileSystemCondition(getFileSystem().listNames(path).size() == 0, path, "filesystem.directoryIsNotEmpty");
+
+ // User must have write permission to the parent directory
+ verifyWritePermission(session, getFileSystem().getParent(path));
+
+ getFileSystem().delete(path);
+ sendReply(session, ReplyCodes.RMD_OK, "rmd", list(path));
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/RnfrCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/RnfrCommandHandler.java
new file mode 100644
index 0000000..3f38e1a
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/RnfrCommandHandler.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.session.SessionKeys;
+
+/**
+ * CommandHandler for the RNFR command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530</li>
+ * <li>If the required FROM pathname parameter is missing, then reply with 501</li>
+ * <li>If the FROM pathname parameter does not specify a valid file or directory, then reply with 550</li>
+ * <li>If the current user does not have read access to the path, then reply with 550</li>
+ * <li>Otherwise, reply with 350 and store the FROM path in the session</li>
+ * </ol>
+ * The supplied pathname may be absolute or relative to the current directory.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class RnfrCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ String fromPath = getRealPath(session, command.getRequiredParameter(0));
+
+ this.replyCodeForFileSystemException = ReplyCodes.READ_FILE_ERROR;
+ verifyFileSystemCondition(getFileSystem().exists(fromPath), fromPath, "filesystem.doesNotExist");
+
+ // User must have read permission to the file
+ verifyReadPermission(session, fromPath);
+
+ session.setAttribute(SessionKeys.RENAME_FROM, fromPath);
+ sendReply(session, ReplyCodes.RNFR_OK, "rnfr");
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/RntoCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/RntoCommandHandler.java
new file mode 100644
index 0000000..2550f5e
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/RntoCommandHandler.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.session.SessionKeys;
+
+/**
+ * CommandHandler for the RNTO command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530</li>
+ * <li>If this command was not preceded by a valid RNFR command, then reply with 503</li>
+ * <li>If the required TO pathname parameter is missing, then reply with 501</li>
+ * <li>If the TO pathname parameter does not specify a valid filename, then reply with 553</li>
+ * <li>If the TO pathname parameter specifies an existing directory, then reply with 553</li>
+ * <li>If the current user does not have write access to the parent directory, then reply with 553</li>
+ * <li>Otherwise, rename the file, remove the FROM path stored in the session by the RNFR command, and reply with 250</li>
+ * </ol>
+ * The supplied pathname may be absolute or relative to the current directory.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class RntoCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ String toPath = getRealPath(session, command.getRequiredParameter(0));
+ String fromPath = (String) getRequiredSessionAttribute(session, SessionKeys.RENAME_FROM);
+
+ this.replyCodeForFileSystemException = ReplyCodes.WRITE_FILE_ERROR;
+ verifyFileSystemCondition(!getFileSystem().isDirectory(toPath), toPath, "filesystem.isDirectory");
+
+ // User must have write permission to the directory
+ String parentPath = getFileSystem().getParent(toPath);
+ verifyFileSystemCondition(notNullOrEmpty(parentPath), parentPath, "filesystem.doesNotExist");
+ verifyFileSystemCondition(getFileSystem().exists(parentPath), parentPath, "filesystem.doesNotExist");
+ verifyWritePermission(session, parentPath);
+
+ getFileSystem().rename(fromPath, toPath);
+
+ session.removeAttribute(SessionKeys.RENAME_FROM);
+ sendReply(session, ReplyCodes.RNTO_OK, "rnto", list(fromPath, toPath));
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/SiteCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/SiteCommandHandler.java
new file mode 100644
index 0000000..320bf28
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/SiteCommandHandler.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the SITE command. Handler logic:
+ * <ol>
+ * <li>Reply with 200</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class SiteCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ sendReply(session, ReplyCodes.SITE_OK, "site");
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/SmntCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/SmntCommandHandler.java
new file mode 100644
index 0000000..30bcb04
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/SmntCommandHandler.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the SMNT command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530</li>
+ * <li>Otherwise, reply with 250</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class SmntCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ sendReply(session, ReplyCodes.SMNT_OK, "smnt");
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/StatCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/StatCommandHandler.java
new file mode 100644
index 0000000..79c23ad
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/StatCommandHandler.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the STAT command. Handler logic:
+ * <ol>
+ * <li>Reply with 211 along with the system status text that has been configured on the
+ * {@link org.mockftpserver.fake.FakeFtpServer}.</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ * @see org.mockftpserver.fake.ServerConfiguration
+ * @see org.mockftpserver.fake.FakeFtpServer
+ */
+public class StatCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ String systemStatus = getServerConfiguration().getSystemStatus();
+ sendReply(session, ReplyCodes.STAT_SYSTEM_OK, "stat", list(systemStatus));
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/StorCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/StorCommandHandler.java
new file mode 100644
index 0000000..492b83e
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/StorCommandHandler.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+/**
+ * CommandHandler for the STOR command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530 and terminate</li>
+ * <li>If the required pathname parameter is missing, then reply with 501 and terminate</li>
+ * <li>If the pathname parameter does not specify a valid filename, then reply with 553 and terminate</li>
+ * <li>If the current user does not have write access to the named file, if it already exists, or else to its
+ * parent directory, then reply with 553 and terminate</li>
+ * <li>If the current user does not have execute access to the parent directory, then reply with 553 and terminate</li>
+ * <li>Send an initial reply of 150</li>
+ * <li>Read all available bytes from the data connection and write out to the named file in the server file system</li>
+ * <li>If file write/store fails, then reply with 553 and terminate</li>
+ * <li>Send a final reply with 226</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class StorCommandHandler extends AbstractStoreFileCommandHandler {
+
+ /**
+ * @return the message key for the reply message sent with the final (226) reply
+ */
+ protected String getMessageKey() {
+ return "stor";
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/StouCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/StouCommandHandler.java
new file mode 100644
index 0000000..2379c53
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/StouCommandHandler.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+
+/**
+ * CommandHandler for the STOU command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530 and terminate</li>
+ * <li>Create new unique filename within the current directory</li>
+ * <li>If the current user does not have write access to the named file, if it already exists, or else to its
+ * parent directory, then reply with 553 and terminate</li>
+ * <li>If the current user does not have execute access to the parent directory, then reply with 553 and terminate</li>
+ * <li>Send an initial reply of 150</li>
+ * <li>Read all available bytes from the data connection and write out to the unique file in the server file system</li>
+ * <li>If file write/store fails, then reply with 553 and terminate</li>
+ * <li>Send a final reply with 226, along with the new unique filename</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class StouCommandHandler extends AbstractStoreFileCommandHandler {
+
+ /**
+ * @return the message key for the reply message sent with the final (226) reply
+ */
+ protected String getMessageKey() {
+ return "stou";
+ }
+
+ /**
+ * Return the path (absolute or relative) for the output file.
+ */
+ protected String getOutputFile(Command command) {
+ String baseName = defaultIfNullOrEmpty(command.getOptionalString(0), "Temp");
+ String suffix = Long.toString(System.currentTimeMillis());
+ return baseName + suffix;
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/StruCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/StruCommandHandler.java
new file mode 100644
index 0000000..7d224e0
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/StruCommandHandler.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the STRU command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530</li>
+ * <li>Otherwise, reply with 200</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class StruCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ sendReply(session, ReplyCodes.STRU_OK, "stru");
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/SystCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/SystCommandHandler.java
new file mode 100644
index 0000000..1850d7a
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/SystCommandHandler.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the SYST command. Handler logic:
+ * <ol>
+ * <li>Reply with 215 along with the system name</li>
+ * </ol>
+ * The default system name is "WINDOWS", but it can be customized on the
+ * {@link org.mockftpserver.fake.FakeFtpServer} .
+ * <p/>
+ * See the available system names listed in the Assigned Numbers document (RFC 943).
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ * @see <a href="http://www.ietf.org/rfc/rfc943">RFC943</a>
+ */
+public class SystCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ sendReply(session, ReplyCodes.SYST_OK, "syst", list(getServerConfiguration().getSystemName()));
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/TypeCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/TypeCommandHandler.java
new file mode 100644
index 0000000..9c7deb3
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/TypeCommandHandler.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.session.SessionKeys;
+
+/**
+ * CommandHandler for the TYPE command. Handler logic:
+ * <ol>
+ * <li>If the user has not logged in, then reply with 530</li>
+ * <li>Otherwise, reply with 200</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class TypeCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ verifyLoggedIn(session);
+ String type = command.getRequiredParameter(0);
+ boolean asciiType = type == "A";
+ session.setAttribute(SessionKeys.ASCII_TYPE, Boolean.valueOf(asciiType));
+ sendReply(session, ReplyCodes.TYPE_OK, "type");
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/command/UserCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/fake/command/UserCommandHandler.java
new file mode 100644
index 0000000..a1c1cb2
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/command/UserCommandHandler.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.session.SessionKeys;
+import org.mockftpserver.fake.UserAccount;
+
+/**
+ * CommandHandler for the USER command. Handler logic:
+ * <ol>
+ * <li>If the required pathname parameter is missing, then reply with 501</li>
+ * <li>If the user account configured for the named user is not valid, then reply with 530</li>
+ * <li>If the named user does not need a password for login, then set the UserAccount and
+ * current directory in the session, and reply with 230</li>
+ * <li>Otherwise, set the username in the session and reply with 331</li>
+ * </ol>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class UserCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ String username = command.getRequiredParameter(0);
+ UserAccount userAccount = getServerConfiguration().getUserAccount(username);
+
+ if (userAccount != null) {
+ if (!validateUserAccount(username, session)) {
+ return;
+ }
+
+ // If the UserAccount is configured to not require password for login
+ if (!userAccount.isPasswordRequiredForLogin()) {
+ login(userAccount, session, ReplyCodes.USER_LOGGED_IN_OK, "user.loggedIn");
+ return;
+ }
+ }
+ session.setAttribute(SessionKeys.USERNAME, username);
+ sendReply(session, ReplyCodes.USER_NEED_PASSWORD_OK, "user.needPassword");
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/AbstractFakeFileSystem.java b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/AbstractFakeFileSystem.java
new file mode 100644
index 0000000..9db6f93
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/AbstractFakeFileSystem.java
@@ -0,0 +1,653 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.util.Assert;
+import org.mockftpserver.core.util.PatternUtil;
+import org.mockftpserver.core.util.StringUtil;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Abstract superclass for implementation of the FileSystem interface that manage the files
+ * and directories in memory, simulating a real file system.
+ * <p/>
+ * If the <code>createParentDirectoriesAutomatically</code> property is set to <code>true</code>,
+ * then creating a directory or file will automatically create any parent directories (recursively)
+ * that do not already exist. If <code>false</code>, then creating a directory or file throws an
+ * exception if its parent directory does not exist. This value defaults to <code>true</code>.
+ * <p/>
+ * The <code>directoryListingFormatter</code> property holds an instance of {@link DirectoryListingFormatter} ,
+ * used by the <code>formatDirectoryListing</code> method to format directory listings in a
+ * filesystem-specific manner. This property must be initialized by concrete subclasses.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public abstract class AbstractFakeFileSystem implements FileSystem {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractFakeFileSystem.class);
+
+ /**
+ * If <code>true</code>, creating a directory or file will automatically create
+ * any parent directories (recursively) that do not already exist. If <code>false</code>,
+ * then creating a directory or file throws an exception if its parent directory
+ * does not exist. This value defaults to <code>true</code>.
+ */
+ private boolean createParentDirectoriesAutomatically = true;
+
+ /**
+ * The {@link DirectoryListingFormatter} used by the {@link #formatDirectoryListing(FileSystemEntry)}
+ * method. This must be initialized by concrete subclasses.
+ */
+ private DirectoryListingFormatter directoryListingFormatter;
+
+ private Map entries = new HashMap();
+
+ //-------------------------------------------------------------------------
+ // Public API
+ //-------------------------------------------------------------------------
+
+ public boolean isCreateParentDirectoriesAutomatically() {
+ return createParentDirectoriesAutomatically;
+ }
+
+ public void setCreateParentDirectoriesAutomatically(boolean createParentDirectoriesAutomatically) {
+ this.createParentDirectoriesAutomatically = createParentDirectoriesAutomatically;
+ }
+
+ public DirectoryListingFormatter getDirectoryListingFormatter() {
+ return directoryListingFormatter;
+ }
+
+ public void setDirectoryListingFormatter(DirectoryListingFormatter directoryListingFormatter) {
+ this.directoryListingFormatter = directoryListingFormatter;
+ }
+
+ /**
+ * Add each of the entries in the specified List to this filesystem. Note that this does not affect
+ * entries already existing within this filesystem.
+ *
+ * @param entriesToAdd - the List of FileSystemEntry entries to add
+ */
+ public void setEntries(List entriesToAdd) {
+ for (Iterator iter = entriesToAdd.iterator(); iter.hasNext();) {
+ FileSystemEntry entry = (FileSystemEntry) iter.next();
+ add(entry);
+ }
+ }
+
+ /**
+ * Add the specified file system entry (file or directory) to this file system
+ *
+ * @param entry - the FileSystemEntry to add
+ */
+ public void add(FileSystemEntry entry) {
+ String path = entry.getPath();
+ checkForInvalidFilename(path);
+ if (getEntry(path) != null) {
+ throw new FileSystemException(path, "filesystem.pathAlreadyExists");
+ }
+
+ if (!parentDirectoryExists(path)) {
+ String parent = getParent(path);
+ if (createParentDirectoriesAutomatically) {
+ add(new DirectoryEntry(parent));
+ } else {
+ throw new FileSystemException(parent, "filesystem.parentDirectoryDoesNotExist");
+ }
+ }
+
+ // Set lastModified, if not already set
+ if (entry.getLastModified() == null) {
+ entry.setLastModified(new Date());
+ }
+
+ entries.put(getFileSystemEntryKey(path), entry);
+ entry.lockPath();
+ }
+
+ /**
+ * Delete the file or directory specified by the path. Return true if the file is successfully
+ * deleted, false otherwise. If the path refers to a directory, it must be empty. Return false
+ * if the path does not refer to a valid file or directory or if it is a non-empty directory.
+ *
+ * @param path - the path of the file or directory to delete
+ * @return true if the file or directory is successfully deleted
+ * @throws org.mockftpserver.core.util.AssertFailedException
+ * - if path is null
+ * @see org.mockftpserver.fake.filesystem.FileSystem#delete(java.lang.String)
+ */
+ public boolean delete(String path) {
+ Assert.notNull(path, "path");
+
+ if (getEntry(path) != null && !hasChildren(path)) {
+ removeEntry(path);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return true if there exists a file or directory at the specified path
+ *
+ * @param path - the path
+ * @return true if the file/directory exists
+ * @throws AssertionError - if path is null
+ * @see org.mockftpserver.fake.filesystem.FileSystem#exists(java.lang.String)
+ */
+ public boolean exists(String path) {
+ Assert.notNull(path, "path");
+ return getEntry(path) != null;
+ }
+
+ /**
+ * Return true if the specified path designates an existing directory, false otherwise
+ *
+ * @param path - the path
+ * @return true if path is a directory, false otherwise
+ * @throws AssertionError - if path is null
+ * @see org.mockftpserver.fake.filesystem.FileSystem#isDirectory(java.lang.String)
+ */
+ public boolean isDirectory(String path) {
+ Assert.notNull(path, "path");
+ FileSystemEntry entry = getEntry(path);
+ return entry != null && entry.isDirectory();
+ }
+
+ /**
+ * Return true if the specified path designates an existing file, false otherwise
+ *
+ * @param path - the path
+ * @return true if path is a file, false otherwise
+ * @throws AssertionError - if path is null
+ * @see org.mockftpserver.fake.filesystem.FileSystem#isFile(java.lang.String)
+ */
+ public boolean isFile(String path) {
+ Assert.notNull(path, "path");
+ FileSystemEntry entry = getEntry(path);
+ return entry != null && !entry.isDirectory();
+ }
+
+ /**
+ * Return the List of FileSystemEntry objects for the files in the specified directory or group of
+ * files. If the path specifies a single file, then return a list with a single FileSystemEntry
+ * object representing that file. If the path does not refer to an existing directory or
+ * group of files, then an empty List is returned.
+ *
+ * @param path - the path specifying a directory or group of files; may contain wildcards (? or *)
+ * @return the List of FileSystemEntry objects for the specified directory or file; may be empty
+ * @see org.mockftpserver.fake.filesystem.FileSystem#listFiles(java.lang.String)
+ */
+ public List listFiles(String path) {
+ if (isFile(path)) {
+ return Collections.singletonList(getEntry(path));
+ }
+
+ List entryList = new ArrayList();
+ List children = children(path);
+ Iterator iter = children.iterator();
+ while (iter.hasNext()) {
+ String childPath = (String) iter.next();
+ FileSystemEntry fileSystemEntry = getEntry(childPath);
+ entryList.add(fileSystemEntry);
+ }
+ return entryList;
+ }
+
+ /**
+ * Return the List of filenames in the specified directory path or file path. If the path specifies
+ * a single file, then return that single filename. The returned filenames do not
+ * include a path. If the path does not refer to a valid directory or file path, then an empty List
+ * is returned.
+ *
+ * @param path - the path specifying a directory or group of files; may contain wildcards (? or *)
+ * @return the List of filenames (not including paths) for all files in the specified directory
+ * or file path; may be empty
+ * @throws AssertionError - if path is null
+ * @see org.mockftpserver.fake.filesystem.FileSystem#listNames(java.lang.String)
+ */
+ public List listNames(String path) {
+ if (isFile(path)) {
+ return Collections.singletonList(getName(path));
+ }
+
+ List filenames = new ArrayList();
+ List children = children(path);
+ Iterator iter = children.iterator();
+ while (iter.hasNext()) {
+ String childPath = (String) iter.next();
+ FileSystemEntry fileSystemEntry = getEntry(childPath);
+ filenames.add(fileSystemEntry.getName());
+ }
+ return filenames;
+ }
+
+ /**
+ * Rename the file or directory. Specify the FROM path and the TO path. Throw an exception if the FROM path or
+ * the parent directory of the TO path do not exist; or if the rename fails for another reason.
+ *
+ * @param fromPath - the source (old) path + filename
+ * @param toPath - the target (new) path + filename
+ * @throws AssertionError - if fromPath or toPath is null
+ * @throws FileSystemException - if the rename fails.
+ */
+ public void rename(String fromPath, String toPath) {
+ Assert.notNull(toPath, "toPath");
+ Assert.notNull(fromPath, "fromPath");
+
+ FileSystemEntry entry = getRequiredEntry(fromPath);
+
+ if (exists(toPath)) {
+ throw new FileSystemException(toPath, "filesystem.alreadyExists");
+ }
+
+ String normalizedFromPath = normalize(fromPath);
+ String normalizedToPath = normalize(toPath);
+
+ if (!entry.isDirectory()) {
+ renamePath(entry, normalizedToPath);
+ return;
+ }
+
+ if (normalizedToPath.startsWith(normalizedFromPath + this.getSeparator())) {
+ throw new FileSystemException(toPath, "filesystem.renameFailed");
+ }
+
+ // Create the TO directory entry first so that the destination path exists when you
+ // move the children. Remove the FROM path after all children have been moved
+ add(new DirectoryEntry(normalizedToPath));
+
+ List children = descendents(fromPath);
+ Iterator iter = children.iterator();
+ while (iter.hasNext()) {
+ String childPath = (String) iter.next();
+ FileSystemEntry child = getRequiredEntry(childPath);
+ String normalizedChildPath = normalize(child.getPath());
+ Assert.isTrue(normalizedChildPath.startsWith(normalizedFromPath), "Starts with FROM path");
+ String childToPath = normalizedToPath + normalizedChildPath.substring(normalizedFromPath.length());
+ renamePath(child, childToPath);
+ }
+ Assert.isTrue(children(normalizedFromPath).isEmpty(), "Must have no children: " + normalizedFromPath);
+ removeEntry(normalizedFromPath);
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return this.getClass().getName() + entries;
+ }
+
+ /**
+ * Return the formatted directory listing entry for the file represented by the specified FileSystemEntry
+ *
+ * @param fileSystemEntry - the FileSystemEntry representing the file or directory entry to be formatted
+ * @return the the formatted directory listing entry
+ */
+ public String formatDirectoryListing(FileSystemEntry fileSystemEntry) {
+ Assert.notNull(directoryListingFormatter, "directoryListingFormatter");
+ Assert.notNull(fileSystemEntry, "fileSystemEntry");
+ return directoryListingFormatter.format(fileSystemEntry);
+ }
+
+ /**
+ * Build a path from the two path components. Concatenate path1 and path2. Insert the path
+ * separator character in between if necessary (i.e., if both are non-empty and path1 does not already
+ * end with a separator character AND path2 does not begin with one).
+ *
+ * @param path1 - the first path component may be null or empty
+ * @param path2 - the second path component may be null or empty
+ * @return the normalized path resulting from concatenating path1 to path2
+ */
+ public String path(String path1, String path2) {
+ StringBuffer buf = new StringBuffer();
+ if (path1 != null && path1.length() > 0) {
+ buf.append(path1);
+ }
+ if (path2 != null && path2.length() > 0) {
+ if ((path1 != null && path1.length() > 0)
+ && (!isSeparator(path1.charAt(path1.length() - 1)))
+ && (!isSeparator(path2.charAt(0)))) {
+ buf.append(this.getSeparator());
+ }
+ buf.append(path2);
+ }
+ return normalize(buf.toString());
+ }
+
+ /**
+ * Return the parent path of the specified path. If <code>path</code> specifies a filename,
+ * then this method returns the path of the directory containing that file. If <code>path</code>
+ * specifies a directory, the this method returns its parent directory. If <code>path</code> is
+ * empty or does not have a parent component, then return an empty string.
+ * <p/>
+ * All path separators in the returned path are converted to the system-dependent separator character.
+ *
+ * @param path - the path
+ * @return the parent of the specified path, or null if <code>path</code> has no parent
+ * @throws AssertionError - if path is null
+ */
+ public String getParent(String path) {
+ List parts = normalizedComponents(path);
+ if (parts.size() < 2) {
+ return null;
+ }
+ parts.remove(parts.size() - 1);
+ return componentsToPath(parts);
+ }
+
+ /**
+ * Returns the name of the file or directory denoted by this abstract
+ * pathname. This is just the last name in the pathname's name
+ * sequence. If the pathname's name sequence is empty, then the empty string is returned.
+ *
+ * @param path - the path
+ * @return The name of the file or directory denoted by this abstract pathname, or the
+ * empty string if this pathname's name sequence is empty
+ */
+ public String getName(String path) {
+ Assert.notNull(path, "path");
+ String normalized = normalize(path);
+ int separatorIndex = normalized.lastIndexOf(this.getSeparator());
+ return (separatorIndex == -1) ? normalized : normalized.substring(separatorIndex + 1);
+ }
+
+ /**
+ * Returns the FileSystemEntry object representing the file system entry at the specified path, or null
+ * if the path does not specify an existing file or directory within this file system.
+ *
+ * @param path - the path of the file or directory within this file system
+ * @return the FileSystemEntry containing the information for the file or directory, or else null
+ * @see FileSystem#getEntry(String)
+ */
+ public FileSystemEntry getEntry(String path) {
+ return (FileSystemEntry) entries.get(getFileSystemEntryKey(path));
+ }
+
+ //-------------------------------------------------------------------------
+ // Abstract Methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * @param path - the path
+ * @return true if the specified dir/file path name is valid according to the current filesystem.
+ */
+ protected abstract boolean isValidName(String path);
+
+ /**
+ * @return the file system-specific file separator as a char
+ */
+ protected abstract char getSeparatorChar();
+
+ /**
+ * @param pathComponent - the component (piece) of the path to check
+ * @return true if the specified path component is a root for this filesystem
+ */
+ protected abstract boolean isRoot(String pathComponent);
+
+ /**
+ * Return true if the specified char is a separator character for this filesystem
+ *
+ * @param c - the character to test
+ * @return true if the specified char is a separator character
+ */
+ protected abstract boolean isSeparator(char c);
+
+ //-------------------------------------------------------------------------
+ // Internal Helper Methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * @return the file system-specific file separator as a String
+ */
+ protected String getSeparator() {
+ return Character.toString(getSeparatorChar());
+ }
+
+ /**
+ * Return the normalized and unique key used to access the file system entry
+ *
+ * @param path - the path
+ * @return the corresponding normalized key
+ */
+ protected String getFileSystemEntryKey(String path) {
+ return normalize(path);
+ }
+
+ /**
+ * Return the standard, normalized form of the path.
+ *
+ * @param path - the path
+ * @return the path in a standard, unique, canonical form
+ * @throws AssertionError - if path is null
+ */
+ protected String normalize(String path) {
+ return componentsToPath(normalizedComponents(path));
+ }
+
+ /**
+ * Throw an InvalidFilenameException if the specified path is not valid.
+ *
+ * @param path - the path
+ */
+ protected void checkForInvalidFilename(String path) {
+ if (!isValidName(path)) {
+ throw new InvalidFilenameException(path);
+ }
+ }
+
+ /**
+ * Rename the file system entry to the specified path name
+ *
+ * @param entry - the file system entry
+ * @param toPath - the TO path (normalized)
+ */
+ protected void renamePath(FileSystemEntry entry, String toPath) {
+ String normalizedFrom = normalize(entry.getPath());
+ String normalizedTo = normalize(toPath);
+ LOG.info("renaming from [" + normalizedFrom + "] to [" + normalizedTo + "]");
+ FileSystemEntry newEntry = entry.cloneWithNewPath(normalizedTo);
+ add(newEntry);
+ // Do this at the end, in case the addEntry() failed
+ removeEntry(normalizedFrom);
+ }
+
+ /**
+ * Return the FileSystemEntry for the specified path. Throw FileSystemException if the
+ * specified path does not exist.
+ *
+ * @param path - the path
+ * @return the FileSystemEntry
+ * @throws FileSystemException - if the specified path does not exist
+ */
+ protected FileSystemEntry getRequiredEntry(String path) {
+ FileSystemEntry entry = getEntry(path);
+ if (entry == null) {
+ LOG.error("Path does not exist: " + path);
+ throw new FileSystemException(normalize(path), "filesystem.doesNotExist");
+ }
+ return entry;
+ }
+
+ /**
+ * Return the components of the specified path as a List. The components are normalized, and
+ * the returned List does not include path separator characters.
+ *
+ * @param path - the path
+ * @return the List of normalized components
+ */
+ protected List normalizedComponents(String path) {
+ Assert.notNull(path, "path");
+ char otherSeparator = this.getSeparatorChar() == '/' ? '\\' : '/';
+ String p = path.replace(otherSeparator, this.getSeparatorChar());
+
+ // TODO better way to do this
+ if (p.equals(this.getSeparator())) {
+ return Collections.singletonList("");
+ }
+ List result = new ArrayList();
+ if (p.length() > 0) {
+ String[] parts = p.split("\\" + this.getSeparator());
+ for (int i = 0; i < parts.length; i++) {
+ String part = parts[i];
+ if (part.equals("..")) {
+ result.remove(result.size() - 1);
+ } else if (!part.equals(".")) {
+ result.add(part);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Build a path from the specified list of path components
+ *
+ * @param components - the list of path components
+ * @return the resulting path
+ */
+ protected String componentsToPath(List components) {
+ if (components.size() == 1) {
+ String first = (String) components.get(0);
+ if (first.length() == 0 || isRoot(first)) {
+ return first + this.getSeparator();
+ }
+ }
+ return StringUtil.join(components, this.getSeparator());
+ }
+
+ /**
+ * Return true if the specified path designates an absolute file path.
+ *
+ * @param path - the path
+ * @return true if path is absolute, false otherwise
+ * @throws AssertionError - if path is null
+ */
+ public boolean isAbsolute(String path) {
+ return isValidName(path);
+ }
+
+ /**
+ * Return true if the specified path exists
+ *
+ * @param path - the path
+ * @return true if the path exists
+ */
+ private boolean pathExists(String path) {
+ return getEntry(path) != null;
+ }
+
+ /**
+ * If the specified path has a parent, then verify that the parent exists
+ *
+ * @param path - the path
+ * @return true if the parent of the specified path exists
+ */
+ private boolean parentDirectoryExists(String path) {
+ String parent = getParent(path);
+ return parent == null || pathExists(parent);
+ }
+
+ /**
+ * Return true if the specified path represents a directory that contains one or more files or subdirectories
+ *
+ * @param path - the path
+ * @return true if the path has child entries
+ */
+ private boolean hasChildren(String path) {
+ if (!isDirectory(path)) {
+ return false;
+ }
+ String key = getFileSystemEntryKey(path);
+ Iterator iter = entries.keySet().iterator();
+ while (iter.hasNext()) {
+ String p = (String) iter.next();
+ if (p.startsWith(key) && !key.equals(p)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return the List of files or subdirectory paths that are descendents of the specified path
+ *
+ * @param path - the path
+ * @return the List of the paths for the files and subdirectories that are children, grandchildren, etc.
+ */
+ private List descendents(String path) {
+ if (isDirectory(path)) {
+ String normalizedPath = getFileSystemEntryKey(path);
+ String separator = (normalizedPath.endsWith(getSeparator())) ? "" : getSeparator();
+ String normalizedDirPrefix = normalizedPath + separator;
+ List descendents = new ArrayList();
+ Iterator iter = entries.entrySet().iterator();
+ while (iter.hasNext()) {
+ Map.Entry mapEntry = (Map.Entry) iter.next();
+ String p = (String) mapEntry.getKey();
+ if (p.startsWith(normalizedDirPrefix) && !normalizedPath.equals(p)) {
+ FileSystemEntry fileSystemEntry = (FileSystemEntry) mapEntry.getValue();
+ descendents.add(fileSystemEntry.getPath());
+ }
+ }
+ return descendents;
+ }
+ return Collections.EMPTY_LIST;
+ }
+
+ /**
+ * Return the List of files or subdirectory paths that are children of the specified path
+ *
+ * @param path - the path
+ * @return the List of the paths for the files and subdirectories that are children
+ */
+ private List children(String path) {
+ String lastComponent = getName(path);
+ boolean containsWildcards = PatternUtil.containsWildcards(lastComponent);
+ String dir = containsWildcards ? getParent(path) : path;
+ String pattern = containsWildcards ? PatternUtil.convertStringWithWildcardsToRegex(getName(path)) : null;
+ LOG.debug("path=" + path + " lastComponent=" + lastComponent + " containsWildcards=" + containsWildcards + " dir=" + dir + " pattern=" + pattern);
+
+ List descendents = descendents(dir);
+ List children = new ArrayList();
+ String normalizedDir = normalize(dir);
+ Iterator iter = descendents.iterator();
+ while (iter.hasNext()) {
+ String descendentPath = (String) iter.next();
+
+ boolean patternEmpty = pattern == null || pattern.length() == 0;
+ if (normalizedDir.equals(getParent(descendentPath)) &&
+ (patternEmpty || (getName(descendentPath).matches(pattern)))) {
+ children.add(descendentPath);
+ }
+ }
+ return children;
+ }
+
+ private void removeEntry(String path) {
+ entries.remove(getFileSystemEntryKey(path));
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/AbstractFileSystemEntry.java b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/AbstractFileSystemEntry.java
new file mode 100644
index 0000000..e737898
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/AbstractFileSystemEntry.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem;
+
+import org.mockftpserver.core.util.Assert;
+
+import java.util.Date;
+
+/**
+ * The abstract superclass for concrete file system entry classes representing files and directories.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public abstract class AbstractFileSystemEntry implements FileSystemEntry {
+
+ private String path;
+ private boolean pathLocked = false;
+
+ private Date lastModified;
+ private String owner;
+ private String group;
+
+ public Date getLastModified() {
+ return lastModified;
+ }
+
+ public void setLastModified(Date lastModified) {
+ this.lastModified = lastModified;
+ }
+
+ public String getOwner() {
+ return owner;
+ }
+
+ public void setOwner(String owner) {
+ this.owner = owner;
+ }
+
+ public String getGroup() {
+ return group;
+ }
+
+ public void setGroup(String group) {
+ this.group = group;
+ }
+
+ public Permissions getPermissions() {
+ return permissions;
+ }
+
+ public void setPermissions(Permissions permissions) {
+ this.permissions = permissions;
+ }
+
+ private Permissions permissions;
+
+ /**
+ * Construct a new instance without setting its path
+ */
+ public AbstractFileSystemEntry() {
+ }
+
+ /**
+ * Construct a new instance with the specified value for its path
+ *
+ * @param path - the value for path
+ */
+ public AbstractFileSystemEntry(String path) {
+ this.path = path;
+ }
+
+ /**
+ * @return the path for this entry
+ */
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * @return the file name or directory name (no path) for this entry
+ */
+ public String getName() {
+ int separatorIndex1 = path.lastIndexOf('/');
+ int separatorIndex2 = path.lastIndexOf('\\');
+// int separatorIndex = [separatorIndex1, separatorIndex2].max();
+ int separatorIndex = separatorIndex1 > separatorIndex2 ? separatorIndex1 : separatorIndex2;
+ return (separatorIndex == -1) ? path : path.substring(separatorIndex + 1);
+ }
+
+ /**
+ * Set the path for this entry. Throw an exception if pathLocked is true.
+ *
+ * @param path - the new path value
+ */
+ public void setPath(String path) {
+ Assert.isFalse(pathLocked, "path is locked");
+ this.path = path;
+ }
+
+ public void lockPath() {
+ this.pathLocked = true;
+ }
+
+ public void setPermissionsFromString(String permissionsString) {
+ this.permissions = new Permissions(permissionsString);
+ }
+
+ /**
+ * Abstract method -- must be implemented within concrete subclasses
+ *
+ * @return true if this file system entry represents a directory
+ */
+ public abstract boolean isDirectory();
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/DirectoryEntry.java b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/DirectoryEntry.java
new file mode 100644
index 0000000..b564924
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/DirectoryEntry.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem;
+
+/**
+ * File system entry representing a directory
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class DirectoryEntry extends AbstractFileSystemEntry {
+
+ /**
+ * Construct a new instance without setting its path
+ */
+ public DirectoryEntry() {
+ }
+
+ /**
+ * Construct a new instance with the specified value for its path
+ *
+ * @param path - the value for path
+ */
+ public DirectoryEntry(String path) {
+ super(path);
+ }
+
+ /**
+ * Return true to indicate that this entry represents a directory
+ *
+ * @return true
+ */
+ public boolean isDirectory() {
+ return true;
+ }
+
+ /**
+ * Return the size of this directory. This method returns zero.
+ *
+ * @return the file size in bytes
+ */
+ public long getSize() {
+ return 0;
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return "Directory['" + getPath() + "' lastModified=" + getLastModified() + " owner=" + getOwner() +
+ " group=" + getGroup() + " permissions=" + getPermissions() + "]";
+ }
+
+ /**
+ * Return a new FileSystemEntry that is a clone of this object, except having the specified path
+ *
+ * @param path - the new path value for the cloned file system entry
+ * @return a new FileSystemEntry that has all the same values as this object except for its path
+ */
+ public FileSystemEntry cloneWithNewPath(String path) {
+ DirectoryEntry clone = new DirectoryEntry(path);
+ clone.setLastModified(getLastModified());
+ clone.setOwner(getOwner());
+ clone.setGroup(getGroup());
+ clone.setPermissions(getPermissions());
+ return clone;
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/DirectoryListingFormatter.java b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/DirectoryListingFormatter.java
new file mode 100644
index 0000000..ef159e4
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/DirectoryListingFormatter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.mockftpserver.fake.filesystem;
+
+/**
+ * Interface for an object that can format a file system directory listing.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public interface DirectoryListingFormatter {
+
+ /**
+ * Format the directory listing for a single file/directory entry.
+ *
+ * @param fileSystemEntry - the FileSystemEntry for a single file system entry
+ * @return the formatted directory listing
+ */
+ String format(FileSystemEntry fileSystemEntry);
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/FileEntry.java b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/FileEntry.java
new file mode 100644
index 0000000..c6bfa04
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/FileEntry.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * File system entry representing a file
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class FileEntry extends AbstractFileSystemEntry {
+
+ private static final byte[] EMPTY = new byte[0];
+
+ private byte[] bytes = EMPTY;
+ private ByteArrayOutputStream out;
+
+ /**
+ * Construct a new instance without setting its path
+ */
+ public FileEntry() {
+ }
+
+ /**
+ * Construct a new instance with the specified value for its path
+ *
+ * @param path - the value for path
+ */
+ public FileEntry(String path) {
+ super(path);
+ }
+
+ /**
+ * Construct a new instance with the specified path and file contents
+ *
+ * @param path - the value for path
+ * @param contents - the contents of the file, as a String
+ */
+ public FileEntry(String path, String contents) {
+ super(path);
+ setContents(contents);
+ }
+
+ /**
+ * Return false to indicate that this entry represents a file
+ *
+ * @return false
+ */
+ public boolean isDirectory() {
+ return false;
+ }
+
+ /**
+ * Return the size of this file
+ *
+ * @return the file size in bytes
+ */
+ public long getSize() {
+ return getCurrentBytes().length;
+ }
+
+ /**
+ * Set the contents of the file represented by this entry
+ *
+ * @param contents - the String whose bytes are used as the contents
+ */
+ public void setContents(String contents) {
+ byte[] newBytes = (contents != null) ? contents.getBytes() : EMPTY;
+ setContentsInternal(newBytes);
+ }
+
+ /**
+ * Set the contents of the file represented by this entry
+ *
+ * @param contents - the byte[] used as the contents
+ */
+ public void setContents(byte[] contents) {
+ // Copy the bytes[] to guard against subsequent modification of the source array
+ byte[] newBytes = EMPTY;
+ if (contents != null) {
+ newBytes = new byte[contents.length];
+ System.arraycopy(contents, 0, newBytes, 0, contents.length);
+ }
+ setContentsInternal(newBytes);
+ }
+
+ /**
+ * Create and return an InputStream for reading the contents of the file represented by this entry
+ *
+ * @return an InputStream
+ */
+ public InputStream createInputStream() {
+ return new ByteArrayInputStream(getCurrentBytes());
+ }
+
+ /**
+ * Create and return an OutputStream for writing the contents of the file represented by this entry
+ *
+ * @param append - true if the OutputStream should append to any existing contents false if
+ * any existing contents should be overwritten
+ * @return an OutputStream
+ * @throws FileSystemException - if an error occurs creating or initializing the OutputStream
+ */
+ public OutputStream createOutputStream(boolean append) {
+ // If appending and we already have an OutputStream, then continue to use it
+ if (append && out != null) {
+ return out;
+ }
+
+ out = new ByteArrayOutputStream();
+ byte[] initialContents = (append) ? bytes : EMPTY;
+ try {
+ out.write(initialContents);
+ }
+ catch (IOException e) {
+ throw new FileSystemException(getPath(), null, e);
+ }
+ return out;
+ }
+
+ /**
+ * Return a new FileSystemEntry that is a clone of this object, except having the specified path
+ *
+ * @param path - the new path value for the cloned file system entry
+ * @return a new FileSystemEntry that has all the same values as this object except for its path
+ */
+ public FileSystemEntry cloneWithNewPath(String path) {
+ FileEntry clone = new FileEntry(path);
+ clone.setLastModified(getLastModified());
+ clone.setOwner(getOwner());
+ clone.setGroup(getGroup());
+ clone.setPermissions(getPermissions());
+ clone.setContents(getCurrentBytes());
+ return clone;
+ }
+
+ //-------------------------------------------------------------------------
+ // Internal Helper Methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * @return the current contents of this file entry as a byte[]
+ */
+ private byte[] getCurrentBytes() {
+ return (out != null) ? out.toByteArray() : bytes;
+ }
+
+ /**
+ * Set the contents of the file represented by this entry
+ *
+ * @param contents - the byte[] used as the contents
+ */
+ private void setContentsInternal(byte[] contents) {
+ this.bytes = contents;
+
+ // Get rid of any OutputStream
+ this.out = null;
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return "File['" + getPath() + "' size=" + getSize() + " lastModified=" + getLastModified() + " owner="
+ + getOwner() + " group=" + getGroup() + " permissions=" + getPermissions() + "]";
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/FileSystem.java b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/FileSystem.java
new file mode 100644
index 0000000..10c47e9
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/FileSystem.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem;
+
+import java.util.List;
+
+/**
+ * Interface for a file system for managing files and directories.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public interface FileSystem {
+
+ /**
+ * Add the specified file system entry (file or directory) to this file system
+ *
+ * @param entry - the FileSystemEntry to add
+ */
+ public void add(FileSystemEntry entry);
+
+ /**
+ * Return the List of FileSystemEntry objects for the files in the specified directory path. If the
+ * path does not refer to a valid directory, then an empty List is returned.
+ *
+ * @param path - the path of the directory whose contents should be returned
+ * @return the List of FileSystemEntry objects for all files in the specified directory may be empty
+ */
+ public List listFiles(String path);
+
+ /**
+ * Return the List of filenames in the specified directory path. The returned filenames do not
+ * include a path. If the path does not refer to a valid directory, then an empty List is
+ * returned.
+ *
+ * @param path - the path of the directory whose contents should be returned
+ * @return the List of filenames (not including paths) for all files in the specified directory
+ * may be empty
+ * @throws AssertionError - if path is null
+ */
+ public List listNames(String path);
+
+ /**
+ * Delete the file or directory specified by the path. Return true if the file is successfully
+ * deleted, false otherwise. If the path refers to a directory, it must be empty. Return false
+ * if the path does not refer to a valid file or directory or if it is a non-empty directory.
+ *
+ * @param path - the path of the file or directory to delete
+ * @return true if the file or directory is successfully deleted
+ * @throws AssertionError - if path is null
+ */
+ public boolean delete(String path);
+
+ /**
+ * Rename the file or directory. Specify the FROM path and the TO path. Throw an exception if the FROM path or
+ * the parent directory of the TO path do not exist; or if the rename fails for another reason.
+ *
+ * @param fromPath - the source (old) path + filename
+ * @param toPath - the target (new) path + filename
+ * @throws AssertionError - if fromPath or toPath is null
+ * @throws FileSystemException - if the rename fails.
+ */
+ public void rename(String fromPath, String toPath);
+
+ /**
+ * Return the formatted directory listing entry for the file represented by the specified FileSystemEntry
+ *
+ * @param fileSystemEntry - the FileSystemEntry representing the file or directory entry to be formatted
+ * @return the the formatted directory listing entry
+ */
+ public String formatDirectoryListing(FileSystemEntry fileSystemEntry);
+
+ //-------------------------------------------------------------------------
+ // Path-related Methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * Return true if there exists a file or directory at the specified path
+ *
+ * @param path - the path
+ * @return true if the file/directory exists
+ * @throws AssertionError - if path is null
+ */
+ public boolean exists(String path);
+
+ /**
+ * Return true if the specified path designates an existing directory, false otherwise
+ *
+ * @param path - the path
+ * @return true if path is a directory, false otherwise
+ * @throws AssertionError - if path is null
+ */
+ public boolean isDirectory(String path);
+
+ /**
+ * Return true if the specified path designates an existing file, false otherwise
+ *
+ * @param path - the path
+ * @return true if path is a file, false otherwise
+ * @throws AssertionError - if path is null
+ */
+ public boolean isFile(String path);
+
+ /**
+ * Return true if the specified path designates an absolute file path. What
+ * constitutes an absolute path is dependent on the file system implementation.
+ *
+ * @param path - the path
+ * @return true if path is absolute, false otherwise
+ * @throws AssertionError - if path is null
+ */
+ public boolean isAbsolute(String path);
+
+ /**
+ * Build a path from the two path components. Concatenate path1 and path2. Insert the file system-dependent
+ * separator character in between if necessary (i.e., if both are non-empty and path1 does not already
+ * end with a separator character AND path2 does not begin with one).
+ *
+ * @param path1 - the first path component may be null or empty
+ * @param path2 - the second path component may be null or empty
+ * @return the path resulting from concatenating path1 to path2
+ */
+ public String path(String path1, String path2);
+
+ /**
+ * Returns the FileSystemEntry object representing the file system entry at the specified path, or null
+ * if the path does not specify an existing file or directory within this file system.
+ *
+ * @param path - the path of the file or directory within this file system
+ * @return the FileSystemEntry containing the information for the file or directory, or else null
+ */
+ public FileSystemEntry getEntry(String path);
+
+ /**
+ * Return the parent path of the specified path. If <code>path</code> specifies a filename,
+ * then this method returns the path of the directory containing that file. If <code>path</code>
+ * specifies a directory, the this method returns its parent directory. If <code>path</code> is
+ * empty or does not have a parent component, then return an empty string.
+ * <p/>
+ * All path separators in the returned path are converted to the system-dependent separator character.
+ *
+ * @param path - the path
+ * @return the parent of the specified path, or null if <code>path</code> has no parent
+ * @throws AssertionError - if path is null
+ */
+ public String getParent(String path);
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/FileSystemEntry.java b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/FileSystemEntry.java
new file mode 100644
index 0000000..be99513
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/FileSystemEntry.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.mockftpserver.fake.filesystem;
+
+import java.util.Date;
+
+/**
+ * Interface for an entry within a fake file system, representing a single file or directory.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public interface FileSystemEntry {
+
+ /**
+ * Return true if this entry represents a directory, false otherwise
+ *
+ * @return true if this file system entry is a directory, false otherwise
+ */
+ public boolean isDirectory();
+
+ /**
+ * Return the path for this file system entry
+ *
+ * @return the path for this file system entry
+ */
+ public String getPath();
+
+ /**
+ * Return the file name or directory name (no path) for this entry
+ *
+ * @return the file name or directory name (no path) for this entry
+ */
+ public String getName();
+
+ /**
+ * Return the size of this file system entry
+ *
+ * @return the file size in bytes
+ */
+ public long getSize();
+
+ /**
+ * Return the timestamp Date for the last modification of this file system entry
+ *
+ * @return the last modified timestamp Date for this file system entry
+ */
+ public Date getLastModified();
+
+ /**
+ * Set the timestamp Date for the last modification of this file system entry
+ *
+ * @param lastModified - the lastModified value, as a Date
+ */
+ public void setLastModified(Date lastModified);
+
+ /**
+ * @return the username of the owner of this file system entry
+ */
+ public String getOwner();
+
+ /**
+ * @return the name of the owning group for this file system entry
+ */
+ public String getGroup();
+
+ /**
+ * @return the Permissions for this file system entry
+ */
+ public Permissions getPermissions();
+
+ /**
+ * Return a new FileSystemEntry that is a clone of this object, except having the specified path
+ *
+ * @param path - the new path value for the cloned file system entry
+ * @return a new FileSystemEntry that has all the same values as this object except for its path
+ */
+ public FileSystemEntry cloneWithNewPath(String path);
+
+ /**
+ * Lock down the path so it cannot be changed
+ */
+ public void lockPath();
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/FileSystemException.java b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/FileSystemException.java
new file mode 100644
index 0000000..5fa4e97
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/FileSystemException.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem;
+
+import org.mockftpserver.core.MockFtpServerException;
+
+/**
+ * Represents an error that occurs while performing a FileSystem operation.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class FileSystemException extends MockFtpServerException {
+
+ /**
+ * The path involved in the file system operation that caused the exception
+ */
+ private String path;
+
+ /**
+ * The message key for the exception message
+ */
+ private String messageKey;
+
+ /**
+ * Construct a new instance for the specified path and message key
+ *
+ * @param path - the path involved in the file system operation that caused the exception
+ * @param messageKey - the exception message key
+ */
+ public FileSystemException(String path, String messageKey) {
+ super(path);
+ this.path = path;
+ this.messageKey = messageKey;
+ }
+
+ /**
+ * @param path - the path involved in the file system operation that caused the exception
+ * @param messageKey - the exception message key
+ * @param cause - the exception cause, wrapped by this exception
+ */
+ public FileSystemException(String path, String messageKey, Throwable cause) {
+ super(path, cause);
+ this.path = path;
+ this.messageKey = messageKey;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public String getMessageKey() {
+ return messageKey;
+ }
+
+ public void setMessageKey(String messageKey) {
+ this.messageKey = messageKey;
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/InvalidFilenameException.java b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/InvalidFilenameException.java
new file mode 100644
index 0000000..b207be9
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/InvalidFilenameException.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem;
+
+/**
+ * Exception thrown when a path/filename is not valid. Causes include:
+ * <ul>
+ * <li>The filename contains invalid characters</li>
+ * <li>The path specifies a new filename, but its parent directory does not exist</li>
+ * <li>The path is expected to be a file, but actually specifies an existing directory</li>
+ * </ul>
+ */
+public class InvalidFilenameException extends FileSystemException {
+
+ private static final String MESSAGE_KEY = "filesystem.pathIsNotValid";
+
+ /**
+ * @param path - the path involved in the file system operation that caused the exception
+ */
+ public InvalidFilenameException(String path) {
+ super(path, MESSAGE_KEY);
+ }
+
+ /**
+ * @param path - the path involved in the file system operation that caused the exception
+ * @param cause - the exception cause, wrapped by this exception
+ */
+ public InvalidFilenameException(String path, Throwable cause) {
+ super(path, MESSAGE_KEY, cause);
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/Permissions.java b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/Permissions.java
new file mode 100644
index 0000000..8c39637
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/Permissions.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem;
+
+import org.mockftpserver.core.util.Assert;
+
+/**
+ * Represents and encapsulates the read/write/execute permissions for a file or directory.
+ * This is conceptually (and somewhat loosely) based on the permissions flags within the Unix
+ * file system. An instance of this class is immutable.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class Permissions {
+ public static final Permissions ALL = new Permissions("rwxrwxrwx");
+ public static final Permissions NONE = new Permissions("---------");
+ public static final Permissions DEFAULT = ALL;
+
+ private static final char READ_CHAR = 'r';
+ private static final char WRITE_CHAR = 'w';
+ private static final char EXECUTE_CHAR = 'x';
+
+ private String rwxString;
+
+ /**
+ * Costruct a new instance for the specified read/write/execute specification String
+ *
+ * @param rwxString - the read/write/execute specification String; must be 9 characters long, with chars
+ * at index 0,3,6 == '-' or 'r', chars at index 1,4,7 == '-' or 'w' and chars at index 2,5,8 == '-' or 'x'.
+ */
+ public Permissions(String rwxString) {
+ Assert.isTrue(rwxString.length() == 9, "The permissions string must be exactly 9 characters");
+ final String RWX = "(-|r)(-|w)(-|x)";
+ final String PATTERN = RWX + RWX + RWX;
+ Assert.isTrue(rwxString.matches(PATTERN), "The permissions string must match [" + PATTERN + "]");
+ this.rwxString = rwxString;
+ }
+
+ /**
+ * Return the read/write/execute specification String representing the set of permissions. For example:
+ * "rwxrwxrwx" or "rw-r-----".
+ *
+ * @return the String containing 9 characters that represent the read/write/execute permissions.
+ */
+ public String asRwxString() {
+ return rwxString;
+ }
+
+ /**
+ * @return the RWX string for this instance
+ */
+ public String getRwxString() {
+ return rwxString;
+ }
+
+ /**
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object object) {
+ return (object != null)
+ && (object.getClass() == this.getClass())
+ && (object.hashCode() == hashCode());
+ }
+
+ /**
+ * Return the hash code for this object.
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ return rwxString.hashCode();
+ }
+
+ /**
+ * @return true if and only if the user has read permission
+ */
+ public boolean canUserRead() {
+ return rwxString.charAt(0) == READ_CHAR;
+ }
+
+ /**
+ * @return true if and only if the user has write permission
+ */
+ public boolean canUserWrite() {
+ return rwxString.charAt(1) == WRITE_CHAR;
+ }
+
+ /**
+ * @return true if and only if the user has execute permission
+ */
+ public boolean canUserExecute() {
+ return rwxString.charAt(2) == EXECUTE_CHAR;
+ }
+
+ /**
+ * @return true if and only if the group has read permission
+ */
+ public boolean canGroupRead() {
+ return rwxString.charAt(3) == READ_CHAR;
+ }
+
+ /**
+ * @return true if and only if the group has write permission
+ */
+ public boolean canGroupWrite() {
+ return rwxString.charAt(4) == WRITE_CHAR;
+ }
+
+ /**
+ * @return true if and only if the group has execute permission
+ */
+ public boolean canGroupExecute() {
+ return rwxString.charAt(5) == EXECUTE_CHAR;
+ }
+
+ /**
+ * @return true if and only if the world has read permission
+ */
+ public boolean canWorldRead() {
+ return rwxString.charAt(6) == READ_CHAR;
+ }
+
+ /**
+ * @return true if and only if the world has write permission
+ */
+ public boolean canWorldWrite() {
+ return rwxString.charAt(7) == WRITE_CHAR;
+ }
+
+ /**
+ * @return true if and only if the world has execute permission
+ */
+ public boolean canWorldExecute() {
+ return rwxString.charAt(8) == EXECUTE_CHAR;
+ }
+
+ /**
+ * @return the String representation of this object.
+ */
+ public String toString() {
+ return "Permissions[" + rwxString + "]";
+ }
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/UnixDirectoryListingFormatter.java b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/UnixDirectoryListingFormatter.java
new file mode 100644
index 0000000..d507345
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/UnixDirectoryListingFormatter.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.util.StringUtil;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+
+/**
+ * Unix-specific implementation of the DirectoryListingFormatter interface.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class UnixDirectoryListingFormatter implements DirectoryListingFormatter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(UnixDirectoryListingFormatter.class);
+
+ private static final String DATE_FORMAT = "MMM dd yyyy";
+ private static final int SIZE_WIDTH = 15;
+ private static final int OWNER_WIDTH = 8;
+ private static final int GROUP_WIDTH = 8;
+ private static final String NONE = "none";
+
+ private Locale locale = Locale.ENGLISH;
+
+ // "-rw-rw-r-- 1 ftp ftp 254 Feb 23 2007 robots.txt"
+ // "-rw-r--r-- 1 ftp ftp 30014925 Apr 15 00:19 md5.sums.gz"
+ // "-rwxr-xr-x 1 henry users 5778 Dec 1 2005 planaccess.sql"
+
+ /**
+ * Format the directory listing for a single file/directory entry.
+ *
+ * @param fileSystemEntry - the FileSystemEntry for a single file system entry
+ * @return the formatted directory listing
+ */
+ public String format(FileSystemEntry fileSystemEntry) {
+ DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT, locale);
+ String dateStr = dateFormat.format(fileSystemEntry.getLastModified());
+ String dirOrFile = fileSystemEntry.isDirectory() ? "d" : "-";
+ Permissions permissions = fileSystemEntry.getPermissions() != null ? fileSystemEntry.getPermissions() : Permissions.DEFAULT;
+ String permissionsStr = StringUtil.padRight(permissions.asRwxString(), 9);
+ String linkCountStr = "1";
+ String ownerStr = StringUtil.padRight(stringOrNone(fileSystemEntry.getOwner()), OWNER_WIDTH);
+ String groupStr = StringUtil.padRight(stringOrNone(fileSystemEntry.getGroup()), GROUP_WIDTH);
+ String sizeStr = StringUtil.padLeft(Long.toString(fileSystemEntry.getSize()), SIZE_WIDTH);
+ String listing = "" + dirOrFile + permissionsStr + " " + linkCountStr + " " + ownerStr + " " + groupStr + " " + sizeStr + " " + dateStr + " " + fileSystemEntry.getName();
+ LOG.info("listing=[" + listing + "]");
+ return listing;
+ }
+
+ /**
+ * Set the Locale to be used in formatting the date within file/directory listings
+ * @param locale - the Locale instance
+ */
+ public void setLocale(Locale locale) {
+ this.locale = locale;
+ }
+
+ private String stringOrNone(String string) {
+ return (string == null) ? NONE : string;
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/UnixFakeFileSystem.java b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/UnixFakeFileSystem.java
new file mode 100644
index 0000000..3d4b579
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/UnixFakeFileSystem.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem;
+
+import org.mockftpserver.core.util.Assert;
+
+/**
+ * Implementation of the {@link FileSystem} interface that simulates a Unix
+ * file system. The rules for file and directory names include:
+ * <ul>
+ * <li>Filenames are case-sensitive</li>
+ * <li>Forward slashes (/) are the only valid path separators</li>
+ * </ul>
+ * <p/>
+ * The <code>directoryListingFormatter</code> property is automatically initialized to an instance
+ * of {@link UnixDirectoryListingFormatter}.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class UnixFakeFileSystem extends AbstractFakeFileSystem {
+
+ public static final char SEPARATOR = '/';
+
+ /**
+ * Construct a new instance and initialize the directoryListingFormatter to a UnixDirectoryListingFormatter.
+ */
+ public UnixFakeFileSystem() {
+ this.setDirectoryListingFormatter(new UnixDirectoryListingFormatter());
+ }
+
+ //-------------------------------------------------------------------------
+ // Abstract Method Implementations
+ //-------------------------------------------------------------------------
+
+ protected char getSeparatorChar() {
+ return SEPARATOR;
+ }
+
+ /**
+ * Return true if the specified path designates a valid (absolute) file path. For Unix,
+ * a path is valid if it starts with the '/' character, followed by zero or more names
+ * (a sequence of any characters except '/'), delimited by '/'. The path may optionally
+ * contain a terminating '/'.
+ *
+ * @param path - the path
+ * @return true if path is valid, false otherwise
+ * @throws AssertionError - if path is null
+ */
+ protected boolean isValidName(String path) {
+ Assert.notNull(path, "path");
+ // Any character but '/'
+ return path.matches("\\/|(\\/[^\\/]+\\/?)+");
+
+ }
+
+ /**
+ * Return true if the specified char is a separator character ('\' or '/')
+ *
+ * @param c - the character to test
+ * @return true if the specified char is a separator character ('\' or '/')
+ */
+ protected boolean isSeparator(char c) {
+ return c == SEPARATOR;
+ }
+
+ /**
+ * @return true if the specified path component is a root for this filesystem
+ */
+ protected boolean isRoot(String pathComponent) {
+ return pathComponent.indexOf(":") != -1;
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/WindowsDirectoryListingFormatter.java b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/WindowsDirectoryListingFormatter.java
new file mode 100644
index 0000000..9479e64
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/WindowsDirectoryListingFormatter.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem;
+
+import org.mockftpserver.core.util.StringUtil;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+
+/**
+ * Windows-specific implementation of the DirectoryListingFormatter interface.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class WindowsDirectoryListingFormatter implements DirectoryListingFormatter {
+
+ private static final String DATE_FORMAT = "MM-dd-yy hh:mmaa";
+ private static final int SIZE_WIDTH = 15;
+
+ /**
+ * Format the directory listing for a single file/directory entry.
+ *
+ * @param fileSystemEntry - the FileSystemEntry for a single file system entry
+ * @return the formatted directory listing
+ */
+ public String format(FileSystemEntry fileSystemEntry) {
+ DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.ENGLISH);
+ String dateStr = dateFormat.format(fileSystemEntry.getLastModified());
+ String dirOrSize = fileSystemEntry.isDirectory()
+ ? StringUtil.padRight("<DIR>", SIZE_WIDTH)
+ : StringUtil.padLeft(Long.toString(fileSystemEntry.getSize()), SIZE_WIDTH);
+ return dateStr + " " + dirOrSize + " " + fileSystemEntry.getName();
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/WindowsFakeFileSystem.java b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/WindowsFakeFileSystem.java
new file mode 100644
index 0000000..71a3b36
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/fake/filesystem/WindowsFakeFileSystem.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem;
+
+import org.mockftpserver.core.util.Assert;
+
+/**
+ * Implementation of the {@link FileSystem} interface that simulates a Microsoft
+ * Windows file system. The rules for file and directory names include:
+ * <ul>
+ * <li>Filenames are case-insensitive (and normalized to lower-case)</li>
+ * <li>Either forward slashes (/) or backward slashes (\) are valid path separators (but are normalized to '\')</li>
+ * <li>An absolute path starts with a drive specifier (e.g. 'a:' or 'c:') followed
+ * by '\' or '/', or else if it starts with "\\"</li>
+ * </ul>
+ * <p/>
+ * The <code>directoryListingFormatter</code> property is automatically initialized to an instance
+ * of {@link WindowsDirectoryListingFormatter}.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class WindowsFakeFileSystem extends AbstractFakeFileSystem {
+
+ public static final char SEPARATOR = '\\';
+ private static final String VALID_PATTERN = "\\p{Alpha}\\:" + "(\\\\|(\\\\[^\\\\\\:\\*\\?\\<\\>\\|\\\"]+)+)";
+ //static final VALID_PATTERN = /\p{Alpha}\:(\\|(\\[^\\\:\*\?\<\>\|\"]+)+)/
+ private static final String LAN_PREFIX = "\\\\";
+
+ /**
+ * Construct a new instance and initialize the directoryListingFormatter to a WindowsDirectoryListingFormatter.
+ */
+ public WindowsFakeFileSystem() {
+ this.setDirectoryListingFormatter(new WindowsDirectoryListingFormatter());
+ }
+
+ //-------------------------------------------------------------------------
+ // Abstract Or Overridden Method Implementations
+ //-------------------------------------------------------------------------
+
+ /**
+ * Return the normalized and unique key used to access the file system entry. Windows is case-insensitive,
+ * so normalize all paths to lower-case.
+ *
+ * @param path - the path
+ * @return the corresponding normalized key
+ */
+ protected String getFileSystemEntryKey(String path) {
+ return normalize(path).toLowerCase();
+ }
+
+ protected char getSeparatorChar() {
+ return SEPARATOR;
+ }
+
+ /**
+ * Return true if the specified path designates a valid (absolute) file path. For Windows
+ * paths, a path is valid if it starts with a drive specifier followed by
+ * '\' or '/', or if it starts with "\\".
+ *
+ * @param path - the path
+ * @return true if path is valid, false otherwise
+ * @throws AssertionError - if path is null
+ */
+ protected boolean isValidName(String path) {
+ // \/:*?"<>|
+ Assert.notNull(path, "path");
+ String standardized = path.replace('/', '\\');
+ return standardized.matches(VALID_PATTERN) || standardized.startsWith(LAN_PREFIX);
+ }
+
+ /**
+ * Return true if the specified char is a separator character ('\' or '/')
+ *
+ * @param c - the character to test
+ * @return true if the specified char is a separator character ('\' or '/')
+ */
+ protected boolean isSeparator(char c) {
+ return c == '\\' || c == '/';
+ }
+
+ /**
+ * @return true if the specified path component is a root for this filesystem
+ */
+ protected boolean isRoot(String pathComponent) {
+ return pathComponent.indexOf(":") != -1;
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/StubFtpServer.java b/tags/2.5/src/main/java/org/mockftpserver/stub/StubFtpServer.java
new file mode 100644
index 0000000..90c9a70
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/StubFtpServer.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub;
+
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ConnectCommandHandler;
+import org.mockftpserver.core.command.ReplyTextBundleUtil;
+import org.mockftpserver.core.command.UnsupportedCommandHandler;
+import org.mockftpserver.core.server.AbstractFtpServer;
+import org.mockftpserver.stub.command.*;
+
+/**
+ * <b>StubFtpServer</b> is the top-level class for a "stub" implementation of an FTP Server,
+ * suitable for testing FTP client code or standing in for a live FTP server. It supports
+ * the main FTP commands by defining handlers for each of the corresponding low-level FTP
+ * server commands (e.g. RETR, DELE, LIST). These handlers implement the {@link CommandHandler}
+ * interface.
+ * <p/>
+ * <b>StubFtpServer</b> works out of the box with default command handlers that return
+ * success reply codes and empty data (for retrieved files, directory listings, etc.).
+ * The command handler for any command can be easily configured to return custom data
+ * or reply codes. Or it can be replaced with a custom {@link CommandHandler}
+ * implementation. This allows simulation of a complete range of both success and
+ * failure scenarios. The command handlers can also be interrogated to verify command
+ * invocation data such as command parameters and timestamps.
+ * <p/>
+ * <b>StubFtpServer</b> can be fully configured programmatically or within the
+ * <a href="http://www.springframework.org/">Spring Framework</a> or similar container.
+ * <p/>
+ * <h4>Starting the StubFtpServer</h4>
+ * Here is how to start the <b>StubFtpServer</b> with the default configuration.
+ * <pre><code>
+ * StubFtpServer stubFtpServer = new StubFtpServer();
+ * stubFtpServer.start();
+ * </code></pre>
+ * <p/>
+ * <h4>FTP Server Control Port</h4>
+ * By default, <b>StubFtpServer</b> binds to the server control port of 21. You can use a different server control
+ * port by setting the <code>serverControlPort</code> property. If you specify a value of <code>0</code>,
+ * then a free port number will be chosen automatically; call <code>getServerControlPort()</code> AFTER
+ * <code>start()</code> has been called to determine the actual port number being used. Using a non-default
+ * port number is usually necessary when running on Unix or some other system where that port number is
+ * already in use or cannot be bound from a user process.
+ * <p/>
+ * <h4>Retrieving Command Handlers</h4>
+ * You can retrieve the existing {@link CommandHandler} defined for an FTP server command
+ * by calling the {@link #getCommandHandler(String)} method, passing in the FTP server
+ * command name. For example:
+ * <pre><code>
+ * PwdCommandHandler pwdCommandHandler = (PwdCommandHandler) stubFtpServer.getCommandHandler("PWD");
+ * </code></pre>
+ * <p/>
+ * <h4>Replacing Command Handlers</h4>
+ * You can replace the existing {@link CommandHandler} defined for an FTP server command
+ * by calling the {@link #setCommandHandler(String, CommandHandler)} method, passing
+ * in the FTP server command name and {@link CommandHandler} instance. For example:
+ * <pre><code>
+ * PwdCommandHandler pwdCommandHandler = new PwdCommandHandler();
+ * pwdCommandHandler.setDirectory("some/dir");
+ * stubFtpServer.setCommandHandler("PWD", pwdCommandHandler);
+ * </code></pre>
+ * You can also replace multiple command handlers at once by using the {@link #setCommandHandlers(java.util.Map)}
+ * method. That is especially useful when configuring the server through the <b>Spring Framework</b>.
+ * <h4>FTP Command Reply Text ResourceBundle</h4>
+ * <p/>
+ * The default text asociated with each FTP command reply code is contained within the
+ * "ReplyText.properties" ResourceBundle file. You can customize these messages by providing a
+ * locale-specific ResourceBundle file on the CLASSPATH, according to the normal lookup rules of
+ * the ResourceBundle class (e.g., "ReplyText_de.properties"). Alternatively, you can
+ * completely replace the ResourceBundle file by calling the calling the
+ * {@link #setReplyTextBaseName(String)} method.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class StubFtpServer extends AbstractFtpServer {
+
+ /**
+ * Create a new instance. Initialize the default command handlers and
+ * reply text ResourceBundle.
+ */
+ public StubFtpServer() {
+ PwdCommandHandler pwdCommandHandler = new PwdCommandHandler();
+
+ // Initialize the default CommandHandler mappings
+ setCommandHandler(CommandNames.ABOR, new AborCommandHandler());
+ setCommandHandler(CommandNames.ACCT, new AcctCommandHandler());
+ setCommandHandler(CommandNames.ALLO, new AlloCommandHandler());
+ setCommandHandler(CommandNames.APPE, new AppeCommandHandler());
+ setCommandHandler(CommandNames.PWD, pwdCommandHandler); // same as XPWD
+ setCommandHandler(CommandNames.CONNECT, new ConnectCommandHandler());
+ setCommandHandler(CommandNames.CWD, new CwdCommandHandler());
+ setCommandHandler(CommandNames.CDUP, new CdupCommandHandler());
+ setCommandHandler(CommandNames.DELE, new DeleCommandHandler());
+ setCommandHandler(CommandNames.EPRT, new EprtCommandHandler());
+ setCommandHandler(CommandNames.EPSV, new EpsvCommandHandler());
+ setCommandHandler(CommandNames.HELP, new HelpCommandHandler());
+ setCommandHandler(CommandNames.LIST, new ListCommandHandler());
+ setCommandHandler(CommandNames.MKD, new MkdCommandHandler());
+ setCommandHandler(CommandNames.MODE, new ModeCommandHandler());
+ setCommandHandler(CommandNames.NOOP, new NoopCommandHandler());
+ setCommandHandler(CommandNames.NLST, new NlstCommandHandler());
+ setCommandHandler(CommandNames.PASS, new PassCommandHandler());
+ setCommandHandler(CommandNames.PASV, new PasvCommandHandler());
+ setCommandHandler(CommandNames.PORT, new PortCommandHandler());
+ setCommandHandler(CommandNames.RETR, new RetrCommandHandler());
+ setCommandHandler(CommandNames.QUIT, new QuitCommandHandler());
+ setCommandHandler(CommandNames.REIN, new ReinCommandHandler());
+ setCommandHandler(CommandNames.REST, new RestCommandHandler());
+ setCommandHandler(CommandNames.RMD, new RmdCommandHandler());
+ setCommandHandler(CommandNames.RNFR, new RnfrCommandHandler());
+ setCommandHandler(CommandNames.RNTO, new RntoCommandHandler());
+ setCommandHandler(CommandNames.SITE, new SiteCommandHandler());
+ setCommandHandler(CommandNames.SMNT, new SmntCommandHandler());
+ setCommandHandler(CommandNames.STAT, new StatCommandHandler());
+ setCommandHandler(CommandNames.STOR, new StorCommandHandler());
+ setCommandHandler(CommandNames.STOU, new StouCommandHandler());
+ setCommandHandler(CommandNames.STRU, new StruCommandHandler());
+ setCommandHandler(CommandNames.SYST, new SystCommandHandler());
+ setCommandHandler(CommandNames.TYPE, new TypeCommandHandler());
+ setCommandHandler(CommandNames.USER, new UserCommandHandler());
+ setCommandHandler(CommandNames.UNSUPPORTED, new UnsupportedCommandHandler());
+ setCommandHandler(CommandNames.XPWD, pwdCommandHandler); // same as PWD
+ }
+
+ //-------------------------------------------------------------------------
+ // Abstract method implementation
+ //-------------------------------------------------------------------------
+
+ protected void initializeCommandHandler(CommandHandler commandHandler) {
+ ReplyTextBundleUtil.setReplyTextBundleIfAppropriate(commandHandler, getReplyTextBundle());
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/AborCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/AborCommandHandler.java
new file mode 100644
index 0000000..6260344
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/AborCommandHandler.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the ABOR command. Return a reply code of 226.
+ * <p>
+ * Each invocation record stored by this CommandHandler contains no data elements.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class AborCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public AborCommandHandler() {
+ setReplyCode(ReplyCodes.ABOR_OK);
+ }
+
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/AbstractStorCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/AbstractStorCommandHandler.java
new file mode 100644
index 0000000..4201516
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/AbstractStorCommandHandler.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * Abstract superclass for CommandHandler for commands that store a file. Send back two replies on the
+ * control connection: a reply code of 150 and another of 226.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #FILE_CONTENTS_KEY} ("fileContents") - the file contents (<code>byte[]</code>) sent on the data connection
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public abstract class AbstractStorCommandHandler extends AbstractStubDataCommandHandler {
+
+ public static final String PATHNAME_KEY = "pathname";
+ public static final String FILE_CONTENTS_KEY = "filecontents";
+
+ /**
+ * @see AbstractStubDataCommandHandler#processData(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session, org.mockftpserver.core.command.InvocationRecord)
+ */
+ protected void processData(Command command, Session session, InvocationRecord invocationRecord) {
+ byte[] data = session.readData();
+ LOG.info("Received " + data.length + " bytes");
+ LOG.trace("Received data [" + new String(data) + "]");
+ invocationRecord.set(FILE_CONTENTS_KEY, data);
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/AbstractStubCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/AbstractStubCommandHandler.java
new file mode 100644
index 0000000..5fdf852
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/AbstractStubCommandHandler.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.AbstractStaticReplyCommandHandler;
+
+/**
+ * The abstract superclass for CommandHandler classes for the {@link org.mockftpserver.stub.StubFtpServer}.
+ * <p>
+ * Subclasses can optionally override the reply code and/or text for the reply by calling
+ * {@link #setReplyCode(int)}, {@link #setReplyMessageKey(String)} and {@link #setReplyText(String)}.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public abstract class AbstractStubCommandHandler extends AbstractStaticReplyCommandHandler {
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/AbstractStubDataCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/AbstractStubDataCommandHandler.java
new file mode 100644
index 0000000..2a7c899
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/AbstractStubDataCommandHandler.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.AbstractTrackingCommandHandler;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.AssertFailedException;
+
+/**
+ * Abstract superclass for CommandHandlers that read from or write to the data connection.
+ * <p/>
+ * Return two replies on the control connection: by default a reply code of 150 before the
+ * data transfer across the data connection and another reply of 226 after the data transfer
+ * is complete.
+ * <p/>
+ * This class implements the <i>Template Method</i> pattern. Subclasses must implement the abstract
+ * <code>processData</code> method to perform read or writes across the data connection.
+ * <p/>
+ * Subclasses can optionally override the {@link #beforeProcessData(Command, Session, InvocationRecord)}
+ * method for logic before the data transfer or the {@link #afterProcessData(Command, Session, InvocationRecord)}
+ * method for logic after the data transfer.
+ * <p/>
+ * Subclasses can optionally override the reply code and/or text for the initial reply (before
+ * the data transfer across the data connection) by calling {@link #setPreliminaryReplyCode(int)},
+ * {@link #setPreliminaryReplyMessageKey(String)} and/or {@link #setPreliminaryReplyText(String)}
+ * methods.
+ * <p/>
+ * Subclasses can optionally override the reply code and/or text for the final reply (after the
+ * the data transfer is complete) by calling {@link #setFinalReplyCode(int)},
+ * {@link #setFinalReplyMessageKey(String)} and/or {@link #setFinalReplyText(String)} methods.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public abstract class AbstractStubDataCommandHandler extends AbstractTrackingCommandHandler implements CommandHandler {
+
+ // The completion reply code sent before the data transfer
+ protected int preliminaryReplyCode = 0;
+
+ // The text for the preliminary reply. If null, use the default message associated with the reply code.
+ // If not null, this value overrides the preliminaryReplyMessageKey - i.e., this text is used instead of
+ // a localized message.
+ protected String preliminaryReplyText = null;
+
+ // The message key for the preliminary reply text. If null, use the default message associated with
+ // the reply code.
+ protected String preliminaryReplyMessageKey = null;
+
+ // The completion reply code sent after data transfer
+ protected int finalReplyCode = 0;
+
+ // The text for the completion reply. If null, use the default message associated with the reply code.
+ // If not null, this value overrides the finalReplyMessageKey - i.e., this text is used instead of
+ // a localized message.
+ protected String finalReplyText = null;
+
+ // The message key for the completion reply text. If null, use the default message associated with the reply code
+ protected String finalReplyMessageKey = null;
+
+ /**
+ * Constructor. Initialize the preliminary and final reply code.
+ */
+ protected AbstractStubDataCommandHandler() {
+ setPreliminaryReplyCode(ReplyCodes.TRANSFER_DATA_INITIAL_OK);
+ setFinalReplyCode(ReplyCodes.TRANSFER_DATA_FINAL_OK);
+ }
+
+ /**
+ * Handle the command. Perform the following steps:
+ * <ol>
+ * <li>Invoke the <code>beforeProcessData()</code> method</li>
+ * <li>Open the data connection</li>
+ * <li>Send an preliminary reply, default reply code 150</li>
+ * <li>Invoke the <code>processData()</code> method</li>
+ * <li>Close the data connection</li>
+ * <li>Send the final reply, default reply code 226</li>
+ * <li>Invoke the <code>afterProcessData()</code> method</li>
+ * </ol>
+ *
+ * @see org.mockftpserver.core.command.AbstractTrackingCommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session, org.mockftpserver.core.command.InvocationRecord)
+ */
+ public final void handleCommand(Command command, Session session, InvocationRecord invocationRecord) throws Exception {
+
+ beforeProcessData(command, session, invocationRecord);
+
+ sendPreliminaryReply(session);
+ session.openDataConnection();
+ processData(command, session, invocationRecord);
+ session.closeDataConnection();
+ sendFinalReply(session);
+
+ afterProcessData(command, session, invocationRecord);
+ }
+
+ /**
+ * Send the final reply. The default implementation sends a reply code of 226 with the
+ * corresponding associated reply text.
+ *
+ * @param session - the Session
+ */
+ protected void sendFinalReply(Session session) {
+ sendReply(session, finalReplyCode, finalReplyMessageKey, finalReplyText, null);
+ }
+
+ /**
+ * Perform any necessary logic before transferring data across the data connection.
+ * Do nothing by default. Subclasses should override to validate command parameters and
+ * store information in the InvocationRecord.
+ *
+ * @param command - the Command to be handled
+ * @param session - the session on which the Command was submitted
+ * @param invocationRecord - the InvocationRecord; CommandHandlers are expected to add
+ * handler-specific data to the InvocationRecord, as appropriate
+ * @throws Exception
+ */
+ protected void beforeProcessData(Command command, Session session, InvocationRecord invocationRecord) throws Exception {
+ // Do nothing by default
+ }
+
+ /**
+ * Abstract method placeholder for subclass transfer of data across the data connection.
+ * Subclasses must override. The data connection is opened before this method and is
+ * closed after this method completes.
+ *
+ * @param command - the Command to be handled
+ * @param session - the session on which the Command was submitted
+ * @param invocationRecord - the InvocationRecord; CommandHandlers are expected to add
+ * handler-specific data to the InvocationRecord, as appropriate
+ * @throws Exception
+ */
+ protected abstract void processData(Command command, Session session, InvocationRecord invocationRecord) throws Exception;
+
+ /**
+ * Perform any necessary logic after transferring data across the data connection.
+ * Do nothing by default.
+ *
+ * @param command - the Command to be handled
+ * @param session - the session on which the Command was submitted
+ * @param invocationRecord - the InvocationRecord; CommandHandlers are expected to add
+ * handler-specific data to the InvocationRecord, as appropriate
+ * @throws Exception
+ */
+ protected void afterProcessData(Command command, Session session, InvocationRecord invocationRecord) throws Exception {
+ // Do nothing by default
+ }
+
+ /**
+ * Send the preliminary reply for this command on the control connection.
+ *
+ * @param session - the Session
+ */
+ private void sendPreliminaryReply(Session session) {
+ sendReply(session, preliminaryReplyCode, preliminaryReplyMessageKey, preliminaryReplyText, null);
+ }
+
+ /**
+ * Set the completion reply code sent after data transfer
+ *
+ * @param finalReplyCode - the final reply code
+ * @throws AssertFailedException - if the finalReplyCode is invalid
+ */
+ public void setFinalReplyCode(int finalReplyCode) {
+ assertValidReplyCode(finalReplyCode);
+ this.finalReplyCode = finalReplyCode;
+ }
+
+ /**
+ * Set the message key for the completion reply text sent after data transfer
+ *
+ * @param finalReplyMessageKey - the final reply message key
+ */
+ public void setFinalReplyMessageKey(String finalReplyMessageKey) {
+ this.finalReplyMessageKey = finalReplyMessageKey;
+ }
+
+ /**
+ * Set the text of the completion reply sent after data transfer
+ *
+ * @param finalReplyText - the final reply text
+ */
+ public void setFinalReplyText(String finalReplyText) {
+ this.finalReplyText = finalReplyText;
+ }
+
+ /**
+ * Set the completion reply code sent before data transfer
+ *
+ * @param preliminaryReplyCode - the preliminary reply code to set
+ * @throws AssertFailedException - if the preliminaryReplyCode is invalid
+ */
+ public void setPreliminaryReplyCode(int preliminaryReplyCode) {
+ assertValidReplyCode(preliminaryReplyCode);
+ this.preliminaryReplyCode = preliminaryReplyCode;
+ }
+
+ /**
+ * Set the message key for the completion reply text sent before data transfer
+ *
+ * @param preliminaryReplyMessageKey - the preliminary reply message key
+ */
+ public void setPreliminaryReplyMessageKey(String preliminaryReplyMessageKey) {
+ this.preliminaryReplyMessageKey = preliminaryReplyMessageKey;
+ }
+
+ /**
+ * Set the text of the completion reply sent before data transfer
+ *
+ * @param preliminaryReplyText - the preliminary reply text
+ */
+ public void setPreliminaryReplyText(String preliminaryReplyText) {
+ this.preliminaryReplyText = preliminaryReplyText;
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/AcctCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/AcctCommandHandler.java
new file mode 100644
index 0000000..ea70df5
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/AcctCommandHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the ACCT command. Send back a reply code of 230.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>"acount" - the account submitted on the invocation (the first command parameter)
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class AcctCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String ACCOUNT_KEY = "account";
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public AcctCommandHandler() {
+ setReplyCode(ReplyCodes.ACCT_OK);
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ invocationRecord.set(ACCOUNT_KEY, command.getRequiredParameter(0));
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/AlloCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/AlloCommandHandler.java
new file mode 100644
index 0000000..3e2eb8c
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/AlloCommandHandler.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.Assert;
+
+import java.util.StringTokenizer;
+
+/**
+ * CommandHandler for the ALLO (Allocate) command. Send back a reply code of 200.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #NUMBER_OF_BYTES_KEY} ("numberOfBytes") - the number of bytes submitted
+ * on the invocation (the first command parameter)
+ * <li>{@link #RECORD_SIZE_KEY} ("recordSize") - the record size optionally submitted
+ * on the invocation (the second command parameter)
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class AlloCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String NUMBER_OF_BYTES_KEY = "numberOfBytes";
+ public static final String RECORD_SIZE_KEY = "recordSize";
+ private static final String RECORD_SIZE_DELIMITER = " R ";
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public AlloCommandHandler() {
+ setReplyCode(ReplyCodes.ALLO_OK);
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ String parametersString = command.getRequiredParameter(0);
+
+ if (parametersString.indexOf(RECORD_SIZE_DELIMITER) == -1) {
+ invocationRecord.set(NUMBER_OF_BYTES_KEY, Integer.valueOf(parametersString));
+ } else {
+ // If the recordSize delimiter (" R ") is specified, then it must be followed by the recordSize.
+ StringTokenizer tokenizer = new StringTokenizer(parametersString, RECORD_SIZE_DELIMITER);
+ invocationRecord.set(NUMBER_OF_BYTES_KEY, Integer.valueOf(tokenizer.nextToken()));
+ Assert.isTrue(tokenizer.hasMoreTokens(), "Missing record size: [" + parametersString + "]");
+ invocationRecord.set(RECORD_SIZE_KEY, Integer.valueOf(tokenizer.nextToken()));
+ }
+
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/AppeCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/AppeCommandHandler.java
new file mode 100644
index 0000000..8fc522b
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/AppeCommandHandler.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the APPE (Append) command. Send back two replies on the control connection: a
+ * reply code of 150 and another of 226.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #PATHNAME_KEY} ("pathname") - the pathname of the directory submitted on the invocation (the first command parameter)
+ * <li>{@link #FILE_CONTENTS_KEY} ("fileContents") - the file contents (<code>byte[]</code>) sent on the data connection
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class AppeCommandHandler extends AbstractStorCommandHandler {
+
+ /**
+ * @see org.mockftpserver.stub.command.AbstractStubDataCommandHandler#beforeProcessData(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session, org.mockftpserver.core.command.InvocationRecord)
+ */
+ protected void beforeProcessData(Command command, Session session, InvocationRecord invocationRecord) throws Exception {
+ String filename = command.getRequiredParameter(0);
+ invocationRecord.set(PATHNAME_KEY, filename);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/CdupCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/CdupCommandHandler.java
new file mode 100644
index 0000000..5ed78e5
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/CdupCommandHandler.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the CDUP (Change To Parent Directory) command. Send back a reply code of 250.
+ * <p>
+ * Each invocation record stored by this CommandHandler contains no data elements.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class CdupCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public CdupCommandHandler() {
+ setReplyCode(ReplyCodes.CDUP_OK);
+ }
+
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/CwdCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/CwdCommandHandler.java
new file mode 100644
index 0000000..412e85d
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/CwdCommandHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the CWD (Change Working Directory) command. Send back a reply code of 250.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #PATHNAME_KEY} ("pathname") - the pathname of the directory submitted on the invocation (the first command parameter)
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class CwdCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String PATHNAME_KEY = "pathname";
+
+ /**
+ * Constructor. Initiate the replyCode.
+ */
+ public CwdCommandHandler() {
+ setReplyCode(ReplyCodes.CWD_OK);
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ invocationRecord.set(PATHNAME_KEY, command.getRequiredParameter(0));
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/DeleCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/DeleCommandHandler.java
new file mode 100644
index 0000000..ef2533b
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/DeleCommandHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the DELE (Delete) command. Send back a reply code of 250.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #PATHNAME_KEY} ("pathname") - the pathname of the file name submitted on the invocation (the first command parameter)
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class DeleCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String PATHNAME_KEY = "pathname";
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public DeleCommandHandler() {
+ setReplyCode(ReplyCodes.DELE_OK);
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ invocationRecord.set(PATHNAME_KEY, command.getRequiredParameter(0));
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/EprtCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/EprtCommandHandler.java
new file mode 100644
index 0000000..fc1ab1e
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/EprtCommandHandler.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.PortParser;
+import org.mockftpserver.core.util.HostAndPort;
+
+import java.net.UnknownHostException;
+
+/**
+ * CommandHandler for the EPRT command. Send back a reply code of 200.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #HOST_KEY} ("host") - the client data host (InetAddress) submitted on the invocation (from parameters 1-4)
+ * <li>{@link #PORT_KEY} ("port") - the port number (Integer) submitted on the invocation (from parameter 5-6)
+ * </ul>
+ * See RFC2428 for more information.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class EprtCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String HOST_KEY = "host";
+ public static final String PORT_KEY = "port";
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public EprtCommandHandler() {
+ setReplyCode(ReplyCodes.EPRT_OK);
+ }
+
+ /**
+ * Handle the command
+ *
+ * @throws java.net.UnknownHostException
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) throws UnknownHostException {
+ String parameter = command.getRequiredParameter(0);
+
+ HostAndPort client = PortParser.parseExtendedAddressHostAndPort(parameter);
+ LOG.debug("host=" + client.host + " port=" + client.port);
+ session.setClientDataHost(client.host);
+ session.setClientDataPort(client.port);
+ invocationRecord.set(HOST_KEY, client.host);
+ invocationRecord.set(PORT_KEY, new Integer(client.port));
+ sendReply(session);
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/EpsvCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/EpsvCommandHandler.java
new file mode 100644
index 0000000..602bd0d
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/EpsvCommandHandler.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+import java.io.IOException;
+import java.net.InetAddress;
+
+/**
+ * CommandHandler for the EPSV (Extended Address Passive Mode) command. Request the Session to switch
+ * to passive data connection mode. Return a reply code of 229, along with response text of the form:
+ * "<i>Entering Extended Passive Mode (|||PORT|)</i>", where <i>PORT</i> is the 16-bit TCP port
+ * address of the data connection on the server to which the client must connect.
+ * See RFC2428 for more information.
+ * <p/>
+ * Each invocation record stored by this CommandHandler contains no data elements.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class EpsvCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public EpsvCommandHandler() {
+ setReplyCode(ReplyCodes.EPSV_OK);
+ }
+
+ /**
+ * @throws java.io.IOException
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord)
+ throws IOException {
+
+ int port = session.switchToPassiveMode();
+ InetAddress server = session.getServerHost();
+ LOG.debug("server=" + server + " port=" + port);
+ sendReply(session, Integer.toString(port));
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/FileRetrCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/FileRetrCommandHandler.java
new file mode 100644
index 0000000..84e35ac
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/FileRetrCommandHandler.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.MockFtpServerException;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.Assert;
+import org.mockftpserver.core.util.AssertFailedException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * CommandHandler for the RETR command. Returns the contents of the specified file on the
+ * data connection, along with two replies on the control connection: a reply code of 150 and
+ * another of 226.
+ * <p/>
+ * The <code>file</code> property specifies the pathname for the file whose contents should
+ * be returned from this command. The file path is relative to the CLASSPATH (using the
+ * ClassLoader for this class).
+ * <p/>
+ * An exception is thrown if the <code>file</code> property has not been set or if the specified
+ * file does not exist or cannot be read.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #PATHNAME_KEY} ("pathname") - the pathname of the file submitted on the invocation (the first command parameter)
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class FileRetrCommandHandler extends AbstractStubDataCommandHandler implements CommandHandler {
+
+ public static final String PATHNAME_KEY = "pathname";
+ static final int BUFFER_SIZE = 512; // package-private for testing
+
+ private String file;
+
+ /**
+ * Create new uninitialized instance
+ */
+ public FileRetrCommandHandler() {
+ }
+
+ /**
+ * Create new instance using the specified file pathname
+ *
+ * @param file - the path to the file
+ * @throws AssertFailedException - if the file is null
+ */
+ public FileRetrCommandHandler(String file) {
+ setFile(file);
+ }
+
+ /**
+ * @see org.mockftpserver.stub.command.AbstractStubDataCommandHandler#beforeProcessData(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session, org.mockftpserver.core.command.InvocationRecord)
+ */
+ protected void beforeProcessData(Command command, Session session, InvocationRecord invocationRecord) throws Exception {
+ Assert.notNull(file, "file");
+ invocationRecord.set(PATHNAME_KEY, command.getRequiredParameter(0));
+ }
+
+ /**
+ * @see org.mockftpserver.stub.command.AbstractStubDataCommandHandler#processData(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session, org.mockftpserver.core.command.InvocationRecord)
+ */
+ protected void processData(Command command, Session session, InvocationRecord invocationRecord) {
+ InputStream inputStream = getClass().getClassLoader().getResourceAsStream(file);
+ Assert.notNull(inputStream, "InputStream for [" + file + "]");
+ byte[] buffer = new byte[BUFFER_SIZE];
+ try {
+ int numBytes;
+ while ((numBytes = inputStream.read(buffer)) != -1) {
+ LOG.trace("Sending " + numBytes + " bytes...");
+ session.sendData(buffer, numBytes);
+ }
+ }
+ catch (IOException e) {
+ throw new MockFtpServerException(e);
+ }
+ }
+
+ /**
+ * Set the path of the file whose contents should be returned when this command is
+ * invoked. The path is relative to the CLASSPATH.
+ *
+ * @param file - the path to the file
+ * @throws AssertFailedException - if the file is null
+ */
+ public void setFile(String file) {
+ Assert.notNull(file, "file");
+ this.file = file;
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/HelpCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/HelpCommandHandler.java
new file mode 100644
index 0000000..8d9e59e
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/HelpCommandHandler.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the HELP command. By default, return an empty help message,
+ * along with a reply code of 214. You can customize the returned help message by
+ * setting the <code>helpMessage</code> property.
+ * <p>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #COMMAND_NAME_KEY} ("commandName") - the command name optionally submitted on
+ * the invocation (the first command parameter). May be null.
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class HelpCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String COMMAND_NAME_KEY = "commandName";
+
+ private String helpMessage = "";
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public HelpCommandHandler() {
+ setReplyCode(ReplyCodes.HELP_OK);
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.AbstractTrackingCommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session, org.mockftpserver.core.command.InvocationRecord)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ invocationRecord.set(COMMAND_NAME_KEY, command.getOptionalString(0));
+ sendReply(session, helpMessage);
+ }
+
+ /**
+ * Set the help message String to be returned by this command
+ *
+ * @param helpMessage - the help message
+ */
+ public void setHelpMessage(String helpMessage) {
+ this.helpMessage = helpMessage;
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/ListCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/ListCommandHandler.java
new file mode 100644
index 0000000..d7ec131
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/ListCommandHandler.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the LIST command. Return the configured directory listing on the data
+ * connection, along with two replies on the control connection: a reply code of 150 and
+ * another of 226. By default, return an empty directory listing. You can customize the
+ * returned directory listing by setting the <code>directoryListing</code> property.
+ * <p/>
+ * The interpretation of the value returned from this command is dependent upon the value returned
+ * by the SYST command. The format of the directory listing should match the format associated with
+ * the system named by the SYST command. For example, if the SYST command returns "WINDOWS", then
+ * the directory listing value from this command should match the Windows-specific format. See the
+ * <code>SystCommandHandler</code> to control the value returned for the SYST command.
+ * <p/>
+ * Here is an example value for <code>directoryListing</code> when the <code>SystCommandHandler</code>
+ * returns "WINDOWS". Note that multiple entries are separated by "\n":
+ * <code><pre>
+ * CommandHandler listCommandHandler = new ListCommandHandler();
+ * listCommandHandler.setDirectoryListing("11-09-01 12:30PM 406348 File2350.log\n" +
+ * "11-01-01 1:30PM &lt;DIR&gt; archive");
+ * </pre></code>
+ * <p/>
+ * And here is an example value for <code>directoryListing</code> when the <code>SystCommandHandler</code>
+ * returns "UNIX". Note that multiple entries are separated by "\n":
+ * <code><pre>
+ * CommandHandler listCommandHandler = new ListCommandHandler();
+ * listCommandHandler.setDirectoryListing("drwxrwxrwx 1 none none 0 Mar 20 2010 archive\n" +
+ * "-rwxrwxrwx 1 none none 19 Mar 20 2010 abc.txt");
+ * </pre></code>
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #PATHNAME_KEY} ("pathname") - the pathname of the directory (or file) submitted on the
+ * invocation (the first command parameter); this parameter is optional, so the value may be null.
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ * @see SystCommandHandler
+ */
+public class ListCommandHandler extends AbstractStubDataCommandHandler implements CommandHandler {
+
+ public static final String PATHNAME_KEY = "pathname";
+
+ private String directoryListing = "";
+
+ /**
+ * @see org.mockftpserver.stub.command.AbstractStubDataCommandHandler#beforeProcessData(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session, org.mockftpserver.core.command.InvocationRecord)
+ */
+ protected void beforeProcessData(Command command, Session session, InvocationRecord invocationRecord) throws Exception {
+ invocationRecord.set(PATHNAME_KEY, command.getOptionalString(0));
+ }
+
+ /**
+ * @see org.mockftpserver.stub.command.AbstractStubDataCommandHandler#processData(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session, org.mockftpserver.core.command.InvocationRecord)
+ */
+ protected void processData(Command command, Session session, InvocationRecord invocationRecord) {
+ session.sendData(directoryListing.getBytes(), directoryListing.length());
+ }
+
+ /**
+ * Set the contents of the directoryListing to send back on the data connection for this command.
+ * The passed-in value is trimmed automatically.
+ *
+ * @param directoryListing - the directoryListing to set
+ */
+ public void setDirectoryListing(String directoryListing) {
+ this.directoryListing = directoryListing.trim();
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/MkdCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/MkdCommandHandler.java
new file mode 100644
index 0000000..40113a5
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/MkdCommandHandler.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the MKD (Make Directory) command. Send back a reply code of 257.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #PATHNAME_KEY} ("pathname") - the pathname of the directory submitted on the invocation (the first command parameter)
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class MkdCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String PATHNAME_KEY = "pathname";
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public MkdCommandHandler() {
+ setReplyCode(ReplyCodes.MKD_OK);
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ String pathname = command.getRequiredParameter(0);
+ invocationRecord.set(PATHNAME_KEY, pathname);
+ sendReply(session, pathname);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/ModeCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/ModeCommandHandler.java
new file mode 100644
index 0000000..87b4f0f
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/ModeCommandHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the MODE command. Send back a reply code of 200.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #MODE_KEY} ("mode") - the code for the transmission mode submitted on the invocation (the first command parameter)
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class ModeCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String MODE_KEY = "mode";
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public ModeCommandHandler() {
+ setReplyCode(ReplyCodes.MODE_OK);
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ invocationRecord.set(MODE_KEY, command.getRequiredParameter(0));
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/NlstCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/NlstCommandHandler.java
new file mode 100644
index 0000000..f3dc5f6
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/NlstCommandHandler.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the NLST command. Return the configured directory listing on the data
+ * connection, along with two replies on the control connection: a reply code of 150 and
+ * another of 226. By default, return an empty directory listing. You can customize the
+ * returned directory listing by setting the <code>directoryListing</code> property.
+ * <p>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #PATHNAME_KEY} ("pathname") - the pathname of the directory (or file) submitted on the
+ * invocation (the first command parameter); this parameter is optional, so the value may be null.
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class NlstCommandHandler extends AbstractStubDataCommandHandler implements CommandHandler {
+
+ public static final String PATHNAME_KEY = "pathname";
+
+ private String directoryListing = "";
+
+ /**
+ * @see org.mockftpserver.stub.command.AbstractStubDataCommandHandler#beforeProcessData(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session, org.mockftpserver.core.command.InvocationRecord)
+ */
+ protected void beforeProcessData(Command command, Session session, InvocationRecord invocationRecord) throws Exception {
+ invocationRecord.set(PATHNAME_KEY, command.getOptionalString(0));
+ }
+
+ /**
+ * @see org.mockftpserver.stub.command.AbstractStubDataCommandHandler#processData(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session, org.mockftpserver.core.command.InvocationRecord)
+ */
+ protected void processData(Command command, Session session, InvocationRecord invocationRecord) {
+ session.sendData(directoryListing.getBytes(), directoryListing.length());
+ }
+
+ /**
+ * Set the contents of the directoryListing to send back on the data connection for this command.
+ * The passed-in value is trimmed automatically.
+ *
+ * @param directoryListing - the directoryListing to set
+ */
+ public void setDirectoryListing(String directoryListing) {
+ this.directoryListing = directoryListing.trim();
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/NoopCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/NoopCommandHandler.java
new file mode 100644
index 0000000..1856db8
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/NoopCommandHandler.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the NOOP command. Return a reply code of 200.
+ * <p>
+ * Each invocation record stored by this CommandHandler contains no data elements.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class NoopCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public NoopCommandHandler() {
+ setReplyCode(ReplyCodes.NOOP_OK);
+ }
+
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/PassCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/PassCommandHandler.java
new file mode 100644
index 0000000..ead9279
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/PassCommandHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the PASS (Password) command. Send back a reply code of 230.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>"password" - the password submitted on the invocation (the first command parameter)
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class PassCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String PASSWORD_KEY = "password";
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public PassCommandHandler() {
+ setReplyCode(ReplyCodes.PASS_OK);
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ invocationRecord.set(PASSWORD_KEY, command.getRequiredParameter(0));
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/PasvCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/PasvCommandHandler.java
new file mode 100644
index 0000000..011462f
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/PasvCommandHandler.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.Assert;
+import org.mockftpserver.core.util.PortParser;
+
+import java.io.IOException;
+import java.net.InetAddress;
+
+/**
+ * CommandHandler for the PASV (Passove Mode) command. Request the Session to switch to passive
+ * data connection mode. Return a reply code of 227, along with response text of the form:
+ * "<i>Entering Passive Mode. (h1,h2,h3,h4,p1,p2)</i>", where <i>h1..h4</i> are the 4
+ * bytes of the 32-bit internet host address of the server, and <i>p1..p2</i> are the 2
+ * bytes of the 16-bit TCP port address of the data connection on the server to which
+ * the client must connect. See RFC959 for more information.
+ * <p/>
+ * Each invocation record stored by this CommandHandler contains no data elements.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class PasvCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public PasvCommandHandler() {
+ setReplyCode(ReplyCodes.PASV_OK);
+ }
+
+ /**
+ * @throws IOException
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord)
+ throws IOException {
+
+ int port = session.switchToPassiveMode();
+ InetAddress server = session.getServerHost();
+
+ Assert.isTrue(port > -1, "The server-side port is invalid: " + port);
+ LOG.debug("server=" + server + " port=" + port);
+ String hostAndPort = "(" + PortParser.convertHostAndPortToCommaDelimitedBytes(server, port) + ")";
+
+ sendReply(session, hostAndPort);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/PortCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/PortCommandHandler.java
new file mode 100644
index 0000000..ce1bddb
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/PortCommandHandler.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.HostAndPort;
+import org.mockftpserver.core.util.PortParser;
+
+import java.net.UnknownHostException;
+
+/**
+ * CommandHandler for the PORT command. Send back a reply code of 200.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #HOST_KEY} ("host") - the client data host (InetAddress) submitted on the invocation (from parameters 1-4)
+ * <li>{@link #PORT_KEY} ("port") - the port number (Integer) submitted on the invocation (from parameter 5-6)
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class PortCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String HOST_KEY = "host";
+ public static final String PORT_KEY = "port";
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public PortCommandHandler() {
+ setReplyCode(ReplyCodes.PORT_OK);
+ }
+
+ /**
+ * Handle the command
+ *
+ * @throws UnknownHostException
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) throws UnknownHostException {
+ HostAndPort client = PortParser.parseHostAndPort(command.getParameters());
+ LOG.debug("host=" + client.host + " port=" + client.port);
+ session.setClientDataHost(client.host);
+ session.setClientDataPort(client.port);
+ invocationRecord.set(HOST_KEY, client.host);
+ invocationRecord.set(PORT_KEY, new Integer(client.port));
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/PwdCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/PwdCommandHandler.java
new file mode 100644
index 0000000..80b3c63
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/PwdCommandHandler.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the PWD (Print Working Directory) and XPWD commands. By default, return
+ * an empty directory name, along with a reply code of 257. You can customize the returned
+ * directory name by setting the <code>directory</code> property.
+ * <p>
+ * Each invocation record stored by this CommandHandler contains no data elements.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class PwdCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ private String directory = "";
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public PwdCommandHandler() {
+ setReplyCode(ReplyCodes.PWD_OK);
+ }
+
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ sendReply(session, quotes(directory));
+ }
+
+ /**
+ * Set the directory String to be returned by this command
+ *
+ * @param directory - the directory
+ */
+ public void setDirectory(String directory) {
+ this.directory = directory;
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/QuitCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/QuitCommandHandler.java
new file mode 100644
index 0000000..e3265cb
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/QuitCommandHandler.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the QUIT command. Return a reply code of 221.
+ * <p>
+ * Each invocation record stored by this CommandHandler contains no data elements.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class QuitCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public QuitCommandHandler() {
+ setReplyCode(ReplyCodes.QUIT_OK);
+ }
+
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ sendReply(session);
+ session.close();
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/ReinCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/ReinCommandHandler.java
new file mode 100644
index 0000000..c323619
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/ReinCommandHandler.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * /**
+ * CommandHandler for the REIN (Reinitialize) command. Send back a reply code of 220.
+ * <p>
+ * Each invocation record stored by this CommandHandler contains no data elements.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class ReinCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public ReinCommandHandler() {
+ setReplyCode(ReplyCodes.REIN_OK);
+ }
+
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/RestCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/RestCommandHandler.java
new file mode 100644
index 0000000..bc55410
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/RestCommandHandler.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the REST (Restart of interrupted transfer) command. Send back a reply code of 350.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #MARKER_KEY} ("marker") - the server marker submitted on the invocation (the first command parameter)
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class RestCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String MARKER_KEY = "marker";
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public RestCommandHandler() {
+ setReplyCode(ReplyCodes.REST_OK);
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ String marker = command.getRequiredParameter(0);
+ invocationRecord.set(MARKER_KEY, marker);
+ sendReply(session, marker);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/RetrCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/RetrCommandHandler.java
new file mode 100644
index 0000000..b634cbe
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/RetrCommandHandler.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.Assert;
+
+/**
+ * CommandHandler for the RETR (Retrieve) command. Return the configured file contents on the data
+ * connection, along with two replies on the control connection: a reply code of 150 and
+ * another of 226. By default, return an empty file (i.e., a zero-length byte[]). You can
+ * customize the returned file contents by setting the <code>fileContents</code> property,
+ * specified either as a String or as a byte array.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #PATHNAME_KEY} ("pathname") - the pathname of the file submitted on the invocation (the first command parameter)
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class RetrCommandHandler extends AbstractStubDataCommandHandler implements CommandHandler {
+
+ public static final String PATHNAME_KEY = "pathname";
+
+ private byte[] fileContents = new byte[0];
+
+ /**
+ * Create new uninitialized instance
+ */
+ public RetrCommandHandler() {
+ }
+
+ /**
+ * Create new instance using the specified fileContents
+ *
+ * @param fileContents - the file contents
+ * @throws org.mockftpserver.core.util.AssertFailedException
+ * - if the fileContents is null
+ */
+ public RetrCommandHandler(String fileContents) {
+ setFileContents(fileContents);
+ }
+
+ /**
+ * Create new instance using the specified fileContents
+ *
+ * @param fileContents - the file contents
+ * @throws org.mockftpserver.core.util.AssertFailedException
+ * - if the fileContents is null
+ */
+ public RetrCommandHandler(byte[] fileContents) {
+ setFileContents(fileContents);
+ }
+
+ /**
+ * @see org.mockftpserver.stub.command.AbstractStubDataCommandHandler#beforeProcessData(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session, org.mockftpserver.core.command.InvocationRecord)
+ */
+ protected void beforeProcessData(Command command, Session session, InvocationRecord invocationRecord) throws Exception {
+ String filename = command.getRequiredParameter(0);
+ invocationRecord.set(PATHNAME_KEY, filename);
+ }
+
+ /**
+ * @see org.mockftpserver.stub.command.AbstractStubDataCommandHandler#processData(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session, org.mockftpserver.core.command.InvocationRecord)
+ */
+ protected void processData(Command command, Session session, InvocationRecord invocationRecord) {
+ LOG.info("Sending " + fileContents.length + " bytes");
+ session.sendData(fileContents, fileContents.length);
+ }
+
+ /**
+ * Set the file contents to return from subsequent command invocations
+ *
+ * @param fileContents - the fileContents to set
+ * @throws org.mockftpserver.core.util.AssertFailedException
+ * - if the fileContents is null
+ */
+ public void setFileContents(String fileContents) {
+ Assert.notNull(fileContents, "fileContents");
+ setFileContents(fileContents.getBytes());
+ }
+
+ /**
+ * Set the file contents to return from subsequent command invocations
+ *
+ * @param fileContents - the file contents
+ * @throws org.mockftpserver.core.util.AssertFailedException
+ * - if the fileContents is null
+ */
+ public void setFileContents(byte[] fileContents) {
+ Assert.notNull(fileContents, "fileContents");
+ this.fileContents = fileContents;
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/RmdCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/RmdCommandHandler.java
new file mode 100644
index 0000000..fbec38e
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/RmdCommandHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the RMD (Remove Working Directory) command. Send back a reply code of 250.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #PATHNAME_KEY} ("pathname") - the pathname of the directory submitted on the invocation (the first command parameter)
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class RmdCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String PATHNAME_KEY = "pathname";
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public RmdCommandHandler() {
+ setReplyCode(ReplyCodes.RMD_OK);
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ invocationRecord.set(PATHNAME_KEY, command.getRequiredParameter(0));
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/RnfrCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/RnfrCommandHandler.java
new file mode 100644
index 0000000..7dac272
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/RnfrCommandHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the RNFR (Rename From) command. Send back a reply code of 350.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #PATHNAME_KEY} ("pathname") - the pathname of the file submitted on the invocation (the first command parameter)
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class RnfrCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String PATHNAME_KEY = "pathname";
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public RnfrCommandHandler() {
+ setReplyCode(ReplyCodes.RNFR_OK);
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ invocationRecord.set(PATHNAME_KEY, command.getRequiredParameter(0));
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/RntoCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/RntoCommandHandler.java
new file mode 100644
index 0000000..db8c84c
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/RntoCommandHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the RNTO (Rename To) command. Send back a reply code of 250.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #PATHNAME_KEY} ("pathname") - the pathname of the file submitted on the invocation (the first command parameter)
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class RntoCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String PATHNAME_KEY = "pathname";
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public RntoCommandHandler() {
+ setReplyCode(ReplyCodes.RNTO_OK);
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ invocationRecord.set(PATHNAME_KEY, command.getRequiredParameter(0));
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/SiteCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/SiteCommandHandler.java
new file mode 100644
index 0000000..7632cd2
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/SiteCommandHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the SITE (Site Parameters) command. Send back a reply code of 200.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #PARAMETERS_KEY} ("parameters") - the site parameters submitted on the invocation (the first command parameter)
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class SiteCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String PARAMETERS_KEY = "parameters";
+
+ /**
+ * Constructor. Initiate the replyCode.
+ */
+ public SiteCommandHandler() {
+ setReplyCode(ReplyCodes.SITE_OK);
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ invocationRecord.set(PARAMETERS_KEY, command.getRequiredParameter(0));
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/SmntCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/SmntCommandHandler.java
new file mode 100644
index 0000000..1d02bd8
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/SmntCommandHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the SMNT (Structure Mount) command. Send back a reply code of 250.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #PATHNAME_KEY} ("pathname") - the pathname of the directory submitted on the invocation (the first command parameter)
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class SmntCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String PATHNAME_KEY = "pathname";
+
+ /**
+ * Constructor. Initiate the replyCode.
+ */
+ public SmntCommandHandler() {
+ setReplyCode(ReplyCodes.SMNT_OK);
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ invocationRecord.set(PATHNAME_KEY, command.getRequiredParameter(0));
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/StatCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/StatCommandHandler.java
new file mode 100644
index 0000000..9b664a5
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/StatCommandHandler.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the STAT (Status) command. By default, return empty status information,
+ * along with a reply code of 211 if no pathname parameter is specified or 213 if a
+ * pathname is specified. You can customize the returned status information by setting
+ * the <code>status</code> property.
+ * <p>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #PATHNAME_KEY} ("pathname") - the pathname of the directory (or file) submitted on the
+ * invocation (the first command parameter); this parameter is optional, so the value may be null.
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ * @see SystCommandHandler
+ */
+public class StatCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String PATHNAME_KEY = "pathname";
+
+ private String status = "";
+
+ /**
+ * Constructor.
+ */
+ public StatCommandHandler() {
+ // Do not initialize replyCode -- will be set dynamically
+ }
+
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ String pathname = command.getOptionalString(0);
+ invocationRecord.set(PATHNAME_KEY, pathname);
+
+ // Only use dynamic reply code if the replyCode property was NOT explicitly set
+ if (replyCode == 0) {
+ int code = (pathname == null) ? ReplyCodes.STAT_SYSTEM_OK : ReplyCodes.STAT_FILE_OK;
+ sendReply(session, code, replyMessageKey, replyText, new String[]{status});
+ } else {
+ sendReply(session, status);
+ }
+ }
+
+ /**
+ * Set the contents of the status to send back as the reply text for this command
+ *
+ * @param status - the status
+ */
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/StorCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/StorCommandHandler.java
new file mode 100644
index 0000000..7de7c84
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/StorCommandHandler.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the STOR (Store) command. Send back two replies on the control connection: a
+ * reply code of 150 and another of 226.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #PATHNAME_KEY} ("pathname") - the pathname of the directory submitted on the invocation (the first command parameter)
+ * <li>{@link #FILE_CONTENTS_KEY} ("fileContents") - the file contents (<code>byte[]</code>) sent on the data connection
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class StorCommandHandler extends AbstractStorCommandHandler {
+
+ /**
+ * @see org.mockftpserver.stub.command.AbstractStubDataCommandHandler#beforeProcessData(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session, org.mockftpserver.core.command.InvocationRecord)
+ */
+ protected void beforeProcessData(Command command, Session session, InvocationRecord invocationRecord) throws Exception {
+ String filename = command.getRequiredParameter(0);
+ invocationRecord.set(PATHNAME_KEY, filename);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/StouCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/StouCommandHandler.java
new file mode 100644
index 0000000..50f52d1
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/StouCommandHandler.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the STOU (Store Unique) command. Send back two replies on the control connection: a
+ * reply code of 150 and another of 226. The text accompanying the final reply (226) is the
+ * unique filename, which is "" by default. You can customize the returned filename by setting
+ * the <code>filename</code> property.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #FILE_CONTENTS_KEY} ("fileContents") - the file contents (<code>byte[]</code>) sent on the data connection
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class StouCommandHandler extends AbstractStorCommandHandler {
+
+ private static final String FINAL_REPLY_TEXT_KEY = "226.WithFilename";
+
+ private String filename = "";
+
+ /**
+ * Override the default implementation to send a custom reply text that includes the STOU response filename
+ *
+ * @see org.mockftpserver.stub.command.AbstractStubDataCommandHandler#sendFinalReply(org.mockftpserver.core.session.Session)
+ */
+ protected void sendFinalReply(Session session) {
+ final String[] ARGS = {filename};
+ sendReply(session, ReplyCodes.TRANSFER_DATA_FINAL_OK, FINAL_REPLY_TEXT_KEY, null, ARGS);
+ }
+
+ /**
+ * Set the filename returned with the final reply of the STOU command
+ *
+ * @param filename - the filename
+ */
+ public void setFilename(String filename) {
+ this.filename = filename;
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/StruCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/StruCommandHandler.java
new file mode 100644
index 0000000..d1cd559
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/StruCommandHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the STRU (File Structure) command. Send back a reply code of 200.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #FILE_STRUCTURE_KEY} ("fileStructure") - the file structure code submitted on the invocation (the first command parameter)
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class StruCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String FILE_STRUCTURE_KEY = "fileStructure";
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public StruCommandHandler() {
+ setReplyCode(ReplyCodes.STRU_OK);
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ invocationRecord.set(FILE_STRUCTURE_KEY, command.getRequiredParameter(0));
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/SystCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/SystCommandHandler.java
new file mode 100644
index 0000000..2744633
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/SystCommandHandler.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.Assert;
+
+/**
+ * CommandHandler for the SYST (System) command. Send back a reply code of 215. By default,
+ * return "WINDOWS" as the system name. You can customize the returned name by
+ * setting the <code>systemName</code> property.
+ * <p/>
+ * See the available system names listed in the Assigned Numbers document
+ * (<a href="http://www.ietf.org/rfc/rfc943">RFC 943</a>).
+ * <p/>
+ * Each invocation record stored by this CommandHandler contains no data elements.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class SystCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ private String systemName = "WINDOWS";
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public SystCommandHandler() {
+ setReplyCode(ReplyCodes.SYST_OK);
+ }
+
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ sendReply(session, quotes(systemName));
+ }
+
+ /**
+ * Set the systemName String to be returned by this command
+ *
+ * @param systemName - the systemName
+ */
+ public void setSystemName(String systemName) {
+ Assert.notNull(systemName, "systemName");
+ this.systemName = systemName;
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/TypeCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/TypeCommandHandler.java
new file mode 100644
index 0000000..28f3af5
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/TypeCommandHandler.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the TYPE command. Send back a reply code of 200.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #TYPE_INFO_KEY} ("typeInfo") - the type information submitted on the
+ * invocation, which is a String[2] containing the first two command parameter values.
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class TypeCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String TYPE_INFO_KEY = "typeInfo";
+
+ /**
+ * Constructor. Initialize the replyCode.
+ */
+ public TypeCommandHandler() {
+ setReplyCode(ReplyCodes.TYPE_OK);
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ LOG.debug("Processing TYPE: " + command);
+ String type = command.getRequiredParameter(0);
+ String format = command.getOptionalString(1);
+ invocationRecord.set(TYPE_INFO_KEY, new String[]{type, format});
+ sendReply(session);
+ }
+
+}
diff --git a/tags/2.5/src/main/java/org/mockftpserver/stub/command/UserCommandHandler.java b/tags/2.5/src/main/java/org/mockftpserver/stub/command/UserCommandHandler.java
new file mode 100644
index 0000000..a38ce5c
--- /dev/null
+++ b/tags/2.5/src/main/java/org/mockftpserver/stub/command/UserCommandHandler.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+
+/**
+ * CommandHandler for the USER command. The <code>passwordRequired</code> property defaults to true,
+ * indicating that a password is required following the user name. If true, this command handler
+ * returns a reply of 331. If false, return a reply of 230.
+ * <p/>
+ * Each invocation record stored by this CommandHandler includes the following data element key/values:
+ * <ul>
+ * <li>{@link #USERNAME_KEY} ("username") - the user name submitted on the invocation (the first command parameter)
+ * </ul>
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class UserCommandHandler extends AbstractStubCommandHandler implements CommandHandler {
+
+ public static final String USERNAME_KEY = "username";
+
+ private boolean passwordRequired = true;
+
+ /**
+ * Constructor.
+ */
+ public UserCommandHandler() {
+ // Do not initialize replyCode -- will be set dynamically
+ }
+
+ /**
+ * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, org.mockftpserver.core.session.Session)
+ */
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ invocationRecord.set(USERNAME_KEY, command.getRequiredParameter(0));
+
+ // Only use dynamic reply code if the replyCode property was NOT explicitly set
+ if (replyCode == 0) {
+ int code = (passwordRequired) ? ReplyCodes.USER_NEED_PASSWORD_OK : ReplyCodes.USER_LOGGED_IN_OK;
+ sendReply(session, code, replyMessageKey, replyText, null);
+ } else {
+ sendReply(session);
+ }
+ }
+
+ /**
+ * Return true if a password is required at login. See {@link #setPasswordRequired(boolean)}.
+ *
+ * @return the passwordRequired flag
+ */
+ public boolean isPasswordRequired() {
+ return passwordRequired;
+ }
+
+ /**
+ * Set true to indicate that a password is required. If true, this command handler returns a reply
+ * of 331. If false, return a reply of 230.
+ *
+ * @param passwordRequired - is a password required for login
+ */
+ public void setPasswordRequired(boolean passwordRequired) {
+ this.passwordRequired = passwordRequired;
+ }
+
+}
diff --git a/tags/2.5/src/main/resources/ReplyText.properties b/tags/2.5/src/main/resources/ReplyText.properties
new file mode 100644
index 0000000..505543d
--- /dev/null
+++ b/tags/2.5/src/main/resources/ReplyText.properties
@@ -0,0 +1,131 @@
+# Copyright 2008 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#-------------------------------------------------------------------------------
+# Mapping of reply code -> reply text
+#-------------------------------------------------------------------------------
+110=Restart marker reply.
+120=Service ready in nnn minutes.
+125=Data connection already open; transfer starting.
+150=File status okay; about to open data connection.
+200=Command okay.
+202=Command not implemented, superfluous at this site.
+211={0}.
+212={0}.
+213={0}.
+214={0}.
+215={0} system type.
+220=Service ready for new user. (MockFtpServer 2.5; see http://mockftpserver.sourceforge.net)
+221=Service closing control connection.
+225=Data connection open; no transfer in progress.
+226=Closing data connection. Requested file action successful.
+226.WithFilename=Closing data connection. Requested file action successful. Filename={0}.
+227=Entering Passive Mode {0}.
+229=Entering Extended Passive Mode (|||{0}|)
+230=User logged in, proceed.
+250=Requested file action okay, completed.
+257={0} created.
+331=User name okay, need password.
+332=Need account for login.
+350=Requested file action pending further information.
+421=Service not available, closing control connection.
+# This may be a reply to any command if the service knows it must shut down.
+425=Can't open data connection.
+426=Connection closed; transfer aborted.
+450=Requested file action not taken.
+# File unavailable (e.g., file busy).
+451=Requested action aborted: local error in processing.
+452=Requested action not taken.
+# Insufficient storage space in system.
+500=Syntax error, command unrecognized.
+# This may include errors such as command line too long.
+501=Syntax error in parameters or arguments.
+502=Command not implemented: {0}.
+503=Bad sequence of commands.
+504=Command not implemented for that parameter.
+530=Not logged in.
+532=Need account for storing files.
+550=File not found or not accessible: {0}.
+# File unavailable (e.g., file not found, no access).
+551=Requested action aborted: page type unknown.
+552=Requested file action aborted.
+# Exceeded storage allocation (for current directory or dataset).
+553=Requested action not taken for {0}
+# File name not allowed.
+
+#-------------------------------------------------------------------------------
+# FTP Command-Specific Reply Messages
+#-------------------------------------------------------------------------------
+abor=ABOR completed.
+acct=ACCT completed for {0}.
+allo=ALLO completed.
+appe=Created or appended to file {0}.
+cdup=CDUP completed. New directory is {0}.
+cwd=CWD completed. New directory is {0}.
+dele="{0}" deleted.
+eprt=EPRT completed.
+epsv=Entering Extended Passive Mode (|||{0}|)
+help={0}.
+help.noHelpTextDefined=No help text has been defined for [{0}]
+mkd="{0}" created.
+mode=MODE completed.
+noop=NOOP completed.
+pass=User logged in, proceed.
+pass.needAccount=Need account for login.
+pass.loginFailed=Not logged in.
+pasv=({0})
+port=PORT completed.
+pwd="{0}" is current directory.
+quit=Service closing control connection.
+rein=REIN completed.
+rest=REST completed.
+rmd="{0}" removed.
+rnfr=Requested file action pending further information.
+rnto=Rename from {0} to {1} completed.
+site=SITE completed.
+smnt=SMNT completed.
+stat={0}.
+stou=Created file {0}.
+stor=Created file {0}.
+stru=STRU completed.
+syst="{0}"
+type=TYPE completed.
+user.loggedIn=User logged in, proceed.
+user.needPassword=User name okay, need password.
+
+#-------------------------------------------------------------------------------
+# FileSystem Messages
+#-------------------------------------------------------------------------------
+filesystem.alreadyExists=The path [{0}] already exists.
+filesystem.parentDirectoryDoesNotExist=The parent directory [{0}] does not exist.
+filesystem.doesNotExist=[{0}] does not exist.
+filesystem.isDirectory=[{0}] is a directory.
+filesystem.isFile=[{0}] is a file.
+filesystem.isNotADirectory=[{0}] is not a directory or does not exist.
+filesystem.isNotAFile=[{0}] is not a file or does not exist.
+filesystem.cannotRead=The current user does not have read permission for [{0}].
+filesystem.cannotWrite=The current user does not have write permission for [{0}].
+filesystem.cannotExecute=The current user does not have execute permission for [{0}].
+filesystem.directoryIsNotEmpty=The [{0}] directory is not empty.
+filesystem.renameFailed=The rename to [{0}] has failed.
+filesystem.pathIsNotValid=The path [{0}] is not valid.
+filesystem.currentDirectoryNotSet=The current directory has not been set.
+
+#-------------------------------------------------------------------------------
+# Other Common Messages
+#-------------------------------------------------------------------------------
+login.userAccountNotValid=UserAccount missing or invalid for user [{0}]
+login.homeDirectoryNotValid=The homeDirectory configured for user [{0}] is not a valid directory: [{1}]
+
+internalError=Internal error: {0} {1} \ No newline at end of file
diff --git a/tags/2.5/src/site/apt/fakeftpserver-features.apt b/tags/2.5/src/site/apt/fakeftpserver-features.apt
new file mode 100644
index 0000000..f02904b
--- /dev/null
+++ b/tags/2.5/src/site/apt/fakeftpserver-features.apt
@@ -0,0 +1,113 @@
+ --------------------------------------------------
+ FakeFtpServer Features and Limitations
+ --------------------------------------------------
+
+FakeFtpServer Features
+~~~~~~~~~~~~~~~~~~~~~~
+
+ * Standalone dummy FTP server. Run either within the same JVM as test code or in a different JVM.
+
+ * Implements common FTP server commands.
+
+ * Works out of the box with reasonable and expected behavior. Can simulate most mainline success and error scenarios.
+
+ * In most cases, requires little or no knowledge or understanding of FTP server commands and reply codes.
+
+ * Provides a simulated server file system, including support for file and directory permissions and owner and
+ group authorization based on Unix. This file system can be populated at startup (or thereafter) with
+ directories and files (including arbitrary content) to be retrieved by an FTP client. Any files sent to the server
+ by an FTP client exist within that file system as well, and can be accessed through the file system API, or
+ can even be subsequently retrieved by an FTP client.
+
+ * Allows defining the set of user accounts that control which users can login to the FTP server, and their home
+ (default) directories.
+
+ * Supports active and passive mode data transfers.
+
+ * Use a dynamically chosen free port number for the server control port instead of using the default (21)
+ or hard-coding some other value (set the serverControlPort property of the server to 0).
+
+ * Supports extended address (IPv6) data transfers (RFC2428)
+
+ * Fully supports configuration within the <<Spring Framework>> or other dependency-injection container.
+
+ * Can be used to test FTP client code written in any language
+
+FTP Scenarios Supported by FakeFtpServer
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Some of the mainline success scenarios that you can simulate with <<FakeFtpServer>> include:
+
+ * Login (USER/PASS): with password, or when no password is required
+
+ * Retrieve existing file (RETR) from the server
+
+ * Send file to the server (STOR,STOU,APPE)
+
+ * List of file entries (LIST) and list of filenames (NLST)
+
+ * Print current working directory (PWD)
+
+ * Change current working directory (CWD)
+
+ * Rename an existing file (RNFR/RNTO)
+
+ * Delete an existing file (DELE)
+
+ * Create directory (MKD)
+
+ * Remove directory (RMD)
+
+ * Both active and passive mode (PASV) data transfers
+
+ * Extended Address (IPv6) data transfers (EPRT and EPSV commands)
+
+ Some of the error scenarios that you can simulate with <<FakeFtpServer>> include:
+
+ * Failed login (USER/PASS): no such user, wrong password
+
+ * Invalid client requests: missing required parameter, not logged in
+
+ * Failed retrieve (RETR): no such file, missing required access permissions for the current user
+
+ * Failed send (STOR,STOU,APPE): missing required access permissions for the current user
+
+ * Failed change current working directory (CWD): no such directory, missing required access permissions for the current user
+
+ * Failed delete an existing file (DELE): file does not exist, missing required access permissions for the current user
+
+ * Failed rename (RNFR/RNTO): no such file, missing required access permissions for the current user
+
+ * Failed create directory (MKD): parent directory does not exist, directory already exists, missing required access permissions for the current user
+
+ * Failed remove directory (RMD): no such directory, directory not empty, missing required access permissions for the current user
+
+
+FakeFtpServer Limitations
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Not all FTP features, error scenarios and reply codes can be simulated using <<FakeFtpServer>>. Features and
+ scenarios not supported include:
+
+ * Leaving the data connection open across multiple client requests.
+
+ * Transmission mode other than 'Stream'. The STRU command is implemented but has no effect (NOOP).
+
+ * Data Types other than ASCII and IMAGE (binary).
+
+ * Vertical Format Control other than the default (NON PRINT).
+
+ * Record Structure and Page Structure. The STRU command is implemented but has no effect (NOOP).
+
+ * Error Recovery and Restart. The REST command is implemented but has no effect (NOOP).
+
+ * Structure Mount. The SMNT command is implemented but has no effect (NOOP).
+
+ * Abort. The ABOR command is implemented but has no effect (NOOP).
+
+ * Allocate. The ALLO command is implemented but has no effect (NOOP).
+
+ []
+
+ For unsupported features, error scenarios and reply codes, consider using <<StubFtpServer>> instead, which
+ provides a lower-level abstraction and finer control over exact server reply codes and responses.
diff --git a/tags/2.5/src/site/apt/fakeftpserver-filesystems.apt b/tags/2.5/src/site/apt/fakeftpserver-filesystems.apt
new file mode 100644
index 0000000..aaa0069
--- /dev/null
+++ b/tags/2.5/src/site/apt/fakeftpserver-filesystems.apt
@@ -0,0 +1,235 @@
+ --------------------------------------------------
+ FakeFtpServer Filesystems
+ --------------------------------------------------
+
+FakeFtpServer Filesystems
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ <<FakeFtpServer>> provides a simulated server file system, including support for file and directory permissions
+ and owner and group authorization based on Unix. This file system can be populated at startup (or thereafter) with
+ directories and files (including arbitrary content) to be retrieved by an FTP client. Any files sent to the server
+ by an FTP client exist within that file system as well, and can be accessed through the file system API, or
+ can even be subsequently retrieved by an FTP client.
+
+ The filesystem abstraction is accessed through the <<<FileSystem>>> interface in the
+ <<<org.mockftpserver.fake.filesystem>>> package. Two implementations of this interface are provided:
+ <<<WindowsFakeFileSystem>>> and <<<UnixFakeFileSystem>>>. They both manage the files and directories in memory,
+ simulating a real file system. You are also free to implement your own <<<FileSystem>>> implementation.
+
+ Note that both <<<WindowsFakeFileSystem>>> and <<<UnixFakeFileSystem>>> are <virtual> file systems, and do
+ not depend on the <real> operating systems or file systems on which <<FakeFtpServer>> is running. In other
+ words, you can configure and run a <<FakeFtpServer>> with a <<<WindowsFakeFileSystem>>> on top of a <real>
+ Unix system, or run a <<FakeFtpServer>> with a <<<UnixFakeFileSystem>>> on top of a <real> Windows system.
+
+ See the javadoc for these classes for more information.
+
+
+* WindowsFakeFileSystem
+~~~~~~~~~~~~~~~~~~~~~~~
+
+ <<WindowsFakeFileSystem>> is an implementation of the <<<FileSystem>>> interface that simulates a Microsoft
+ Windows file system. The rules for file and directory names include:
+
+ * Filenames are case-insensitive
+
+ * Either forward slashes (/) or backward slashes (\) are valid path separators (but are normalized to '\')
+
+ * An absolute path starts with a drive specifier (e.g. 'a:' or 'c:') followed by '\' or '/',
+ or else it starts with "\\"</li>
+
+
+* UnixFakeFileSystem
+~~~~~~~~~~~~~~~~~~~~
+
+ <<UnixFakeFileSystem>> is an implementation of the <<<FileSystem>>> interface that simulates a Unix
+ file system. The rules for file and directory names include:
+
+ * Filenames are case-sensitive
+
+ * Forward slashes (/) are the only valid path separators
+
+
+* WindowsFakeFileSystem and UnixFakeFileSystem: Common Behavior and Configuration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Both <<<WindowsFakeFileSystem>>> and <<<UnixFakeFileSystem>>> are subclasses of <<<AbstractFakeFileSystem>>>. They
+ manage the files and directories in memory, simulating a real file system.
+
+ If the <createParentDirectoriesAutomatically> property is set to <true>,
+ then creating a directory or file will automatically create any parent directories (recursively)
+ that do not already exist. If <false>, then creating a directory or file throws an
+ exception if its parent directory does not exist. This value defaults to <true>.
+
+ The <directoryListingFormatter> property holds an instance of <<DirectoryListingFormatter>>,
+ used by the <formatDirectoryListing> method to format directory listings in a
+ filesystem-specific manner. This property is initialized by concrete subclasses.
+
+
+* File Permissions, Owners and Groups
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Each <file> or <directory> entry within a <<<FileSystem>>> has associated <owner>, <group> and <permissions>
+ attributes. All of these attributes are optional. If none are specified for a file or directory, then full
+ access by all users is the default.
+
+ If, however, these values are specified for a filesystem entry, then they affect whether a file can be created,
+ read, written or deleted, and whether a directory can be created, listed or deleted.
+
+ This approach for access control is conceptually (and somewhat loosely) based on the Unix file system, but
+ don't expect a comprehensive implementation fully matching Unix's capabilities.
+
+
+** Permissions
+~~~~~~~~~~~~~~
+
+ The permissions for a file or directory entry in the filesystem are represented by a 9-character string of
+ the form "rwxrwxrwx", consisting of three "rwx" triples. Each triple indicates the READ ("r"), WRITE ("w") and
+ EXECUTE ("x") permissions for a specific set of users. Each position can alternatively contain a "-" to
+ indicate no READ/WRITE/EXECUTE access, depending on its position.
+
+ The first "rwx" triple indicates the READ, WRITE and EXECUTE permissions for the owner of the file. The
+ second triple indicates the permissions for the group associated with the file. The third triple
+ indicates the permissions for the rest of the world.
+
+ For example, the permissions string "rwx--xrw-" is interpreted to mean that users have READ/WRITE/EXECUTE access,
+ the group has only EXECUTE, and the world has only READ and WRITE.
+
+ There are plenty of good tutorials and references for understanding Unix file permissions, including
+ {{{http://www.dartmouth.edu/~rc/help/faq/permissions.html}this one}}.
+
+ The <<<Permissions>>> class represents and encapsulates the read/write/execute permissions for a file or
+ directory. Its constructor takes a 9-character "rwx" String as described above.
+
+ The <<<AbstractFileSystemEntry>>> contains a <permissions> attribute, so that every file and directory in the
+ file system can be assigned a unique set of permissions from a <<<Permissions>>> object. There is also a
+ <<<setPermissionsFromString()>>> convenience setter that allows setting the permissions directly from a String.
+
+
+** FileSystem Access Rules
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+*** When Are READ, WRITE or EXECUTE Access Required?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ If the <permissions> are configured for a file or directory within the <<<FileSystem>>>, then
+ those permissions affect whether and how that file/directory can be accessed.
+ Here are the rules for applying permissions for file access:
+
+*------------------------*-------------------------------------------------------------------*
+| <<Operation>> | <<Required Permissions>> |
+*------------------------*-------------------------------------------------------------------*
+| Create a new file | EXECUTE access to the directory and WRITE access to the directory |
+*------------------------*-------------------------------------------------------------------*
+| Read a file | EXECUTE access to the directory and READ access to the file |
+*------------------------*-------------------------------------------------------------------*
+| Write a file | EXECUTE access to the directory and WRITE access to the file |
+*------------------------*-------------------------------------------------------------------*
+| Delete a file | WRITE access to the directory |
+*------------------------*-------------------------------------------------------------------*
+| Rename a file | READ access to the FROM file and WRITE access to the directory |
+*------------------------*-------------------------------------------------------------------*
+| Create a directory | WRITE and EXECUTE acccess to the parent directory |
+*------------------------*-------------------------------------------------------------------*
+| List a directory | READ acccess to the directory/file |
+*------------------------*-------------------------------------------------------------------*
+| CD to a directory | EXECUTE acccess to the directory |
+*------------------------*-------------------------------------------------------------------*
+| Delete a directory | WRITE acccess to the parent directory |
+*------------------------*-------------------------------------------------------------------*
+
+*** How Do Owner and Group Affect Access?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Each file and directory in the filesystem (subclass of <<<AbstractFileSystemEntry>>>) contains <owner>
+ and <group> attributes. These attributes are optional.
+
+ If the <owner> is configured for a file/directory, AND the <permissions> are configured as well,
+ then the <<owner>> triple from the <permissions> are applied if and only if the <<<UserAccount>>> for the
+ currently logged in FTP user (client) matches the <owner> configured for the file/directory.
+
+ Similarly, if the <group> is configured for a file/directory, AND the <permissions> are configured as well,
+ then the <<group>> triple from the <permissions> are applied if and only if <groups> configured for the
+ <<<UserAccount>>> for the currently logged in FTP user (client) contain the <group> configured for the file/directory.
+
+ Otherwise, the <<world>> triple from the <permissions> are applied.
+
+* Example Code
+~~~~~~~~~~~~~~
+
+ This example illustrates setting the permissions, owner and group for directories and files within the
+ <<<FakeFtpServer>>> filesystem. In this case, the filesystem is an instance of <<<WindowsFakeFileSystem>>>,
+ but the code would be almost exactly the same for <<<UnixFakeFileSystem>>> as well.
+
++------------------------------------------------------------------------------
+ final String USER1 = "joe";
+ final String USER2 = "mary";
+ final String GROUP = "dev";
+ final String CONTENTS = "abcdef 1234567890";
+
+ FileSystem fileSystem = new WindowsFakeFileSystem();
+ DirectoryEntry directoryEntry1 = new DirectoryEntry("c:\\");
+ directoryEntry1.setPermissions(new Permissions("rwxrwx---"));
+ directoryEntry1.setOwner(USER1);
+ directoryEntry1.setGroup(GROUP);
+
+ DirectoryEntry directoryEntry2 = new DirectoryEntry("c:\\data");
+ directoryEntry2.setPermissions(Permissions.ALL);
+ directoryEntry2.setOwner(USER1);
+ directoryEntry2.setGroup(GROUP);
+
+ FileEntry fileEntry1 = new FileEntry("c:\\data\\file1.txt", CONTENTS);
+ fileEntry1.setPermissionsFromString("rw-rw-rw-");
+ fileEntry1.setOwner(USER1);
+ fileEntry1.setGroup(GROUP);
+
+ FileEntry fileEntry2 = new FileEntry("c:\\data\\run.exe");
+ fileEntry2.setPermissionsFromString("rwxrwx---");
+ fileEntry2.setOwner(USER2);
+ fileEntry2.setGroup(GROUP);
+
+ fileSystem.add(directoryEntry1);
+ fileSystem.add(directoryEntry2);
+ fileSystem.add(fileEntry1);
+ fileSystem.add(fileEntry2);
+
+ FakeFtpServer fakeFtpServer = new FakeFtpServer();
+ fakeFtpServer.setFileSystem(fileSystem);
++------------------------------------------------------------------------------
+
+ Things to note about the above example:
+
+ * The <<<FakeFtpServer>>> instance is configured with a <<<WindowsFakeFileSystem>>> and a "c:\" root
+ directory with a "data" sub-directory containing two files. Permissions and owner/group are specified for
+ both directories and both files.
+
+ * The permissions for the directories are specified using the "permissions" setter, which takes an
+ instance of the <<<Permissions>>> class. The permissions for both files are specified using the
+ "permissionsFromString" shortcut method. Either way is fine -- use whichever method you prefer on
+ both files and directories.
+
+ []
+
+ When you want to retrieve and/or verify the contents of the <<<FakeFtpServer>>> filesystem, you can use
+ the <<<FileSystem#getEntry(String path)>>> method, as shown in the following code.
+
++------------------------------------------------------------------------------
+ DirectoryEntry dirEntry = (DirectoryEntry)fileSystem.getEntry("c:/data");
+
+ FileEntry fileEntry = (FileEntry)fileSystem.getEntry("c:/data/file1.txt");
+
+ FileEntry newFileEntry = (FileEntry)fileSystem.getEntry("c:/data/new.txt");
+ InputStream inputStream = newFileEntry.createInputStream();
+ // read the file contents using inputStream
++------------------------------------------------------------------------------
+
+ See the javadoc for <<<FileSystem>>>, <<<FileEntry>>> and <<<DirectoryEntry>>> for more information
+ on the methods available.
+
+
+** Example Using Spring Configuration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ See the {{{./fakeftpserver-getting-started.html#Spring}FakeFtpServer Getting Started - Spring Configuration}}
+ for an example of how to configure a <<<FakeFtpServer>>> instance and associated filesystem in the
+ {{{http://www.springframework.org/}Spring Framework}}.
+ \ No newline at end of file
diff --git a/tags/2.5/src/site/apt/fakeftpserver-getting-started.apt b/tags/2.5/src/site/apt/fakeftpserver-getting-started.apt
new file mode 100644
index 0000000..c342978
--- /dev/null
+++ b/tags/2.5/src/site/apt/fakeftpserver-getting-started.apt
@@ -0,0 +1,449 @@
+ --------------------------------------------------
+ FakeFtpServer Getting Started
+ --------------------------------------------------
+
+FakeFtpServer - Getting Started
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ <<FakeFtpServer>> is a "fake" implementation of an FTP server. It provides a high-level abstraction for
+ an FTP Server and is suitable for most testing and simulation scenarios. You define a virtual filesystem
+ (internal, in-memory) containing an arbitrary set of files and directories. These files and directories can
+ (optionally) have associated access permissions. You also configure a set of one or more user accounts that
+ control which users can login to the FTP server, and their home (default) directories. The user account is
+ also used when assigning file and directory ownership for new files.
+
+ <<FakeFtpServer>> processes FTP client requests and responds with reply codes and reply messages
+ consistent with its configured file system and user accounts, including file and directory permissions,
+ if they have been configured.
+
+ See the {{{./fakeftpserver-features.html}FakeFtpServer Features and Limitations}} page for more information on
+ which features and scenarios are supported.
+
+ In general the steps for setting up and starting the <<<FakeFtpServer>>> are:
+
+ * Create a new <<<FakeFtpServer>>> instance, and optionally set the server control port (use a value of 0
+ to automatically choose a free port number).
+
+ * Create and configure a <<<FileSystem>>>, and attach to the <<<FakeFtpServer>>> instance.
+
+ * Create and configure one or more <<<UserAccount>>> objects and attach to the <<<FakeFtpServer>>> instance.
+
+ []
+
+ Here is an example showing configuration and starting of an <<FakeFtpServer>> with a single user
+ account and a (simulated) Windows file system, defining a directory containing two files.
+
++------------------------------------------------------------------------------
+FakeFtpServer fakeFtpServer = new FakeFtpServer();
+fakeFtpServer.addUserAccount(new UserAccount("user", "password", "c:\\data"));
+
+FileSystem fileSystem = new WindowsFakeFileSystem();
+fileSystem.add(new DirectoryEntry("c:\\data"));
+fileSystem.add(new FileEntry("c:\\data\\file1.txt", "abcdef 1234567890"));
+fileSystem.add(new FileEntry("c:\\data\\run.exe"));
+fakeFtpServer.setFileSystem(fileSystem);
+
+fakeFtpServer.start();
++------------------------------------------------------------------------------
+
+ If you are running on a system where the default port (21) is already in use or cannot be bound
+ from a user process (such as Unix), you probably need to use a different server control port. Use the
+ <<<FakeFtpServer.setServerControlPort(int serverControlPort)>>> method to use a different port
+ number. If you specify a value of <<<0>>>, then the server will use a free port number. Then call
+ <<<getServerControlPort()>>> AFTER calling <<<start()>>> has been called to determine the actual port
+ number being used. Or, you can pass in a specific port number, such as 9187.
+
+ <<FakeFtpServer>> can be fully configured programmatically or within the
+ {{{http://www.springframework.org/}Spring Framework}} or other dependency-injection container.
+ The {{{#Example}Example Test Using FakeFtpServer}} below illustrates programmatic configuration of
+ <<<FakeFtpServer>>>. Alternatively, the {{{#Spring}Configuration}} section later on illustrates how to use
+ the <Spring Framework> to configure a <<<FakeFtpServer>>> instance.
+
+* {Example} Test Using FakeFtpServer
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ This section includes a simplified example of FTP client code to be tested, and a JUnit
+ test for it that programmatically configures and uses <<FakeFtpServer>>.
+
+** FTP Client Code
+~~~~~~~~~~~~~~~~~~
+
+ The following <<<RemoteFile>>> class includes a <<<readFile()>>> method that retrieves a remote
+ ASCII file and returns its contents as a String. This class uses the <<<FTPClient>>> from the
+ {{{http://commons.apache.org/net/}Apache Commons Net}} framework.
+
++------------------------------------------------------------------------------
+public class RemoteFile {
+
+ public static final String USERNAME = "user";
+ public static final String PASSWORD = "password";
+
+ private String server;
+ private int port;
+
+ public String readFile(String filename) throws IOException {
+
+ FTPClient ftpClient = new FTPClient();
+ ftpClient.connect(server, port);
+ ftpClient.login(USERNAME, PASSWORD);
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ boolean success = ftpClient.retrieveFile(filename, outputStream);
+ ftpClient.disconnect();
+
+ if (!success) {
+ throw new IOException("Retrieve file failed: " + filename);
+ }
+ return outputStream.toString();
+ }
+
+ public void setServer(String server) {
+ this.server = server;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ // Other methods ...
+}
++------------------------------------------------------------------------------
+
+** JUnit Test For FTP Client Code Using FakeFtpServer
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ The following <<<RemoteFileTest>>> class includes a couple of JUnit tests that use
+ <<FakeFtpServer>>.
+
++------------------------------------------------------------------------------
+import org.mockftpserver.fake.filesystem.FileEntry;
+import org.mockftpserver.fake.filesystem.FileSystem;
+import org.mockftpserver.fake.filesystem.UnixFakeFileSystem;
+import org.mockftpserver.fake.FakeFtpServer;
+import org.mockftpserver.fake.UserAccount;
+import org.mockftpserver.stub.example.RemoteFile;
+import org.mockftpserver.test.AbstractTest;
+import java.io.IOException;
+import java.util.List;
+
+public class RemoteFileTest extends AbstractTest {
+
+ private static final String HOME_DIR = "/";
+ private static final String FILE = "/dir/sample.txt";
+ private static final String CONTENTS = "abcdef 1234567890";
+
+ private RemoteFile remoteFile;
+ private FakeFtpServer fakeFtpServer;
+
+ public void testReadFile() throws Exception {
+ String contents = remoteFile.readFile(FILE);
+ assertEquals("contents", CONTENTS, contents);
+ }
+
+ public void testReadFileThrowsException() {
+ try {
+ remoteFile.readFile("NoSuchFile.txt");
+ fail("Expected IOException");
+ }
+ catch (IOException expected) {
+ // Expected this
+ }
+ }
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ fakeFtpServer = new FakeFtpServer();
+ fakeFtpServer.setServerControlPort(0); // use any free port
+
+ FileSystem fileSystem = new UnixFakeFileSystem();
+ fileSystem.add(new FileEntry(FILE, CONTENTS));
+ fakeFtpServer.setFileSystem(fileSystem);
+
+ UserAccount userAccount = new UserAccount(RemoteFile.USERNAME, RemoteFile.PASSWORD, HOME_DIR);
+ fakeFtpServer.addUserAccount(userAccount);
+
+ fakeFtpServer.start();
+ int port = fakeFtpServer.getServerControlPort();
+
+ remoteFile = new RemoteFile();
+ remoteFile.setServer("localhost");
+ remoteFile.setPort(port);
+ }
+
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ fakeFtpServer.stop();
+ }
+}
++------------------------------------------------------------------------------
+
+ Things to note about the above test:
+
+ * The <<<FakeFtpServer>>> instance is created and started in the <<<setUp()>>> method and
+ stopped in the <<<tearDown()>>> method, to ensure that it is stopped, even if the test fails.
+
+ * The server control port is set to 0 using <<<fakeFtpServer.setServerControlPort(PORT)>>>.
+ This means it will dynamically choose a free port. This is necessary if you are running on a
+ system where the default port (21) is already in use or cannot be bound from a user process (such as Unix).
+
+ * The <<<UnixFakeFileSystem>>> filesystem is configured and attached to the <<<FakeFtpServer>>> instance
+ in the <<<setUp()>>> method. That includes creating a predefined <<<"/dir/sample.txt">>> file with the
+ specified file contents. The <<<UnixFakeFileSystem>>> has a <<<createParentDirectoriesAutomatically>>>
+ attribute, which defaults to <<<true>>>, meaning that parent directories will be created automatically,
+ as necessary. In this case, that means that the <<<"/">>> and <<<"/dir">>> parent directories will be created,
+ even though not explicitly specified.
+
+ * A single <<<UserAccount>>> with the specified username, password and home directory is configured and
+ attached to the <<<FakeFtpServer>>> instance in the <<<setUp()>>> method. That configured user ("user")
+ is the only one that will be able to sucessfully log in to the <<<FakeFtpServer>>>.
+
+
+* {Spring} Configuration
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+ You can easily configure a <<<FakeFtpServer>>> instance in the
+ {{{http://www.springframework.org/}Spring Framework}} or another, similar dependency-injection container.
+
+** Simple Spring Configuration Example
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ The following example shows a <Spring> configuration file for a simple <<<FakeFtpServer>>> instance.
+
++------------------------------------------------------------------------------
+<?xml version="1.0" encoding="UTF-8"?>
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
+
+ <bean id="fakeFtpServer" class="org.mockftpserver.fake.FakeFtpServer">
+ <property name="serverControlPort" value="9981"/>
+ <property name="systemName" value="UNIX"/>
+ <property name="userAccounts">
+ <list>
+ <bean class="org.mockftpserver.fake.UserAccount">
+ <property name="username" value="joe"/>
+ <property name="password" value="password"/>
+ <property name="homeDirectory" value="/"/>
+ </bean>
+ </list>
+ </property>
+
+ <property name="fileSystem">
+ <bean class="org.mockftpserver.fake.filesystem.UnixFakeFileSystem">
+ <property name="createParentDirectoriesAutomatically" value="false"/>
+ <property name="entries">
+ <list>
+ <bean class="org.mockftpserver.fake.filesystem.DirectoryEntry">
+ <property name="path" value="/"/>
+ </bean>
+ <bean class="org.mockftpserver.fake.filesystem.FileEntry">
+ <property name="path" value="/File.txt"/>
+ <property name="contents" value="abcdefghijklmnopqrstuvwxyz"/>
+ </bean>
+ </list>
+ </property>
+ </bean>
+ </property>
+
+ </bean>
+
+</beans>
++------------------------------------------------------------------------------
+
+ Things to note about the above example:
+
+ * The <<<FakeFtpServer>>> instance has a single user account for username "joe", password "password"
+ and home (default) directory of "/".
+
+ * A <<<UnixFakeFileSystem>>> instance is configured with a predefined directory of "/" and a
+ "/File.txt" file with the specified contents.
+
+ []
+
+ And here is the Java code to load the above <Spring> configuration file and start the
+ configured <<FakeFtpServer>>.
+
++------------------------------------------------------------------------------
+ApplicationContext context = new ClassPathXmlApplicationContext("fakeftpserver-beans.xml");
+FakeFtpServer = (FakeFtpServer) context.getBean("FakeFtpServer");
+FakeFtpServer.start();
++------------------------------------------------------------------------------
+
+
+** Spring Configuration Example With File and Directory Permissions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ The following example shows a <Spring> configuration file for a <<<FakeFtpServer>>> instance that
+ also configures file and directory permissions. This will enable the <<<FakeFtpServer>>> to reply
+ with proper error codes when the logged in user does not have the required permissions to access
+ directories or files.
+
++------------------------------------------------------------------------------
+<?xml version="1.0" encoding="UTF-8"?>
+
+beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
+
+ <bean id="fakeFtpServer" class="org.mockftpserver.fake.FakeFtpServer">
+ <property name="serverControlPort" value="9981"/>
+ <property name="userAccounts">
+ <list>
+ <bean class="org.mockftpserver.fake.UserAccount">
+ <property name="username" value="joe"/>
+ <property name="password" value="password"/>
+ <property name="homeDirectory" value="c:\"/>
+ </bean>
+ </list>
+ </property>
+
+ <property name="fileSystem">
+ <bean class="org.mockftpserver.fake.filesystem.WindowsFakeFileSystem">
+ <property name="createParentDirectoriesAutomatically" value="false"/>
+ <property name="entries">
+ <list>
+ <bean class="org.mockftpserver.fake.filesystem.DirectoryEntry">
+ <property name="path" value="c:\"/>
+ <property name="permissionsFromString" value="rwxrwxrwx"/>
+ <property name="owner" value="joe"/>
+ <property name="group" value="users"/>
+ </bean>
+ <bean class="org.mockftpserver.fake.filesystem.FileEntry">
+ <property name="path" value="c:\File1.txt"/>
+ <property name="contents" value="1234567890"/>
+ <property name="permissionsFromString" value="rwxrwxrwx"/>
+ <property name="owner" value="peter"/>
+ <property name="group" value="users"/>
+ </bean>
+ <bean class="org.mockftpserver.fake.filesystem.FileEntry">
+ <property name="path" value="c:\File2.txt"/>
+ <property name="contents" value="abcdefghijklmnopqrstuvwxyz"/>
+ <property name="permissions">
+ <bean class="org.mockftpserver.fake.filesystem.Permissions">
+ <constructor-arg value="rwx------"/>
+ </bean>
+ </property>
+ <property name="owner" value="peter"/>
+ <property name="group" value="users"/>
+ </bean>
+ </list>
+ </property>
+ </bean>
+ </property>
+
+ </bean>
+</beans>
++------------------------------------------------------------------------------
+
+
+ Things to note about the above example:
+
+ * The <<<FakeFtpServer>>> instance is configured with a <<<WindowsFakeFileSystem>>> and a "c:\" root
+ directory containing two files. Permissions and owner/group are specified for that directory, as well
+ as the two predefined files contained within it.
+
+ * The permissions for "File1.txt" ("rwxrwxrwx") are specified using the "permissionsFromString" shortcut
+ method, while the permissions for "File2.txt" ("rwx------") are specified using the "permissions" setter,
+ which takes an instance of the <<<Permissions>>> class. Either method is fine.
+
+ []
+
+
+* Configuring Custom CommandHandlers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ <<FakeFtpServer>> is intentionally designed to keep the lower-level details of FTP server implementation
+ hidden from the user. In most cases, you can simply define the files and directories in the file
+ system, configure one or more login users, and then fire up the server, expecting it to behave like
+ a <real> FTP server.
+
+ There are some cases, however, where you might want to further customize the internal behavior of the
+ server. Such cases might include:
+
+ * You want to have a particular FTP server command return a predetermined error reply
+
+ * You want to add support for a command that is not provided out of the box by <<FakeFtpServer>>
+
+ Note that if you need the FTP server to reply with entirely predetermined (canned) responses, then
+ you may want to consider using <<StubFtpServer>> instead.
+
+
+** Using a StaticReplyCommandHandler
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ You can use one of the <CommandHandler> classes defined within the <<<org.mockftpserver.core.command>>>
+ package to configure a custom <CommandHandler>. The following example uses the <<<StaticReplyCommandHandler>>>
+ from that package to add support for the FEAT command. Note that in this case, we are setting the
+ <CommandHandler> for a new command (i.e., one that is not supported out of the box by <<FakeFtpServer>>).
+ We could just as easily set the <CommandHandler> for an existing command, overriding the default <CommandHandler>.
+
++------------------------------------------------------------------------------
+import org.mockftpserver.core.command.StaticReplyCommandHandler
+
+FakeFtpServer ftpServer = new FakeFtpServer()
+// ... set up files, directories and user accounts as usual
+
+StaticReplyCommandHandler featCommandHandler = new StaticReplyCommandHandler(211, "No Features");
+ftpServer.setCommandHandler("FEAT", featCommandHandler);
+
+// ...
+ftpServer.start()
++------------------------------------------------------------------------------
+
+
+** Using a Stub CommandHandler
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ You can also use a <<StubFtpServer>> <CommandHandler> -- i.e., one defined within the
+ <<<org.mockftpserver.stub.command>>> package. The following example uses the <stub> version of the
+ <<<CwdCommandHandler>>> from that package.
+
++------------------------------------------------------------------------------
+import org.mockftpserver.stub.command.CwdCommandHandler
+
+FakeFtpServer ftpServer = new FakeFtpServer()
+// ... set up files, directories and user accounts as usual
+
+final int REPLY_CODE = 502;
+CwdCommandHandler cwdCommandHandler = new CwdCommandHandler();
+cwdCommandHandler.setReplyCode(REPLY_CODE);
+ftpServer.setCommandHandler(CommandNames.CWD, cwdCommandHandler);
+
+// ...
+ftpServer.start()
++------------------------------------------------------------------------------
+
+
+** Creating Your Own Custom CommandHandler Class
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ If one of the existing <CommandHandler> classes does not fulfill your needs, you can alternately create
+ your own custom <CommandHandler> class. The only requirement is that it implement the
+ <<<org.mockftpserver.core.command.CommandHandler>>> interface. You would, however, likely benefit from
+ inheriting from one of the existing abstract <CommandHandler> superclasses, such as
+ <<<org.mockftpserver.core.command.AbstractStaticReplyCommandHandler>>> or
+ <<<org.mockftpserver.core.command.AbstractCommandHandler>>>. See the javadoc of these classes for
+ more information.
+
+
+* FTP Command Reply Text ResourceBundle
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ The default text asociated with each FTP command reply code is contained within the
+ "ReplyText.properties" ResourceBundle file. You can customize these messages by providing a
+ locale-specific ResourceBundle file on the CLASSPATH, according to the normal lookup rules of
+ the ResourceBundle class (e.g., "ReplyText_de.properties"). Alternatively, you can
+ completely replace the ResourceBundle file by calling the calling the
+ <<<FakeFtpServer.setReplyTextBaseName(String)>>> method.
+
+* SLF4J Configuration Required to See Log Output
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Note that <<FakeFtpServer>> uses {{{http://www.slf4j.org/}SLF4J}} for logging. If you want to
+ see the logging output, then you must configure <<SLF4J>>. (If no binding is found on the class
+ path, then <<SLF4J>> will default to a no-operation implementation.)
+
+ See the {{{http://www.slf4j.org/manual.html}SLF4J User Manual}} for more information.
diff --git a/tags/2.5/src/site/apt/fakeftpserver-versus-stubftpserver.apt b/tags/2.5/src/site/apt/fakeftpserver-versus-stubftpserver.apt
new file mode 100644
index 0000000..66d6fa6
--- /dev/null
+++ b/tags/2.5/src/site/apt/fakeftpserver-versus-stubftpserver.apt
@@ -0,0 +1,61 @@
+ --------------------------------------------------
+ FakeFtpServer versus StubFtpServer
+ --------------------------------------------------
+
+FakeFtpServer or StubFtpServer?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ The <<MockFtpServer>> project includes two separate <mock> implementations of an FTP Server. Which one you
+ use is dependent on what kind of FTP scenario(s) you wish to simulate, and what level of control you need
+ over exact server replies.
+
+* FakeFtpServer
+~~~~~~~~~~~~~~~
+
+ <<FakeFtpServer>> provides a high-level abstraction for an FTP Server and is suitable for most testing
+ and simulation scenarios. You define a filesystem (internal, in-memory) containing an arbitrary set of
+ files and directories. These files and directories can (optionally) have associated access permissions.
+ You also configure a set of one or more user accounts that control which users can login to the FTP server,
+ and their home (default) directories. The user account is also used when assigning file and directory
+ ownership for new files.
+
+ <<FakeFtpServer>> processes FTP client requests and responds with reply codes and reply messages
+ consistent with its configuration and the contents of its internal filesystem, including file and
+ directory permissions, if they have been configured.
+
+ <<FakeFtpServer>> can be fully configured programmatically or within a
+ {{{http://www.springframework.org/}Spring Framework}} or other dependency-injection container.
+
+ See the {{{./fakeftpserver-features.html}FakeFtpServer Features and Limitations}} page for more information on
+ which features and scenarios are supported.
+
+* StubFtpServer
+~~~~~~~~~~~~~~~
+
+ <<StubFtpServer>> is a "stub" implementation of an FTP server. It supports the main FTP commands by
+ implementing command handlers for each of the corresponding low-level FTP server commands (e.g. RETR,
+ DELE, LIST). These <CommandHandler>s can be individually configured to return custom data or reply codes,
+ allowing simulation of a complete range of both success and failure scenarios. The <CommandHandler>s can
+ also be interrogated to verify command invocation data such as command parameters and timestamps.
+
+ <<StubFtpServer>> works out of the box with reasonable defaults, but can be fully configured programmatically
+ or within a {{{http://www.springframework.org/}Spring Framework}} or other dependency-injection container.
+
+ See the {{{./stubftpserver-features.html}StubFtpServer Features and Limitations}} page for more information on
+ which features and scenarios are supported.
+
+* So, Which One Should I Use?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ In general, if your testing and simulation needs are pretty straightforward, then using <<FakeFtpServer>> is
+ probably the best choice. See the {{{./fakeftpserver-features.html}FakeFtpServer Features and Limitations}} page
+ for more information on which features and scenarios are supported.
+
+ Some reasons to use <<StubFtpServer>> include:
+
+ * If you need to simulate an FTP server scenario not supported by <<FakeFtpServer>>.
+
+ * You want to test a very specific and/or limited FTP scenario. In this case, the setup of the
+ <<StubFtpServer>> might be simpler -- you don't have to setup fake files and directories and user accounts.
+
+ * You are more comfortable with configuring and using the lower-level FTP server command reply codes and behavior.
diff --git a/tags/2.5/src/site/apt/index.apt b/tags/2.5/src/site/apt/index.apt
new file mode 100644
index 0000000..12c4c94
--- /dev/null
+++ b/tags/2.5/src/site/apt/index.apt
@@ -0,0 +1,60 @@
+ --------------------------------------------------
+ Home
+ --------------------------------------------------
+
+MockFtpServer - Providing a Fake/Stub FTP Server
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ The <<MockFtpServer>> project provides mock/dummy FTP server implementations that can be very
+ useful for testing of FTP client code. Two FTP Server implementations are provided, each at a different
+ level of abstraction.
+
+ <<FakeFtpServer>> provides a higher-level abstraction for an FTP server and is suitable for most testing
+ and simulation scenarios. You define a filesystem (virtual, in-memory) containing an arbitrary set of
+ files and directories. These files and directories can (optionally) have associated access permissions.
+ You also configure a set of one or more user accounts that control which users can login to the FTP server,
+ and their home (default) directories. The user account is also used when assigning file and directory
+ ownership for new files. See {{{./fakeftpserver-features.html}FakeFtpServer Features and Limitations}}.
+
+ <<StubFtpServer>> is a "stub" implementation of an FTP server. It supports the main FTP commands by
+ implementing command handlers for each of the corresponding low-level FTP server commands (e.g. RETR,
+ DELE, LIST). These <CommandHandler>s can be individually configured to return custom data or reply codes,
+ allowing simulation of a complete range of both success and failure scenarios. The <CommandHandler>s can
+ also be interrogated to verify command invocation data such as command parameters and timestamps.
+ See {{{./stubftpserver-features.html}StubFtpServer Features and Limitations}}.
+
+ See the {{{./fakeftpserver-versus-stubftpserver.html}FakeFtpServer or StubFtpServer?}} page for more
+ information on deciding whether to use <<FakeFtpServer>> or <<StubFtpServer>>.
+
+ The <<MockFtpServer>> project is written in Java, and is ideally suited to testing Java code. But because
+ communication with the FTP server is across the network using sockets, it can be used to test FTP client
+ code written in any language.
+
+ NOTE: Starting with <<MockFtpServer>> 2.4, the <<Log4J>> dependency has been replaced with {{{http://www.slf4j.org/}SLF4J}}.
+
+
+* Requirements
+~~~~~~~~~~~~~~
+
+ The <<MockFtpServer>> project requires:
+
+ * Java (JDK) version 1.4 or later
+
+ * The {{{http://www.slf4j.org/}SLF4J}} API jar, accessible on the CLASSPATH. An SLF4J binding (logging
+ framework-specific jar) is optional.
+
+
+* Maven Support
+~~~~~~~~~~~~~~~
+
+ For projects built using {{{http://maven.apache.org/}Maven}}, <<MockFtpServer>> is now available
+ from the <<Maven Central Repository>>. Add a dependency to your POM like this:
+
+--------------------
+ <dependency>
+ <groupId>org.mockftpserver</groupId>
+ <artifactId>MockFtpServer</artifactId>
+ <version>2.4</version>
+ <scope>test</scope>
+ </dependency>
+-------------------- \ No newline at end of file
diff --git a/tags/2.5/src/site/apt/stubftpserver-commandhandlers.apt b/tags/2.5/src/site/apt/stubftpserver-commandhandlers.apt
new file mode 100644
index 0000000..e24a7e5
--- /dev/null
+++ b/tags/2.5/src/site/apt/stubftpserver-commandhandlers.apt
@@ -0,0 +1,98 @@
+ ------------------------------------------------------
+ StubFtpServer FTP Commands and CommandHandlers
+ ------------------------------------------------------
+
+StubFtpServer - FTP Commands and CommandHandlers
+
+ The following table lists the main FTP server commands with their corresponding FTP client commands,
+ and the <<StubFtpServer>> <CommandHandler> classes that implements support for the FTP server command.
+ See the Javadoc for each <CommandHandler> class for information on how to customize its behavior
+ through configuration, as well as what command invocation data is available.
+
+*------------------------*------------------------*------------------------------------------*
+| <<FTP Server Command>> | <<FTP Client Command>> | <<CommandHandler Class(es)>> |
+*------------------------*------------------------*------------------------------------------*
+| ABOR | -- | AborCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| ACCT | -- | AcctCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| ALLO | -- | AlloCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| APPE | APPEND | AppeCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| CDUP | -- | CdupCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| CWD | CD | CwdCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| DELE | DELETE | DeleCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| EPRT | -- | EprtCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| EPSV | -- | EpsvCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| HELP | REMOTEHELP | HelpCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| LIST | DIR / LS | ListCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| MKD | MKDIR | MkdCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| MODE | -- | ModeCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| NLST | -- | NlstCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| NOOP | -- | NoopCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| PASS | USER | PassCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| PASV | -- | PasvCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| PORT | -- | PortCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| PWD | PWD | PwdCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| QUIT | QUIT / BYE | QuitCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| REIN | -- | ReinCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| REST | -- | RestCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| RETR | GET / RECV | RetrCommandHandler |
+| | | FileRetrCommandHandler (1) |
+*------------------------*------------------------*------------------------------------------*
+| RMD | RMDIR | RmdCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| RNFR | RENAME | RnfrCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| RNTO | RENAME | RntoCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| SITE | -- | SiteCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| SMNT | -- | SmntCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| STAT | STATUS | StatCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| STOR | PUT / SEND | StorCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| STOU | -- | StouCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| STRU | -- | StruCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| SYST | -- | SystCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| TYPE | ASCII / BINARY / TYPE | TypeCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+| USER | USER | UserCommandHandler |
+*------------------------*------------------------*------------------------------------------*
+
+ (1) An alternative to the default <CommandHandler> implementation. See its class Javadoc.
+
+
+* Special Command Handlers
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ There are also <special> <CommandHandler> classes defined (in the <<core>> package).
+
+ * <<ConnectCommandHandler>> - Sends a 220 reply code after the initial connection to the server.
+
+ * <<UnsupportedCommandHandler>> - Sends a 502 reply when an unrecognized/unsupported
+ command name is sent from a client. \ No newline at end of file
diff --git a/tags/2.5/src/site/apt/stubftpserver-features.apt b/tags/2.5/src/site/apt/stubftpserver-features.apt
new file mode 100644
index 0000000..758019d
--- /dev/null
+++ b/tags/2.5/src/site/apt/stubftpserver-features.apt
@@ -0,0 +1,35 @@
+ --------------------------------------------------
+ StubFtpServer Features and Limitations
+ --------------------------------------------------
+
+StubFtpServer Features
+
+ * Standalone dummy FTP server. Run either within the same JVM as test code or in a different JVM.
+
+ * Implements common FTP server commands. See {{{./stubftpserver-commandhandlers.html}FTP Commands and CommandHandlers}}.
+
+ * Supports active and passive mode data transfers.
+
+ * Works out of the box with reasonable defaults: success reply codes and empty data.
+
+ * Easy to configure command handlers for individual FTP server commands to return success/failure reply codes and custom data.
+
+ * Can verify expected FTP server command invocations.
+
+ * Easy to implement command handlers for other commands or replace existing command handlers.
+
+ * Use a dynamically chosen free port number for the server control port instead of using the default (21)
+ or hard-coding some other value (set the serverControlPort property of the server to 0).
+
+ * Fully supports configuration within the <<Spring Framework>>.
+
+ * Can be used to test FTP client code written in any language
+
+
+StubFtpServer Limitations
+
+ * Using <<StubFtpServer>> for testing and simulation of non-default scenarios requires
+ some understanding of the FTP Specification and a configuration of the low-level
+ FTP Server commands.
+
+ \ No newline at end of file
diff --git a/tags/2.5/src/site/apt/stubftpserver-getting-started.apt b/tags/2.5/src/site/apt/stubftpserver-getting-started.apt
new file mode 100644
index 0000000..cbe2339
--- /dev/null
+++ b/tags/2.5/src/site/apt/stubftpserver-getting-started.apt
@@ -0,0 +1,341 @@
+ --------------------------------------------------
+ StubFtpServer Getting Started
+ --------------------------------------------------
+
+StubFtpServer - Getting Started
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ <<StubFtpServer>> is a "stub" implementation of an FTP server. It supports the main FTP commands by
+ implementing command handlers for each of the corresponding low-level FTP server commands (e.g. RETR,
+ DELE, LIST). These <CommandHandler>s can be individually configured to return custom data or reply codes,
+ allowing simulation of a complete range of both success and failure scenarios. The <CommandHandler>s can
+ also be interrogated to verify command invocation data such as command parameters and timestamps.
+
+ <<StubFtpServer>> works out of the box with reasonable defaults, but can be fully configured
+ programmatically or within a {{{http://www.springframework.org/}Spring Framework}} (or similar) container.
+
+ Here is how to start the <<StubFtpServer>> with the default configuration. This will return
+ success reply codes, and return empty data (for retrieved files, directory listings, etc.).
+
++------------------------------------------------------------------------------
+StubFtpServer stubFtpServer = new StubFtpServer();
+stubFtpServer.start();
++------------------------------------------------------------------------------
+
+ If you are running on a system where the default port (21) is already in use or cannot be bound
+ from a user process (such as Unix), you will need to use a different server control port. Use the
+ <<<StubFtpServer.setServerControlPort(int serverControlPort)>>> method to use a different port
+ number. If you specify a value of <<<0>>>, then the server will use a free port number. Then call
+ <<<getServerControlPort()>>> AFTER calling <<<start()>>> has been called to determine the actual port
+ number being used. Or, you can pass in a specific port number, such as 9187.
+
+* CommandHandlers
+~~~~~~~~~~~~~~~~~
+
+ <CommandHandler>s are the heart of the <<StubFtpServer>>.
+
+ <<StubFtpServer>> creates an appropriate default <CommandHandler> for each (supported) FTP server
+ command. See the list of <CommandHandler> classes associated with FTP server commands in
+ {{{./stubftpserver-commandhandlers.html}FTP Commands and CommandHandlers}}.
+
+ You can retrieve the existing <CommandHandler> defined for an FTP server command by calling the
+ <<<StubFtpServer.getCommandHandler(String name)>>> method, passing in the FTP server command
+ name. For example:
+
++------------------------------------------------------------------------------
+PwdCommandHandler pwdCommandHandler = (PwdCommandHandler) stubFtpServer.getCommandHandler("PWD");
++------------------------------------------------------------------------------
+
+ You can replace the existing <CommandHandler> defined for an FTP server command by calling the
+ <<<StubFtpServer.setCommandHandler(String name, CommandHandler commandHandler)>>> method, passing
+ in the FTP server command name, such as <<<"STOR">>> or <<<"USER">>>, and the
+ <<<CommandHandler>>> instance. For example:
+
++------------------------------------------------------------------------------
+PwdCommandHandler pwdCommandHandler = new PwdCommandHandler();
+pwdCommandHandler.setDirectory("some/dir");
+stubFtpServer.setCommandHandler("PWD", pwdCommandHandler);
++------------------------------------------------------------------------------
+
+
+** Generic CommandHandlers
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ <<StubFtpServer>> includes a couple generic <CommandHandler> classes that can be used to replace
+ the default command handler for an FTP command. See the Javadoc for more information.
+
+ * <<StaticReplyCommadHandler>>
+
+ <<<StaticReplyCommadHandler>>> is a <CommandHandler> that always sends back the configured reply
+ code and text. This can be a useful replacement for a default <CommandHandler> if you want a
+ certain FTP command to always send back an error reply code.
+
+ * <<SimpleCompositeCommandHandler>>
+
+ <<<SimpleCompositeCommandHandler>>> is a composite <CommandHandler> that manages an internal
+ ordered list of <CommandHandler>s to which it delegates. Starting with the first
+ <CommandHandler> in the list, each invocation of this composite handler will invoke (delegate to)
+ the current internal <CommandHander>. Then it moves on the next <CommandHandler> in the internal list.
+
+
+** Configuring CommandHandler for a New (Unsupported) Command
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ If you want to add support for a command that is not provided out of the box by <<StubFtpServer>>,
+ you can create a <CommandHandler> instance and set it within the <<StubFtpServer>> using the
+ <<<StubFtpServer.setCommandHandler(String name, CommandHandler commandHandler)>>> method in the
+ same way that you replace an existing <CommandHandler> (see above). The following example uses
+ the <<<StaticReplyCommandHandler>>> to add support for the FEAT command.
+
++------------------------------------------------------------------------------
+final String FEAT_TEXT = "Extensions supported:\n" +
+ "MLST size*;create;modify*;perm;media-type\n" +
+ "SIZE\n" +
+ "COMPRESSION\n" +
+ "END";
+StaticReplyCommandHandler featCommandHandler = new StaticReplyCommandHandler(211, FEAT_TEXT);
+stubFtpServer.setCommandHandler("FEAT", featCommandHandler);
++------------------------------------------------------------------------------
+
+
+** Creating Your Own Custom CommandHandler Class
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ If one of the existing <CommandHandler> classes does not fulfill your needs, you can alternately create
+ your own custom <CommandHandler> class. The only requirement is that it implement the
+ <<<org.mockftpserver.core.command.CommandHandler>>> interface. You would, however, likely benefit from
+ inheriting from one of the existing abstract <CommandHandler> superclasses, such as
+ <<<org.mockftpserver.stub.command.AbstractStubCommandHandler>>> or
+ <<<org.mockftpserver.core.command.AbstractCommandHandler>>>. See the javadoc of these classes for
+ more information.
+
+
+* Retrieving Command Invocation Data
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Each predefined <<StubFtpServer>> <CommandHandler> manages a List of <<<InvocationRecord>>> objects -- one
+ for each time the <CommandHandler> is invoked. An <<<InvocationRecord>>> contains the <<<Command>>>
+ that triggered the invocation (containing the command name and parameters), as well as the invocation
+ timestamp and client host address. The <<<InvocationRecord>>> also contains a <<<Map>>>, with optional
+ <CommandHandler>-specific data. See the Javadoc for more information.
+
+ You can retrieve the <<<InvocationRecord>>> from a <CommandHandler> by calling the
+ <<<getInvocation(int index)>>> method, passing in the (zero-based) index of the desired
+ invocation. You can get the number of invocations for a <CommandHandler> by calling
+ <<<numberOfInvocations()>>>. The {{{#Example}Example Test Using Stub Ftp Server}} below illustrates
+ retrieving and interrogating an <<<InvocationRecord>>> from a <CommandHandler>.
+
+
+* {Example} Test Using StubFtpServer
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ This section includes a simplified example of FTP client code to be tested, and a JUnit
+ test for it that uses <<StubFtpServer>>.
+
+** FTP Client Code
+~~~~~~~~~~~~~~~~~~
+
+ The following <<<RemoteFile>>> class includes a <<<readFile()>>> method that retrieves a remote
+ ASCII file and returns its contents as a String. This class uses the <<<FTPClient>>> from the
+ {{{http://commons.apache.org/net/}Apache Commons Net}} framework.
+
++------------------------------------------------------------------------------
+public class RemoteFile {
+
+ private String server;
+
+ public String readFile(String filename) throws SocketException, IOException {
+
+ FTPClient ftpClient = new FTPClient();
+ ftpClient.connect(server);
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ boolean success = ftpClient.retrieveFile(filename, outputStream);
+ ftpClient.disconnect();
+
+ if (!success) {
+ throw new IOException("Retrieve file failed: " + filename);
+ }
+ return outputStream.toString();
+ }
+
+ public void setServer(String server) {
+ this.server = server;
+ }
+
+ // Other methods ...
+}
++------------------------------------------------------------------------------
+
+** JUnit Test For FTP Client Code Using StubFtpServer
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ The following <<<RemoteFileTest>>> class includes a couple of JUnit tests that use
+ <<StubFtpServer>>. The test illustrates replacing the default <CommandHandler> with
+ a customized handler.
+
++------------------------------------------------------------------------------
+import java.io.IOException;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.stub.StubFtpServer;
+import org.mockftpserver.stub.command.RetrCommandHandler;
+import org.mockftpserver.test.AbstractTest;
+
+public class RemoteFileTest extends AbstractTest {
+
+ private static final String FILENAME = "dir/sample.txt";
+
+ private RemoteFile remoteFile;
+ private StubFtpServer stubFtpServer;
+
+ public void testReadFile() throws Exception {
+
+ final String CONTENTS = "abcdef 1234567890";
+
+ // Replace the default RETR CommandHandler; customize returned file contents
+ RetrCommandHandler retrCommandHandler = new RetrCommandHandler();
+ retrCommandHandler.setFileContents(CONTENTS);
+ stubFtpServer.setCommandHandler("RETR", retrCommandHandler);
+
+ stubFtpServer.start();
+
+ String contents = remoteFile.readFile(FILENAME);
+
+ // Verify returned file contents
+ assertEquals("contents", CONTENTS, contents);
+
+ // Verify the submitted filename
+ InvocationRecord invocationRecord = retrCommandHandler.getInvocation(0);
+ String filename = invocationRecord.getString(RetrCommandHandler.PATHNAME_KEY);
+ assertEquals("filename", FILENAME, filename);
+ }
+
+ /**
+ * Test the readFile() method when the FTP transfer fails (returns a non-success reply code)
+ */
+ public void testReadFileThrowsException() {
+
+ // Replace the default RETR CommandHandler; return failure reply code
+ RetrCommandHandler retrCommandHandler = new RetrCommandHandler();
+ retrCommandHandler.setFinalReplyCode(550);
+ stubFtpServer.setCommandHandler("RETR", retrCommandHandler);
+
+ stubFtpServer.start();
+
+ try {
+ remoteFile.readFile(FILENAME);
+ fail("Expected IOException");
+ }
+ catch (IOException expected) {
+ // Expected this
+ }
+ }
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ remoteFile = new RemoteFile();
+ remoteFile.setServer("localhost");
+ stubFtpServer = new StubFtpServer();
+ }
+
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ stubFtpServer.stop();
+ }
+}
++------------------------------------------------------------------------------
+
+ Things to note about the above test:
+
+ * The <<<StubFtpServer>>> instance is created in the <<<setUp()>>> method, but is not started
+ there because it must be configured differently for each test. The <<<StubFtpServer>>> instance
+ is stopped in the <<<tearDown()>>> method, to ensure that it is stopped, even if the test fails.
+
+
+* Spring Configuration
+~~~~~~~~~~~~~~~~~~~~~~
+
+ You can easily configure a <<StubFtpServer>> instance in the
+ {{{http://www.springframework.org/}Spring Framework}}. The following example shows a <Spring>
+ configuration file.
+
++------------------------------------------------------------------------------
+<?xml version="1.0" encoding="UTF-8"?>
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
+
+ <bean id="stubFtpServer" class="org.mockftpserver.stub.StubFtpServer">
+
+ <property name="commandHandlers">
+ <map>
+ <entry key="LIST">
+ <bean class="org.mockftpserver.stub.command.ListCommandHandler">
+ <property name="directoryListing">
+ <value>
+ 11-09-01 12:30PM 406348 File2350.log
+ 11-01-01 1:30PM &lt;DIR&gt; 0 archive
+ </value>
+ </property>
+ </bean>
+ </entry>
+
+ <entry key="PWD">
+ <bean class="org.mockftpserver.stub.command.PwdCommandHandler">
+ <property name="directory" value="foo/bar" />
+ </bean>
+ </entry>
+
+ <entry key="DELE">
+ <bean class="org.mockftpserver.stub.command.DeleCommandHandler">
+ <property name="replyCode" value="450" />
+ </bean>
+ </entry>
+
+ <entry key="RETR">
+ <bean class="org.mockftpserver.stub.command.RetrCommandHandler">
+ <property name="fileContents"
+ value="Sample file contents line 1&#10;Line 2&#10;Line 3"/>
+ </bean>
+ </entry>
+
+ </map>
+ </property>
+ </bean>
+
+</beans>
++------------------------------------------------------------------------------
+
+ This example overrides the default handlers for the following FTP commands:
+
+ * LIST - replies with a predefined directory listing
+
+ * PWD - replies with a predefined directory pathname
+
+ * DELE - replies with an error reply code (450)
+
+ * RETR - replies with predefined contents for a retrieved file
+
+ []
+
+ And here is the Java code to load the above <Spring> configuration file and start the
+ configured <<StubFtpServer>>.
+
++------------------------------------------------------------------------------
+ApplicationContext context = new ClassPathXmlApplicationContext("stubftpserver-beans.xml");
+stubFtpServer = (StubFtpServer) context.getBean("stubFtpServer");
+stubFtpServer.start();
++------------------------------------------------------------------------------
+
+
+* FTP Command Reply Text ResourceBundle
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ The default text asociated with each FTP command reply code is contained within the
+ "ReplyText.properties" ResourceBundle file. You can customize these messages by providing a
+ locale-specific ResourceBundle file on the CLASSPATH, according to the normal lookup rules of
+ the ResourceBundle class (e.g., "ReplyText_de.properties"). Alternatively, you can
+ completely replace the ResourceBundle file by calling the calling the
+ <<<StubFtpServer.setReplyTextBaseName(String)>>> method.
diff --git a/tags/2.5/src/site/fml/faq.fml b/tags/2.5/src/site/fml/faq.fml
new file mode 100644
index 0000000..c4d1495
--- /dev/null
+++ b/tags/2.5/src/site/fml/faq.fml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<faqs title="Frequently Asked Questions" toplink="false">
+
+ <part id="general">
+ <title>General</title>
+
+ <faq id="whats-foo">
+ <question>
+ What is Foo?
+ </question>
+ <answer>
+ <p>some markup goes here</p>
+
+ <source>some source code</source>
+
+ <p>some markup goes here</p>
+ </answer>
+ </faq>
+
+ <!--
+ <faq id="whats-bar">
+ <question>
+ What is Bar?
+ </question>
+ <answer>
+ <p>some markup goes here</p>
+ </answer>
+ </faq>
+ -->
+
+ </part>
+
+ <!--
+ <part id="install">
+
+ <title>Installation</title>
+
+ <faq id="how-install">
+ <question>
+ How do I install Foo?
+ </question>
+ <answer>
+ <p>some markup goes here</p>
+ </answer>
+ </faq>
+
+ </part>
+ -->
+
+</faqs>
diff --git a/tags/2.5/src/site/resources/css/site.css b/tags/2.5/src/site/resources/css/site.css
new file mode 100644
index 0000000..4f24aa3
--- /dev/null
+++ b/tags/2.5/src/site/resources/css/site.css
@@ -0,0 +1,4 @@
+tt {
+ font-size: 110%;
+ font-weight: bolder;
+} \ No newline at end of file
diff --git a/tags/2.5/src/site/resources/images/mockftpserver-logo.png b/tags/2.5/src/site/resources/images/mockftpserver-logo.png
new file mode 100644
index 0000000..a41e069
--- /dev/null
+++ b/tags/2.5/src/site/resources/images/mockftpserver-logo.png
Binary files differ
diff --git a/tags/2.5/src/site/site.xml b/tags/2.5/src/site/site.xml
new file mode 100644
index 0000000..d778777
--- /dev/null
+++ b/tags/2.5/src/site/site.xml
@@ -0,0 +1,51 @@
+<project name="MockFtpServer">
+
+ <bannerLeft>
+ <name>MockFtpServer</name>
+ <src>images/mockftpserver-logo.png</src>
+ <href>/</href>
+ </bannerLeft>
+
+ <publishDate format="dd MMM yyyy"/>
+
+ <poweredBy>
+ <logo
+ name="Hosted on SourceForge.net"
+ href="http://sourceforge.net"
+ img="http://sflogo.sourceforge.net/sflogo.php?group_id=208647&amp;type=2"/>
+ <logo
+ name="Build with Maven 2"
+ href="http://maven.apache.org"
+ img="http://maven.apache.org/images/logos/maven-feather.png"/>
+ </poweredBy>
+
+ <body>
+ <links>
+ </links>
+ <head>
+ <meta name="faq" content="mockftpserver"/>
+ </head>
+ <menu name="General">
+ <item name="Home" href="/index.html"/>
+ <item name="FakeFtpServer or StubFtpServer?" href="/fakeftpserver-versus-stubftpserver.html"/>
+ <!-- <item name="FAQs" href="/faq.html"/> -->
+ <item name="Javadocs" href="/apidocs/index.html"/>
+ <item name="Downloads" href="http://sourceforge.net/project/showfiles.php?group_id=208647"/>
+ <item name="SourceForge Project Page" href="http://sourceforge.net/projects/mockftpserver"/>
+ </menu>
+
+ <menu name="FakeFtpServer">
+ <item name="Features and Limitations" href="/fakeftpserver-features.html"/>
+ <item name="Getting Started" href="/fakeftpserver-getting-started.html"/>
+ <item name="File Systems" href="/fakeftpserver-filesystems.html"/>
+ </menu>
+
+ <menu name="StubFtpServer">
+ <item name="Features and Limitations" href="/stubftpserver-features.html"/>
+ <item name="Getting Started" href="/stubftpserver-getting-started.html"/>
+ <item name="CommandHandlers" href="/stubftpserver-commandhandlers.html"/>
+ </menu>
+
+ ${reports}
+ </body>
+</project> \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/core/server/AbstractFtpServer_MultipleStartAndStopTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/core/server/AbstractFtpServer_MultipleStartAndStopTest.groovy
new file mode 100644
index 0000000..9ab39ad
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/core/server/AbstractFtpServer_MultipleStartAndStopTest.groovy
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.server
+
+import org.mockftpserver.fake.FakeFtpServer
+import org.mockftpserver.test.AbstractGroovyTestCase
+import org.mockftpserver.test.PortTestUtil
+
+/**
+ * Test starting and stopping Abstract(Fake)FtpServer multiple times.
+ *
+ * @version $Revision: 242 $ - $Date: 2010-03-21 07:41:01 -0400 (Sun, 21 Mar 2010) $
+ *
+ * @author Chris Mair
+ */
+class AbstractFtpServer_MultipleStartAndStopTest extends AbstractGroovyTestCase {
+
+ private FakeFtpServer ftpServer = new FakeFtpServer()
+
+ // Takes ~ 500ms per start/stop
+
+ void testStartAndStop() {
+ 10.times {
+ final def port = PortTestUtil.getFtpServerControlPort()
+ ftpServer.setServerControlPort(port);
+
+ ftpServer.start();
+ assert ftpServer.getServerControlPort() == port
+ Thread.sleep(100L); // give it some time to get started
+ assertEquals("started - after start()", true, ftpServer.isStarted());
+ assertEquals("shutdown - after start()", false, ftpServer.isShutdown());
+
+ ftpServer.stop();
+
+ assertEquals("shutdown - after stop()", true, ftpServer.isShutdown());
+ }
+ }
+
+ void testStartAndStop_UseDynamicFreePort() {
+ 5.times {
+ ftpServer.setServerControlPort(0);
+ assert ftpServer.getServerControlPort() == 0
+
+ ftpServer.start();
+ log("Using port ${ftpServer.getServerControlPort()}")
+ assert ftpServer.getServerControlPort() != 0
+
+ ftpServer.stop();
+ }
+ }
+
+ void tearDown() {
+ super.tearDown()
+ ftpServer.stop(); // just to be sure
+ }
+}
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/core/session/StubSession.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/core/session/StubSession.groovy
new file mode 100644
index 0000000..139fcf9
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/core/session/StubSession.groovy
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.session
+
+/**
+ * Stub implementation of the {@link Session} interface for testing
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class StubSession implements Session {
+
+ Map attributes = [:]
+ private List sentReplies = []
+ List sentData = []
+ //byte[] dataToRead
+ Object dataToRead
+ boolean closed
+ InetAddress clientDataHost
+ int clientDataPort
+ boolean dataConnectionOpen = false
+ int switchToPassiveModeReturnValue
+ boolean switchedToPassiveMode = false
+ InetAddress serverHost
+
+ /**
+ * @see org.mockftpserver.core.session.Session#close()
+ */
+ public void close() {
+ closed = true
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#closeDataConnection()
+ */
+ public void closeDataConnection() {
+ dataConnectionOpen = false
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#getAttribute(java.lang.String)
+ */
+ public Object getAttribute(String name) {
+ return attributes[name]
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#getAttributeNames()
+ */
+ public Set getAttributeNames() {
+ return attributes.keySet()
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#getClientHost()
+ */
+ public InetAddress getClientHost() {
+ return null
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#getServerHost()
+ */
+ public InetAddress getServerHost() {
+ return serverHost
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#openDataConnection()
+ */
+ public void openDataConnection() {
+ dataConnectionOpen = true
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#readData()
+ */
+ public byte[] readData() {
+ assert dataConnectionOpen, "The data connection must be OPEN"
+ return dataToRead
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#readData()
+ */
+ public byte[] readData(int numBytes) {
+ assert dataConnectionOpen, "The data connection must be OPEN"
+ return dataToRead
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#removeAttribute(java.lang.String)
+ */
+ public void removeAttribute(String name) {
+ attributes.remove(name)
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#sendData(byte [], int)
+ */
+ public void sendData(byte[] data, int numBytes) {
+ assert dataConnectionOpen, "The data connection must be OPEN"
+ sentData << new String(data, 0, numBytes)
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#sendReply(int, java.lang.String)
+ */
+ public void sendReply(int replyCode, String replyText) {
+ sentReplies << [replyCode, replyText]
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#setAttribute(java.lang.String, java.lang.Object)
+ */
+ public void setAttribute(String name, Object value) {
+ attributes[name] = value
+ }
+
+ /**
+ * @see org.mockftpserver.core.session.Session#switchToPassiveMode()
+ */
+ public int switchToPassiveMode() {
+ switchedToPassiveMode = true
+ return switchToPassiveModeReturnValue
+ }
+
+ /**
+ * @see java.lang.Runnable#run()
+ */
+ public void run() {
+
+ }
+
+ //-------------------------------------------------------------------------
+ // Stub-specific API - Helper methods not part of Session interface
+ //-------------------------------------------------------------------------
+
+ /**
+ * @return the reply code for the session reply at the specified index
+ */
+ int getReplyCode(int replyIndex) {
+ return getReply(replyIndex)[0]
+ }
+
+ /**
+ * @return the reply message for the session reply at the specified index
+ */
+ String getReplyMessage(int replyIndex) {
+ return getReply(replyIndex)[1]
+ }
+
+ /**
+ * @return the String representation of this object, including property names and values of interest
+ */
+ String toString() {
+ "StubSession[sentReplies=$sentReplies sentData=$sentData attributes=$attributes closed=$closed " +
+ "clientDataHost=$clientDataHost clientDataPort=$clientDataPort]"
+ }
+
+ //-------------------------------------------------------------------------
+ // Internal Helper Methods
+ //-------------------------------------------------------------------------
+
+ private List getReply(int replyIndex) {
+ def reply = sentReplies[replyIndex]
+ assert reply, "No reply for index [$replyIndex] sent for ${this}"
+ return reply
+ }
+
+}
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/core/util/IoUtilTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/core/util/IoUtilTest.groovy
new file mode 100644
index 0000000..7151322
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/core/util/IoUtilTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.util
+
+import org.mockftpserver.test.AbstractGroovyTestCase
+
+/**
+ * Tests for the IoUtil class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public class IoUtilTest extends AbstractGroovyTestCase {
+
+ /**
+ * Test the readBytes() method
+ */
+ void testReadBytes() {
+ final byte[] BYTES = "abc 123 %^&".getBytes()
+ InputStream input = new ByteArrayInputStream(BYTES)
+ assert IoUtil.readBytes(input) == BYTES
+ }
+
+ /**
+ * Test the readBytes() method, passing in a null
+ */
+ void testReadBytes_Null() {
+ shouldFailWithMessageContaining("input") { IoUtil.readBytes(null) }
+ }
+
+}
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/core/util/PatternUtilTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/core/util/PatternUtilTest.groovy
new file mode 100644
index 0000000..894d5c8
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/core/util/PatternUtilTest.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.util
+
+import org.mockftpserver.test.AbstractGroovyTestCase
+
+/**
+ * Tests for the PatternUtil class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public class PatternUtilTest extends AbstractGroovyTestCase {
+
+ void testConvertStringWithWildcardsToRegex() {
+ assert PatternUtil.convertStringWithWildcardsToRegex('abc') == /abc/
+ assert PatternUtil.convertStringWithWildcardsToRegex('abc.def') == /abc\.def/
+ assert PatternUtil.convertStringWithWildcardsToRegex('(abc):{def}') == /\(abc\)\:\{def\}/
+ assert PatternUtil.convertStringWithWildcardsToRegex('|[23]^a+$b') == /\|\[23\]\^a\+/ + '\\$b'
+
+ assert PatternUtil.convertStringWithWildcardsToRegex('*.txt') == /.*\.txt/
+ assert PatternUtil.convertStringWithWildcardsToRegex('abc*') == /abc.*/
+ assert PatternUtil.convertStringWithWildcardsToRegex('??x?.*') == /..x.\..*/
+ }
+
+ void testContainsWildcards() {
+ assert !PatternUtil.containsWildcards('')
+ assert !PatternUtil.containsWildcards('abc')
+ assert !PatternUtil.containsWildcards('abc.def')
+
+ assert PatternUtil.containsWildcards('*.txt')
+ assert PatternUtil.containsWildcards('abc.*_OLD')
+ assert PatternUtil.containsWildcards('a??.txt')
+ assert PatternUtil.containsWildcards('?a*.*HH???')
+ }
+
+}
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/core/util/PortParserTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/core/util/PortParserTest.groovy
new file mode 100644
index 0000000..1bf5ed8
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/core/util/PortParserTest.groovy
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.util
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.CommandSyntaxException
+import org.mockftpserver.test.AbstractGroovyTestCase
+
+/**
+ * Tests for the PortParser class
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+class PortParserTest extends AbstractGroovyTestCase {
+
+ static final Logger LOG = LoggerFactory.getLogger(PortParserTest.class)
+ static final String[] PARAMETERS = ["192", "22", "250", "44", "1", "206"]
+ static final String[] PARAMETERS_INSUFFICIENT = ["7", "29", "99", "11", "77"]
+ static final int PORT = (1 << 8) + 206
+ static final InetAddress HOST = inetAddress("192.22.250.44")
+
+ static final PARAMETER_IPV4 = "|1|132.235.1.2|6275|"
+ static final HOST_IPV4 = InetAddress.getByName("132.235.1.2")
+ static final PARAMETER_IPV6 = "|2|1080::8:800:200C:417A|6275|"
+ static final HOST_IPV6 = InetAddress.getByName("1080::8:800:200C:417A")
+ static final E_PORT = 6275
+
+ void testParseExtendedAddressHostAndPort_IPv4() {
+ def client = PortParser.parseExtendedAddressHostAndPort(PARAMETER_IPV4)
+ assert client.host == HOST_IPV4
+ assert client.port == E_PORT
+ }
+
+ void testParseExtendedAddressHostAndPort_IPv6() {
+ def client = PortParser.parseExtendedAddressHostAndPort(PARAMETER_IPV6)
+ assert client.host == HOST_IPV6
+ assert client.port == E_PORT
+ }
+
+ void testParseExtendedAddressHostAndPort_IPv6_CustomDelimiter() {
+ def client = PortParser.parseExtendedAddressHostAndPort("@2@1080::8:800:200C:417A@6275@")
+ assert client.host == HOST_IPV6
+ assert client.port == E_PORT
+ }
+
+ void testParseExtendedAddressHostAndPort_IllegalParameterFormat() {
+ final PARM = 'abcdef'
+ shouldFail(CommandSyntaxException) { PortParser.parseExtendedAddressHostAndPort(PARM) }
+ }
+
+ void testParseExtendedAddressHostAndPort_PortMissing() {
+ final PARM = '|1|132.235.1.2|'
+ shouldFail(CommandSyntaxException) { PortParser.parseExtendedAddressHostAndPort(PARM) }
+ }
+
+ void testParseExtendedAddressHostAndPort_IllegalHostName() {
+ final PARM = '|1|132.@|6275|'
+ shouldFail(CommandSyntaxException) { PortParser.parseExtendedAddressHostAndPort(PARM) }
+ }
+
+ void testParseExtendedAddressHostAndPort_Null() {
+ shouldFail(CommandSyntaxException) { PortParser.parseExtendedAddressHostAndPort(null) }
+ }
+
+ void testParseExtendedAddressHostAndPort_Empty() {
+ shouldFail(CommandSyntaxException) { PortParser.parseExtendedAddressHostAndPort('') }
+ }
+
+ void testParseHostAndPort() {
+ def client = PortParser.parseHostAndPort(PARAMETERS)
+ assert client.host == HOST
+ assert client.port == PORT
+ }
+
+ void testParseHostAndPort_Null() {
+ shouldFail(CommandSyntaxException) { PortParser.parseHostAndPort(null) }
+ }
+
+ void testParseHostAndPort_InsufficientParameters() throws UnknownHostException {
+ shouldFail(CommandSyntaxException) { PortParser.parseHostAndPort(PARAMETERS_INSUFFICIENT) }
+ }
+
+ void testConvertHostAndPortToStringOfBytes() {
+ int port = (23 << 8) + 77
+ InetAddress host = InetAddress.getByName("196.168.44.55")
+ String result = PortParser.convertHostAndPortToCommaDelimitedBytes(host, port)
+ LOG.info("result=" + result)
+ assertEquals("result", "196,168,44,55,23,77", result)
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/core/util/StringUtilTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/core/util/StringUtilTest.groovy
new file mode 100644
index 0000000..cf4800b
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/core/util/StringUtilTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.util
+
+import org.mockftpserver.test.AbstractGroovyTestCase
+
+/**
+ * Tests for the IoUtil class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class StringUtilTest extends AbstractGroovyTestCase {
+
+ void testPadRight() {
+ assert StringUtil.padRight('', 0) == ''
+ assert StringUtil.padRight('', 1) == ' '
+ assert StringUtil.padRight('z', 1) == 'z'
+ assert StringUtil.padRight(' z', 3) == ' z '
+ assert StringUtil.padRight('z', 1) == 'z'
+ assert StringUtil.padRight('zzz', 1) == 'zzz'
+ assert StringUtil.padRight('z', 5) == 'z '
+ }
+
+ void testPadLeft() {
+ assert StringUtil.padLeft('', 0) == ''
+ assert StringUtil.padLeft('', 1) == ' '
+ assert StringUtil.padLeft('z', 1) == 'z'
+ assert StringUtil.padLeft(' z', 3) == ' z'
+ assert StringUtil.padLeft('z', 1) == 'z'
+ assert StringUtil.padLeft('zzz', 1) == 'zzz'
+ assert StringUtil.padLeft('z', 5) == ' z'
+ }
+
+ void testJoin() {
+ assert StringUtil.join([], ' ') == ''
+ assert StringUtil.join([], 'x') == ''
+ assert StringUtil.join(['a'], 'x') == 'a'
+ assert StringUtil.join(['a', 'b'], '') == 'ab'
+ assert StringUtil.join(['a', 'b'], ',') == 'a,b'
+ assert StringUtil.join(['a', 'b', 'c'], ':') == 'a:b:c'
+
+ shouldFailWithMessageContaining('parts') { StringUtil.join(null, '') }
+ shouldFailWithMessageContaining('delimiter') { StringUtil.join([], null) }
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/CustomUserAccount.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/CustomUserAccount.groovy
new file mode 100644
index 0000000..3a38551
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/CustomUserAccount.groovy
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake
+
+import org.mockftpserver.fake.UserAccount
+
+/**
+ * Test-only subclass of UserAccount tha provides a custom implementation of password comparison
+ */
+class CustomUserAccount extends UserAccount {
+ protected boolean comparePassword(String password) {
+ return password == this.password + "123"
+ }
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/FakeFtpServerIntegrationTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/FakeFtpServerIntegrationTest.groovy
new file mode 100644
index 0000000..7c5349f
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/FakeFtpServerIntegrationTest.groovy
@@ -0,0 +1,510 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake
+
+import org.apache.commons.net.ftp.FTP
+import org.apache.commons.net.ftp.FTPClient
+import org.apache.commons.net.ftp.FTPFile
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.StaticReplyCommandHandler
+
+import org.mockftpserver.fake.filesystem.DirectoryEntry
+import org.mockftpserver.fake.filesystem.FileEntry
+import org.mockftpserver.fake.filesystem.FileSystem
+import org.mockftpserver.fake.filesystem.UnixFakeFileSystem
+import org.mockftpserver.fake.filesystem.WindowsFakeFileSystem
+import org.mockftpserver.stub.command.CwdCommandHandler
+import org.mockftpserver.test.AbstractGroovyTestCase
+import org.mockftpserver.test.PortTestUtil
+
+/**
+ * Integration tests for FakeFtpServer.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class FakeFtpServerIntegrationTest extends AbstractGroovyTestCase {
+
+ static final SERVER = "localhost"
+ static final USERNAME = "user123"
+ static final PASSWORD = "password"
+ static final ACCOUNT = "account123"
+ static final ASCII_DATA = "abcdef\tghijklmnopqr"
+ static final BINARY_DATA = new byte[256]
+ static final ROOT_DIR = "c:/"
+ static final HOME_DIR = p(ROOT_DIR, "home")
+ static final SUBDIR_NAME = 'sub'
+ static final SUBDIR_NAME2 = "archive"
+ static final SUBDIR = p(HOME_DIR, SUBDIR_NAME)
+ static final FILENAME1 = "abc.txt"
+ static final FILENAME2 = "SomeOtherFile.xml"
+ static final FILE1 = p(HOME_DIR, FILENAME1)
+ static final SYSTEM_NAME = "WINDOWS"
+
+ private FakeFtpServer ftpServer
+ private FTPClient ftpClient
+ private FileSystem fileSystem
+ private UserAccount userAccount
+
+ //-------------------------------------------------------------------------
+ // Tests
+ //-------------------------------------------------------------------------
+
+ void testAbor() {
+ ftpClientConnectAndLogin()
+ assert ftpClient.abort()
+ verifyReplyCode("ABOR", 226)
+ }
+
+ void testAcct() {
+ ftpClientConnectAndLogin()
+ assert ftpClient.acct(ACCOUNT) == 230
+ }
+
+ void testAllo() {
+ ftpClientConnectAndLogin()
+ assert ftpClient.allocate(99)
+ verifyReplyCode("ALLO", 200)
+ }
+
+ void testAppe() {
+ def ORIGINAL_CONTENTS = '123 456 789'
+ fileSystem.add(new FileEntry(path: FILE1, contents: ORIGINAL_CONTENTS))
+
+ ftpClientConnectAndLogin()
+
+ LOG.info("Put File for local path [$FILE1]")
+ def inputStream = new ByteArrayInputStream(ASCII_DATA.getBytes())
+ assert ftpClient.appendFile(FILE1, inputStream)
+ def contents = fileSystem.getEntry(FILE1).createInputStream().text
+ LOG.info("File contents=[" + contents + "]")
+ assert contents == ORIGINAL_CONTENTS + ASCII_DATA
+ }
+
+ void testCdup() {
+ ftpClientConnectAndLogin()
+ assert ftpClient.changeToParentDirectory()
+ verifyReplyCode("changeToParentDirectory", 200)
+ }
+
+ void testCwd() {
+ ftpClientConnectAndLogin()
+ assert ftpClient.changeWorkingDirectory(SUBDIR_NAME)
+ verifyReplyCode("changeWorkingDirectory", 250)
+ }
+
+ /**
+ * Test that a CWD to ".." properly resolves the current dir (without the "..") so that PWD returns the parent
+ */
+ void testCwd_DotDot_Pwd() {
+ ftpClientConnectAndLogin()
+ assert ftpClient.changeWorkingDirectory("..")
+ verifyReplyCode("changeWorkingDirectory", 250)
+ assert p(ftpClient.printWorkingDirectory()) == p(ROOT_DIR)
+ assert ftpClient.changeWorkingDirectory("home")
+ assert p(ftpClient.printWorkingDirectory()) == p(HOME_DIR)
+ }
+
+ /**
+ * Test that a CWD to "." properly resolves the current dir (without the ".") so that PWD returns the parent
+ */
+ void testCwd_Dot_Pwd() {
+ ftpClientConnectAndLogin()
+ assert ftpClient.changeWorkingDirectory(".")
+ verifyReplyCode("changeWorkingDirectory", 250)
+ assert p(ftpClient.printWorkingDirectory()) == p(HOME_DIR)
+ }
+
+ void testCwd_UseStaticReplyCommandHandler() {
+ final int REPLY_CODE = 500;
+ StaticReplyCommandHandler cwdCommandHandler = new StaticReplyCommandHandler(REPLY_CODE);
+ ftpServer.setCommandHandler(CommandNames.CWD, cwdCommandHandler);
+
+ ftpClientConnectAndLogin()
+ assert !ftpClient.changeWorkingDirectory(SUBDIR_NAME)
+ verifyReplyCode("changeWorkingDirectory", REPLY_CODE)
+ }
+
+ void testCwd_UseStubCommandHandler() {
+ final int REPLY_CODE = 502;
+ CwdCommandHandler cwdCommandHandler = new CwdCommandHandler(); // Stub command handler
+ cwdCommandHandler.setReplyCode(REPLY_CODE);
+ ftpServer.setCommandHandler(CommandNames.CWD, cwdCommandHandler);
+
+ ftpClientConnectAndLogin()
+ assert !ftpClient.changeWorkingDirectory(SUBDIR_NAME)
+ verifyReplyCode("changeWorkingDirectory", REPLY_CODE)
+ assert cwdCommandHandler.getInvocation(0)
+ }
+
+ void testDele() {
+ fileSystem.add(new FileEntry(FILE1))
+
+ ftpClientConnectAndLogin()
+ assert ftpClient.deleteFile(FILENAME1)
+ verifyReplyCode("deleteFile", 250)
+ assert !fileSystem.exists(FILENAME1)
+ }
+
+ void testEprt() {
+ log("Skipping...")
+// ftpClientConnectAndLogin()
+// assert ftpClient.sendCommand("EPRT", "|2|1080::8:800:200C:417A|5282|") == 200
+ }
+
+ void testEpsv() {
+ ftpClientConnectAndLogin()
+ assert ftpClient.sendCommand("EPSV") == 229
+ }
+
+ void testFeat_UseStaticReplyCommandHandler() {
+ // The FEAT command is not supported out of the box
+ StaticReplyCommandHandler featCommandHandler = new StaticReplyCommandHandler(211, "No Features");
+ ftpServer.setCommandHandler("FEAT", featCommandHandler);
+
+ ftpClientConnectAndLogin()
+ assert ftpClient.sendCommand("FEAT") == 211
+ }
+
+ void testHelp() {
+ ftpServer.helpText = [a: 'aaa', '': 'default']
+ ftpClientConnect()
+
+ String help = ftpClient.listHelp()
+ assert help.contains('default')
+ verifyReplyCode("listHelp", 214)
+
+ help = ftpClient.listHelp('a')
+ assert help.contains('aaa')
+ verifyReplyCode("listHelp", 214)
+
+ help = ftpClient.listHelp('bad')
+ assert help.contains('bad')
+ verifyReplyCode("listHelp", 214)
+ }
+
+ void testList() {
+ def LAST_MODIFIED = new Date()
+ fileSystem.add(new FileEntry(path: p(SUBDIR, FILENAME1), lastModified: LAST_MODIFIED, contents: ASCII_DATA))
+ fileSystem.add(new DirectoryEntry(path: p(SUBDIR, SUBDIR_NAME2), lastModified: LAST_MODIFIED))
+
+ ftpClientConnectAndLogin()
+
+ FTPFile[] files = ftpClient.listFiles(SUBDIR)
+ assert files.length == 2
+
+ // Can't be sure of order
+ FTPFile fileEntry = (files[0].getType() == FTPFile.FILE_TYPE) ? files[0] : files[1]
+ FTPFile dirEntry = (files[0].getType() == FTPFile.FILE_TYPE) ? files[1] : files[0]
+ verifyFTPFile(fileEntry, FTPFile.FILE_TYPE, FILENAME1, ASCII_DATA.size())
+ verifyFTPFile(dirEntry, FTPFile.DIRECTORY_TYPE, SUBDIR_NAME2, 0)
+
+ verifyReplyCode("list", 226)
+ }
+
+ void testList_Unix() {
+ ftpServer.systemName = 'UNIX'
+ userAccount.homeDirectory = '/'
+
+ def unixFileSystem = new UnixFakeFileSystem()
+ unixFileSystem.createParentDirectoriesAutomatically = true
+ unixFileSystem.add(new DirectoryEntry('/'))
+ ftpServer.fileSystem = unixFileSystem
+
+ def LAST_MODIFIED = new Date()
+ unixFileSystem.add(new FileEntry(path: p('/', FILENAME1), lastModified: LAST_MODIFIED, contents: ASCII_DATA))
+ unixFileSystem.add(new DirectoryEntry(path: p('/', SUBDIR_NAME2), lastModified: LAST_MODIFIED))
+
+ ftpClientConnectAndLogin()
+
+ FTPFile[] files = ftpClient.listFiles('/')
+ assert files.length == 2
+
+ // Can't be sure of order
+ FTPFile fileEntry = (files[0].getType() == FTPFile.FILE_TYPE) ? files[0] : files[1]
+ FTPFile dirEntry = (files[0].getType() == FTPFile.FILE_TYPE) ? files[1] : files[0]
+
+ verifyFTPFile(dirEntry, FTPFile.DIRECTORY_TYPE, SUBDIR_NAME2, 0)
+ verifyFTPFile(fileEntry, FTPFile.FILE_TYPE, FILENAME1, ASCII_DATA.size())
+ verifyReplyCode("list", 226)
+ }
+
+ void testLogin() {
+ ftpClientConnect()
+ LOG.info("Logging in as $USERNAME/$PASSWORD")
+ assert ftpClient.login(USERNAME, PASSWORD)
+ verifyReplyCode("login with $USERNAME/$PASSWORD", 230)
+
+ assertTrue("isStarted", ftpServer.isStarted());
+ assertFalse("isShutdown", ftpServer.isShutdown());
+ }
+
+ void testLogin_WithAccount() {
+ userAccount.accountRequiredForLogin = true
+ ftpClientConnect()
+ LOG.info("Logging in as $USERNAME/$PASSWORD with $ACCOUNT")
+ assert ftpClient.login(USERNAME, PASSWORD, ACCOUNT)
+ verifyReplyCode("login with $USERNAME/$PASSWORD with $ACCOUNT", 230)
+ }
+
+ void testMkd() {
+ ftpClientConnectAndLogin()
+
+ def DIR = p(HOME_DIR, 'NewDir')
+ assert ftpClient.makeDirectory(DIR)
+ verifyReplyCode("makeDirectory", 257)
+ assert fileSystem.isDirectory(DIR)
+ }
+
+ void testMode() {
+ ftpClientConnectAndLogin()
+ assert ftpClient.setFileTransferMode(FTP.STREAM_TRANSFER_MODE);
+ verifyReplyCode("MODE", 200)
+ }
+
+ void testNlst() {
+ fileSystem.add(new FileEntry(path: p(SUBDIR, FILENAME1)))
+ fileSystem.add(new DirectoryEntry(path: p(SUBDIR, SUBDIR_NAME2)))
+
+ ftpClientConnectAndLogin()
+
+ String[] filenames = ftpClient.listNames(SUBDIR)
+ assert filenames as Set == [FILENAME1, SUBDIR_NAME2] as Set
+ verifyReplyCode("listNames", 226)
+ }
+
+ void testNoop() {
+ ftpClientConnectAndLogin()
+ assert ftpClient.sendNoOp()
+ verifyReplyCode("NOOP", 200)
+ }
+
+ void testPasv_Nlst() {
+ fileSystem.add(new FileEntry(path: p(SUBDIR, FILENAME1)))
+ fileSystem.add(new FileEntry(path: p(SUBDIR, FILENAME2)))
+
+ ftpClientConnectAndLogin()
+ ftpClient.enterLocalPassiveMode();
+
+ String[] filenames = ftpClient.listNames(SUBDIR)
+ assert filenames == [FILENAME1, FILENAME2]
+ verifyReplyCode("listNames", 226)
+ }
+
+ void testPwd() {
+ ftpClientConnectAndLogin()
+ assert ftpClient.printWorkingDirectory() == HOME_DIR
+ verifyReplyCode("printWorkingDirectory", 257)
+ }
+
+ void testQuit() {
+ ftpClientConnect()
+ ftpClient.quit()
+ verifyReplyCode("quit", 221)
+ }
+
+ void testRein() {
+ ftpClientConnectAndLogin()
+ assert ftpClient.rein() == 220
+ assert ftpClient.cdup() == 530 // now logged out
+ }
+
+ void testRest() {
+ ftpClientConnectAndLogin()
+ assert ftpClient.rest("marker") == 350
+ }
+
+ void testRetr() {
+ fileSystem.add(new FileEntry(path: FILE1, contents: ASCII_DATA))
+
+ ftpClientConnectAndLogin()
+
+ LOG.info("Get File for remotePath [$FILE1]")
+ def outputStream = new ByteArrayOutputStream()
+ assert ftpClient.retrieveFile(FILE1, outputStream)
+ LOG.info("File contents=[${outputStream.toString()}]")
+ assert outputStream.toString() == ASCII_DATA
+ }
+
+ void testRmd() {
+ ftpClientConnectAndLogin()
+
+ assert ftpClient.removeDirectory(SUBDIR)
+ verifyReplyCode("removeDirectory", 250)
+ assert !fileSystem.exists(SUBDIR)
+ }
+
+ void testRename() { // RNFR and RNTO
+ fileSystem.add(new FileEntry(FILE1))
+
+ ftpClientConnectAndLogin()
+
+ assert ftpClient.rename(FILE1, FILE1 + "NEW")
+ verifyReplyCode("rename", 250)
+ assert !fileSystem.exists(FILE1)
+ assert fileSystem.exists(FILE1 + "NEW")
+ }
+
+ void testSite() {
+ ftpClientConnectAndLogin()
+ assert ftpClient.site("parameters,1,2,3") == 200
+ }
+
+ void testSmnt() {
+ ftpClientConnectAndLogin()
+ assert ftpClient.smnt("dir") == 250
+ }
+
+ void testStat() {
+ ftpClientConnectAndLogin()
+ def status = ftpClient.getStatus()
+ assert status.contains('Connected')
+ verifyReplyCode("stat", 211)
+ }
+
+ void testStor() {
+ ftpClientConnectAndLogin()
+
+ LOG.info("Put File for local path [$FILE1]")
+ def inputStream = new ByteArrayInputStream(ASCII_DATA.getBytes())
+ assert ftpClient.storeFile(FILENAME1, inputStream) // relative to homeDirectory
+ def contents = fileSystem.getEntry(FILE1).createInputStream().text
+ LOG.info("File contents=[" + contents + "]")
+ assert contents == ASCII_DATA
+ }
+
+ void testStou() {
+ ftpClientConnectAndLogin()
+
+ def inputStream = new ByteArrayInputStream(ASCII_DATA.getBytes())
+ assert ftpClient.storeUniqueFile(FILENAME1, inputStream)
+
+ def names = fileSystem.listNames(HOME_DIR)
+ def filename = names.find {name -> name.startsWith(FILENAME1) }
+ assert filename
+
+ def contents = fileSystem.getEntry(p(HOME_DIR, filename)).createInputStream().text
+ LOG.info("File contents=[" + contents + "]")
+ assert contents == ASCII_DATA
+ }
+
+ void testStru() {
+ ftpClientConnectAndLogin()
+ assert ftpClient.setFileStructure(FTP.FILE_STRUCTURE);
+ verifyReplyCode("STRU", 200)
+ }
+
+ void testSyst() {
+ ftpClientConnectAndLogin()
+
+ def systemName = ftpClient.getSystemName()
+ LOG.info("system name = [$systemName]")
+ assert systemName.contains('"' + SYSTEM_NAME + '"')
+ verifyReplyCode("getSystemName", 215)
+ }
+
+ void testType() {
+ ftpClientConnectAndLogin()
+ assert ftpClient.type(FTP.ASCII_FILE_TYPE)
+ verifyReplyCode("TYPE", 200)
+ }
+
+ void testUnrecognizedCommand() {
+ ftpClientConnectAndLogin()
+ assert ftpClient.sendCommand("XXX") == 502
+ verifyReplyCode("XXX", 502)
+ }
+
+ // -------------------------------------------------------------------------
+ // Test setup and tear-down
+ // -------------------------------------------------------------------------
+
+ /**
+ * Perform initialization before each test
+ * @see org.mockftpserver.test.AbstractTestCase#setUp()
+ */
+ void setUp() {
+ super.setUp()
+
+ for (int i = 0; i < BINARY_DATA.length; i++) {
+ BINARY_DATA[i] = (byte) i
+ }
+
+ ftpServer = new FakeFtpServer()
+ ftpServer.serverControlPort = PortTestUtil.getFtpServerControlPort()
+ ftpServer.systemName = SYSTEM_NAME
+
+ fileSystem = new WindowsFakeFileSystem()
+ fileSystem.createParentDirectoriesAutomatically = true
+ fileSystem.add(new DirectoryEntry(SUBDIR))
+ ftpServer.fileSystem = fileSystem
+
+ userAccount = new UserAccount(USERNAME, PASSWORD, HOME_DIR)
+ ftpServer.addUserAccount(userAccount)
+
+ ftpServer.start()
+ ftpClient = new FTPClient()
+ }
+
+ /**
+ * Perform cleanup after each test
+ * @see org.mockftpserver.test.AbstractTestCase#tearDown()
+ */
+ void tearDown() {
+ super.tearDown()
+ ftpServer.stop()
+ }
+
+ // -------------------------------------------------------------------------
+ // Internal Helper Methods
+ // -------------------------------------------------------------------------
+
+ private ftpClientConnectAndLogin() {
+ ftpClientConnect()
+ assert ftpClient.login(USERNAME, PASSWORD)
+ }
+
+ /**
+ * Connect to the server from the FTPClient
+ */
+ private void ftpClientConnect() {
+ def port = PortTestUtil.getFtpServerControlPort()
+ LOG.info("Conecting to $SERVER on port $port")
+ ftpClient.connect(SERVER, port)
+ verifyReplyCode("connect", 220)
+ }
+
+ /**
+ * Assert that the FtpClient reply code is equal to the expected value
+ *
+ * @param operation - the description of the operation performed used in the error message
+ * @param expectedReplyCode - the expected FtpClient reply code
+ */
+ private void verifyReplyCode(String operation, int expectedReplyCode) {
+ int replyCode = ftpClient.getReplyCode()
+ LOG.info("Reply: operation=\"" + operation + "\" replyCode=" + replyCode)
+ assertEquals("Unexpected replyCode for " + operation, expectedReplyCode, replyCode)
+ }
+
+ private void verifyFTPFile(FTPFile ftpFile, int type, String name, long size) {
+ LOG.info(ftpFile.toString())
+ assertEquals("type: " + ftpFile, type, ftpFile.getType())
+ assertEquals("name: " + ftpFile, name, ftpFile.getName())
+ assertEquals("size: " + ftpFile, size, ftpFile.getSize())
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/FakeFtpServerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/FakeFtpServerTest.groovy
new file mode 100644
index 0000000..e7e2597
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/FakeFtpServerTest.groovy
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.ReplyTextBundleAware
+import org.mockftpserver.core.server.AbstractFtpServer
+import org.mockftpserver.core.server.AbstractFtpServerTestCase
+import org.mockftpserver.core.session.Session
+
+/**
+ * Tests for FakeFtpServer.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class FakeFtpServerTest extends AbstractFtpServerTestCase {
+
+ def commandHandler
+ def commandHandler_NotServerConfigurationAware
+
+ //-------------------------------------------------------------------------
+ // Extra tests (Standard tests defined in superclass)
+ //-------------------------------------------------------------------------
+
+ void testSetCommandHandler_NotServerConfigurationAware() {
+ ftpServer.setCommandHandler("ZZZ", commandHandler_NotServerConfigurationAware)
+ assert ftpServer.getCommandHandler("ZZZ") == commandHandler_NotServerConfigurationAware
+ }
+
+ void testSetCommandHandler_ServerConfigurationAware() {
+ ftpServer.setCommandHandler("ZZZ", commandHandler)
+ assert ftpServer.getCommandHandler("ZZZ") == commandHandler
+ assert ftpServer == commandHandler.serverConfiguration
+ }
+
+ void testSetCommandHandler_ReplyTextBundleAware() {
+ def cmdHandler = new TestCommandHandlerReplyTextBundleAware()
+ ftpServer.setCommandHandler("ZZZ", cmdHandler)
+ assert ftpServer.getCommandHandler("ZZZ") == cmdHandler
+ assert ftpServer.replyTextBundle == cmdHandler.replyTextBundle
+ }
+
+ void testUserAccounts() {
+ def userAccount = new UserAccount(username: 'abc')
+
+ // addUserAccount()
+ ftpServer.addUserAccount(userAccount)
+ assert ftpServer.getUserAccount("abc") == userAccount
+
+ // setUserAccounts
+ def userAccounts = [userAccount]
+ ftpServer.userAccounts = userAccounts
+ assert ftpServer.getUserAccount("abc") == userAccount
+ }
+
+ void testHelpText() {
+ ftpServer.helpText = [a: 'aaaaa', b: 'bbbbb', '': 'default']
+ assert ftpServer.getHelpText('a') == 'aaaaa'
+ assert ftpServer.getHelpText('b') == 'bbbbb'
+ assert ftpServer.getHelpText('') == 'default'
+ assert ftpServer.getHelpText('unrecognized') == null
+ }
+
+ void testSystemName() {
+ assert ftpServer.systemName == "WINDOWS"
+ ftpServer.systemName = "abc"
+ assert ftpServer.systemName == "abc"
+ }
+
+ void testSystemStatus() {
+ assert ftpServer.systemStatus == "Connected"
+ ftpServer.systemStatus = "abc"
+ assert ftpServer.systemStatus == "abc"
+ }
+
+ void testReplyText() {
+ ftpServer.replyTextBaseName = "SampleReplyText"
+
+ ResourceBundle resourceBundle = ftpServer.replyTextBundle
+ assert resourceBundle.getString("110") == "Testing123"
+ }
+
+ //-------------------------------------------------------------------------
+ // Test set up
+ //-------------------------------------------------------------------------
+
+ void setUp() {
+ super.setUp();
+ commandHandler = new TestCommandHandler()
+ commandHandler_NotServerConfigurationAware = new TestCommandHandlerNotServerConfigurationAware()
+ }
+
+ //-------------------------------------------------------------------------
+ // Abstract method implementations
+ //-------------------------------------------------------------------------
+
+ protected AbstractFtpServer createFtpServer() {
+ return new FakeFtpServer();
+ }
+
+ protected CommandHandler createCommandHandler() {
+ return new TestCommandHandler();
+ }
+
+ protected void verifyCommandHandlerInitialized(CommandHandler commandHandler) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+}
+class TestCommandHandlerReplyTextBundleAware implements CommandHandler, ReplyTextBundleAware {
+ ResourceBundle replyTextBundle
+
+ public void handleCommand(Command command, Session session) {
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/FakeFtpServer_AlreadyStartedTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/FakeFtpServer_AlreadyStartedTest.groovy
new file mode 100644
index 0000000..be918c4
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/FakeFtpServer_AlreadyStartedTest.groovy
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake
+
+import org.mockftpserver.test.AbstractGroovyTestCase
+import org.mockftpserver.test.PortTestUtil
+
+class FakeFtpServer_AlreadyStartedTest extends AbstractGroovyTestCase {
+ private FakeFtpServer ftpServer1 = new FakeFtpServer()
+ private FakeFtpServer ftpServer2 = new FakeFtpServer()
+
+ void testStartServer_WhenAlreadyStarted() {
+ ftpServer1.setServerControlPort(PortTestUtil.getFtpServerControlPort())
+ ftpServer1.start();
+ Thread.sleep(200L); // give it some time to get started
+ assertEquals("started - after start()", true, ftpServer1.isStarted());
+
+ ftpServer2.setServerControlPort(PortTestUtil.getFtpServerControlPort())
+ ftpServer2.start();
+ log("started ftpServer2")
+ sleep(200L) // give it a chance to start and terminate
+ assert !ftpServer2.isStarted()
+ }
+
+ void tearDown() {
+ super.tearDown()
+ ftpServer1.stop();
+ ftpServer2.stop();
+ }
+}
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/FakeFtpServer_StartTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/FakeFtpServer_StartTest.groovy
new file mode 100644
index 0000000..2140ca4
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/FakeFtpServer_StartTest.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake
+
+import org.mockftpserver.core.server.AbstractFtpServer
+import org.mockftpserver.core.server.AbstractFtpServer_StartTestCase
+
+/**
+ * Tests for FakeFtpServer that require the server thread to be started.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class FakeFtpServer_StartTest extends AbstractFtpServer_StartTestCase {
+
+ protected AbstractFtpServer createFtpServer() {
+ return new FakeFtpServer();
+ }
+
+}
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/RunFakeFtpServer.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/RunFakeFtpServer.groovy
new file mode 100644
index 0000000..49b5b17
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/RunFakeFtpServer.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake
+
+import org.mockftpserver.fake.FakeFtpServer
+import org.mockftpserver.fake.UserAccount
+import org.mockftpserver.fake.filesystem.DirectoryEntry
+import org.mockftpserver.fake.filesystem.FileEntry
+import org.mockftpserver.fake.filesystem.UnixFakeFileSystem
+
+/**
+ * Run the FakeFtpServer with a minimal configuration for interactive testing and exploration.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class RunFakeFtpServer {
+
+ static final ANONYMOUS = 'anonymous'
+ static final HOME_DIR = '/home'
+
+ static main(args) {
+ def fileSystem = new UnixFakeFileSystem()
+ fileSystem.createParentDirectoriesAutomatically = true
+ fileSystem.add(new DirectoryEntry(HOME_DIR))
+ fileSystem.add(new DirectoryEntry("$HOME_DIR/subdir"))
+ fileSystem.add(new DirectoryEntry("$HOME_DIR/subdir2"))
+ fileSystem.add(new FileEntry(path: "$HOME_DIR/abc.txt", contents: '1234567890'))
+ fileSystem.add(new FileEntry(path: "$HOME_DIR/def.txt", contents: '1234567890'))
+ fileSystem.add(new FileEntry(path: "$HOME_DIR/subdir/xyz.txt", contents: '1234567890'))
+
+ def userAccount = new UserAccount(username: ANONYMOUS, passwordRequiredForLogin: false, homeDirectory: HOME_DIR)
+
+ def ftpServer = new FakeFtpServer()
+ ftpServer.fileSystem = fileSystem
+ ftpServer.userAccounts = [userAccount]
+ ftpServer.run()
+ }
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/StubServerConfiguration.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/StubServerConfiguration.groovy
new file mode 100644
index 0000000..09d39e4
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/StubServerConfiguration.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake
+
+import org.mockftpserver.fake.ServerConfiguration
+import org.mockftpserver.fake.UserAccount
+import org.mockftpserver.fake.filesystem.FileSystem
+
+/**
+ * Stub implementation of the {@link org.mockftpserver.fake.ServerConfiguration} interface for testing
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class StubServerConfiguration implements ServerConfiguration {
+
+ Map userAccounts = [:]
+ Map helpText = [:]
+ FileSystem fileSystem
+ String systemName = "WINDOWS"
+ String systemStatus
+
+ UserAccount getUserAccount(String username) {
+ (UserAccount) userAccounts[username]
+ }
+
+ public String getHelpText(String name) {
+ def key = name == null ? '' : name
+ return helpText[key]
+ }
+
+}
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/TestCommandHandler.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/TestCommandHandler.groovy
new file mode 100644
index 0000000..af055ef
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/TestCommandHandler.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.session.Session
+import org.mockftpserver.fake.command.AbstractFakeCommandHandler
+
+/**
+ * Test CommandHandler - subclass of AbstractFakeCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class TestCommandHandler extends AbstractFakeCommandHandler {
+
+ protected void handle(Command command, Session session) {
+ // Do nothing
+ }
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/TestCommandHandlerNotServerConfigurationAware.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/TestCommandHandlerNotServerConfigurationAware.groovy
new file mode 100644
index 0000000..8932394
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/TestCommandHandlerNotServerConfigurationAware.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.session.Session
+
+/**
+ * Test CommandHandler - does not implement ServerConfigurationAware
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class TestCommandHandlerNotServerConfigurationAware implements CommandHandler {
+
+ public void handleCommand(Command command, Session session) {
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/UserAccountTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/UserAccountTest.groovy
new file mode 100644
index 0000000..93683a3
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/UserAccountTest.groovy
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake
+
+import org.mockftpserver.fake.filesystem.FileEntry
+import org.mockftpserver.fake.filesystem.FileSystemEntry
+import org.mockftpserver.fake.filesystem.Permissions
+import org.mockftpserver.test.AbstractGroovyTestCase
+
+/**
+ * Tests for UserAccount
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class UserAccountTest extends AbstractGroovyTestCase {
+
+ private static final USERNAME = "user123"
+ private static final PASSWORD = "password123"
+ private static final HOME_DIR = "/usr/user123"
+ private static final GROUP = 'group'
+
+ private UserAccount userAccount
+
+ void testConstructor() {
+ def acct = new UserAccount(USERNAME, PASSWORD, HOME_DIR)
+ assert acct.username == USERNAME
+ assert acct.password == PASSWORD
+ assert acct.homeDirectory == HOME_DIR
+ }
+
+ void testGetPrimaryGroup() {
+ assert userAccount.primaryGroup == UserAccount.DEFAULT_GROUP
+
+ userAccount.groups = ['abc']
+ assert userAccount.primaryGroup == 'abc'
+
+ userAccount.groups.add('def')
+ assert userAccount.primaryGroup == 'abc'
+
+ userAccount.groups = []
+ assert userAccount.primaryGroup == UserAccount.DEFAULT_GROUP
+ }
+
+ void testIsValidPassword() {
+ userAccount.username = USERNAME
+ userAccount.password = PASSWORD
+ assert userAccount.isValidPassword(PASSWORD)
+
+ assert !userAccount.isValidPassword("")
+ assert !userAccount.isValidPassword("wrong")
+ assert !userAccount.isValidPassword(null)
+ }
+
+ void testIsValidPassword_UsernameNullOrEmpty() {
+ userAccount.password = PASSWORD
+ shouldFailWithMessageContaining('username') { userAccount.isValidPassword(PASSWORD) }
+
+ userAccount.username = ''
+ shouldFailWithMessageContaining('username') { userAccount.isValidPassword(PASSWORD) }
+ }
+
+ void testIsValidPassword_OverrideComparePassword() {
+ def customUserAccount = new CustomUserAccount()
+ customUserAccount.username = USERNAME
+ customUserAccount.password = PASSWORD
+ println customUserAccount
+ assert customUserAccount.isValidPassword(PASSWORD) == false
+ assert customUserAccount.isValidPassword(PASSWORD + "123")
+ }
+
+ void testIsValidPassword_PasswordNotCheckedDuringValidation() {
+ userAccount.username = USERNAME
+ userAccount.password = PASSWORD
+ userAccount.passwordCheckedDuringValidation = false
+ assert userAccount.isValidPassword("wrong")
+ }
+
+ void testIsValid() {
+ assert !userAccount.valid
+ userAccount.homeDirectory = ""
+ assert !userAccount.valid
+ userAccount.homeDirectory = "/abc"
+ assert userAccount.valid
+ }
+
+ void testCanRead() {
+ // No file permissions - readable by all
+ testCanRead(USERNAME, GROUP, null, true)
+
+ // UserAccount has no username or group; use World permissions
+ testCanRead(USERNAME, GROUP, '------r--', true)
+ testCanRead(USERNAME, GROUP, 'rwxrwx-wx', false)
+
+ userAccount.username = USERNAME
+ userAccount.groups = [GROUP]
+
+ testCanRead(USERNAME, GROUP, 'rwxrwxrwx', true) // ALL
+ testCanRead(USERNAME, GROUP, '---------', false) // NONE
+
+ testCanRead(USERNAME, null, 'r--------', true) // User
+ testCanRead(USERNAME, null, '-wxrwxrwx', false)
+
+ testCanRead(null, GROUP, '---r-----', true) // Group
+ testCanRead(null, GROUP, 'rwx-wxrwx', false)
+
+ testCanRead(null, null, '------r--', true) // World
+ testCanRead(null, null, 'rwxrwx-wx', false)
+ }
+
+ void testCanWrite() {
+ // No file permissions - writable by all
+ testCanWrite(USERNAME, GROUP, null, true)
+
+ // UserAccount has no username or group; use World permissions
+ testCanWrite(USERNAME, GROUP, '-------w-', true)
+ testCanWrite(USERNAME, GROUP, 'rwxrwxr-x', false)
+
+ userAccount.username = USERNAME
+ userAccount.groups = [GROUP]
+
+ testCanWrite(USERNAME, GROUP, 'rwxrwxrwx', true) // ALL
+ testCanWrite(USERNAME, GROUP, '---------', false) // NONE
+
+ testCanWrite(USERNAME, null, '-w-------', true) // User
+ testCanWrite(USERNAME, null, 'r-xrwxrwx', false)
+
+ testCanWrite(null, GROUP, '----w----', true) // Group
+ testCanWrite(null, GROUP, 'rwxr-xrwx', false)
+
+ testCanWrite(null, null, '-------w-', true) // World
+ testCanWrite(null, null, 'rwxrwxr-x', false)
+ }
+
+ void testCanExecute() {
+ // No file permissions - executable by all
+ testCanExecute(USERNAME, GROUP, null, true)
+
+ // UserAccount has no username or group; use World permissions
+ testCanExecute(USERNAME, GROUP, '--------x', true)
+ testCanExecute(USERNAME, GROUP, 'rwxrwxrw-', false)
+
+ userAccount.username = USERNAME
+ userAccount.groups = [GROUP]
+
+ testCanExecute(USERNAME, GROUP, 'rwxrwxrwx', true) // ALL
+ testCanExecute(USERNAME, GROUP, '---------', false) // NONE
+
+ testCanExecute(USERNAME, null, '--x------', true) // User
+ testCanExecute(USERNAME, null, 'rw-rwxrwx', false)
+
+ testCanExecute(null, GROUP, '-----x---', true) // Group
+ testCanExecute(null, GROUP, 'rwxrw-rwx', false)
+
+ testCanExecute(null, null, '--------x', true) // World
+ testCanExecute(null, null, 'rwxrwxrw-', false)
+ }
+
+ void testDefaultPermissions() {
+ assert userAccount.defaultPermissionsForNewFile == new Permissions('rw-rw-rw-')
+ assert userAccount.defaultPermissionsForNewDirectory == Permissions.ALL
+ }
+
+ //--------------------------------------------------------------------------
+ // Helper Methods
+ //--------------------------------------------------------------------------
+
+ private void testCanRead(owner, group, permissionsString, expectedResult) {
+ def file = createFileEntry(owner, permissionsString, group)
+ assert userAccount.canRead(file) == expectedResult, file
+ }
+
+ private void testCanWrite(owner, group, permissionsString, expectedResult) {
+ def file = createFileEntry(owner, permissionsString, group)
+ assert userAccount.canWrite(file) == expectedResult, file
+ }
+
+ private void testCanExecute(owner, group, permissionsString, expectedResult) {
+ def file = createFileEntry(owner, permissionsString, group)
+ assert userAccount.canExecute(file) == expectedResult, file
+ }
+
+ private FileSystemEntry createFileEntry(owner, permissionsString, group) {
+ def permissions = permissionsString ? new Permissions(permissionsString) : null
+ return new FileEntry(path: '', owner: owner, group: group, permissions: permissions)
+ }
+
+ void setUp() {
+ super.setUp()
+ userAccount = new UserAccount()
+ }
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AborCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AborCommandHandlerTest.groovy
new file mode 100644
index 0000000..95f0201
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AborCommandHandlerTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+
+/**
+ * Tests for AborCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class AborCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ void testHandleCommand() {
+ handleCommand([])
+ assertSessionReply(ReplyCodes.ABOR_OK, 'abor')
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new AborCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.ABOR, [])
+ }
+
+ void setUp() {
+ super.setUp()
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AbstractFakeCommandHandlerTestCase.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AbstractFakeCommandHandlerTestCase.groovy
new file mode 100644
index 0000000..2423aec
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AbstractFakeCommandHandlerTestCase.groovy
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.core.session.SessionKeys
+import org.mockftpserver.core.session.StubSession
+import org.mockftpserver.fake.StubServerConfiguration
+import org.mockftpserver.fake.UserAccount
+import org.mockftpserver.fake.filesystem.DirectoryEntry
+import org.mockftpserver.fake.filesystem.FileEntry
+import org.mockftpserver.fake.filesystem.FileSystemException
+import org.mockftpserver.fake.filesystem.TestUnixFakeFileSystem
+import org.mockftpserver.test.AbstractGroovyTestCase
+import org.mockftpserver.test.StubResourceBundle
+
+/**
+ * Abstract superclass for CommandHandler tests
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+abstract class AbstractFakeCommandHandlerTestCase extends AbstractGroovyTestCase {
+
+ protected static final ERROR_MESSAGE_KEY = 'msgkey'
+
+ protected session
+ protected serverConfiguration
+ protected replyTextBundle
+ protected commandHandler
+ protected fileSystem
+ protected userAccount
+
+ /** Set this to false to skip the test that verifies that the CommandHandler requires a logged in user */
+ boolean testNotLoggedIn = true
+
+ //-------------------------------------------------------------------------
+ // Tests (common to all subclasses)
+ //-------------------------------------------------------------------------
+
+ void testHandleCommand_ServerConfigurationIsNull() {
+ commandHandler.serverConfiguration = null
+ def command = createValidCommand()
+ shouldFailWithMessageContaining("serverConfiguration") { commandHandler.handleCommand(command, session) }
+ }
+
+ void testHandleCommand_CommandIsNull() {
+ shouldFailWithMessageContaining("command") { commandHandler.handleCommand(null, session) }
+ }
+
+ void testHandleCommand_SessionIsNull() {
+ def command = createValidCommand()
+ shouldFailWithMessageContaining("session") { commandHandler.handleCommand(command, null) }
+ }
+
+ void testHandleCommand_NotLoggedIn() {
+ if (getProperty('testNotLoggedIn')) {
+ def command = createValidCommand()
+ session.removeAttribute(SessionKeys.USER_ACCOUNT)
+ commandHandler.handleCommand(command, session)
+ assertSessionReply(ReplyCodes.NOT_LOGGED_IN)
+ }
+ }
+
+ //-------------------------------------------------------------------------
+ // Abstract Method Declarations (must be implemented by all subclasses)
+ //-------------------------------------------------------------------------
+
+ /**
+ * Create and return a new instance of the CommandHandler class under test. Concrete subclasses must implement.
+ */
+ abstract CommandHandler createCommandHandler()
+
+ /**
+ * Create and return a valid instance of the Command for the CommandHandler class
+ * under test. Concrete subclasses must implement.
+ */
+ abstract Command createValidCommand()
+
+ //-------------------------------------------------------------------------
+ // Test Setup
+ //-------------------------------------------------------------------------
+
+ void setUp() {
+ super.setUp()
+ session = new StubSession()
+ serverConfiguration = new StubServerConfiguration()
+ replyTextBundle = new StubResourceBundle()
+ fileSystem = new TestUnixFakeFileSystem()
+ fileSystem.createParentDirectoriesAutomatically = true
+ serverConfiguration.setFileSystem(fileSystem)
+
+ userAccount = new UserAccount()
+ session.setAttribute(SessionKeys.USER_ACCOUNT, userAccount)
+
+ commandHandler = createCommandHandler()
+ commandHandler.serverConfiguration = serverConfiguration
+ commandHandler.replyTextBundle = replyTextBundle
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * Perform a test of the handleCommand() method on the specified command
+ * parameters, which are missing a required parameter for this CommandHandler.
+ */
+ protected void testHandleCommand_MissingRequiredParameter(List commandParameters) {
+ commandHandler.handleCommand(createCommand(commandParameters), session)
+ assertSessionReply(ReplyCodes.COMMAND_SYNTAX_ERROR)
+ }
+
+ /**
+ * Perform a test of the handleCommand() method on the specified command
+ * parameters, which are missing a required parameter for this CommandHandler.
+ */
+ protected testHandleCommand_MissingRequiredSessionAttribute() {
+ def command = createValidCommand()
+ commandHandler.handleCommand(command, session)
+ assertSessionReply(ReplyCodes.ILLEGAL_STATE)
+ }
+
+ /**
+ * @return a new Command with the specified parameters for this CommandHandler
+ */
+ protected Command createCommand(List commandParameters) {
+ new Command(createValidCommand().name, commandParameters)
+ }
+
+ /**
+ * Invoke the handleCommand() method for the current CommandHandler, passing in
+ * the specified parameters
+ * @param parameters - the List of command parameters; may be empty, but not null
+ */
+ protected void handleCommand(List parameters) {
+ commandHandler.handleCommand(createCommand(parameters), session)
+ }
+
+ /**
+ * Assert that the specified reply code and message containing text was sent through the session.
+ * @param expectedReplyCode - the expected reply code
+ * @param text - the text expected within the reply message; defaults to the reply code as a String
+ */
+ protected assertSessionReply(int expectedReplyCode, text = expectedReplyCode as String) {
+ assertSessionReply(0, expectedReplyCode, text)
+ }
+
+ /**
+ * Assert that the specified reply code and message containing text was sent through the session.
+ * @param replyIndex - the index of the reply to compare
+ * @param expectedReplyCode - the expected reply code
+ * @param text - the text expected within the reply message; defaults to the reply code as a String
+ */
+ protected assertSessionReply(int replyIndex, int expectedReplyCode, text = expectedReplyCode as String) {
+ LOG.info(session.toString())
+ String actualMessage = session.getReplyMessage(replyIndex)
+ def actualReplyCode = session.getReplyCode(replyIndex)
+ assert actualReplyCode == expectedReplyCode
+ if (text instanceof List) {
+ text.each { assert actualMessage.contains(it), "[$actualMessage] does not contain [$it]" }
+ }
+ else {
+ assert actualMessage.contains(text), "[$actualMessage] does not contain [$text]"
+ }
+ }
+
+ /**
+ * Assert that the specified reply codes were sent through the session.
+ * @param replyCodes - the List of expected sent reply codes
+ */
+ protected assertSessionReplies(List replyCodes) {
+ LOG.info(session.toString())
+ replyCodes.eachWithIndex {replyCode, replyIndex ->
+ assertSessionReply(replyIndex, replyCode)
+ }
+ }
+
+ /**
+ * Assert that the specified data was sent through the session.
+ * @param expectedData - the expected data
+ */
+ protected assertSessionData(String expectedData) {
+ def actual = session.sentData[0]
+ assert actual != null, "No data for index [0] sent for $session"
+ assert actual == expectedData
+ }
+
+ /**
+ * Assert that the specified data was sent through the session, terminated by an end-of-line.
+ * @param expectedData - the expected data
+ */
+ protected assertSessionDataWithEndOfLine(String expectedData) {
+ assertSessionData(expectedData + endOfLine())
+ }
+
+ /**
+ * Assert that the data sent through the session terminated with an end-of-line.
+ */
+ protected assertSessionDataEndsWithEndOfLine() {
+ assert session.sentData[0].endsWith(endOfLine())
+ }
+
+ /**
+ * Execute the handleCommand() method with the specified parameters and
+ * assert that the standard SEND DATA replies were sent through the session.
+ * @param parameters - the command parameters to use; defaults to []
+ * @param finalReplyCode - the expected final reply code; defaults to ReplyCodes.TRANSFER_DATA_FINAL_OK
+ */
+ protected handleCommandAndVerifySendDataReplies(parameters = [], int finalReplyCode = ReplyCodes.TRANSFER_DATA_FINAL_OK) {
+ handleCommand(parameters)
+ assertSessionReplies([ReplyCodes.TRANSFER_DATA_INITIAL_OK, finalReplyCode])
+ }
+
+ /**
+ * Execute the handleCommand() method with the specified parameters and
+ * assert that the standard SEND DATA replies were sent through the session.
+ * @param parameters - the command parameters to use
+ * @param initialReplyMessageKey - the expected reply message key for the initial reply
+ * @param finalReplyMessageKey - the expected reply message key for the final reply
+ * @param finalReplyCode - the expected final reply code; defaults to ReplyCodes.TRANSFER_DATA_FINAL_OK
+ */
+ protected handleCommandAndVerifySendDataReplies(parameters, String initialReplyMessageKey, String finalReplyMessageKey, int finalReplyCode = ReplyCodes.TRANSFER_DATA_FINAL_OK) {
+ handleCommand(parameters)
+ assertSessionReply(0, ReplyCodes.TRANSFER_DATA_INITIAL_OK, initialReplyMessageKey)
+ assertSessionReply(1, finalReplyCode, finalReplyMessageKey)
+ }
+
+ /**
+ * Override the named method for the specified object instance
+ * @param object - the object instance
+ * @param methodName - the name of the method to override
+ * @param newMethod - the Closure representing the new method for this single instance
+ */
+ protected void overrideMethod(object, String methodName, Closure newMethod) {
+ LOG.info("Overriding method [$methodName] for class [${object.class}]")
+ def emc = new ExpandoMetaClass(object.class, false)
+ emc."$methodName" = newMethod
+ emc.initialize()
+ object.metaClass = emc
+ }
+
+ /**
+ * Override the named method (that takes a single String arg) of the fileSystem object to throw a (generic) FileSystemException
+ * @param methodName - the name of the fileSystem method to override
+ */
+ protected void overrideMethodToThrowFileSystemException(String methodName) {
+ def newMethod = {String path -> throw new FileSystemException("Error thrown by method [$methodName]", ERROR_MESSAGE_KEY) }
+ overrideMethod(fileSystem, methodName, newMethod)
+ }
+
+ /**
+ * Set the current directory within the session
+ * @param path - the new path value for the current directory
+ */
+ protected void setCurrentDirectory(String path) {
+ session.setAttribute(SessionKeys.CURRENT_DIRECTORY, path)
+ }
+
+ /**
+ * Convenience method to return the end-of-line character(s) for the current CommandHandler.
+ */
+ protected endOfLine() {
+ commandHandler.endOfLine()
+ }
+
+ /**
+ * Create a new directory entry with the specified path in the file system
+ * @param path - the path of the new directory entry
+ * @return the newly created DirectoryEntry
+ */
+ protected DirectoryEntry createDirectory(String path) {
+ DirectoryEntry entry = new DirectoryEntry(path)
+ fileSystem.add(entry)
+ return entry
+ }
+
+ /**
+ * Create a new file entry with the specified path in the file system
+ * @param path - the path of the new file entry
+ * @param contents - the contents for the file; defaults to null
+ * @return the newly created FileEntry
+ */
+ protected FileEntry createFile(String path, contents = null) {
+ FileEntry entry = new FileEntry(path: path, contents: contents)
+ fileSystem.add(entry)
+ return entry
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AbstractStoreFileCommandHandlerTestCase.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AbstractStoreFileCommandHandlerTestCase.groovy
new file mode 100644
index 0000000..62fe519
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AbstractStoreFileCommandHandlerTestCase.groovy
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.fake.filesystem.FileEntry
+import org.mockftpserver.fake.filesystem.FileSystemEntry
+import org.mockftpserver.fake.filesystem.FileSystemException
+import org.mockftpserver.fake.filesystem.Permissions
+
+/**
+ * Abstract superclass for tests of Fake CommandHandlers that store a file (STOR, STOU, APPE)
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+abstract class AbstractStoreFileCommandHandlerTestCase extends AbstractFakeCommandHandlerTestCase {
+
+ protected static final DIR = "/"
+ protected static final FILENAME = "file.txt"
+ protected static final FILE = p(DIR, FILENAME)
+ protected static final CONTENTS = "abc"
+
+ //-------------------------------------------------------------------------
+ // Tests Common to All Subclasses
+ //-------------------------------------------------------------------------
+
+ void testHandleCommand_NoWriteAccessToExistingFile() {
+ fileSystem.add(new FileEntry(path: FILE))
+ fileSystem.getEntry(FILE).permissions = Permissions.NONE
+ handleCommand([FILE])
+ assertSessionReply(ReplyCodes.WRITE_FILE_ERROR, ['filesystem.cannotWrite', FILE])
+ }
+
+ void testHandleCommand_NoWriteAccessToDirectoryForNewFile() {
+ fileSystem.getEntry(DIR).permissions = new Permissions('r-xr-xr-x')
+ handleCommand([FILE])
+ assertSessionReply(ReplyCodes.WRITE_FILE_ERROR, ['filesystem.cannotWrite', DIR])
+ }
+
+ void testHandleCommand_NoExecuteAccessToDirectory() {
+ fileSystem.add(new FileEntry(path: FILE))
+ fileSystem.getEntry(DIR).permissions = new Permissions('rw-rw-rw-')
+ handleCommand([FILE])
+ assertSessionReply(ReplyCodes.WRITE_FILE_ERROR, ['filesystem.cannotExecute', DIR])
+ }
+
+ void testHandleCommand_ThrowsFileSystemException() {
+ fileSystem.addMethodException = new FileSystemException("bad", ERROR_MESSAGE_KEY)
+
+ handleCommand([FILE])
+ assertSessionReply(0, ReplyCodes.TRANSFER_DATA_INITIAL_OK)
+ assertSessionReply(1, ReplyCodes.WRITE_FILE_ERROR, ERROR_MESSAGE_KEY)
+ }
+
+ //-------------------------------------------------------------------------
+ // Abstract Method Declarations
+ //-------------------------------------------------------------------------
+
+ /**
+ * Verify the created output file and return its full path
+ * @return the full path to the created output file; the path may be absolute or relative
+ */
+ protected abstract String verifyOutputFile()
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ protected void testHandleCommand(List parameters, String messageKey, String contents) {
+ session.dataToRead = CONTENTS.bytes
+ handleCommand(parameters)
+ assertSessionReply(0, ReplyCodes.TRANSFER_DATA_INITIAL_OK)
+ assertSessionReply(1, ReplyCodes.TRANSFER_DATA_FINAL_OK, messageKey)
+
+ def outputFile = verifyOutputFile()
+
+ FileSystemEntry fileEntry = fileSystem.getEntry(outputFile)
+ def actualContents = fileEntry.createInputStream().text
+ assert actualContents == contents
+ assert fileEntry.permissions == userAccount.defaultPermissionsForNewFile
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.APPE, [FILE])
+ }
+
+ void setUp() {
+ super.setUp()
+ createDirectory(DIR)
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AcctCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AcctCommandHandlerTest.groovy
new file mode 100644
index 0000000..1f92b76
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AcctCommandHandlerTest.groovy
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.core.session.SessionKeys
+
+/**
+ * Tests for AcctCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class AcctCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ def USERNAME = "user123"
+ def ACCOUNT_NAME = "account123"
+
+ boolean testNotLoggedIn = false
+
+ void testHandleCommand() {
+ handleCommand([ACCOUNT_NAME])
+ assertSessionReply(ReplyCodes.ACCT_OK, ['acct', USERNAME])
+ assertAccountNameInSession(true)
+ }
+
+ void testHandleCommand_UsernameNotSetInSession() {
+ session.removeAttribute(SessionKeys.USERNAME)
+ testHandleCommand_MissingRequiredSessionAttribute()
+ assertAccountNameInSession(false)
+ }
+
+ void testHandleCommand_MissingAccountNameParameter() {
+ testHandleCommand_MissingRequiredParameter([])
+ assertAccountNameInSession(false)
+ }
+
+ //-------------------------------------------------------------------------
+ // Abstract and Overridden Methods
+ //-------------------------------------------------------------------------
+
+ void setUp() {
+ super.setUp()
+ session.setAttribute(SessionKeys.USERNAME, USERNAME)
+ }
+
+ CommandHandler createCommandHandler() {
+ new AcctCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.ACCT, [ACCOUNT_NAME])
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * Assert that the account name is stored in the session, depending on the value of isAccountNameInSession.
+ * @param isAccountNameInSession - true if the account name is expected in the session; false if it is not expected
+ */
+ private void assertAccountNameInSession(boolean isAccountNameInSession) {
+ def expectedValue = isAccountNameInSession ? ACCOUNT_NAME : null
+ assert session.getAttribute(SessionKeys.ACCOUNT_NAME) == expectedValue
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AlloCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AlloCommandHandlerTest.groovy
new file mode 100644
index 0000000..5016cc5
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AlloCommandHandlerTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+
+/**
+ * Tests for AlloCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class AlloCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ void testHandleCommand() {
+ handleCommand([])
+ assertSessionReply(ReplyCodes.ALLO_OK, 'allo')
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new AlloCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.ALLO, [])
+ }
+
+ void setUp() {
+ super.setUp()
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AppeCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AppeCommandHandlerTest.groovy
new file mode 100644
index 0000000..6fc4434
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/AppeCommandHandlerTest.groovy
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.fake.filesystem.FileEntry
+import org.mockftpserver.fake.filesystem.Permissions
+
+/**
+ * Tests for AppeCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class AppeCommandHandlerTest extends AbstractStoreFileCommandHandlerTestCase {
+
+ void testHandleCommand_MissingPathParameter() {
+ testHandleCommand_MissingRequiredParameter([])
+ }
+
+ void testHandleCommand_AbsolutePath() {
+ userAccount.defaultPermissionsForNewFile = Permissions.NONE
+ testHandleCommand([FILE], 'appe', CONTENTS)
+ }
+
+ void testHandleCommand_AbsolutePath_FileAlreadyExists() {
+ def ORIGINAL_CONTENTS = '123 456 789'
+ fileSystem.add(new FileEntry(path: FILE, contents: ORIGINAL_CONTENTS))
+ testHandleCommand([FILE], 'appe', ORIGINAL_CONTENTS + CONTENTS)
+ }
+
+ void testHandleCommand_RelativePath() {
+ setCurrentDirectory(DIR)
+ testHandleCommand([FILENAME], 'appe', CONTENTS)
+ }
+
+ void testHandleCommand_PathSpecifiesAnExistingDirectory() {
+ createDirectory(FILE)
+ handleCommand([FILE])
+ assertSessionReply(ReplyCodes.FILENAME_NOT_VALID, FILE)
+ }
+
+ void testHandleCommand_ParentDirectoryDoesNotExist() {
+ def NO_SUCH_DIR = "/path/DoesNotExist"
+ handleCommand([p(NO_SUCH_DIR, FILENAME)])
+ assertSessionReply(ReplyCodes.FILENAME_NOT_VALID, NO_SUCH_DIR)
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new AppeCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.APPE, [FILE])
+ }
+
+ void setUp() {
+ super.setUp()
+ }
+
+ protected String verifyOutputFile() {
+ assert fileSystem.isFile(FILE)
+ assert session.getReplyMessage(1).contains(FILENAME)
+ return FILE
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/CdupCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/CdupCommandHandlerTest.groovy
new file mode 100644
index 0000000..763cc00
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/CdupCommandHandlerTest.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.core.session.SessionKeys
+import org.mockftpserver.fake.filesystem.Permissions
+
+/**
+ * Tests for CdupCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class CdupCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ def DIR = "/usr"
+ def SUBDIR = "${DIR}/sub"
+
+ void testHandleCommand() {
+ setCurrentDirectory(SUBDIR)
+ handleCommand([])
+ assertSessionReply(ReplyCodes.CDUP_OK, ['cdup', DIR])
+ assert session.getAttribute(SessionKeys.CURRENT_DIRECTORY) == DIR
+ }
+
+ void testHandleCommand_NoParentDirectory() {
+ setCurrentDirectory('/')
+ handleCommand([])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.parentDirectoryDoesNotExist', '/'])
+ assert session.getAttribute(SessionKeys.CURRENT_DIRECTORY) == '/'
+ }
+
+ void testHandleCommand_NoExecuteAccessToDirectory() {
+ setCurrentDirectory(SUBDIR)
+ def dir = fileSystem.getEntry(DIR)
+ dir.permissions = new Permissions('rw-rw-rw-')
+ handleCommand([])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.cannotExecute', DIR])
+ assert session.getAttribute(SessionKeys.CURRENT_DIRECTORY) == SUBDIR
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new CdupCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.CDUP, [])
+ }
+
+ void setUp() {
+ super.setUp()
+ createDirectory(SUBDIR)
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/CwdCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/CwdCommandHandlerTest.groovy
new file mode 100644
index 0000000..d63210b
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/CwdCommandHandlerTest.groovy
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.core.session.SessionKeys
+import org.mockftpserver.fake.filesystem.Permissions
+
+/**
+ * Tests for CwdCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class CwdCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ def DIR = "/usr"
+
+ void testHandleCommand() {
+ createDirectory(DIR)
+ handleCommand([DIR])
+ assertSessionReply(ReplyCodes.CWD_OK, ['cwd', DIR])
+ assert session.getAttribute(SessionKeys.CURRENT_DIRECTORY) == DIR
+ }
+
+ void testHandleCommand_PathIsRelative() {
+ def SUB = "sub"
+ createDirectory(p(DIR, SUB))
+ session.setAttribute(SessionKeys.CURRENT_DIRECTORY, DIR)
+ handleCommand([SUB])
+ assertSessionReply(ReplyCodes.CWD_OK, ['cwd', SUB])
+ assert session.getAttribute(SessionKeys.CURRENT_DIRECTORY) == p(DIR, SUB)
+ }
+
+ void testHandleCommand_PathDoesNotExistInFileSystem() {
+ handleCommand([DIR])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.doesNotExist', DIR])
+ assert session.getAttribute(SessionKeys.CURRENT_DIRECTORY) == null
+ }
+
+ void testHandleCommand_PathSpecifiesAFile() {
+ createFile(DIR)
+ handleCommand([DIR])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.isNotADirectory', DIR])
+ assert session.getAttribute(SessionKeys.CURRENT_DIRECTORY) == null
+ }
+
+ void testHandleCommand_NoExecuteAccessToParentDirectory() {
+ def dir = createDirectory(DIR)
+ dir.permissions = new Permissions('rw-rw-rw-')
+ handleCommand([DIR])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.cannotExecute', DIR])
+ assert session.getAttribute(SessionKeys.CURRENT_DIRECTORY) == null
+ }
+
+ void testHandleCommand_MissingPathParameter() {
+ testHandleCommand_MissingRequiredParameter([])
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new CwdCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.CWD, [DIR])
+ }
+
+ void setUp() {
+ super.setUp()
+ userAccount.username = 'user'
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/DeleCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/DeleCommandHandlerTest.groovy
new file mode 100644
index 0000000..8becd17
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/DeleCommandHandlerTest.groovy
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.fake.filesystem.FileSystemException
+import org.mockftpserver.fake.filesystem.Permissions
+
+/**
+ * Tests for DeleCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class DeleCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ static final DIR = '/'
+ static final FILENAME = "f.txt"
+ static final FILE = p(DIR, FILENAME)
+
+ void testHandleCommand() {
+ createFile(FILE)
+ handleCommand([FILE])
+ assertSessionReply(ReplyCodes.DELE_OK, ['dele', FILE])
+ assert fileSystem.exists(FILE) == false
+ }
+
+ void testHandleCommand_PathIsRelative() {
+ createFile(FILE)
+ setCurrentDirectory("/")
+ handleCommand([FILENAME])
+ assertSessionReply(ReplyCodes.DELE_OK, ['dele', FILENAME])
+ assert fileSystem.exists(FILE) == false
+ }
+
+ void testHandleCommand_PathDoesNotExistInFileSystem() {
+ handleCommand([FILE])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.isNotAFile', FILE])
+ }
+
+ void testHandleCommand_PathSpecifiesADirectory() {
+ createDirectory(FILE)
+ handleCommand([FILE])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.isNotAFile', FILE])
+ assert fileSystem.exists(FILE)
+ }
+
+ void testHandleCommand_MissingPathParameter() {
+ testHandleCommand_MissingRequiredParameter([])
+ }
+
+ void testHandleCommand_DeleteThrowsException() {
+ createFile(FILE)
+// overrideMethodToThrowFileSystemException("delete")
+ fileSystem.deleteMethodException = new FileSystemException("bad", ERROR_MESSAGE_KEY)
+ handleCommand([FILE])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ERROR_MESSAGE_KEY)
+ }
+
+ void testHandleCommand_NoWriteAccessToParentDirectory() {
+ createFile(FILE)
+ fileSystem.getEntry(DIR).permissions = new Permissions('r-xr-xr-x')
+ handleCommand([FILE])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.cannotWrite', DIR])
+ assert fileSystem.exists(FILE)
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new DeleCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.DELE, [FILE])
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/EprtCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/EprtCommandHandlerTest.groovy
new file mode 100644
index 0000000..7ed28e5
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/EprtCommandHandlerTest.groovy
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+
+/**
+ * Tests for PortCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class EprtCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ static final PARAMETERS_IPV4 = ["|1|132.235.1.2|6275|"]
+ static final HOST_IPV4 = InetAddress.getByName("132.235.1.2")
+ static final PARAMETERS_IPV6 = ["|2|1080::8:800:200C:417A|6275|"]
+ static final HOST_IPV6 = InetAddress.getByName("1080::8:800:200C:417A")
+ static final PORT = 6275
+
+ boolean testNotLoggedIn = false
+
+ void testHandleCommand_IPv4() {
+ handleCommand(PARAMETERS_IPV4)
+ assertSessionReply(ReplyCodes.EPRT_OK, 'eprt')
+ assert session.clientDataHost == HOST_IPV4
+ assert session.clientDataPort == PORT
+ }
+
+ void testHandleCommand_IPv6() {
+ handleCommand(PARAMETERS_IPV6)
+ assertSessionReply(ReplyCodes.EPRT_OK, 'eprt')
+ assert session.clientDataHost == HOST_IPV6
+ assert session.clientDataPort == PORT
+ }
+
+ void testHandleCommand_IPv6_CustomDelimiter() {
+ handleCommand(["@2@1080::8:800:200C:417A@6275@"])
+ assertSessionReply(ReplyCodes.EPRT_OK, 'eprt')
+ assert session.clientDataHost == HOST_IPV6
+ assert session.clientDataPort == PORT
+ }
+
+ void testHandleCommand_IllegalParameterFormat() {
+ handleCommand(['abcdef'])
+ assertSessionReply(ReplyCodes.COMMAND_SYNTAX_ERROR)
+ }
+
+ void testHandleCommand_PortMissing() {
+ handleCommand(['|1|132.235.1.2|'])
+ assertSessionReply(ReplyCodes.COMMAND_SYNTAX_ERROR)
+ }
+
+ void testHandleCommand_IllegalHostName() {
+ handleCommand(['|1|132.@|6275|'])
+ assertSessionReply(ReplyCodes.COMMAND_SYNTAX_ERROR)
+ }
+
+ void testHandleCommand_MissingRequiredParameter() {
+ testHandleCommand_MissingRequiredParameter([])
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ void setUp() {
+ super.setUp()
+ }
+
+ CommandHandler createCommandHandler() {
+ new EprtCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.EPRT, PARAMETERS_IPV4)
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/EpsvCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/EpsvCommandHandlerTest.groovy
new file mode 100644
index 0000000..e43eb30
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/EpsvCommandHandlerTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+
+/**
+ * Tests for EpsvCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class EpsvCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ static final SERVER = InetAddress.getByName("1080::8:800:200C:417A")
+ static final PORT = 6275
+
+ void testHandleCommand() {
+ session.switchToPassiveModeReturnValue = PORT
+ session.serverHost = SERVER
+ handleCommand([])
+
+ assertSessionReply(ReplyCodes.EPSV_OK, PORT as String)
+ assert session.switchedToPassiveMode
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new EpsvCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.EPSV, [])
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/HelpCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/HelpCommandHandlerTest.groovy
new file mode 100644
index 0000000..8b84667
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/HelpCommandHandlerTest.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+
+/**
+ * Tests for HelpCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class HelpCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ boolean testNotLoggedIn = false
+
+ void testHandleCommand_Arg() {
+ serverConfiguration.helpText = [abc: '_abc']
+ handleCommand(['abc'])
+ assertSessionReply(ReplyCodes.HELP_OK, ['help', '_abc'])
+ }
+
+ void testHandleCommand_MultiWordArg() {
+ serverConfiguration.helpText = ["abc def": 'abcdef']
+ handleCommand(['abc', 'def'])
+ assertSessionReply(ReplyCodes.HELP_OK, ['help', 'abcdef'])
+ }
+
+ void testHandleCommand_NoArg_UseDefault() {
+ serverConfiguration.helpText = ['': 'default']
+ handleCommand([])
+ assertSessionReply(ReplyCodes.HELP_OK, ['help', 'default'])
+ }
+
+ void testHandleCommand_Unrecognized() {
+ serverConfiguration.helpText = ['': 'default']
+ handleCommand(['unrecognized'])
+
+ // Reply text includes the message text and the passed-in command as a message parameter
+ assertSessionReply(ReplyCodes.HELP_OK, ['help.noHelpTextDefined', 'unrecognized'])
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new HelpCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.HELP, [])
+ }
+
+ void setUp() {
+ super.setUp()
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/ListCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/ListCommandHandlerTest.groovy
new file mode 100644
index 0000000..5596977
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/ListCommandHandlerTest.groovy
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.core.session.SessionKeys
+import org.mockftpserver.fake.filesystem.DirectoryEntry
+import org.mockftpserver.fake.filesystem.DirectoryListingFormatter
+import org.mockftpserver.fake.filesystem.FileEntry
+import org.mockftpserver.fake.filesystem.FileSystemEntry
+import org.mockftpserver.fake.filesystem.FileSystemException
+import org.mockftpserver.fake.filesystem.Permissions
+
+/**
+ * Tests for ListCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class ListCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ private static final DIR = "/usr"
+ private static final NAME = "abc.txt"
+ private static final LAST_MODIFIED = new Date()
+
+ void testHandleCommand_SingleFile() {
+ final entry = new FileEntry(path: p(DIR, NAME), lastModified: LAST_MODIFIED, contents: "abc")
+ fileSystem.add(entry)
+ handleCommandAndVerifySendDataReplies([DIR])
+ assertSessionDataWithEndOfLine(listingFor(entry))
+ }
+
+ void testHandleCommand_FilesAndDirectories() {
+ def DATA3 = "".padRight(1000, 'x')
+ final entry1 = new FileEntry(path: p(DIR, "abc.txt"), lastModified: LAST_MODIFIED, contents: "abc")
+ final entry2 = new DirectoryEntry(path: p(DIR, "OtherFiles"), lastModified: LAST_MODIFIED)
+ final entry3 = new FileEntry(path: p(DIR, "another_file.doc"), lastModified: LAST_MODIFIED, contents: DATA3)
+ fileSystem.add(entry1)
+ fileSystem.add(entry2)
+ fileSystem.add(entry3)
+
+ handleCommandAndVerifySendDataReplies([DIR])
+
+ def actualLines = session.sentData[0].tokenize(endOfLine()) as Set
+ LOG.info("actualLines=$actualLines")
+ def EXPECTED = [
+ listingFor(entry1),
+ listingFor(entry2),
+ listingFor(entry3)] as Set
+ assert actualLines == EXPECTED
+ assertSessionDataEndsWithEndOfLine()
+ }
+
+ void testHandleCommand_NoPath_UseCurrentDirectory() {
+ final entry = new FileEntry(path: p(DIR, NAME), lastModified: LAST_MODIFIED, contents: "abc")
+ fileSystem.add(entry)
+ session.setAttribute(SessionKeys.CURRENT_DIRECTORY, DIR)
+ handleCommandAndVerifySendDataReplies([])
+ assertSessionDataWithEndOfLine(listingFor(entry))
+ }
+
+ void testHandleCommand_EmptyDirectory() {
+ handleCommandAndVerifySendDataReplies([DIR])
+ assertSessionData("")
+ }
+
+ void testHandleCommand_PathSpecifiesAFile() {
+ final entry = new FileEntry(path: p(DIR, NAME), lastModified: LAST_MODIFIED, contents: "abc")
+ fileSystem.add(entry)
+ handleCommandAndVerifySendDataReplies([p(DIR, NAME)])
+ assertSessionDataWithEndOfLine(listingFor(entry))
+ }
+
+ void testHandleCommand_PathDoesNotExist() {
+ handleCommandAndVerifySendDataReplies(["/DoesNotExist"])
+ assertSessionData("")
+ }
+
+ void testHandleCommand_NoReadAccessToDirectory() {
+ fileSystem.getEntry(DIR).permissions = new Permissions('-wx-wx-wx')
+ handleCommand([DIR])
+ assertSessionReply(0, ReplyCodes.TRANSFER_DATA_INITIAL_OK)
+ assertSessionReply(1, ReplyCodes.READ_FILE_ERROR, ['filesystem.cannotRead', DIR])
+ }
+
+ void testHandleCommand_ListFilesThrowsException() {
+ fileSystem.listFilesMethodException = new FileSystemException("bad", ERROR_MESSAGE_KEY)
+ handleCommand([DIR])
+ assertSessionReply(0, ReplyCodes.TRANSFER_DATA_INITIAL_OK)
+ assertSessionReply(1, ReplyCodes.SYSTEM_ERROR, ERROR_MESSAGE_KEY)
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new ListCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.LIST, [DIR])
+ }
+
+ void setUp() {
+ super.setUp()
+ createDirectory(DIR)
+ fileSystem.directoryListingFormatter = [format: {entry -> entry.toString()}] as DirectoryListingFormatter
+ }
+
+ private listingFor(FileSystemEntry fileSystemEntry) {
+ fileSystemEntry.toString()
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/MkdCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/MkdCommandHandlerTest.groovy
new file mode 100644
index 0000000..81dac95
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/MkdCommandHandlerTest.groovy
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.core.session.SessionKeys
+import org.mockftpserver.fake.UserAccount
+import org.mockftpserver.fake.filesystem.FileSystemException
+import org.mockftpserver.fake.filesystem.Permissions
+
+/**
+ * Tests for MkdCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class MkdCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ static final PARENT = '/'
+ static final DIRNAME = "usr"
+ static final DIR = p(PARENT, DIRNAME)
+ static final PERMISSIONS = new Permissions('rwx------')
+
+ void testHandleCommand() {
+ userAccount.defaultPermissionsForNewDirectory = PERMISSIONS
+ handleCommand([DIR])
+ assertSessionReply(ReplyCodes.MKD_OK, ['mkd', DIR])
+ assert fileSystem.exists(DIR)
+ def dirEntry = fileSystem.getEntry(DIR)
+ assert dirEntry.permissions == PERMISSIONS
+ }
+
+ void testHandleCommand_PathIsRelative() {
+ session.setAttribute(SessionKeys.CURRENT_DIRECTORY, '/')
+ handleCommand([DIRNAME])
+ assertSessionReply(ReplyCodes.MKD_OK, ['mkd', DIRNAME])
+ assert fileSystem.exists(DIR)
+ def dirEntry = fileSystem.getEntry(DIR)
+ assert dirEntry.permissions == UserAccount.DEFAULT_PERMISSIONS_FOR_NEW_DIRECTORY
+ }
+
+ void testHandleCommand_ParentDirectoryDoesNotExist() {
+ handleCommand(['/abc/def'])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.doesNotExist', '/abc'])
+ }
+
+ void testHandleCommand_PathSpecifiesAFile() {
+ createFile(DIR)
+ handleCommand([DIR])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.alreadyExists', DIR])
+ assert fileSystem.exists(DIR)
+ }
+
+ void testHandleCommand_MissingPathParameter() {
+ testHandleCommand_MissingRequiredParameter([])
+ }
+
+ void testHandleCommand_NoWriteAccessToParentDirectory() {
+ fileSystem.getEntry(PARENT).permissions = new Permissions('r-xr-xr-x')
+ handleCommand([DIR])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.cannotWrite', PARENT])
+ }
+
+ void testHandleCommand_NoExecuteAccessToParentDirectory() {
+ fileSystem.getEntry(PARENT).permissions = new Permissions('rw-rw-rw-')
+ handleCommand([DIR])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.cannotExecute', PARENT])
+ }
+
+ void testHandleCommand_CreateDirectoryThrowsException() {
+ fileSystem.addMethodException = new FileSystemException("bad", ERROR_MESSAGE_KEY)
+ handleCommand([DIR])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ERROR_MESSAGE_KEY)
+ }
+
+ void setUp() {
+ super.setUp()
+ createDirectory(PARENT)
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new MkdCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.MKD, [DIR])
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/ModeCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/ModeCommandHandlerTest.groovy
new file mode 100644
index 0000000..a4dec34
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/ModeCommandHandlerTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+
+/**
+ * Tests for ModeCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class ModeCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ void testHandleCommand() {
+ handleCommand([])
+ assertSessionReply(ReplyCodes.MODE_OK, 'mode')
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new ModeCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.MODE, [])
+ }
+
+ void setUp() {
+ super.setUp()
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/NlstCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/NlstCommandHandlerTest.groovy
new file mode 100644
index 0000000..5444394
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/NlstCommandHandlerTest.groovy
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.core.session.SessionKeys
+import org.mockftpserver.fake.filesystem.FileSystemException
+import org.mockftpserver.fake.filesystem.Permissions
+
+/**
+ * Tests for NlstCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class NlstCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ def DIR = "/usr"
+
+ void testHandleCommand_SingleFile() {
+ createFile("/usr/f1.txt")
+ handleCommandAndVerifySendDataReplies([DIR])
+ assertSessionDataWithEndOfLine("f1.txt")
+ }
+
+ void testHandleCommand_FilesAndDirectories() {
+ createFile("/usr/f1.txt")
+ createDirectory("/usr/OtherFiles")
+ createFile("/usr/f2.txt")
+ createDirectory("/usr/Archive")
+ handleCommandAndVerifySendDataReplies([DIR])
+
+ def EXPECTED = ["f1.txt", "OtherFiles", "f2.txt", "Archive"] as Set
+ def actualLines = session.sentData[0].tokenize(endOfLine()) as Set
+ LOG.info("actualLines=$actualLines")
+ assert actualLines == EXPECTED
+ assertSessionDataEndsWithEndOfLine()
+ }
+
+ void testHandleCommand_NoPath_UseCurrentDirectory() {
+ createFile("/usr/f1.txt")
+ session.setAttribute(SessionKeys.CURRENT_DIRECTORY, DIR)
+ handleCommandAndVerifySendDataReplies([])
+ assertSessionDataWithEndOfLine("f1.txt")
+ }
+
+ void testHandleCommand_EmptyDirectory() {
+ handleCommandAndVerifySendDataReplies([DIR])
+ assertSessionData("")
+ }
+
+ void testHandleCommand_PathSpecifiesAFile() {
+ createFile("/usr/f1.txt")
+ handleCommandAndVerifySendDataReplies(["/usr/f1.txt"])
+ assertSessionDataWithEndOfLine("f1.txt")
+ }
+
+ void testHandleCommand_PathDoesNotExist() {
+ handleCommandAndVerifySendDataReplies(["/DoesNotExist"])
+ assertSessionData("")
+ }
+
+ void testHandleCommand_NoReadAccessToDirectory() {
+ fileSystem.getEntry(DIR).permissions = new Permissions('-wx-wx-wx')
+ handleCommand([DIR])
+ assertSessionReply(0, ReplyCodes.TRANSFER_DATA_INITIAL_OK)
+ assertSessionReply(1, ReplyCodes.READ_FILE_ERROR, ['filesystem.cannotRead', DIR])
+ }
+
+ void testHandleCommand_ListNamesThrowsException() {
+ fileSystem.listNamesMethodException = new FileSystemException("bad", ERROR_MESSAGE_KEY)
+ handleCommand([DIR])
+ assertSessionReply(0, ReplyCodes.TRANSFER_DATA_INITIAL_OK)
+ assertSessionReply(1, ReplyCodes.SYSTEM_ERROR, ERROR_MESSAGE_KEY)
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new NlstCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.NLST, [DIR])
+ }
+
+ void setUp() {
+ super.setUp()
+ createDirectory(DIR)
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/NoopCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/NoopCommandHandlerTest.groovy
new file mode 100644
index 0000000..ff998e4
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/NoopCommandHandlerTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+
+/**
+ * Tests for NoopCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class NoopCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ boolean testNotLoggedIn = false
+
+ void testHandleCommand() {
+ handleCommand([])
+ assertSessionReply(ReplyCodes.NOOP_OK, 'noop')
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ void setUp() {
+ super.setUp()
+ }
+
+ CommandHandler createCommandHandler() {
+ new NoopCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.NOOP, [])
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/PassCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/PassCommandHandlerTest.groovy
new file mode 100644
index 0000000..2017155
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/PassCommandHandlerTest.groovy
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.core.session.SessionKeys
+import org.mockftpserver.fake.UserAccount
+
+/**
+ * Tests for PassCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class PassCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ def USERNAME = "user123"
+ def PASSWORD = "password123"
+ def HOME_DIRECTORY = "/"
+ UserAccount userAccount
+
+ boolean testNotLoggedIn = false
+
+ void testHandleCommand_UserExists_PasswordCorrect() {
+ serverConfiguration.userAccounts[USERNAME] = userAccount
+ handleCommand([PASSWORD])
+ assertSessionReply(ReplyCodes.PASS_OK, 'pass')
+ assertUserAccountInSession(true)
+ assertCurrentDirectory(HOME_DIRECTORY)
+ }
+
+ void testHandleCommand_UserExists_PasswordCorrect_AccountRequired() {
+ serverConfiguration.userAccounts[USERNAME] = userAccount
+ userAccount.accountRequiredForLogin = true
+ handleCommand([PASSWORD])
+ assertSessionReply(ReplyCodes.PASS_NEED_ACCOUNT, 'pass.needAccount')
+ assertUserAccountInSession(true)
+ assertCurrentDirectory(HOME_DIRECTORY)
+ }
+
+ void testHandleCommand_UserExists_PasswordIncorrect() {
+ serverConfiguration.userAccounts[USERNAME] = userAccount
+ handleCommand(["wrong"])
+ assertSessionReply(ReplyCodes.PASS_LOG_IN_FAILED, 'pass.loginFailed')
+ assertUserAccountInSession(false)
+ assertCurrentDirectory(null)
+ }
+
+ void testHandleCommand_UserExists_PasswordWrongButIgnored() {
+ userAccount.passwordCheckedDuringValidation = false
+ serverConfiguration.userAccounts[USERNAME] = userAccount
+ handleCommand(["wrong"])
+ assertSessionReply(ReplyCodes.PASS_OK, 'pass')
+ assertUserAccountInSession(true)
+ assertCurrentDirectory(HOME_DIRECTORY)
+ }
+
+ void testHandleCommand_UserExists_HomeDirectoryNotDefinedForUserAccount() {
+ userAccount.homeDirectory = ''
+ serverConfiguration.userAccounts[USERNAME] = userAccount
+ handleCommand([PASSWORD])
+ assertSessionReply(ReplyCodes.USER_ACCOUNT_NOT_VALID, "login.userAccountNotValid")
+ assertUserAccountInSession(false)
+ assertCurrentDirectory(null)
+ }
+
+ void testHandleCommand_UserExists_HomeDirectoryDoesNotExist() {
+ userAccount.homeDirectory = '/abc/def'
+ serverConfiguration.userAccounts[USERNAME] = userAccount
+ handleCommand([PASSWORD])
+ assertSessionReply(ReplyCodes.USER_ACCOUNT_NOT_VALID, "login.homeDirectoryNotValid")
+ assertUserAccountInSession(false)
+ assertCurrentDirectory(null)
+ }
+
+ void testHandleCommand_UserDoesNotExist() {
+ handleCommand([PASSWORD])
+ assertSessionReply(ReplyCodes.USER_ACCOUNT_NOT_VALID, "login.userAccountNotValid")
+ assertUserAccountInSession(false)
+ assertCurrentDirectory(null)
+ }
+
+ void testHandleCommand_UsernameNotSetInSession() {
+ session.removeAttribute(SessionKeys.USERNAME)
+ testHandleCommand_MissingRequiredSessionAttribute()
+ assertUserAccountInSession(false)
+ assertCurrentDirectory(null)
+ }
+
+ void testHandleCommand_MissingPasswordParameter() {
+ testHandleCommand_MissingRequiredParameter([])
+ assertUserAccountInSession(false)
+ assertCurrentDirectory(null)
+ }
+
+ //-------------------------------------------------------------------------
+ // Abstract and Overridden Methods
+ //-------------------------------------------------------------------------
+
+ void setUp() {
+ super.setUp()
+
+ createDirectory(HOME_DIRECTORY)
+
+ userAccount = new UserAccount(USERNAME, PASSWORD, HOME_DIRECTORY)
+
+ session.setAttribute(SessionKeys.USERNAME, USERNAME)
+ session.removeAttribute(SessionKeys.USER_ACCOUNT)
+ }
+
+ CommandHandler createCommandHandler() {
+ new PassCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.PASS, [PASSWORD])
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * Assert that the UserAccount object is in the session, depending on the value of isUserAccountInSession.
+ * @param isUserAccountInSession - true if the UserAccount is expected in the session; false if it is not expected
+ */
+ private void assertUserAccountInSession(boolean isUserAccountInSession) {
+ def expectedValue = isUserAccountInSession ? userAccount : null
+ assert session.getAttribute(SessionKeys.USER_ACCOUNT) == expectedValue
+ }
+
+ /**
+ * Assert that the current directory is set in the session, but only if currentDirectory is not null.
+ * @param currentDirectory - the curent directory expected in the session; null if it is not expected
+ */
+ private void assertCurrentDirectory(String currentDirectory) {
+ assert session.getAttribute(SessionKeys.CURRENT_DIRECTORY) == currentDirectory
+ }
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/PasvCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/PasvCommandHandlerTest.groovy
new file mode 100644
index 0000000..e913d57
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/PasvCommandHandlerTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+
+/**
+ * Tests for PasvCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class PasvCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ static final PORT = (23 << 8) + 77
+ static final InetAddress SERVER = inetAddress("192.168.0.2")
+
+ void testHandleCommand() {
+ final HOST_AND_PORT = "192,168,0,2,23,77"
+ session.switchToPassiveModeReturnValue = PORT
+ session.serverHost = SERVER
+ handleCommand([])
+
+ assertSessionReply(ReplyCodes.PASV_OK, HOST_AND_PORT)
+ assert session.switchedToPassiveMode
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new PasvCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.PASV, [])
+ }
+
+ void setUp() {
+ super.setUp()
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/PortCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/PortCommandHandlerTest.groovy
new file mode 100644
index 0000000..035652b
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/PortCommandHandlerTest.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+
+/**
+ * Tests for PortCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class PortCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ static final PARAMETERS = ["11", "22", "33", "44", "1", "206"]
+ static final PARAMETERS_INSUFFICIENT = ["7", "29", "99", "11", "77"]
+ static final PORT = (1 << 8) + 206
+ static final HOST = InetAddress.getByName("11.22.33.44")
+
+ boolean testNotLoggedIn = false
+
+ void testHandleCommand() {
+ handleCommand(PARAMETERS)
+ assertSessionReply(ReplyCodes.PORT_OK, 'port')
+ assert session.clientDataPort == PORT
+ assert session.clientDataHost == HOST
+ }
+
+ void testHandleCommand_MissingRequiredParameter() {
+ testHandleCommand_MissingRequiredParameter(PARAMETERS_INSUFFICIENT)
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ void setUp() {
+ super.setUp()
+ }
+
+ CommandHandler createCommandHandler() {
+ new PortCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.PORT, PARAMETERS)
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/PwdCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/PwdCommandHandlerTest.groovy
new file mode 100644
index 0000000..a26d451
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/PwdCommandHandlerTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.core.session.SessionKeys
+
+/**
+ * Tests for PwdCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class PwdCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ static final DIR = "/usr/abc"
+
+ boolean testNotLoggedIn = false
+
+ void testHandleCommand() {
+ session.setAttribute(SessionKeys.CURRENT_DIRECTORY, DIR)
+ handleCommand([])
+ assertSessionReply(ReplyCodes.PWD_OK, ["pwd", DIR])
+ }
+
+ void testHandleCommand_CurrentDirectoryNotSet() {
+ handleCommand([])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, 'filesystem.currentDirectoryNotSet')
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new PwdCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.PWD, [])
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/QuitCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/QuitCommandHandlerTest.groovy
new file mode 100644
index 0000000..83b611d
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/QuitCommandHandlerTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+
+/**
+ * Tests for QuitCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class QuitCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ boolean testNotLoggedIn = false
+
+ void testHandleCommand() {
+ assert !session.closed
+ handleCommand([])
+ assertSessionReply(ReplyCodes.QUIT_OK, 'quit')
+ assert session.closed
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new QuitCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.QUIT, [])
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/ReinCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/ReinCommandHandlerTest.groovy
new file mode 100644
index 0000000..365bf9a
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/ReinCommandHandlerTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.core.session.SessionKeys
+import org.mockftpserver.fake.UserAccount
+
+/**
+ * Tests for ReinCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class ReinCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ boolean testNotLoggedIn = false
+
+ UserAccount userAccount
+
+ void testHandleCommand_AlreadyLoggedIn() {
+ session.setAttribute(SessionKeys.USER_ACCOUNT, userAccount)
+ assert isLoggedIn()
+ handleCommand([])
+ assertSessionReply(ReplyCodes.REIN_OK, 'rein')
+ assert !isLoggedIn()
+ }
+
+ void testHandleCommand_NotLoggedIn() {
+ handleCommand([])
+ assertSessionReply(ReplyCodes.REIN_OK, 'rein')
+ assert !isLoggedIn()
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new ReinCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.REIN, [])
+ }
+
+ void setUp() {
+ super.setUp()
+ userAccount = new UserAccount(username: 'user')
+ }
+
+ private boolean isLoggedIn() {
+ return session.getAttribute(SessionKeys.USER_ACCOUNT) != null
+ }
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RestCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RestCommandHandlerTest.groovy
new file mode 100644
index 0000000..263c4a6
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RestCommandHandlerTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+
+/**
+ * Tests for RestCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class RestCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ void testHandleCommand() {
+ handleCommand([])
+ assertSessionReply(ReplyCodes.REST_OK, 'rest')
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new RestCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.REST, [])
+ }
+
+ void setUp() {
+ super.setUp()
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RetrCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RetrCommandHandlerTest.groovy
new file mode 100644
index 0000000..7f52409
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RetrCommandHandlerTest.groovy
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.core.session.SessionKeys
+import org.mockftpserver.fake.filesystem.FileEntry
+import org.mockftpserver.fake.filesystem.FileSystemException
+import org.mockftpserver.fake.filesystem.Permissions
+
+/**
+ * Tests for RetrCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class RetrCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ def DIR = "/"
+ def FILENAME = "file.txt"
+ def FILE = p(DIR, FILENAME)
+ def CONTENTS = "abc\ndef\nghi"
+ def CONTENTS_ASCII = "abc\r\ndef\r\nghi"
+
+ void testHandleCommand_MissingPathParameter() {
+ testHandleCommand_MissingRequiredParameter([])
+ }
+
+ void testHandleCommand_AbsolutePath() {
+ handleCommandAndVerifySendDataReplies([FILE])
+ assertSessionData(CONTENTS_ASCII)
+ }
+
+ void testHandleCommand_AbsolutePath_NonAsciiMode() {
+ session.setAttribute(SessionKeys.ASCII_TYPE, false)
+ handleCommandAndVerifySendDataReplies([FILE])
+ assertSessionData(CONTENTS)
+ }
+
+ void testHandleCommand_RelativePath() {
+ setCurrentDirectory(DIR)
+ handleCommandAndVerifySendDataReplies([FILENAME])
+ assertSessionData(CONTENTS_ASCII)
+ }
+
+ void testHandleCommand_PathSpecifiesAnExistingDirectory() {
+ handleCommand([DIR])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.isNotAFile', DIR])
+ }
+
+ void testHandleCommand_PathDoesNotExist() {
+ def path = FILE + "XXX"
+ handleCommand([path])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.doesNotExist', path])
+ }
+
+ void testHandleCommand_NoReadAccessToFile() {
+ fileSystem.getEntry(FILE).permissions = Permissions.NONE
+ handleCommand([FILE])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.cannotRead', FILE])
+ }
+
+ void testHandleCommand_NoExecuteAccessToDirectory() {
+ fileSystem.getEntry(DIR).permissions = Permissions.NONE
+ handleCommand([FILE])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.cannotExecute', DIR])
+ }
+
+ void testHandleCommand_ThrowsFileSystemException() {
+ fileSystem.delete(FILE)
+ def fileEntry = new BadFileEntry(FILE)
+ fileSystem.add(fileEntry)
+
+ handleCommand([FILE])
+ assertSessionReply(0, ReplyCodes.TRANSFER_DATA_INITIAL_OK)
+ assertSessionReply(1, ReplyCodes.READ_FILE_ERROR, ERROR_MESSAGE_KEY)
+ }
+
+ void testConvertLfToCrLf() {
+ // LF='\n' and CRLF='\r\n'
+ assert commandHandler.convertLfToCrLf('abc'.bytes) == 'abc'.bytes
+ assert commandHandler.convertLfToCrLf('abc\r\ndef'.bytes) == 'abc\r\ndef'.bytes
+ assert commandHandler.convertLfToCrLf('abc\ndef'.bytes) == 'abc\r\ndef'.bytes
+ assert commandHandler.convertLfToCrLf('abc\ndef\nghi'.bytes) == 'abc\r\ndef\r\nghi'.bytes
+ assert commandHandler.convertLfToCrLf('\n'.bytes) == '\r\n'.bytes
+ assert commandHandler.convertLfToCrLf('\r\nabc\n'.bytes) == '\r\nabc\r\n'.bytes
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new RetrCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.RETR, [FILE])
+ }
+
+ void setUp() {
+ super.setUp()
+ createDirectory(DIR)
+ createFile(FILE, CONTENTS)
+ }
+
+}
+
+class BadFileEntry extends FileEntry {
+
+ BadFileEntry(String path) {
+ super(path)
+ }
+
+ InputStream createInputStream() {
+ throw new FileSystemException("BAD", AbstractFakeCommandHandlerTestCase.ERROR_MESSAGE_KEY)
+ }
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RmdCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RmdCommandHandlerTest.groovy
new file mode 100644
index 0000000..968f14f
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RmdCommandHandlerTest.groovy
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.core.session.SessionKeys
+import org.mockftpserver.fake.filesystem.FileSystemException
+import org.mockftpserver.fake.filesystem.Permissions
+
+/**
+ * Tests for RmdCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class RmdCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ static final PARENT = '/'
+ static final DIR = p(PARENT, "usr")
+
+ void testHandleCommand() {
+ createDirectory(DIR)
+ handleCommand([DIR])
+ assertSessionReply(ReplyCodes.RMD_OK, ['rmd', DIR])
+ assert fileSystem.exists(DIR) == false
+ }
+
+ void testHandleCommand_PathIsRelative() {
+ def SUB = "sub"
+ createDirectory(p(DIR, SUB))
+ session.setAttribute(SessionKeys.CURRENT_DIRECTORY, DIR)
+ handleCommand([SUB])
+ assertSessionReply(ReplyCodes.RMD_OK, ['rmd', SUB])
+ assert fileSystem.exists(p(DIR, SUB)) == false
+ }
+
+ void testHandleCommand_PathDoesNotExistInFileSystem() {
+ handleCommand([DIR])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.doesNotExist', DIR])
+ }
+
+ void testHandleCommand_PathSpecifiesAFile() {
+ createFile(DIR)
+ handleCommand([DIR])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.isNotADirectory', DIR])
+ assert fileSystem.exists(DIR)
+ }
+
+ void testHandleCommand_DirectoryIsNotEmpty() {
+ final FILE = DIR + "/file.txt"
+ createFile(FILE)
+ handleCommand([DIR])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.directoryIsNotEmpty', DIR])
+ assert fileSystem.exists(DIR)
+ assert fileSystem.exists(FILE)
+ }
+
+ void testHandleCommand_MissingPathParameter() {
+ testHandleCommand_MissingRequiredParameter([])
+ }
+
+ void testHandleCommand_ListNamesThrowsException() {
+ createDirectory(DIR)
+ fileSystem.listNamesMethodException = new FileSystemException("bad", ERROR_MESSAGE_KEY)
+ handleCommand([DIR])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ERROR_MESSAGE_KEY)
+ }
+
+ void testHandleCommand_DeleteThrowsException() {
+ createDirectory(DIR)
+ fileSystem.deleteMethodException = new FileSystemException("bad", ERROR_MESSAGE_KEY)
+ handleCommand([DIR])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ERROR_MESSAGE_KEY)
+ }
+
+ void testHandleCommand_NoWriteAccessToParentDirectory() {
+ createDirectory(DIR)
+ fileSystem.getEntry(PARENT).permissions = new Permissions('r-xr-xr-x')
+ handleCommand([DIR])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.cannotWrite', PARENT])
+ assert fileSystem.exists(DIR)
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new RmdCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.RMD, [DIR])
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RnfrCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RnfrCommandHandlerTest.groovy
new file mode 100644
index 0000000..1f31171
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RnfrCommandHandlerTest.groovy
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.core.session.SessionKeys
+import org.mockftpserver.fake.filesystem.Permissions
+
+/**
+ * Tests for RnfrCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class RnfrCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ private static final FILE = "/file.txt"
+ private static final DIR = "/subdir"
+
+ void testHandleCommand() {
+ createFile(FILE)
+ handleCommand([FILE])
+ assertSessionReply(ReplyCodes.RNFR_OK, 'rnfr')
+ assert session.getAttribute(SessionKeys.RENAME_FROM) == FILE
+ }
+
+ void testHandleCommand_PathIsRelative() {
+ createFile(FILE)
+ session.setAttribute(SessionKeys.CURRENT_DIRECTORY, "/")
+ handleCommand(["file.txt"])
+ assertSessionReply(ReplyCodes.RNFR_OK, 'rnfr')
+ assert session.getAttribute(SessionKeys.RENAME_FROM) == FILE
+ }
+
+ void testHandleCommand_PathDoesNotExistInFileSystem() {
+ handleCommand([FILE])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.doesNotExist', FILE])
+ assert session.getAttribute(SessionKeys.RENAME_FROM) == null
+ }
+
+ void testHandleCommand_PathSpecifiesADirectory() {
+ createDirectory(DIR)
+ handleCommand([DIR])
+ assertSessionReply(ReplyCodes.RNFR_OK, 'rnfr')
+ assert session.getAttribute(SessionKeys.RENAME_FROM) == DIR
+ }
+
+ void testHandleCommand_NoReadAccessToFile() {
+ createFile(FILE)
+ fileSystem.getEntry(FILE).permissions = new Permissions('-wx-wx-wx')
+ handleCommand([FILE])
+ assertSessionReply(ReplyCodes.READ_FILE_ERROR, ['filesystem.cannotRead', FILE])
+ }
+
+ void testHandleCommand_MissingPathParameter() {
+ testHandleCommand_MissingRequiredParameter([])
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new RnfrCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.RNFR, [FILE])
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RntoCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RntoCommandHandlerTest.groovy
new file mode 100644
index 0000000..ebd5481
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/RntoCommandHandlerTest.groovy
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.core.session.SessionKeys
+import org.mockftpserver.fake.filesystem.FileSystemException
+import org.mockftpserver.fake.filesystem.Permissions
+
+/**
+ * Tests for RntoCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class RntoCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ private static final DIR = '/'
+ private static final FROM_FILE = "/from.txt"
+ private static final TO_FILE = "/file.txt"
+ private static final FROM_DIR = "/subdir"
+
+ void testHandleCommand_SingleFile() {
+ createFile(FROM_FILE)
+ handleCommand([TO_FILE])
+ assertSessionReply(ReplyCodes.RNTO_OK, ['rnto', FROM_FILE, TO_FILE])
+ assert !fileSystem.exists(FROM_FILE), FROM_FILE
+ assert fileSystem.exists(TO_FILE), TO_FILE
+ assertRenameFromSessionProperty(null)
+ }
+
+ void testHandleCommand_SingleFile_PathIsRelative() {
+ createFile(FROM_FILE)
+ handleCommand(["file.txt"])
+ assertSessionReply(ReplyCodes.RNTO_OK, ['rnto', FROM_FILE, 'file.txt'])
+ assert !fileSystem.exists(FROM_FILE), FROM_FILE
+ assert fileSystem.exists(TO_FILE), TO_FILE
+ assertRenameFromSessionProperty(null)
+ }
+
+ void testHandleCommand_FromFileNotSetInSession() {
+ session.removeAttribute(SessionKeys.RENAME_FROM)
+ testHandleCommand_MissingRequiredSessionAttribute()
+ }
+
+ void testHandleCommand_ToFilenameNotValid() {
+ createFile(FROM_FILE)
+ handleCommand([""])
+ assertSessionReply(ReplyCodes.FILENAME_NOT_VALID, "")
+ assertRenameFromSessionProperty(FROM_FILE)
+ }
+
+ void testHandleCommand_EmptyDirectory() {
+ final TO_DIR = "/newdir"
+ createDirectory(FROM_DIR)
+ setRenameFromSessionProperty(FROM_DIR)
+ handleCommand([TO_DIR])
+ assertSessionReply(ReplyCodes.RNTO_OK, ['rnto', FROM_DIR, TO_DIR])
+ assert !fileSystem.exists(FROM_DIR), FROM_DIR
+ assert fileSystem.exists(TO_DIR), TO_DIR
+ assertRenameFromSessionProperty(null)
+ }
+
+ void testHandleCommand_DirectoryContainingFilesAndSubdirectory() {
+ final TO_DIR = "/newdir"
+ createDirectory(FROM_DIR)
+ createFile(FROM_DIR + "/a.txt")
+ createFile(FROM_DIR + "/b.txt")
+ createDirectory(FROM_DIR + "/child/grandchild")
+ setRenameFromSessionProperty(FROM_DIR)
+ handleCommand([TO_DIR])
+ assertSessionReply(ReplyCodes.RNTO_OK, ['rnto', FROM_DIR, TO_DIR])
+ assert !fileSystem.exists(FROM_DIR), FROM_DIR
+ assert fileSystem.exists(TO_DIR), TO_DIR
+ assert fileSystem.isFile(TO_DIR + "/a.txt")
+ assert fileSystem.isFile(TO_DIR + "/b.txt")
+ assert fileSystem.isDirectory(TO_DIR + "/child")
+ assert fileSystem.isDirectory(TO_DIR + "/child/grandchild")
+ assertRenameFromSessionProperty(null)
+ }
+
+ void testHandleCommand_ToDirectoryIsChildOfFromDirectory() {
+ final TO_DIR = FROM_DIR + "/child"
+ createDirectory(FROM_DIR)
+ setRenameFromSessionProperty(FROM_DIR)
+ handleCommand([TO_DIR])
+ assertSessionReply(ReplyCodes.WRITE_FILE_ERROR, ['filesystem.renameFailed', TO_DIR])
+ assertRenameFromSessionProperty(FROM_DIR)
+ }
+
+ void testHandleCommand_NoWriteAccessToDirectory() {
+ createFile(FROM_FILE)
+ fileSystem.getEntry(DIR).permissions = new Permissions('r-xr-xr-x')
+ handleCommand([TO_FILE])
+ assertSessionReply(ReplyCodes.WRITE_FILE_ERROR, ['filesystem.cannotWrite', DIR])
+ assertRenameFromSessionProperty(FROM_FILE)
+ }
+
+ void testHandleCommand_FromFileDoesNotExist() {
+ createDirectory(DIR)
+ handleCommand([TO_FILE])
+ assertSessionReply(ReplyCodes.FILENAME_NOT_VALID, ['filesystem.doesNotExist', FROM_FILE])
+ assertRenameFromSessionProperty(FROM_FILE)
+ }
+
+ void testHandleCommand_ToFileParentDirectoryDoesNotExist() {
+ createFile(FROM_FILE)
+ final BAD_DIR = p(DIR, 'SUB')
+ final BAD_TO_FILE = p(BAD_DIR, 'Filename.txt')
+ handleCommand([BAD_TO_FILE])
+ assertSessionReply(ReplyCodes.FILENAME_NOT_VALID, ['filesystem.doesNotExist', BAD_DIR])
+ assertRenameFromSessionProperty(FROM_FILE)
+ }
+
+ void testHandleCommand_RenameThrowsException() {
+ createDirectory(DIR)
+ fileSystem.renameMethodException = new FileSystemException("bad", ERROR_MESSAGE_KEY)
+ handleCommand([TO_FILE])
+ assertSessionReply(ReplyCodes.WRITE_FILE_ERROR, ERROR_MESSAGE_KEY)
+ assertRenameFromSessionProperty(FROM_FILE)
+ }
+
+ void testHandleCommand_MissingPathParameter() {
+ testHandleCommand_MissingRequiredParameter([])
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new RntoCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.RNTO, [TO_FILE])
+ }
+
+ void setUp() {
+ super.setUp()
+ setCurrentDirectory(DIR)
+ setRenameFromSessionProperty(FROM_FILE)
+ }
+
+ private void setRenameFromSessionProperty(String renameFrom) {
+ session.setAttribute(SessionKeys.RENAME_FROM, renameFrom)
+ }
+
+ private void assertRenameFromSessionProperty(String value) {
+ assert session.getAttribute(SessionKeys.RENAME_FROM) == value
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/SiteCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/SiteCommandHandlerTest.groovy
new file mode 100644
index 0000000..9fbfdcd
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/SiteCommandHandlerTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+
+/**
+ * Tests for SiteCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class SiteCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ void testHandleCommand() {
+ handleCommand([])
+ assertSessionReply(ReplyCodes.SITE_OK, 'site')
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ void setUp() {
+ super.setUp()
+ }
+
+ CommandHandler createCommandHandler() {
+ new SiteCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.SITE, [])
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/SmntCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/SmntCommandHandlerTest.groovy
new file mode 100644
index 0000000..2f38f17
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/SmntCommandHandlerTest.groovy
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+
+
+/**
+ * Tests for SmntCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class SmntCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ void testHandleCommand() {
+ handleCommand([])
+ assertSessionReply(ReplyCodes.SMNT_OK, 'smnt')
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new SmntCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.SMNT, [])
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/StatCommandHandlerText.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/StatCommandHandlerText.groovy
new file mode 100644
index 0000000..4ac9bc1
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/StatCommandHandlerText.groovy
@@ -0,0 +1,52 @@
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Tests for StatCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class StatCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ boolean testNotLoggedIn = false
+
+ void testHandleCommand() {
+ serverConfiguration.systemStatus = '12345'
+ handleCommand([])
+ assertSessionReply(ReplyCodes.STAT_SYSTEM_OK, ['12345'])
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new StatCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.STAT, [])
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/StorCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/StorCommandHandlerTest.groovy
new file mode 100644
index 0000000..25a3afe
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/StorCommandHandlerTest.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+
+/**
+ * Tests for StorCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class StorCommandHandlerTest extends AbstractStoreFileCommandHandlerTestCase {
+
+ void testHandleCommand_MissingPathParameter() {
+ testHandleCommand_MissingRequiredParameter([])
+ }
+
+ void testHandleCommand_AbsolutePath() {
+ testHandleCommand([FILE], 'stor', CONTENTS)
+ }
+
+ void testHandleCommand_RelativePath() {
+ setCurrentDirectory(DIR)
+ testHandleCommand([FILENAME], 'stor', CONTENTS)
+ }
+
+ void testHandleCommand_PathSpecifiesAnExistingDirectory() {
+ createDirectory(FILE)
+ handleCommand([FILE])
+ assertSessionReply(ReplyCodes.FILENAME_NOT_VALID, FILE)
+ }
+
+ void testHandleCommand_ParentDirectoryDoesNotExist() {
+ def NO_SUCH_DIR = "/path/DoesNotExist"
+ handleCommand([p(NO_SUCH_DIR, FILENAME)])
+ assertSessionReply(ReplyCodes.FILENAME_NOT_VALID, NO_SUCH_DIR)
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new StorCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.STOR, [FILE])
+ }
+
+ void setUp() {
+ super.setUp()
+ }
+
+ protected String verifyOutputFile() {
+ assert fileSystem.isFile(FILE)
+ assert session.getReplyMessage(1).contains(FILENAME)
+ return FILE
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/StouCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/StouCommandHandlerTest.groovy
new file mode 100644
index 0000000..44a88a3
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/StouCommandHandlerTest.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+
+/**
+ * Tests for StouCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class StouCommandHandlerTest extends AbstractStoreFileCommandHandlerTestCase {
+
+ def expectedBaseName
+
+ void testHandleCommand_SpecifyBaseFilename() {
+ setCurrentDirectory(DIR)
+ expectedBaseName = FILENAME
+ testHandleCommand([expectedBaseName], 'stou', CONTENTS)
+ }
+
+ void testHandleCommand_UseDefaultBaseFilename() {
+ setCurrentDirectory(DIR)
+ expectedBaseName = 'Temp'
+ testHandleCommand([expectedBaseName], 'stou', CONTENTS)
+ }
+
+ void testHandleCommand_AbsolutePath() {
+ expectedBaseName = FILENAME
+ testHandleCommand([FILE], 'stou', CONTENTS)
+ }
+
+ void testHandleCommand_NoWriteAccessToExistingFile() {
+ // This command always stores a new (unique) file, so this test does not apply
+ }
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new StouCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.STOU, [])
+ }
+
+ void setUp() {
+ super.setUp()
+ session.dataToRead = CONTENTS.bytes
+ }
+
+ protected String verifyOutputFile() {
+ def names = fileSystem.listNames(DIR)
+ def filename = names.find {name -> name.startsWith(expectedBaseName) }
+ assert filename
+ return p(DIR, filename)
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/StruCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/StruCommandHandlerTest.groovy
new file mode 100644
index 0000000..fced21b
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/StruCommandHandlerTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+
+/**
+ * Tests for StruCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class StruCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ void testHandleCommand() {
+ handleCommand([])
+ assertSessionReply(ReplyCodes.STRU_OK, 'stru')
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new StruCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.STRU, [])
+ }
+
+ void setUp() {
+ super.setUp()
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/SystCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/SystCommandHandlerTest.groovy
new file mode 100644
index 0000000..e33e164
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/SystCommandHandlerTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+
+/**
+ * Tests for SystCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class SystCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ static final SYSTEM_NAME = "UNIX"
+
+ boolean testNotLoggedIn = false
+
+ void testHandleCommand() {
+ serverConfiguration.systemName = SYSTEM_NAME
+ handleCommand([])
+ assertSessionReply(ReplyCodes.SYST_OK, ['syst', SYSTEM_NAME])
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ void setUp() {
+ super.setUp()
+ }
+
+ CommandHandler createCommandHandler() {
+ new SystCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.SYST, [])
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/TypeCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/TypeCommandHandlerTest.groovy
new file mode 100644
index 0000000..0a48679
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/TypeCommandHandlerTest.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.core.session.SessionKeys
+
+/**
+ * Tests for TestCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class TypeCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ void testHandleCommand_Ascii() {
+ handleCommand(['A'])
+ assertSessionReply(ReplyCodes.TYPE_OK, 'type')
+ assert session.getAttribute(SessionKeys.ASCII_TYPE) == true
+ }
+
+ void testHandleCommand_NonAscii() {
+ handleCommand(['I'])
+ assertSessionReply(ReplyCodes.TYPE_OK, 'type')
+ assert session.getAttribute(SessionKeys.ASCII_TYPE) == false
+ }
+
+ void testHandleCommand_MissingRequiredParameter() {
+ testHandleCommand_MissingRequiredParameter([])
+ assert session.getAttribute(SessionKeys.ASCII_TYPE) == null
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ CommandHandler createCommandHandler() {
+ new TypeCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.TYPE, ['A'])
+ }
+
+ void setUp() {
+ super.setUp()
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/UserCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/UserCommandHandlerTest.groovy
new file mode 100644
index 0000000..609897e
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/UserCommandHandlerTest.groovy
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.CommandHandler
+import org.mockftpserver.core.command.CommandNames
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.core.session.SessionKeys
+import org.mockftpserver.fake.UserAccount
+
+/**
+ * Tests for UserCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class UserCommandHandlerTest extends AbstractFakeCommandHandlerTestCase {
+
+ static final USERNAME = "user123"
+ static final HOME_DIRECTORY = "/"
+ UserAccount userAccount
+
+ boolean testNotLoggedIn = false
+
+ void testHandleCommand_UserExists() {
+ serverConfiguration.userAccounts[USERNAME] = userAccount
+ handleCommand([USERNAME])
+ assertSessionReply(ReplyCodes.USER_NEED_PASSWORD_OK, 'user.needPassword')
+ assertUsernameInSession(true)
+ assertCurrentDirectory(null)
+ }
+
+ void testHandleCommand_NoSuchUser() {
+ handleCommand([USERNAME])
+ // Will return OK, even if username is not recognized
+ assertSessionReply(ReplyCodes.USER_NEED_PASSWORD_OK, 'user.needPassword')
+ assertUsernameInSession(true)
+ assertCurrentDirectory(null)
+ }
+
+ void testHandleCommand_PasswordNotRequiredForLogin() {
+ userAccount.passwordRequiredForLogin = false
+ serverConfiguration.userAccounts[USERNAME] = userAccount
+
+ handleCommand([USERNAME])
+ assertSessionReply(ReplyCodes.USER_LOGGED_IN_OK, 'user.loggedIn')
+ assert session.getAttribute(SessionKeys.USER_ACCOUNT) == userAccount
+ assertUsernameInSession(false)
+ assertCurrentDirectory(HOME_DIRECTORY)
+ }
+
+ void testHandleCommand_UserExists_HomeDirectoryNotDefinedForUser() {
+ userAccount.homeDirectory = ''
+ serverConfiguration.userAccounts[USERNAME] = userAccount
+ handleCommand([USERNAME])
+ assertSessionReply(ReplyCodes.USER_ACCOUNT_NOT_VALID, "login.userAccountNotValid")
+ assertUsernameInSession(false)
+ assertCurrentDirectory(null)
+ }
+
+ void testHandleCommand_UserExists_HomeDirectoryDoesNotExist() {
+ userAccount.homeDirectory = '/abc/def'
+ serverConfiguration.userAccounts[USERNAME] = userAccount
+ handleCommand([USERNAME])
+ assertSessionReply(ReplyCodes.USER_ACCOUNT_NOT_VALID, "login.homeDirectoryNotValid")
+ assertUsernameInSession(false)
+ assertCurrentDirectory(null)
+ }
+
+ void testHandleCommand_MissingUsernameParameter() {
+ testHandleCommand_MissingRequiredParameter([])
+ assertUsernameInSession(false)
+ assertCurrentDirectory(null)
+ }
+
+ //-------------------------------------------------------------------------
+ // Abstract and Overridden Methods
+ //-------------------------------------------------------------------------
+
+ void setUp() {
+ super.setUp()
+
+ createDirectory(HOME_DIRECTORY)
+ userAccount = new UserAccount(username: USERNAME, homeDirectory: HOME_DIRECTORY)
+ }
+
+ CommandHandler createCommandHandler() {
+ new UserCommandHandler()
+ }
+
+ Command createValidCommand() {
+ return new Command(CommandNames.USER, [USERNAME])
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * Assert that the Username is stored in the session, depending on the value of isUsernameInSession.
+ * @param isUsernameInSession - true if the Username is expected in the session; false if it is not expected
+ */
+ private void assertUsernameInSession(boolean isUsernameInSession) {
+ def expectedValue = isUsernameInSession ? USERNAME : null
+ assert session.getAttribute(SessionKeys.USERNAME) == expectedValue
+ }
+
+ /**
+ * Assert that the current directory is set in the session, but only if currentDirectory is not null.
+ * @param currentDirectory - the curent directory expected in the session; null if it is not expected
+ */
+ private void assertCurrentDirectory(String currentDirectory) {
+ assert session.getAttribute(SessionKeys.CURRENT_DIRECTORY) == currentDirectory
+ }
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/_AbstractFakeCommandHandlerTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/_AbstractFakeCommandHandlerTest.groovy
new file mode 100644
index 0000000..7b2400b
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/command/_AbstractFakeCommandHandlerTest.groovy
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command
+
+import org.mockftpserver.core.CommandSyntaxException
+import org.mockftpserver.core.IllegalStateException
+import org.mockftpserver.core.NotLoggedInException
+import org.mockftpserver.core.command.Command
+import org.mockftpserver.core.command.ReplyCodes
+import org.mockftpserver.core.session.Session
+import org.mockftpserver.core.session.SessionKeys
+import org.mockftpserver.core.session.StubSession
+import org.mockftpserver.fake.StubServerConfiguration
+import org.mockftpserver.fake.UserAccount
+import org.mockftpserver.fake.filesystem.FileSystemException
+import org.mockftpserver.fake.filesystem.InvalidFilenameException
+import org.mockftpserver.fake.filesystem.UnixFakeFileSystem
+import org.mockftpserver.test.AbstractGroovyTestCase
+import org.mockftpserver.test.StubResourceBundle
+
+/**
+ * Tests for AbstractFakeCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class AbstractFakeCommandHandlerClassTest extends AbstractGroovyTestCase {
+
+ static PATH = "some/path"
+ static REPLY_CODE = 99
+ static MESSAGE_KEY = "99.WithFilename"
+ static ARG = "ABC"
+ static MSG = "text {0}"
+ static MSG_WITH_ARG = "text ABC"
+ static MSG_FOR_KEY = "some other message"
+ static INTERNAL_ERROR = AbstractFakeCommandHandler.INTERNAL_ERROR_KEY
+ static MSG_INTERNAL_ERROR = "internal error message {0}"
+ private AbstractFakeCommandHandler commandHandler
+ private session
+ private serverConfiguration
+ private replyTextBundle
+ private fileSystem
+ private userAccount
+
+ //-------------------------------------------------------------------------
+ // Tests
+ //-------------------------------------------------------------------------
+
+ void testHandleCommand() {
+ def command = new Command("C1", ["abc"])
+ commandHandler.handleCommand(command, session)
+ assert commandHandler.handled
+
+ assertHandleCommandReplyCode(new CommandSyntaxException(""), ReplyCodes.COMMAND_SYNTAX_ERROR)
+ assertHandleCommandReplyCode(new IllegalStateException(""), ReplyCodes.ILLEGAL_STATE)
+ assertHandleCommandReplyCode(new NotLoggedInException(""), ReplyCodes.NOT_LOGGED_IN)
+ assertHandleCommandReplyCode(new InvalidFilenameException(""), ReplyCodes.FILENAME_NOT_VALID)
+
+ shouldFail { commandHandler.handleCommand(null, session) }
+ shouldFail { commandHandler.handleCommand(command, null) }
+ }
+
+ void testHandleCommand_FileSystemException() {
+ assertHandleCommandReplyCode(new FileSystemException(PATH, ''), ReplyCodes.READ_FILE_ERROR, PATH)
+ commandHandler.replyCodeForFileSystemException = ReplyCodes.WRITE_FILE_ERROR
+ assertHandleCommandReplyCode(new FileSystemException(PATH, ''), ReplyCodes.WRITE_FILE_ERROR, PATH)
+ }
+
+ void testSendReply() {
+ commandHandler.sendReply(session, REPLY_CODE)
+ assert session.sentReplies[0] == [REPLY_CODE, MSG], session.sentReplies[0]
+
+ commandHandler.sendReply(session, REPLY_CODE, [ARG])
+ assert session.sentReplies[1] == [REPLY_CODE, MSG_WITH_ARG], session.sentReplies[0]
+
+ shouldFailWithMessageContaining('session') { commandHandler.sendReply(null, REPLY_CODE) }
+ shouldFailWithMessageContaining('reply code') { commandHandler.sendReply(session, 0) }
+ }
+
+ void testSendReply_MessageKey() {
+ commandHandler.sendReply(session, REPLY_CODE, MESSAGE_KEY)
+ assert session.sentReplies[0] == [REPLY_CODE, MSG_FOR_KEY], session.sentReplies[0]
+
+ shouldFailWithMessageContaining('session') { commandHandler.sendReply(null, REPLY_CODE, MESSAGE_KEY) }
+ shouldFailWithMessageContaining('reply code') { commandHandler.sendReply(session, 0, MESSAGE_KEY) }
+ }
+
+ void testSendReply_NullMessageKey() {
+ commandHandler.sendReply(session, REPLY_CODE, null, null)
+ assert session.sentReplies[0] == [REPLY_CODE, MSG_INTERNAL_ERROR], session.sentReplies[0]
+ }
+
+ void testAssertValidReplyCode() {
+ commandHandler.assertValidReplyCode(1) // no exception expected
+ shouldFail { commandHandler.assertValidReplyCode(0) }
+ }
+
+ void testGetRequiredSessionAttribute() {
+ shouldFail(IllegalStateException) { commandHandler.getRequiredSessionAttribute(session, "undefined") }
+
+ session.setAttribute("abc", "not empty")
+ commandHandler.getRequiredSessionAttribute(session, "abc") // no exception
+
+ session.setAttribute("abc", "")
+ commandHandler.getRequiredSessionAttribute(session, "abc") // no exception
+ }
+
+ void testVerifyLoggedIn() {
+ shouldFail(NotLoggedInException) { commandHandler.verifyLoggedIn(session) }
+ session.setAttribute(SessionKeys.USER_ACCOUNT, userAccount)
+ commandHandler.verifyLoggedIn(session) // no exception expected
+ }
+
+ void testGetUserAccount() {
+ assert commandHandler.getUserAccount(session) == null
+ session.setAttribute(SessionKeys.USER_ACCOUNT, userAccount)
+ assert commandHandler.getUserAccount(session)
+ }
+
+ void testVerifyFileSystemCondition() {
+ commandHandler.verifyFileSystemCondition(true, PATH, '') // no exception expected
+ shouldFail(FileSystemException) { commandHandler.verifyFileSystemCondition(false, PATH, '') }
+ }
+
+ void testGetRealPath() {
+ assert commandHandler.getRealPath(session, "/xxx") == "/xxx"
+
+ session.setAttribute(SessionKeys.CURRENT_DIRECTORY, "/usr/me")
+ assert commandHandler.getRealPath(session, null) == "/usr/me"
+ assert commandHandler.getRealPath(session, "/xxx") == "/xxx"
+ assert commandHandler.getRealPath(session, "xxx") == "/usr/me/xxx"
+ assert commandHandler.getRealPath(session, "../xxx") == "/usr/xxx"
+ assert commandHandler.getRealPath(session, "./xxx") == "/usr/me/xxx"
+ }
+
+ //-------------------------------------------------------------------------
+ // Test Setup
+ //-------------------------------------------------------------------------
+
+ void setUp() {
+ super.setUp()
+ commandHandler = new TestFakeCommandHandler()
+ session = new StubSession()
+ serverConfiguration = new StubServerConfiguration()
+ replyTextBundle = new StubResourceBundle()
+ userAccount = new UserAccount()
+ fileSystem = new UnixFakeFileSystem()
+ serverConfiguration.setFileSystem(fileSystem)
+
+ replyTextBundle.put(REPLY_CODE as String, MSG)
+ replyTextBundle.put(MESSAGE_KEY as String, MSG_FOR_KEY)
+ replyTextBundle.put(INTERNAL_ERROR as String, MSG_INTERNAL_ERROR)
+
+ commandHandler.serverConfiguration = serverConfiguration
+ commandHandler.replyTextBundle = replyTextBundle
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * Assert that when the CommandHandler handleCommand() method throws the
+ * specified exception, that the expected reply is sent through the session.
+ */
+ private void assertHandleCommandReplyCode(Throwable exception, int expected, text = null) {
+ commandHandler.exception = exception
+ def command = new Command("C1", ["abc"])
+ session.sentReplies.clear()
+ commandHandler.handleCommand(command, session)
+ def sentReply = session.sentReplies[0][0]
+ assert sentReply == expected
+ if (text) {
+ def sentMessage = session.sentReplies[0][1]
+ assert sentMessage.contains(text), "sentMessage=[$sentMessage] text=[$text]"
+ }
+ }
+
+}
+
+/**
+ * Concrete subclass of AbstractFakeCommandHandler for testing
+ */
+class TestFakeCommandHandler extends AbstractFakeCommandHandler {
+ boolean handled = false
+ def exception
+
+ protected void handle(Command command, Session session) {
+ if (exception) {
+ throw exception
+ }
+ this.handled = true
+ }
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/example/FakeFtpServerSpringConfigurationTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/example/FakeFtpServerSpringConfigurationTest.groovy
new file mode 100644
index 0000000..2d42431
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/example/FakeFtpServerSpringConfigurationTest.groovy
@@ -0,0 +1,100 @@
+package org.mockftpserver.fake.example
+
+import org.apache.commons.net.ftp.FTPClient
+import org.apache.commons.net.ftp.FTPFile
+import org.mockftpserver.fake.FakeFtpServer
+import org.mockftpserver.test.AbstractGroovyTestCase
+import org.springframework.context.ApplicationContext
+import org.springframework.context.support.ClassPathXmlApplicationContext
+
+/*
+* Copyright 2008 the original author or authors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+class FakeFtpServerSpringConfigurationTest extends AbstractGroovyTestCase {
+
+ static final SERVER = "localhost"
+ static final PORT = 9981
+ static final USERNAME = 'joe' // Must match Spring config
+ static final PASSWORD = 'password' // Must match Spring config
+
+ private FakeFtpServer fakeFtpServer
+ private FTPClient ftpClient
+
+ void testFakeFtpServer_Unix() {
+ startFtpServer('fakeftpserver-beans.xml')
+ connectAndLogin()
+
+ // PWD
+ String dir = ftpClient.printWorkingDirectory()
+ assert dir == '/'
+
+ // LIST
+ FTPFile[] files = ftpClient.listFiles()
+ LOG.info("FTPFile[0]=" + files[0])
+ assert files.length == 1
+
+ // RETR
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream()
+ assert ftpClient.retrieveFile("File.txt", outputStream)
+ LOG.info("File contents=[" + outputStream.toString() + "]")
+ }
+
+ void testFakeFtpServer_Windows_WithPermissions() {
+ startFtpServer('fakeftpserver-permissions-beans.xml')
+ connectAndLogin()
+
+ // PWD
+ String dir = ftpClient.printWorkingDirectory()
+ assert dir == 'c:\\'
+
+ // LIST
+ FTPFile[] files = ftpClient.listFiles()
+ assert files.length == 2
+ LOG.info("FTPFile[0]=" + files[0])
+ LOG.info("FTPFile[1]=" + files[1])
+
+ // RETR - File1.txt; we have required permissions
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream()
+ assert ftpClient.retrieveFile("File1.txt", outputStream)
+ LOG.info("File contents=[" + outputStream.toString() + "]")
+
+ // RETR - File2.txt; we DO NOT have required permissions
+ outputStream = new ByteArrayOutputStream()
+ assert !ftpClient.retrieveFile("File2.txt", outputStream)
+ assert ftpClient.replyCode == 550
+ }
+
+ void setUp() {
+ super.setUp()
+ ftpClient = new FTPClient()
+ }
+
+ void tearDown() {
+ super.tearDown()
+ fakeFtpServer?.stop()
+ }
+
+ private void startFtpServer(String springConfigFile) {
+ ApplicationContext context = new ClassPathXmlApplicationContext(springConfigFile)
+ fakeFtpServer = (FakeFtpServer) context.getBean("fakeFtpServer")
+ fakeFtpServer.start()
+ }
+
+ private void connectAndLogin() {
+ ftpClient.connect(SERVER, PORT)
+ assert ftpClient.login(USERNAME, PASSWORD)
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/AbstractFakeFileSystemTestCase.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/AbstractFakeFileSystemTestCase.groovy
new file mode 100644
index 0000000..440c9d5
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/AbstractFakeFileSystemTestCase.groovy
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem
+
+import org.mockftpserver.core.util.IoUtil
+
+/**
+ * Tests for subclasses of AbstractFakeFileSystem. Subclasses must define
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+abstract class AbstractFakeFileSystemTestCase extends AbstractFileSystemTestCase {
+
+ // -------------------------------------------------------------------------
+ // Tests
+ // -------------------------------------------------------------------------
+
+ void testDefaultDirectoryListingFormatterClass() {
+ assert fileSystem.directoryListingFormatter.class == expectedDirectoryListingFormatterClass
+ }
+
+ void testAdd_PathLocked() {
+ def dirEntry = new DirectoryEntry(NEW_DIR)
+ fileSystem.add(dirEntry)
+ def fileEntry = new FileEntry(NEW_FILE)
+ fileSystem.add(fileEntry)
+
+ // The path should be locked for both entries
+ shouldFail { dirEntry.setPath('abc') }
+ shouldFail { fileEntry.setPath('abc') }
+ }
+
+ void testAdd_Directory_CreateParentDirectoriesAutomatically() {
+ final NEW_SUBDIR = fileSystem.path(NEW_DIR, "sub")
+ assert !fileSystem.exists(NEW_DIR), "Before createDirectory"
+ assert !fileSystem.exists(NEW_SUBDIR), "Before createDirectory"
+
+ fileSystem.createParentDirectoriesAutomatically = true
+ fileSystem.add(new DirectoryEntry(NEW_SUBDIR))
+ assert fileSystem.exists(NEW_DIR), "After createDirectory"
+ assert fileSystem.exists(NEW_SUBDIR), "$NEW_SUBDIR: After createDirectory"
+ }
+
+ void testAdd_File_CreateParentDirectoriesAutomatically() {
+ final NEW_FILE_IN_SUBDIR = fileSystem.path(NEW_DIR, "abc.txt")
+ assert !fileSystem.exists(NEW_DIR), "Before createDirectory"
+ assert !fileSystem.exists(NEW_FILE_IN_SUBDIR), "Before createDirectory"
+
+ fileSystem.createParentDirectoriesAutomatically = true
+ fileSystem.add(new FileEntry(NEW_FILE_IN_SUBDIR))
+ assert fileSystem.exists(NEW_DIR), "After createDirectory"
+ assert fileSystem.exists(NEW_FILE_IN_SUBDIR), "$NEW_FILE_IN_SUBDIR: After createDirectory"
+ }
+
+ void testAdd_File_CreateParentDirectoriesAutomatically_False() {
+ fileSystem.createParentDirectoriesAutomatically = false
+ final NEW_FILE_IN_SUBDIR = fileSystem.path(NEW_DIR, "abc.txt")
+ assert !fileSystem.exists(NEW_DIR), "Before createDirectory"
+
+ shouldFail(FileSystemException) { fileSystem.add(new FileEntry(NEW_FILE_IN_SUBDIR)) }
+ assert !fileSystem.exists(NEW_DIR), "After createDirectory"
+ }
+
+ void testSetEntries() {
+ fileSystem.createParentDirectoriesAutomatically = false
+ def entries = [new FileEntry(NEW_FILE), new DirectoryEntry(NEW_DIR)]
+ fileSystem.setEntries(entries)
+ assert fileSystem.exists(NEW_DIR)
+ assert fileSystem.exists(NEW_FILE)
+ }
+
+ void testToString() {
+ String toString = fileSystem.toString()
+ LOG.info("toString=" + toString)
+ assert toString.contains(EXISTING_DIR)
+ assert toString.contains(EXISTING_FILE)
+ }
+
+ void testFormatDirectoryListing() {
+ def fileEntry = new FileEntry(path: 'abc')
+ def formatter = [format: {f ->
+ assert f == fileEntry
+ return 'abc'
+ }] as DirectoryListingFormatter
+ fileSystem.directoryListingFormatter = formatter
+ assert fileSystem.formatDirectoryListing(fileEntry) == 'abc'
+ }
+
+ void testFormatDirectoryListing_NullDirectoryListingFormatter() {
+ fileSystem.directoryListingFormatter = null
+ def fileEntry = new FileEntry('abc')
+ shouldFailWithMessageContaining('directoryListingFormatter') { assert fileSystem.formatDirectoryListing(fileEntry) }
+ }
+
+ void testFormatDirectoryListing_NullFileSystemEntry() {
+ def formatter = [format: {f -> }] as DirectoryListingFormatter
+ fileSystem.directoryListingFormatter = formatter
+ shouldFailWithMessageContaining('fileSystemEntry') { assert fileSystem.formatDirectoryListing(null) }
+ }
+
+ void testGetEntry() {
+ assert fileSystem.getEntry(NO_SUCH_DIR) == null
+ assert fileSystem.getEntry(NO_SUCH_FILE) == null
+
+ assert fileSystem.getEntry(EXISTING_FILE).path == EXISTING_FILE
+ assert fileSystem.getEntry(EXISTING_DIR).path == EXISTING_DIR
+
+ def permissions = new Permissions('-wxrwx---')
+ def fileEntry = new FileEntry(path: NEW_FILE, lastModified: DATE, contents: 'abc', owner: 'owner',
+ group: 'group', permissions: permissions)
+ fileSystem.add(fileEntry)
+ def entry = fileSystem.getEntry(NEW_FILE)
+ LOG.info(entry.toString())
+ assert entry.path == NEW_FILE
+ assert !entry.directory
+ assert entry.size == 3
+ assert entry.owner == 'owner'
+ assert entry.group == 'group'
+ assert entry.permissions == permissions
+ }
+
+ void testNormalize_Null() {
+ shouldFailWithMessageContaining("path") { fileSystem.normalize(null) }
+ }
+
+ void testGetName_Null() {
+ shouldFailWithMessageContaining("path") { fileSystem.getName(null) }
+ }
+
+ //--------------------------------------------------------------------------
+ // Abstract Methods
+ //--------------------------------------------------------------------------
+
+ protected abstract Class getExpectedDirectoryListingFormatterClass()
+
+ //--------------------------------------------------------------------------
+ // Internal Helper Methods
+ //--------------------------------------------------------------------------
+
+ /**
+ * Verify the contents of the file at the specified path read from its InputSteam
+ *
+ * @param fileSystem - the FileSystem instance
+ * @param expectedContents - the expected contents
+ * @throws IOException
+ * @see org.mockftpserver.fake.filesystem.AbstractFileSystemTestCase#verifyFileContents(FileSystem,String,String )
+ */
+ protected void verifyFileContents(FileSystem fileSystem, String path, String expectedContents) throws IOException {
+ def fileEntry = fileSystem.getEntry(path)
+ InputStream input = fileEntry.createInputStream()
+ byte[] bytes = IoUtil.readBytes(input)
+ LOG.info("bytes=[" + new String(bytes) + "]")
+ assertEquals("contents: actual=[" + new String(bytes) + "]", expectedContents.getBytes(), bytes)
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/AbstractFileSystemEntryTestCase.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/AbstractFileSystemEntryTestCase.groovy
new file mode 100644
index 0000000..2d2580b
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/AbstractFileSystemEntryTestCase.groovy
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem
+
+import java.lang.reflect.Constructor
+import org.mockftpserver.test.AbstractGroovyTestCase
+
+/**
+ * Abstract test superclass for subclasses of AbstractFileSystemEntry
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public abstract class AbstractFileSystemEntryTestCase extends AbstractGroovyTestCase {
+
+ protected static final PATH = "c:/test/dir"
+ protected static final NEW_PATH = "d:/other/dir"
+ protected static final USER = 'user77'
+ protected static final GROUP = 'group88'
+ protected static final PERMISSIONS = new Permissions('rwxrwx---')
+ protected static final LAST_MODIFIED = new Date()
+
+ void testConstructor_NoArgs() {
+ AbstractFileSystemEntry entry = (AbstractFileSystemEntry) getImplementationClass().newInstance()
+ assertNull("path", entry.getPath())
+ entry.setPath(PATH)
+ assert entry.getPath() == PATH
+ assert isDirectory() == entry.isDirectory()
+ }
+
+ void testConstructor_Path() {
+ Constructor constructor = getImplementationClass().getConstructor([String.class] as Class[])
+ AbstractFileSystemEntry entry = (AbstractFileSystemEntry) constructor.newInstance([PATH] as Object[])
+ LOG.info(entry.toString())
+ assertEquals("path", PATH, entry.getPath())
+ entry.setPath("")
+ assert entry.getPath() == ""
+ assert isDirectory() == entry.isDirectory()
+ }
+
+ void testLockPath() {
+ def entry = createFileSystemEntry(PATH)
+ entry.lockPath()
+ shouldFail { entry.path = 'abc' }
+ assert entry.path == PATH
+ }
+
+ void testGetName() {
+ assert createFileSystemEntry('abc').name == 'abc'
+ assert createFileSystemEntry('/abc').name == 'abc'
+ assert createFileSystemEntry('/dir/abc').name == 'abc'
+ assert createFileSystemEntry('\\abc').name == 'abc'
+ }
+
+ void testSetPermissionsFromString() {
+ def entry = createFileSystemEntry('abc')
+ final PERM = 'rw-r---wx'
+ entry.setPermissionsFromString(PERM)
+ assert entry.permissions == new Permissions(PERM)
+ }
+
+ protected AbstractFileSystemEntry createFileSystemEntry(String path) {
+ def entry = (AbstractFileSystemEntry) getImplementationClass().newInstance()
+ entry.setPath(path)
+ return entry
+ }
+
+ /**
+ * @return the subclass of AbstractFileSystemEntry to be tested
+ */
+ protected abstract Class getImplementationClass()
+
+ /**
+ * @return true if the class being tested represents a directory entry
+ */
+ protected abstract boolean isDirectory()
+
+}
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/AbstractFileSystemTestCase.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/AbstractFileSystemTestCase.groovy
new file mode 100644
index 0000000..d3ceaf9
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/AbstractFileSystemTestCase.groovy
@@ -0,0 +1,372 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem
+
+import org.mockftpserver.test.AbstractGroovyTestCase
+
+/**
+ * Abstract superclass for tests of FileSystem implementation classes. Contains common
+ * tests and test infrastructure.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+abstract class AbstractFileSystemTestCase extends AbstractGroovyTestCase {
+
+ public static final FILENAME1 = "File1.txt"
+ public static final FILENAME2 = "file2.txt"
+ public static final DIR1 = "dir1"
+ public static final NEW_DIRNAME = "testDir"
+ public static final ILLEGAL_FILE = "xx/yy////z!<>?*z.txt"
+ public static final EXISTING_FILE_CONTENTS = "abc 123 %^& xxx"
+ public static final DATE = new Date()
+
+ // These must be set by the concrete subclass (in its constructor)
+ protected String NEW_DIR = null
+ protected String NEW_FILE = null
+ protected String EXISTING_DIR = null
+ protected String EXISTING_FILE = null
+ protected NO_SUCH_DIR = null
+ protected NO_SUCH_FILE = null
+
+ protected FileSystem fileSystem
+
+ //-------------------------------------------------------------------------
+ // Common Tests
+ //-------------------------------------------------------------------------
+
+ void testExists() {
+ assert !fileSystem.exists(NEW_FILE)
+ assert !fileSystem.exists(NEW_DIR)
+ assert !fileSystem.exists(ILLEGAL_FILE)
+ assert fileSystem.exists(EXISTING_FILE)
+ assert fileSystem.exists(EXISTING_DIR)
+
+ shouldFailWithMessageContaining("path") { fileSystem.exists(null) }
+ }
+
+ void testIsDirectory() {
+ assert fileSystem.isDirectory(EXISTING_DIR)
+ assert !fileSystem.isDirectory(EXISTING_FILE)
+ assert !fileSystem.isDirectory(NO_SUCH_DIR)
+ assert !fileSystem.isDirectory(NO_SUCH_FILE)
+ assert !fileSystem.isDirectory(ILLEGAL_FILE)
+
+ shouldFailWithMessageContaining("path") { fileSystem.isDirectory(null) }
+ }
+
+ void testIsFile() {
+ assert fileSystem.isFile(EXISTING_FILE)
+ assert !fileSystem.isFile(EXISTING_DIR)
+ assert !fileSystem.isFile(NO_SUCH_DIR)
+ assert !fileSystem.isFile(NO_SUCH_FILE)
+ assert !fileSystem.isFile(ILLEGAL_FILE)
+
+ shouldFailWithMessageContaining("path") { fileSystem.isFile(null) }
+ }
+
+ void testAdd_Directory() {
+ assert !fileSystem.exists(NEW_DIR), "Before createDirectory"
+ fileSystem.add(new DirectoryEntry(NEW_DIR))
+ assert fileSystem.exists(NEW_DIR), "After createDirectory"
+
+ // Duplicate directory
+ shouldThrowFileSystemExceptionWithMessageKey('filesystem.pathAlreadyExists') {
+ fileSystem.add(new DirectoryEntry(NEW_DIR))
+ }
+
+ // The parent of the path does not exist
+ shouldThrowFileSystemExceptionWithMessageKey('filesystem.parentDirectoryDoesNotExist') {
+ fileSystem.add(new DirectoryEntry(NEW_DIR + "/abc/def"))
+ }
+
+ shouldFail(InvalidFilenameException) { fileSystem.add(new DirectoryEntry(ILLEGAL_FILE)) }
+ shouldFailWithMessageContaining("path") { fileSystem.add(new DirectoryEntry(null)) }
+ }
+
+ void testAdd_File() {
+ assert !fileSystem.exists(NEW_FILE), "Before createFile"
+ fileSystem.add(new FileEntry(NEW_FILE))
+ assert fileSystem.exists(NEW_FILE), "After createFile"
+
+ // File already exists
+ shouldThrowFileSystemExceptionWithMessageKey('filesystem.pathAlreadyExists') {
+ fileSystem.add(new FileEntry(NEW_FILE))
+ }
+
+ // The parent of the path does not exist
+ shouldThrowFileSystemExceptionWithMessageKey('filesystem.parentDirectoryDoesNotExist') {
+ fileSystem.add(new FileEntry(NEW_DIR + "/abc/def"))
+ }
+
+ shouldThrowFileSystemExceptionWithMessageKey('filesystem.parentDirectoryDoesNotExist') {
+ fileSystem.add(new FileEntry(NO_SUCH_DIR))
+ }
+
+ shouldFail(InvalidFilenameException) { fileSystem.add(new FileEntry(ILLEGAL_FILE)) }
+
+ shouldFailWithMessageContaining("path") { fileSystem.add(new FileEntry(null)) }
+ }
+
+ void testRename_NullFromPath() {
+ shouldFailWithMessageContaining("fromPath") { fileSystem.rename(null, FILENAME1) }
+ }
+
+ void testRename_NullToPath() {
+ shouldFailWithMessageContaining("toPath") { fileSystem.rename(FILENAME1, null) }
+ }
+
+ void testListNames() {
+ fileSystem.add(new DirectoryEntry(NEW_DIR))
+ assert fileSystem.listNames(NEW_DIR) == []
+
+ fileSystem.add(new FileEntry(p(NEW_DIR, FILENAME1)))
+ fileSystem.add(new FileEntry(p(NEW_DIR, FILENAME2)))
+ fileSystem.add(new DirectoryEntry(p(NEW_DIR, DIR1)))
+ fileSystem.add(new FileEntry(p(NEW_DIR, DIR1, "/abc.def")))
+
+ List filenames = fileSystem.listNames(NEW_DIR)
+ LOG.info("filenames=" + filenames)
+ assertSameIgnoringOrder(filenames, [FILENAME1, FILENAME2, DIR1])
+
+ // Specify a filename instead of a directory name
+ assert [FILENAME1] == fileSystem.listNames(p(NEW_DIR, FILENAME1))
+
+ assert [] == fileSystem.listNames(NO_SUCH_DIR)
+
+ shouldFailWithMessageContaining("path") { fileSystem.listNames(null) }
+ }
+
+ void testListNames_Wildcards() {
+ fileSystem.add(new DirectoryEntry(NEW_DIR))
+ fileSystem.add(new FileEntry(p(NEW_DIR, 'abc.txt')))
+ fileSystem.add(new FileEntry(p(NEW_DIR, 'def.txt')))
+
+ assertSameIgnoringOrder(fileSystem.listNames(p(NEW_DIR, '*.txt')), ['abc.txt', 'def.txt'])
+ assertSameIgnoringOrder(fileSystem.listNames(p(NEW_DIR, '*')), ['abc.txt', 'def.txt'])
+ assertSameIgnoringOrder(fileSystem.listNames(p(NEW_DIR, '???.???')), ['abc.txt', 'def.txt'])
+ assertSameIgnoringOrder(fileSystem.listNames(p(NEW_DIR, '*.exe')), [])
+ assertSameIgnoringOrder(fileSystem.listNames(p(NEW_DIR, 'abc.???')), ['abc.txt'])
+ assertSameIgnoringOrder(fileSystem.listNames(p(NEW_DIR, 'a?c.?xt')), ['abc.txt'])
+ assertSameIgnoringOrder(fileSystem.listNames(p(NEW_DIR, 'd?f.*')), ['def.txt'])
+ }
+
+ void testListFiles() {
+ fileSystem.add(new DirectoryEntry(NEW_DIR))
+ assert [] == fileSystem.listFiles(NEW_DIR)
+
+ def path1 = p(NEW_DIR, FILENAME1)
+ def fileEntry1 = new FileEntry(path1)
+ fileSystem.add(fileEntry1)
+ assert fileSystem.listFiles(NEW_DIR) == [fileEntry1]
+
+ // Specify a filename instead of a directory name
+ assert fileSystem.listFiles(p(NEW_DIR, FILENAME1)) == [fileEntry1]
+
+ def fileEntry2 = new FileEntry(p(NEW_DIR, FILENAME2))
+ fileSystem.add(fileEntry2)
+ assert fileSystem.listFiles(NEW_DIR) as Set == [fileEntry1, fileEntry2] as Set
+
+ // Write to the file to get a non-zero length
+ final byte[] CONTENTS = "1234567890".getBytes()
+ OutputStream out = fileEntry1.createOutputStream(false)
+ out.write(CONTENTS)
+ out.close()
+ assert fileSystem.listFiles(NEW_DIR) as Set == [fileEntry1, fileEntry2] as Set
+
+ def dirEntry3 = new DirectoryEntry(p(NEW_DIR, DIR1))
+ fileSystem.add(dirEntry3)
+ assert fileSystem.listFiles(NEW_DIR) as Set == [fileEntry1, fileEntry2, dirEntry3] as Set
+
+ assert fileSystem.listFiles(NO_SUCH_DIR) == []
+
+ shouldFailWithMessageContaining("path") { fileSystem.listFiles(null) }
+ }
+
+ void testListFiles_Wildcards() {
+ def dirEntry = new DirectoryEntry(NEW_DIR)
+ def fileEntry1 = new FileEntry(p(NEW_DIR, 'abc.txt'))
+ def fileEntry2 = new FileEntry(p(NEW_DIR, 'def.txt'))
+
+ fileSystem.add(dirEntry)
+ fileSystem.add(fileEntry1)
+ fileSystem.add(fileEntry2)
+
+ assert fileSystem.listFiles(p(NEW_DIR, '*.txt')) as Set == [fileEntry1, fileEntry2] as Set
+ assert fileSystem.listFiles(p(NEW_DIR, '*')) as Set == [fileEntry1, fileEntry2] as Set
+ assert fileSystem.listFiles(p(NEW_DIR, '???.???')) as Set == [fileEntry1, fileEntry2] as Set
+ assert fileSystem.listFiles(p(NEW_DIR, '*.exe')) as Set == [] as Set
+ assert fileSystem.listFiles(p(NEW_DIR, 'abc.???')) as Set == [fileEntry1] as Set
+ assert fileSystem.listFiles(p(NEW_DIR, 'a?c.?xt')) as Set == [fileEntry1] as Set
+ assert fileSystem.listFiles(p(NEW_DIR, 'd?f.*')) as Set == [fileEntry2] as Set
+ }
+
+ void testDelete() {
+ fileSystem.add(new FileEntry(NEW_FILE))
+ assert fileSystem.delete(NEW_FILE)
+ assert !fileSystem.exists(NEW_FILE)
+
+ assert !fileSystem.delete(NO_SUCH_FILE)
+
+ fileSystem.add(new DirectoryEntry(NEW_DIR))
+ assert fileSystem.delete(NEW_DIR)
+ assert !fileSystem.exists(NEW_DIR)
+
+ fileSystem.add(new DirectoryEntry(NEW_DIR))
+ fileSystem.add(new FileEntry(NEW_DIR + "/abc.txt"))
+
+ assert !fileSystem.delete(NEW_DIR), "Directory containing files"
+ assert fileSystem.exists(NEW_DIR)
+
+ shouldFailWithMessageContaining("path") { fileSystem.delete(null) }
+ }
+
+ void testRename() {
+ final FROM_FILE = NEW_FILE + "2"
+ fileSystem.add(new FileEntry(FROM_FILE))
+
+ fileSystem.rename(FROM_FILE, NEW_FILE)
+ assert fileSystem.exists(NEW_FILE)
+
+ fileSystem.add(new DirectoryEntry(NEW_DIR))
+
+ // Rename existing directory
+ final String TO_DIR = NEW_DIR + "2"
+ fileSystem.rename(NEW_DIR, TO_DIR)
+ assert !fileSystem.exists(NEW_DIR)
+ assert fileSystem.exists(TO_DIR)
+ }
+
+ void testRename_ToPathFileAlreadyExists() {
+ final FROM_FILE = EXISTING_FILE
+ final String TO_FILE = NEW_FILE
+ fileSystem.add(new FileEntry(TO_FILE))
+ shouldThrowFileSystemExceptionWithMessageKey('filesystem.alreadyExists') {
+ fileSystem.rename(FROM_FILE, TO_FILE)
+ }
+ }
+
+ void testRename_FromPathDoesNotExist() {
+ final TO_FILE2 = NEW_FILE + "2"
+ shouldThrowFileSystemExceptionWithMessageKey('filesystem.doesNotExist') {
+ fileSystem.rename(NO_SUCH_FILE, TO_FILE2)
+ }
+ assert !fileSystem.exists(TO_FILE2), "After failed rename"
+ }
+
+ void testRename_ToPathIsChildOfFromPath() {
+ final FROM_DIR = NEW_DIR
+ final TO_DIR = FROM_DIR + "/child"
+ fileSystem.add(new DirectoryEntry(FROM_DIR))
+ shouldThrowFileSystemExceptionWithMessageKey('filesystem.renameFailed') {
+ fileSystem.rename(FROM_DIR, TO_DIR)
+ }
+ assert !fileSystem.exists(TO_DIR), "After failed rename"
+ }
+
+ void testRename_EmptyDirectory() {
+ final FROM_DIR = NEW_DIR
+ final TO_DIR = FROM_DIR + "2"
+ fileSystem.add(new DirectoryEntry(FROM_DIR))
+ fileSystem.rename(FROM_DIR, TO_DIR)
+ assert !fileSystem.exists(FROM_DIR)
+ assert fileSystem.exists(TO_DIR)
+ }
+
+ void testRename_DirectoryContainsFiles() {
+ fileSystem.add(new DirectoryEntry(NEW_DIR))
+ fileSystem.add(new FileEntry(NEW_DIR + "/a.txt"))
+ fileSystem.add(new FileEntry(NEW_DIR + "/b.txt"))
+ fileSystem.add(new DirectoryEntry(NEW_DIR + "/subdir"))
+
+ final String TO_DIR = NEW_DIR + "2"
+ fileSystem.rename(NEW_DIR, TO_DIR)
+ assert !fileSystem.exists(NEW_DIR)
+ assert !fileSystem.exists(NEW_DIR + "/a.txt")
+ assert !fileSystem.exists(NEW_DIR + "/b.txt")
+ assert !fileSystem.exists(NEW_DIR + "/subdir")
+
+ assert fileSystem.exists(TO_DIR)
+ assert fileSystem.exists(TO_DIR + "/a.txt")
+ assert fileSystem.exists(TO_DIR + "/b.txt")
+ assert fileSystem.exists(TO_DIR + "/subdir")
+ }
+
+ void testRename_ParentOfToPathDoesNotExist() {
+ final String FROM_FILE = NEW_FILE
+ final String TO_FILE = fileSystem.path(NO_SUCH_DIR, "abc")
+ fileSystem.add(new FileEntry(FROM_FILE))
+
+ shouldThrowFileSystemExceptionWithMessageKey('filesystem.parentDirectoryDoesNotExist') {
+ fileSystem.rename(FROM_FILE, TO_FILE)
+ }
+ assert fileSystem.exists(FROM_FILE)
+ assert !fileSystem.exists(TO_FILE)
+ }
+
+ void testGetParent_Null() {
+ shouldFailWithMessageContaining("path") { fileSystem.getParent(null) }
+ }
+
+ //-------------------------------------------------------------------------
+ // Test setup
+ //-------------------------------------------------------------------------
+
+ void setUp() {
+ super.setUp()
+ fileSystem = createFileSystem()
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ protected void shouldThrowFileSystemExceptionWithMessageKey(String messageKey, Closure closure) {
+ def e = shouldThrow(FileSystemException, closure)
+ assert e.messageKey == messageKey, "Expected message key [$messageKey], but was [${e.messageKey}]"
+ }
+
+ private verifyEntries(List expected, List actual) {
+ expected.eachWithIndex {entry, index ->
+ def entryStr = entry.toString()
+ LOG.info("expected=$entryStr")
+ assert actual.find {actualEntry -> actualEntry.toString() == entryStr }
+ }
+ }
+
+ protected void assertSameIgnoringOrder(list1, list2) {
+ LOG.info("Comparing $list1 to $list2")
+ assert list1 as Set == list2 as Set, "list1=$list1 list2=$list2"
+ }
+
+ /**
+ * Return a new instance of the FileSystem implementation class under test
+ * @return a new FileSystem instance
+ * @throws Exception
+ */
+ protected abstract FileSystem createFileSystem()
+
+ /**
+ * Verify the contents of the file at the specified path read from its InputSteam
+ *
+ * @param fileSystem - the FileSystem instance
+ * @param expectedContents - the expected contents
+ * @throws IOException
+ */
+ protected abstract void verifyFileContents(FileSystem fileSystem, String path, String contents) throws Exception
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/DirectoryEntryTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/DirectoryEntryTest.groovy
new file mode 100644
index 0000000..ee755db
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/DirectoryEntryTest.groovy
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem
+
+/**
+ * Tests for DirectoryEntry
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public class DirectoryEntryTest extends AbstractFileSystemEntryTestCase {
+
+ private DirectoryEntry entry
+
+ void testCloneWithNewPath() {
+ entry.lastModified = LAST_MODIFIED
+ entry.owner = USER
+ entry.group = GROUP
+ entry.permissions = PERMISSIONS
+ def clone = entry.cloneWithNewPath(NEW_PATH)
+
+ assert !clone.is(entry)
+ assert clone.path == NEW_PATH
+ assert clone.lastModified == LAST_MODIFIED
+ assert clone.owner == USER
+ assert clone.group == GROUP
+ assert clone.permissions == PERMISSIONS
+ assert clone.size == 0
+ assert clone.directory
+ }
+
+ /**
+ * @see org.mockftpserver.fake.filesystem.AbstractFileSystemEntryTestCase#getImplementationClass()
+ */
+ protected Class getImplementationClass() {
+ return DirectoryEntry.class
+ }
+
+ /**
+ * @see org.mockftpserver.fake.filesystem.AbstractFileSystemEntryTestCase#isDirectory()
+ */
+ protected boolean isDirectory() {
+ return true
+ }
+
+ void setUp() {
+ super.setUp()
+ entry = new DirectoryEntry(PATH)
+ }
+
+}
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/FileEntryTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/FileEntryTest.groovy
new file mode 100644
index 0000000..a58a1b7
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/FileEntryTest.groovy
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.util.IoUtil
+
+/**
+ * Tests for FileEntry
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public class FileEntryTest extends AbstractFileSystemEntryTestCase {
+
+ private static final LOG = LoggerFactory.getLogger(FileEntryTest)
+ private static final CONTENTS = "abc 123 %^& xxx"
+
+ private FileEntry entry
+
+ void testConstructorWithStringContents() {
+ entry = new FileEntry(PATH, CONTENTS)
+ verifyContents(CONTENTS)
+ }
+
+ void testSettingContentsFromString() {
+ entry.setContents(CONTENTS)
+ verifyContents(CONTENTS)
+ }
+
+ void testSettingContentsFromBytes() {
+ byte[] contents = CONTENTS.getBytes()
+ entry.setContents(contents)
+ // Now corrupt the original byte array to make sure the file entry is not affected
+ contents[1] = (byte) '#'
+ verifyContents(CONTENTS)
+ }
+
+ void testSetContents_BytesNotInCharSet() {
+ byte[] contents = [65, -99, 91, -115] as byte[]
+ entry.setContents(contents)
+ verifyContents(contents)
+ }
+
+ void testSetContents_NullString() {
+ entry.setContents((String) null)
+ assert entry.size == 0
+ }
+
+ void testSetContents_NullBytes() {
+ entry.setContents((byte[]) null)
+ assert entry.size == 0
+ }
+
+ void testCreateOutputStream() {
+ // New, empty file
+ OutputStream out = entry.createOutputStream(false)
+ out.write(CONTENTS.getBytes())
+ verifyContents(CONTENTS)
+
+ // Another OutputStream, append=false
+ out = entry.createOutputStream(false)
+ out.write(CONTENTS.getBytes())
+ verifyContents(CONTENTS)
+
+ // Another OutputStream, append=true
+ out = entry.createOutputStream(true)
+ out.write(CONTENTS.getBytes())
+ verifyContents(CONTENTS + CONTENTS)
+
+ // Set contents directly
+ final String NEW_CONTENTS = ",./'\t\r[]-\n="
+ entry.setContents(NEW_CONTENTS)
+ verifyContents(NEW_CONTENTS)
+
+ // New OutputStream, append=true (so should append to contents we set directly)
+ out = entry.createOutputStream(true)
+ out.write(CONTENTS.getBytes())
+ verifyContents(NEW_CONTENTS + CONTENTS)
+
+ // Yet another OutputStream, append=true (so should append to accumulated contents)
+ OutputStream out2 = entry.createOutputStream(true)
+ out2.write(CONTENTS.getBytes())
+ out2.close() // should have no effect
+ verifyContents(NEW_CONTENTS + CONTENTS + CONTENTS)
+
+ // Write with the previous OutputStream (simulate 2 OututStreams writing "concurrently")
+ out.write(NEW_CONTENTS.getBytes())
+ verifyContents(NEW_CONTENTS + CONTENTS + CONTENTS + NEW_CONTENTS)
+ }
+
+ void testCreateInputStream_NullContents() {
+ verifyContents("")
+ }
+
+ void testCloneWithNewPath() {
+ entry.lastModified = LAST_MODIFIED
+ entry.owner = USER
+ entry.group = GROUP
+ entry.permissions = PERMISSIONS
+ entry.setContents('abc')
+ def clone = entry.cloneWithNewPath(NEW_PATH)
+
+ assert !clone.is(entry)
+ assert clone.path == NEW_PATH
+ assert clone.lastModified == LAST_MODIFIED
+ assert clone.owner == USER
+ assert clone.group == GROUP
+ assert clone.permissions == PERMISSIONS
+ assert clone.createInputStream().text == 'abc'
+ assert !clone.directory
+ }
+
+ void testCloneWithNewPath_WriteToOutputStream() {
+ def outputStream = entry.createOutputStream(false)
+ outputStream.withWriter { writer -> writer.write('ABCDEF') }
+ def clone = entry.cloneWithNewPath(NEW_PATH)
+
+ assert !clone.is(entry)
+ assert clone.path == NEW_PATH
+ assert clone.createInputStream().text == 'ABCDEF'
+ assert !clone.directory
+ }
+
+// void testEquals() {
+// assert entry.equals(entry)
+// assert entry.equals(new FileEntry(path:PATH, lastModified:LAST_MODIFIED))
+// assert entry.equals(new FileEntry(path:PATH, lastModified:new Date())) // lastModified ignored
+//
+// assert !entry.equals(new FileEntry("xyz", lastModified:LAST_MODIFIED))
+// assert !entry.equals(new FileEntry(path:PATH, contents:'abc', lastModified:LAST_MODIFIED))
+// assert !entry.equals("ABC")
+// assert !entry.equals(null)
+// }
+//
+// void testHashCode() {
+// assert entry.hashCode() == entry.hashCode()
+// assert entry.hashCode() == new FileEntry(path:PATH, contents:'abc', lastModified:LAST_MODIFIED).hashCode()
+// assert entry.hashCode() == new FileEntry(path:PATH, contents:'abc', new Date()).hashCode() // lastModified ignored
+//
+// assert entry.hashCode() != new FileEntry(path:PATH, contents:'abc', lastModified:LAST_MODIFIED).hashCode()
+// assert entry.hashCode() != new FileEntry(path:PATH, contents:'abcdef', lastModified:LAST_MODIFIED).hashCode()
+//
+// assert entry.hashCode() == new DirectoryEntry(path:PATH, lastModified:LAST_MODIFIED).hashCode()
+// }
+
+ //-------------------------------------------------------------------------
+ // Implementation of Required Abstract Methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * @see org.mockftpserver.fake.filesystem.AbstractFileSystemEntryTestCase#getImplementationClass()
+ */
+ protected Class getImplementationClass() {
+ return FileEntry.class
+ }
+
+ /**
+ * @see org.mockftpserver.fake.filesystem.AbstractFileSystemEntryTestCase#isDirectory()
+ */
+ protected boolean isDirectory() {
+ return false
+ }
+
+ //-------------------------------------------------------------------------
+ // Test setup
+ //-------------------------------------------------------------------------
+
+ void setUp() {
+ super.setUp()
+ entry = new FileEntry(PATH)
+ }
+
+ //-------------------------------------------------------------------------
+ // Internal Helper Methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * Verify the expected contents of the file entry, read from its InputSteam
+ * @param expectedContents - the expected contents, as a String
+ * @throws IOException
+ */
+ private void verifyContents(String expectedContents) {
+ LOG.info("expectedContents=$expectedContents")
+ verifyContents(expectedContents.bytes)
+ }
+
+ /**
+ * Verify the expected contents of the file entry, read from its InputSteam
+ * @param expectedContents - the expected contents, as a byte[]
+ * @throws IOException
+ */
+ private void verifyContents(byte[] expectedContents) {
+ byte[] bytes = IoUtil.readBytes(entry.createInputStream())
+ def bytesAsList = bytes as List
+ LOG.info("bytes=$bytesAsList")
+ assert bytes == expectedContents, "actual=$bytesAsList expected=${expectedContents as byte[]}"
+ assert entry.getSize() == expectedContents.length
+ }
+
+}
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/PermissionsTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/PermissionsTest.groovy
new file mode 100644
index 0000000..343df87
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/PermissionsTest.groovy
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem
+
+import org.mockftpserver.test.AbstractGroovyTestCase
+
+/**
+ * Tests for the Permissions class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class PermissionsTest extends AbstractGroovyTestCase {
+
+ void testConstructor() {
+ testConstructorWithValidString('rwxrwxrwx')
+ testConstructorWithValidString('rwxr--r--')
+ testConstructorWithValidString('---------')
+ }
+
+ void testConstructor_InvalidString() {
+ testConstructorWithInvalidString('')
+ testConstructorWithInvalidString('------')
+ testConstructorWithInvalidString('-')
+ testConstructorWithInvalidString('r')
+ testConstructorWithInvalidString('rwx')
+ testConstructorWithInvalidString('rwxrwxrw')
+ testConstructorWithInvalidString('123456789')
+ testConstructorWithInvalidString('rwxrZxrwx')
+ testConstructorWithInvalidString('--------Z')
+ }
+
+ void testCanReadWriteExecute() {
+ testCanReadWriteExecute('rwxrwxrwx', true, true, true, true, true, true, true, true, true)
+ testCanReadWriteExecute('r--r--r--', true, false, false, true, false, false, true, false, false)
+ testCanReadWriteExecute('-w-r----x', false, true, false, true, false, false, false, false, true)
+ testCanReadWriteExecute('---------', false, false, false, false, false, false, false, false, false)
+ }
+
+ void testHashCode() {
+ assert new Permissions('rwxrwxrwx').hashCode() == Permissions.DEFAULT.hashCode()
+ assert new Permissions('---------').hashCode() == Permissions.NONE.hashCode()
+ }
+
+ void testEquals() {
+ assert new Permissions('rwxrwxrwx').equals(Permissions.DEFAULT)
+ assert new Permissions('---------').equals(Permissions.NONE)
+ assert Permissions.NONE.equals(Permissions.NONE)
+
+ assert !(new Permissions('------rwx').equals(Permissions.NONE))
+ assert !Permissions.NONE.equals(null)
+ assert !Permissions.NONE.equals(123)
+ }
+
+ //--------------------------------------------------------------------------
+ // Helper Methods
+ //--------------------------------------------------------------------------
+
+ private testCanReadWriteExecute(rwxString,
+ canUserRead, canUserWrite, canUserExecute,
+ canGroupRead, canGroupWrite, canGroupExecute,
+ canWorldRead, canWorldWrite, canWorldExecute) {
+
+ def permissions = new Permissions(rwxString)
+ LOG.info("Testing can read/write/execute for $permissions")
+ assert permissions.canUserRead() == canUserRead
+ assert permissions.canUserWrite() == canUserWrite
+ assert permissions.canUserExecute() == canUserExecute
+ assert permissions.canGroupRead() == canGroupRead
+ assert permissions.canGroupWrite() == canGroupWrite
+ assert permissions.canGroupExecute() == canGroupExecute
+ assert permissions.canWorldRead() == canWorldRead
+ assert permissions.canWorldWrite() == canWorldWrite
+ assert permissions.canWorldExecute() == canWorldExecute
+ }
+
+ private testConstructorWithInvalidString(String string) {
+ LOG.info("Verifying invalid: [$string]")
+ shouldFail { new Permissions(string) }
+ }
+
+ private testConstructorWithValidString(String string) {
+ LOG.info("Verifying valid: [$string]")
+ def permissions = new Permissions(string)
+ LOG.info(permissions.toString())
+ assert permissions.asRwxString() == string
+ }
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/TestUnixFakeFileSystem.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/TestUnixFakeFileSystem.groovy
new file mode 100644
index 0000000..9d48cd8
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/TestUnixFakeFileSystem.groovy
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem
+
+/**
+ * Test-only subclass of UnixFakeFileSystem. Groovy implementation enables access to metaclass.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class TestUnixFakeFileSystem extends UnixFakeFileSystem {
+
+ Throwable addMethodException
+ Throwable renameMethodException
+ Throwable listNamesMethodException
+ Throwable listFilesMethodException
+ Throwable deleteMethodException
+
+ void add(FileSystemEntry entry) {
+ if (addMethodException) {
+ throw addMethodException
+ }
+ super.add(entry)
+ }
+
+ void rename(String fromPath, String toPath) {
+ if (renameMethodException) {
+ throw renameMethodException
+ }
+ super.rename(fromPath, toPath)
+ }
+
+ List listNames(String path) {
+ if (listNamesMethodException) {
+ throw listNamesMethodException
+ }
+ super.listNames(path)
+ }
+
+ List listFiles(String path) {
+ if (listFilesMethodException) {
+ throw listFilesMethodException
+ }
+ super.listFiles(path)
+ }
+
+ boolean delete(String path) {
+ if (deleteMethodException) {
+ throw deleteMethodException
+ }
+ super.delete(path)
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/UnixDirectoryListingFormatterTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/UnixDirectoryListingFormatterTest.groovy
new file mode 100644
index 0000000..fb10e5a
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/UnixDirectoryListingFormatterTest.groovy
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem
+
+import java.text.SimpleDateFormat
+import org.mockftpserver.test.AbstractGroovyTestCase
+
+/**
+ * Tests for UnixDirectoryListingFormatter
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class UnixDirectoryListingFormatterTest extends AbstractGroovyTestCase {
+
+ static final FILE_NAME = "def.txt"
+ static final FILE_PATH = "/dir/$FILE_NAME"
+ static final DIR_NAME = "etc"
+ static final DIR_PATH = "/dir/$DIR_NAME"
+ static final OWNER = 'owner123'
+ static final GROUP = 'group456'
+ static final SIZE = 11L
+ static final LAST_MODIFIED = new Date()
+ static final FILE_PERMISSIONS = new Permissions('rw-r--r--')
+ static final DIR_PERMISSIONS = new Permissions('rwxr-xr-x')
+
+ private formatter
+ private lastModifiedFormatted
+ private defaultLocale
+
+ // "-rw-rw-r-- 1 ftp ftp 254 Feb 23 2007 robots.txt"
+ // "-rw-r--r-- 1 ftp ftp 30014925 Apr 15 00:19 md5.sums.gz"
+ // "-rwxr-xr-x 1 c096336 iawebgrp 5778 Dec 1 2005 FU_WyCONN_updateplanaccess.sql"
+ // "drwxr-xr-x 2 c096336 iawebgrp 8192 Nov 7 2006 tmp"
+ // "drwxr-xr-x 39 ftp ftp 4096 Mar 19 2004 a"
+
+ void testFormat_File() {
+ def fileSystemEntry = new FileEntry(path: FILE_PATH, contents: '12345678901', lastModified: LAST_MODIFIED,
+ owner: OWNER, group: GROUP, permissions: FILE_PERMISSIONS)
+ LOG.info(fileSystemEntry.toString())
+ verifyFormat(fileSystemEntry, "-rw-r--r-- 1 owner123 group456 11 $lastModifiedFormatted def.txt")
+ }
+
+ void testFormat_File_Defaults() {
+ def fileSystemEntry = new FileEntry(path: FILE_PATH, contents: '12345678901', lastModified: LAST_MODIFIED)
+ LOG.info(fileSystemEntry.toString())
+ verifyFormat(fileSystemEntry, "-rwxrwxrwx 1 none none 11 $lastModifiedFormatted def.txt")
+ }
+
+ void testFormat_File_NonEnglishDefaultLocale() {
+ Locale.setDefault(Locale.GERMAN)
+ def fileSystemEntry = new FileEntry(path: FILE_PATH, contents: '12345678901', lastModified: LAST_MODIFIED)
+ LOG.info(fileSystemEntry.toString())
+ verifyFormat(fileSystemEntry, "-rwxrwxrwx 1 none none 11 $lastModifiedFormatted def.txt")
+ }
+
+ void testFormat_File_NonEnglishLocale() {
+ formatter.setLocale(Locale.FRENCH)
+ def fileSystemEntry = new FileEntry(path: FILE_PATH, contents: '12345678901', lastModified: LAST_MODIFIED)
+ LOG.info(fileSystemEntry.toString())
+ def dateFormat = new SimpleDateFormat(UnixDirectoryListingFormatter.DATE_FORMAT, Locale.FRENCH)
+ def formattedDate = dateFormat.format(LAST_MODIFIED)
+ def result = formatter.format(fileSystemEntry)
+ assert result.contains(formattedDate)
+ }
+
+ void testFormat_Directory() {
+ def fileSystemEntry = new DirectoryEntry(path: DIR_PATH, lastModified: LAST_MODIFIED,
+ owner: OWNER, group: GROUP, permissions: DIR_PERMISSIONS)
+ LOG.info(fileSystemEntry.toString())
+ verifyFormat(fileSystemEntry, "drwxr-xr-x 1 owner123 group456 0 $lastModifiedFormatted etc")
+ }
+
+ void testFormat_Directory_Defaults() {
+ def fileSystemEntry = new DirectoryEntry(path: DIR_PATH, lastModified: LAST_MODIFIED)
+ LOG.info(fileSystemEntry.toString())
+ verifyFormat(fileSystemEntry, "drwxrwxrwx 1 none none 0 $lastModifiedFormatted etc")
+ }
+
+ void setUp() {
+ super.setUp()
+ formatter = new UnixDirectoryListingFormatter()
+ def dateFormat = new SimpleDateFormat(UnixDirectoryListingFormatter.DATE_FORMAT, Locale.ENGLISH)
+ lastModifiedFormatted = dateFormat.format(LAST_MODIFIED)
+ defaultLocale = Locale.default
+ }
+
+ void tearDown() {
+ super.tearDown()
+ Locale.setDefault(defaultLocale)
+ }
+
+ private void verifyFormat(FileSystemEntry fileSystemEntry, String expectedResult) {
+ def result = formatter.format(fileSystemEntry)
+ LOG.info("result= [$result]")
+ LOG.info("expected=[$expectedResult]")
+ assert result == expectedResult
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/UnixFakeFileSystemTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/UnixFakeFileSystemTest.groovy
new file mode 100644
index 0000000..c50d988
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/UnixFakeFileSystemTest.groovy
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem
+
+/**
+ * Tests for UnixFakeFileSystem.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class UnixFakeFileSystemTest extends AbstractFakeFileSystemTestCase {
+
+ private static final String SEP = "/"
+
+ UnixFakeFileSystemTest() {
+ // These need to be set in the constructor because these values are used in setUp()
+ NEW_DIR = SEP + NEW_DIRNAME
+ NEW_FILE = "/NewFile.txt"
+ EXISTING_DIR = "/"
+ EXISTING_FILE = "/ExistingFile.txt"
+ NO_SUCH_DIR = "/xx/yy"
+ NO_SUCH_FILE = "/xx/yy/zz.txt"
+ }
+
+
+ void testListNames_FromRoot() {
+ final DIR = '/'
+ final FILENAME = 'abc.txt'
+ final FILE = p(DIR, FILENAME)
+
+ assert !fileSystem.exists(FILE)
+ fileSystem.add(new FileEntry(FILE))
+ def names = fileSystem.listNames(DIR)
+ assert names.find { it == FILENAME }
+ }
+
+ void testPath() {
+ assert fileSystem.path(null, null) == ""
+ assert fileSystem.path(null, "abc") == "abc"
+ assert fileSystem.path("abc", null) == "abc"
+ assert fileSystem.path("", "") == ""
+ assert fileSystem.path("", "abc") == "abc"
+ assert fileSystem.path("abc", "") == "abc"
+ assert fileSystem.path("abc", "DEF") == "abc/DEF"
+ assert fileSystem.path("abc/", "def") == "abc/def"
+ assert fileSystem.path("/abc/", "def") == "/abc/def"
+ assert fileSystem.path("/ABC", "/def") == "/ABC/def"
+ assert fileSystem.path("abc", "/def") == "abc/def"
+ assert fileSystem.path("abc", "def/..") == "abc"
+ assert fileSystem.path("abc", "./def") == "abc/def"
+ assert fileSystem.path("abc/.", null) == "abc"
+ }
+
+ void testNormalize() {
+ assert fileSystem.normalize("/") == "/"
+ assert fileSystem.normalize("/aBc") == "/aBc"
+ assert fileSystem.normalize("/abc/DEF") == "/abc/DEF"
+ assert fileSystem.normalize("/Abc/def/..") == "/Abc"
+ assert fileSystem.normalize("/abc/def/../ghi") == "/abc/ghi"
+ assert fileSystem.normalize("/abc/def/.") == "/abc/def"
+ assert fileSystem.normalize("/abc/def/./gHI") == "/abc/def/gHI"
+ }
+
+ void testGetName() {
+ assert fileSystem.getName("/") == ""
+ assert fileSystem.getName("/aBC") == "aBC"
+ assert fileSystem.getName("/abc/def") == "def"
+ assert fileSystem.getName("/abc/def/../GHI") == "GHI"
+ }
+
+ public void testGetParent() {
+ assert fileSystem.getParent("/") == null
+ assert fileSystem.getParent("/abc") == "/"
+ assert fileSystem.getParent("/abc/def") == "/abc"
+ }
+
+ void testIsValidName() {
+ ["/abc",
+ "/test/",
+ "/ABC/def",
+ "/abc/d!ef",
+ "/abc/DEF/h(ij)!@#\$%^&*()-_+=~`,.<>?;:[]{}\\|abc",
+ ].each {
+ assert fileSystem.isValidName(it), "[$it]"
+ }
+
+ ["",
+ "abc",
+ "abc/def",
+ "a:/abc:",
+ "//a*bc",
+ "C:/?abc",
+ ].each {
+ assert !fileSystem.isValidName(it), "[$it]"
+ }
+ }
+
+ void testIsAbsolute() {
+ assert fileSystem.isAbsolute("/")
+ assert fileSystem.isAbsolute("/abc")
+
+ assert !fileSystem.isAbsolute("abc")
+ assert !fileSystem.isAbsolute("c:\\usr")
+
+ shouldFailWithMessageContaining("path") { fileSystem.isAbsolute(null) }
+ }
+
+ //-----------------------------------------------------------------------------------
+ // Helper Methods
+ //-----------------------------------------------------------------------------------
+
+ /**
+ * Return a new instance of the FileSystem implementation class under test
+ * @return a new FileSystem instance
+ */
+ protected FileSystem createFileSystem() {
+ UnixFakeFileSystem fs = new UnixFakeFileSystem()
+ fs.add(new DirectoryEntry(EXISTING_DIR))
+ fs.add(new FileEntry(EXISTING_FILE, EXISTING_FILE_CONTENTS))
+ assert fs.createParentDirectoriesAutomatically
+ fs.createParentDirectoriesAutomatically = false
+ return fs
+ }
+
+ protected Class getExpectedDirectoryListingFormatterClass() {
+ return UnixDirectoryListingFormatter
+ }
+
+}
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/WindowsDirectoryListingFormatterTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/WindowsDirectoryListingFormatterTest.groovy
new file mode 100644
index 0000000..04eed35
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/WindowsDirectoryListingFormatterTest.groovy
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem
+
+import java.text.SimpleDateFormat
+import org.mockftpserver.test.AbstractGroovyTestCase
+
+/**
+ * Tests for WindowsDirectoryListingFormatter
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class WindowsDirectoryListingFormatterTest extends AbstractGroovyTestCase {
+
+ static final NAME = "def.txt"
+ static final PATH = "/dir/$NAME"
+ static final LAST_MODIFIED = new Date()
+ static final SIZE_WIDTH = WindowsDirectoryListingFormatter.SIZE_WIDTH
+
+ private formatter
+ private dateFormat
+ private lastModifiedFormatted
+ private defaultLocale
+
+ void testFormat_File() {
+ def fileEntry = new FileEntry(path: PATH, contents: 'abcd', lastModified: LAST_MODIFIED)
+ def sizeStr = 4.toString().padLeft(SIZE_WIDTH)
+ def expected = "$lastModifiedFormatted $sizeStr $NAME"
+ def result = formatter.format(fileEntry)
+ LOG.info("result=$result")
+ assert result == expected
+ }
+
+ void testFormat_Directory() {
+ def fileEntry = new DirectoryEntry(path: PATH, lastModified: LAST_MODIFIED)
+ def dirStr = "<DIR>".padRight(SIZE_WIDTH)
+ def expected = "$lastModifiedFormatted $dirStr $NAME"
+ def result = formatter.format(fileEntry)
+ LOG.info("result=$result")
+ assert result == expected
+ }
+
+ void testFormat_File_NonEnglishDefaultLocale() {
+ Locale.setDefault(Locale.GERMAN)
+ def fileEntry = new FileEntry(path: PATH, contents: 'abcd', lastModified: LAST_MODIFIED)
+ def sizeStr = 4.toString().padLeft(SIZE_WIDTH)
+ def expected = "$lastModifiedFormatted $sizeStr $NAME"
+ def result = formatter.format(fileEntry)
+ LOG.info("result=$result")
+ assert result == expected
+ }
+
+ void setUp() {
+ super.setUp()
+ formatter = new WindowsDirectoryListingFormatter()
+ dateFormat = new SimpleDateFormat(WindowsDirectoryListingFormatter.DATE_FORMAT)
+ lastModifiedFormatted = dateFormat.format(LAST_MODIFIED)
+ defaultLocale = Locale.default
+ }
+
+ void tearDown() {
+ super.tearDown()
+ Locale.setDefault(defaultLocale)
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/WindowsFakeFileSystemTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/WindowsFakeFileSystemTest.groovy
new file mode 100644
index 0000000..0c415d1
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/fake/filesystem/WindowsFakeFileSystemTest.groovy
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.filesystem
+
+/**
+ * Tests for WindowsFakeFileSystem.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class WindowsFakeFileSystemTest extends AbstractFakeFileSystemTestCase {
+
+ private static final String SEP = "\\"
+
+ WindowsFakeFileSystemTest() {
+ // These need to be set in the constructor because these values are used in setUp()
+ NEW_DIR = "d:/" + NEW_DIRNAME
+ NEW_FILE = "d:/NewFile.txt"
+ EXISTING_DIR = "d:/"
+ EXISTING_FILE = "d:/ExistingFile.txt"
+ NO_SUCH_DIR = 'x:/xx/yy'
+ NO_SUCH_FILE = "x:/xx/yy/zz.txt"
+ }
+
+ // -------------------------------------------------------------------------
+ // Tests
+ // -------------------------------------------------------------------------
+
+ void testOtherRoots() {
+ final String X = "x:/"
+ final String Y = "y:\\"
+ assertFalse(X, fileSystem.exists(X))
+ assertFalse(Y, fileSystem.exists(Y))
+
+ fileSystem.add(new DirectoryEntry(X))
+ fileSystem.add(new DirectoryEntry(Y))
+
+ assertTrue(X, fileSystem.exists(X))
+ assertTrue(Y, fileSystem.exists(Y))
+ }
+
+ void testPath() {
+ assert fileSystem.path(null, null) == ""
+ assert fileSystem.path(null, "abc") == "abc"
+ assert fileSystem.path("abc", null) == "abc"
+ assert fileSystem.path("", "") == ""
+ assert fileSystem.path("", "abc") == "abc"
+ assert fileSystem.path("abc", "") == "abc"
+ assert fileSystem.path("abc", "def") == "abc" + SEP + "def"
+ assert fileSystem.path("abc\\", "def") == "abc\\def"
+ assert fileSystem.path("c:/abc/", "def") == "c:\\abc\\def"
+ assert fileSystem.path("d:\\abc", "\\def") == "d:\\abc\\def"
+ assert fileSystem.path("abc", "/def") == "abc\\def"
+ assert fileSystem.path("abc/def", "..") == "abc"
+ assert fileSystem.path("abc", "def/..") == "abc"
+ assert fileSystem.path("abc", "./def") == "abc\\def"
+ assert fileSystem.path("abc/.", null) == "abc"
+ }
+
+ void testNormalize() {
+ assert fileSystem.normalize("a:\\") == "a:\\"
+ assert fileSystem.normalize("a:/") == "a:\\"
+ assert fileSystem.normalize("b:/abc") == path("b:", "abc")
+ assert fileSystem.normalize("c:\\abc\\def") == path("c:", "abc", "def")
+ assert fileSystem.normalize("d:/abc/def") == path("d:", "abc", "def")
+ assert fileSystem.normalize("e:\\abc/def/..") == path("e:", "abc")
+ assert fileSystem.normalize("f:/abc/def/../ghi") == path("f:", "abc", "ghi")
+ assert fileSystem.normalize("g:\\abc\\def\\.") == path("g:", "abc", "def")
+ assert fileSystem.normalize("h:/abc\\def\\./ghi") == path("h:", "abc", "def", "ghi")
+ assert fileSystem.normalize("c:\\abc").toLowerCase() == path("c:", "abc")
+ assert fileSystem.normalize("c:/abc").toLowerCase() == path("c:", "abc")
+ assert fileSystem.normalize("z:/abc").toLowerCase() == path("z:", "abc")
+ }
+
+ void testGetName() {
+ assert fileSystem.getName("l:\\") == ""
+ assert fileSystem.getName("m:\\abc") == "abc"
+ assert fileSystem.getName("n:/abc\\def") == "def"
+ assert fileSystem.getName("o:/abc/def") == "def"
+ }
+
+ public void testGetParent() {
+ assert fileSystem.getParent("p:/") == null
+ assert fileSystem.getParent("q:\\abc") == "q:\\"
+ assert fileSystem.getParent("r:/abc\\def") == path("r:", "abc")
+ assert fileSystem.getParent("s:\\abc/def") == path("s:", "abc")
+ }
+
+ void testIsValidName() {
+ // \/:*?"<>|
+ ["a:\\abc",
+ "c:/abc",
+ "d:/abc\\def",
+ "e:/abc\\d!ef",
+ "f:\\abc\\def\\h(ij)",
+ "g:\\abc",
+ "z:/abc/def",
+ "\\\\shared"
+ ].each {
+ assert fileSystem.isValidName(it), "[$it]"
+ }
+
+ ["",
+ "abc",
+ "abc/def",
+ "a:/abc:",
+ "B:\\a*bc",
+ "C:/?abc",
+ "D:\\abc/<def",
+ "E:/abc/def>",
+ "aa:\\abc",
+ "X:X:/abc",
+ ":\\abc\\def",
+ "X:\\\\abc"
+ ].each {
+ assert !fileSystem.isValidName(it), "[$it]"
+ }
+ }
+
+ void testIsAbsolute() {
+ assert fileSystem.isAbsolute("c:\\")
+ assert fileSystem.isAbsolute("x:\\Documents")
+ assert fileSystem.isAbsolute("a:/")
+ assert fileSystem.isAbsolute("\\\\shared\\docs")
+
+ assert !fileSystem.isAbsolute("abc")
+ assert !fileSystem.isAbsolute("/usr")
+ assert !fileSystem.isAbsolute("c:usr")
+
+ shouldFailWithMessageContaining("path") { fileSystem.isAbsolute(null) }
+ }
+
+ void testCaseInsensitive() {
+ def fileEntry = fileSystem.getEntry(EXISTING_FILE)
+ assert fileEntry
+ assert fileEntry == fileSystem.getEntry(EXISTING_FILE.toLowerCase())
+ }
+
+ //-------------------------------------------------------------------------
+ // Test setup
+ //-------------------------------------------------------------------------
+
+ void setUp() {
+ super.setUp()
+ }
+
+ protected Class getExpectedDirectoryListingFormatterClass() {
+ return WindowsDirectoryListingFormatter
+ }
+
+ //-----------------------------------------------------------------------------------
+ // Helper Methods
+ //-----------------------------------------------------------------------------------
+
+ /**
+ * Return a new instance of the FileSystem implementation class under test
+ *
+ * @return a new FileSystem instance
+ */
+ protected FileSystem createFileSystem() {
+ WindowsFakeFileSystem fs = new WindowsFakeFileSystem()
+ fs.add(new DirectoryEntry(EXISTING_DIR))
+ fs.add(new FileEntry(EXISTING_FILE, EXISTING_FILE_CONTENTS))
+ fs.createParentDirectoriesAutomatically = false
+ return fs
+ }
+
+ /**
+ * Return the specified paths concatenated with the system-dependent separator in between
+ * @param p1 - the first path
+ * @param p2 - the second path
+ * @return p1 + SEPARATOR + p2
+ */
+ private String path(String[] paths) {
+ return paths.join(SEP)
+ }
+}
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/stub/RunStubFtpServer.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/stub/RunStubFtpServer.groovy
new file mode 100644
index 0000000..6baa4ed
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/stub/RunStubFtpServer.groovy
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub
+
+import org.mockftpserver.stub.StubFtpServer
+import org.mockftpserver.test.PortTestUtil
+import org.mockftpserver.stub.command.PwdCommandHandler
+import org.mockftpserver.core.command.CommandNames
+
+/**
+ * Run the StubFtpServer with a minimal configuration for interactive testing and exploration.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class RunStubFtpServer {
+
+ static main(args) {
+ def stubFtpServer = new StubFtpServer();
+ stubFtpServer.setServerControlPort(PortTestUtil.getFtpServerControlPort());
+
+ stubFtpServer.getCommandHandler(CommandNames.PWD).setDirectory("/MyDir");
+
+ final LISTING = "11-09-01 12:30PM 406348 File2350.log\n" + "11-01-01 1:30PM <DIR> 0 archive"
+ stubFtpServer.getCommandHandler(CommandNames.LIST).setDirectoryListing(LISTING)
+
+ stubFtpServer.start();
+ }
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/stub/StubFtpServer_RestartTest.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/stub/StubFtpServer_RestartTest.groovy
new file mode 100644
index 0000000..af13413
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/stub/StubFtpServer_RestartTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub
+
+import org.mockftpserver.test.AbstractGroovyTestCase
+import org.apache.commons.net.ftp.FTPClient
+import org.mockftpserver.test.PortTestUtil
+
+/**
+ * Integration tests for restart of an StubFtpServer.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+class StubFtpServer_RestartTest extends AbstractGroovyTestCase {
+ static final SERVER = "localhost"
+ private stubFtpServer
+ private ftpClient
+
+ void testRestart() {
+ stubFtpServer.start()
+ ftpClient.connect(SERVER, PortTestUtil.getFtpServerControlPort())
+ assert ftpClient.changeWorkingDirectory("dir1")
+
+ stubFtpServer.stop()
+ LOG.info("Restarting...")
+
+ stubFtpServer.start()
+ ftpClient.connect(SERVER, PortTestUtil.getFtpServerControlPort())
+ assert ftpClient.changeWorkingDirectory("dir1")
+ }
+
+ void setUp() {
+ super.setUp()
+ stubFtpServer = new StubFtpServer()
+ stubFtpServer.setServerControlPort(PortTestUtil.getFtpServerControlPort())
+ ftpClient = new FTPClient()
+ }
+
+ void tearDown() {
+ super.tearDown()
+ stubFtpServer.stop()
+ }
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/test/AbstractGroovyTestCase.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/test/AbstractGroovyTestCase.groovy
new file mode 100644
index 0000000..27ab7f7
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/test/AbstractGroovyTestCase.groovy
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.test
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.test.LoggingUtil
+
+/**
+ * Abstract superclass for Groovy tests
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+abstract class AbstractGroovyTestCase extends GroovyTestCase {
+
+ protected final Logger LOG = LoggerFactory.getLogger(this.class)
+ private LoggingUtil testLogger
+
+ /**
+ * Write out the specified log message, prefixing with the current class name.
+ * @param message - the message to log; toString() is applied first
+ */
+ protected void log(message) {
+ println "[${classNameNoPackage()}] ${message.toString()}"
+ }
+
+ private String classNameNoPackage() {
+ def className = getClass().name
+ def index = className.lastIndexOf('.')
+ return index > -1 ? className.substring(index+1) : className
+ }
+
+ /**
+ * Assert that the specified code throws an exception of the specified type.
+ * @param expectedExceptionClass - the Class of exception that is expected
+ * @param code - the Closure containing the code to be executed, which is expected to throw an exception of the specified type
+ * @return the thrown Exception instance
+ *
+ * @throws AssertionError - if no exception is thrown by the code or if the thrown exception is not of the expected type
+ */
+ protected Throwable shouldThrow(Class expectedExceptionClass, Closure code) {
+ def actualException = null
+ try {
+ code.call()
+ } catch (Throwable thrown) {
+ actualException = thrown
+ }
+ assert actualException, "No exception thrown. Expected [${expectedExceptionClass.getName()}]"
+ assert actualException.class == expectedExceptionClass, "Expected [${expectedExceptionClass.getName()}] but was [${actualException.class.name}]"
+ return actualException
+ }
+
+ /**
+ * Assert that the specified code throws an exception with an error message
+ * containing the specified text.
+ * @param text - the text expected within the exception message
+ * @param code - the Closure containing the code to be executed, which is expected to throw an exception of the specified type
+ * @return the message from the thrown Exception
+ *
+ * @throws AssertionError - if no exception is thrown by the code or if the thrown
+ * exception message does not contain the expected text
+ */
+ protected String shouldFailWithMessageContaining(String text, Closure code) {
+ def message = shouldFail(code)
+ assert message.contains(text), "message=[$message], text=[$text]"
+ return message
+ }
+
+ /**
+ * Return the specified paths concatenated with the path separator in between
+ * @param paths - the varargs list of path components to concatenate
+ * @return p[0] + '/' + p[1] + '/' + p[2] + ...
+ */
+ protected static String p(String[] paths) {
+ return paths.join("/").replace('\\', '/').replace("//", "/")
+ }
+
+ /**
+ * Create a new InetAddress from the specified host String, using the
+ * {@link InetAddress#getByName(String)} method.
+ * @param host
+ * @return an InetAddress for the specified host
+ */
+ protected static InetAddress inetAddress(String host) {
+ return InetAddress.getByName(host);
+ }
+
+ //------------------------------------------------------------------------------------
+ // Test Setup and Tear Down
+ //------------------------------------------------------------------------------------
+
+ void setUp() {
+ testLogger = LoggingUtil.getTestCaseLogger(this)
+ testLogger.logStartOfTest()
+
+ super.setUp()
+ }
+
+ void tearDown() {
+ super.tearDown();
+ if (testLogger) {
+ testLogger.logEndOfTest()
+ }
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/groovy/org/mockftpserver/test/StubResourceBundle.groovy b/tags/2.5/src/test/groovy/org/mockftpserver/test/StubResourceBundle.groovy
new file mode 100644
index 0000000..90ce2a0
--- /dev/null
+++ b/tags/2.5/src/test/groovy/org/mockftpserver/test/StubResourceBundle.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.test
+
+/**
+ * Stub implementation of ResourceBundle for testing. Provide an optional Map of entries in the constructor,
+ * and allow dynamic adding or changing of map contents. Automatically define default value for key if no entry
+ * exists for the key.
+ */
+class StubResourceBundle extends ResourceBundle {
+
+ Map map
+
+ StubResourceBundle(Map map = [:]) {
+ this.map = map
+ }
+
+ void put(String key, String value) {
+ map.put(key, value)
+ }
+
+ Object handleGetObject(String key) {
+ // Return default if no entry is defined
+ return map[key] ?: "key=$key arg0={0} arg1={1}".toString()
+ }
+
+ public Enumeration getKeys() {
+ return new Vector(map.keySet()).elements()
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/command/AbstractCommandHandlerTestCase.java b/tags/2.5/src/test/java/org/mockftpserver/core/command/AbstractCommandHandlerTestCase.java
new file mode 100644
index 0000000..6694c0f
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/command/AbstractCommandHandlerTestCase.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.easymock.MockControl;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.test.AbstractTestCase;
+
+import java.text.MessageFormat;
+import java.util.ListResourceBundle;
+import java.util.ResourceBundle;
+
+/**
+ * Abstract superclass for CommandHandler tests
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public abstract class AbstractCommandHandlerTestCase extends AbstractTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractCommandHandlerTestCase.class);
+
+ // Some common test constants
+ protected static final String DIR1 = "dir1";
+ protected static final String DIR2 = "dir2";
+ protected static final String FILENAME1 = "sample1.txt";
+ protected static final String FILENAME2 = "sample2.txt";
+
+ protected Session session;
+ protected ResourceBundle replyTextBundle;
+
+ /**
+ * Test the handleCommand() method, when one or more parameter is missing or invalid
+ *
+ * @param commandHandler - the CommandHandler to test
+ * @param commandName - the name for the Command
+ * @param parameters - the Command parameters
+ */
+ protected void testHandleCommand_InvalidParameters(AbstractTrackingCommandHandler commandHandler,
+ String commandName, String[] parameters) throws Exception {
+ Command command = new Command(commandName, parameters);
+ session.sendReply(ReplyCodes.COMMAND_SYNTAX_ERROR, replyTextFor(ReplyCodes.COMMAND_SYNTAX_ERROR));
+ replay(session);
+
+ commandHandler.handleCommand(command, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyNoDataElements(commandHandler.getInvocation(0));
+ }
+
+ /**
+ * Verify that the CommandHandler contains the specified number of invocation records
+ *
+ * @param commandHandler - the CommandHandler
+ * @param expected - the expected number of invocations
+ */
+ protected void verifyNumberOfInvocations(InvocationHistory commandHandler, int expected) {
+ assertEquals("number of invocations", expected, commandHandler.numberOfInvocations());
+ }
+
+ /**
+ * Verify that the InvocationRecord contains no data elements
+ *
+ * @param invocationRecord - the InvocationRecord
+ */
+ protected void verifyNoDataElements(InvocationRecord invocationRecord) {
+ LOG.info("Verifying: " + invocationRecord);
+ assertEquals("number of data elements", 0, invocationRecord.keySet().size());
+ }
+
+ /**
+ * Verify that the InvocationRecord contains exactly one data element, with the specified key
+ * and value.
+ *
+ * @param invocationRecord - the InvocationRecord
+ * @param key - the expected key
+ * @param value - the expected value
+ */
+ protected void verifyOneDataElement(InvocationRecord invocationRecord, String key, Object value) {
+ LOG.info("Verifying: " + invocationRecord);
+ assertEquals("number of data elements", 1, invocationRecord.keySet().size());
+ assertEqualsAllTypes("value:" + value, value, invocationRecord.getObject(key));
+ }
+
+ /**
+ * Verify that the InvocationRecord contains exactly two data element, with the specified keys
+ * and values.
+ *
+ * @param invocationRecord - the InvocationRecord
+ * @param key1 - the expected key1
+ * @param value1 - the expected value1
+ * @param key2 - the expected key2
+ * @param value2- the expected value2
+ */
+ protected void verifyTwoDataElements(InvocationRecord invocationRecord, String key1, Object value1,
+ String key2, Object value2) {
+
+ LOG.info("Verifying: " + invocationRecord);
+ assertEquals("number of data elements", 2, invocationRecord.keySet().size());
+ assertEqualsAllTypes("value1:" + value1, value1, invocationRecord.getObject(key1));
+ assertEqualsAllTypes("value2:" + value2, value2, invocationRecord.getObject(key2));
+ }
+
+ /**
+ * Assert that the actual is equal to the expected, using arrays equality comparison if
+ * necessary
+ *
+ * @param message - the message, used if the comparison fails
+ * @param expected - the expected value
+ * @param actual - the actual value
+ */
+ private void assertEqualsAllTypes(String message, Object expected, Object actual) {
+
+ if (expected instanceof byte[] || actual instanceof byte[]) {
+ assertEquals(message, (byte[]) expected, (byte[]) actual);
+ } else if (expected instanceof Object[] || actual instanceof Object[]) {
+ assertEquals(message, (Object[]) expected, (Object[]) actual);
+ } else {
+ assertEquals(message, expected, actual);
+ }
+ }
+
+ /**
+ * Perform setup before each test
+ *
+ * @see org.mockftpserver.test.AbstractTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ session = (Session) createMock(Session.class);
+ control(session).setDefaultMatcher(MockControl.ARRAY_MATCHER);
+ control(session).expectAndDefaultReturn(session.getClientHost(), DEFAULT_HOST);
+
+ replyTextBundle = new ListResourceBundle() {
+ protected Object[][] getContents() {
+ return new Object[][]{
+ {"150", replyTextFor(150)},
+ {"200", replyTextFor(200)},
+ {"211", replyTextWithParameterFor(211)},
+ {"213", replyTextWithParameterFor(213)},
+ {"214", replyTextWithParameterFor(214)},
+ {"215", replyTextWithParameterFor(215)},
+ {"220", replyTextFor(220)},
+ {"221", replyTextFor(221)},
+ {"226", replyTextFor(226)},
+ {"226.WithFilename", replyTextWithParameterFor("226.WithFilename")},
+ {"227", replyTextWithParameterFor(227)},
+ {"229", replyTextWithParameterFor(229)},
+ {"230", replyTextFor(230)},
+ {"250", replyTextFor(250)},
+ {"257", replyTextWithParameterFor(257)},
+ {"331", replyTextFor(331)},
+ {"350", replyTextFor(350)},
+ {"501", replyTextFor(501)},
+ {"502", replyTextFor(502)},
+ };
+ }
+ };
+ }
+
+ /**
+ * Return the test-specific reply text for the specified reply code
+ *
+ * @param replyCode - the reply code
+ * @return the reply text for the specified reply code
+ */
+ protected String replyTextFor(int replyCode) {
+ return "Reply for " + replyCode;
+ }
+
+ /**
+ * Return the test-specific parameterized reply text for the specified reply code
+ *
+ * @param replyCode - the reply code
+ * @return the reply text for the specified reply code
+ */
+ protected String replyTextWithParameterFor(int replyCode) {
+ return "Reply for " + replyCode + ":{0}";
+ }
+
+ /**
+ * Return the test-specific parameterized reply text for the specified messageKey
+ *
+ * @param messageKey - the messageKey
+ * @return the reply text for the specified messageKey
+ */
+ protected String replyTextWithParameterFor(String messageKey) {
+ return "Reply for " + messageKey + ":{0}";
+ }
+
+ /**
+ * Return the test-specific reply text for the specified reply code and message parameter
+ *
+ * @param replyCode - the reply code
+ * @param parameter - the message parameter value
+ * @return the reply text for the specified reply code
+ */
+ protected String formattedReplyTextFor(int replyCode, Object parameter) {
+ return MessageFormat.format(replyTextWithParameterFor(replyCode), objArray(parameter));
+ }
+
+ /**
+ * Return the test-specific reply text for the specified message key and message parameter
+ *
+ * @param messageKey - the messageKey
+ * @param parameter - the message parameter value
+ * @return the reply text for the specified message key and parameter
+ */
+ protected String formattedReplyTextFor(String messageKey, Object parameter) {
+ return MessageFormat.format(replyTextWithParameterFor(messageKey), objArray(parameter));
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/command/CommandTest.java b/tags/2.5/src/test/java/org/mockftpserver/core/command/CommandTest.java
new file mode 100644
index 0000000..9182046
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/command/CommandTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.CommandSyntaxException;
+import org.mockftpserver.core.util.AssertFailedException;
+import org.mockftpserver.test.AbstractTestCase;
+
+import java.util.List;
+
+/**
+ * Tests for the Command class
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class CommandTest extends AbstractTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(CommandTest.class);
+
+ /**
+ * Test the Command(String,String[]) constructor
+ */
+ public void testConstructor() {
+ final String[] PARAMETERS = array("123");
+ Command command = new Command("abc", PARAMETERS);
+ assertEquals("name", "abc", command.getName());
+ assertEquals("parameters", PARAMETERS, command.getParameters());
+ }
+
+ /**
+ * Test the Command(String,List) constructor
+ */
+ public void testConstructor_List() {
+ final List PARAMETERS_LIST = list("123");
+ final String[] PARAMETERS_ARRAY = array("123");
+ Command command = new Command("abc", PARAMETERS_LIST);
+ assertEquals("name", "abc", command.getName());
+ assertEquals("parameters String[]", PARAMETERS_ARRAY, command.getParameters());
+ }
+
+ /**
+ * Test the Constructor method, passing in a null name
+ */
+ public void testConstructor_NullName() {
+ try {
+ new Command(null, EMPTY);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the Constructor method, passing in a null parameters
+ */
+ public void testConstructor_NullParameters() {
+ try {
+ new Command("OK", (String[]) null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the normalizeName() method
+ */
+ public void testNormalizeName() {
+ assertEquals("XXX", "XXX", Command.normalizeName("XXX"));
+ assertEquals("xxx", "XXX", Command.normalizeName("xxx"));
+ assertEquals("Xxx", "XXX", Command.normalizeName("Xxx"));
+ }
+
+ /**
+ * Test the getRequiredParameter method
+ */
+ public void testGetRequiredParameter() {
+ Command command = new Command("abc", array("123", "456"));
+ assertEquals("123", "123", command.getRequiredParameter(0));
+ assertEquals("456", "456", command.getRequiredParameter(1));
+ }
+
+ /**
+ * Test the getRequiredParameter method, when the index is not valid
+ */
+ public void testGetRequiredParameter_IndexNotValid() {
+ Command command = new Command("abc", array("123", "456"));
+ try {
+ command.getRequiredParameter(2);
+ fail("Expected CommandSyntaxException");
+ }
+ catch (CommandSyntaxException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the getOptionalString method
+ */
+ public void testGetOptionalString() {
+ Command command = new Command("abc", array("123", "456"));
+ assertEquals("123", "123", command.getOptionalString(0));
+ assertEquals("456", "456", command.getOptionalString(1));
+ assertEquals("null", null, command.getOptionalString(2));
+ }
+
+ /**
+ * Test the getParameter method
+ */
+ public void testGetParameter() {
+ Command command = new Command("abc", array("123", "456"));
+ assertEquals("123", "123", command.getParameter(0));
+ assertEquals("456", "456", command.getParameter(1));
+ assertEquals("null", null, command.getParameter(2));
+ }
+
+ /**
+ * Test that a Command object is immutable, changing the original parameters passed in to the constructor
+ */
+ public void testImmutable_ChangeOriginalParameters() {
+ final String[] PARAMETERS = {"a", "b", "c"};
+ final Command COMMAND = new Command("command", PARAMETERS);
+ PARAMETERS[2] = "xxx";
+ assertEquals("parameters", COMMAND.getParameters(), new String[]{"a", "b", "c"});
+ }
+
+ /**
+ * Test that a Command object is immutable, changing the parameters returned from getParameters
+ */
+ public void testImmutable_ChangeRetrievedParameters() {
+ final String[] PARAMETERS = {"a", "b", "c"};
+ final Command COMMAND = new Command("command", PARAMETERS);
+ String[] parameters = COMMAND.getParameters();
+ parameters[2] = "xxx";
+ assertEquals("parameters", PARAMETERS, COMMAND.getParameters());
+ }
+
+ /**
+ * Test the equals() method, and tests the hasCode() method implicitly
+ *
+ * @throws Exception
+ */
+ public void testEquals() throws Exception {
+ final Command COMMAND1 = new Command("a", EMPTY);
+ final Command COMMAND2 = new Command("a", EMPTY);
+ final Command COMMAND3 = new Command("b", array("1"));
+ final Command COMMAND4 = new Command("b", array("2"));
+ final Command COMMAND5 = new Command("c", array("1"));
+ _testEquals(COMMAND1, null, false);
+ _testEquals(COMMAND1, COMMAND1, true);
+ _testEquals(COMMAND1, COMMAND2, true);
+ _testEquals(COMMAND1, COMMAND3, false);
+ _testEquals(COMMAND3, COMMAND4, false);
+ _testEquals(COMMAND3, COMMAND5, false);
+ }
+
+ /**
+ * Test that command1 equals command2 if and only if expectedEqual is true
+ *
+ * @param command1 - the first command
+ * @param command2 - the second command
+ * @param expectedEqual - true if command1 is expected to equal command2
+ */
+ private void _testEquals(Command command1, Command command2, boolean expectedEqual) {
+ assertEquals(command1.toString() + " and " + command2, expectedEqual, command1.equals(command2));
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/command/ConnectCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/core/command/ConnectCommandHandlerTest.java
new file mode 100644
index 0000000..127b403
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/command/ConnectCommandHandlerTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+/**
+ * Tests for the ConnectCommandHandler class
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class ConnectCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private ConnectCommandHandler commandHandler;
+ private Command command1;
+
+ public void testHandleCommand() throws Exception {
+
+ session.sendReply(ReplyCodes.CONNECT_OK, replyTextFor(ReplyCodes.CONNECT_OK));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyNoDataElements(commandHandler.getInvocation(0));
+ }
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new ConnectCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ command1 = new Command(CommandNames.CONNECT, EMPTY);
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/command/InvocationRecordTest.java b/tags/2.5/src/test/java/org/mockftpserver/core/command/InvocationRecordTest.java
new file mode 100644
index 0000000..026a7e2
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/command/InvocationRecordTest.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.util.AssertFailedException;
+import org.mockftpserver.test.AbstractTestCase;
+
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Tests for InvocationRecord
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class InvocationRecordTest extends AbstractTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(InvocationRecordTest.class);
+ private static final Command COMMAND = new Command("command", EMPTY);
+ private static final String KEY1 = "key1";
+ private static final String KEY2 = "key2";
+ private static final String STRING = "abc123";
+ private static final Integer INT = new Integer(77);
+
+ private InvocationRecord invocationRecord;
+
+ /**
+ * Test the Constructor
+ */
+ public void testConstructor() {
+ final Command COMMAND = new Command("ABC", EMPTY);
+ long beforeTime = System.currentTimeMillis();
+ InvocationRecord commandInvocation = new InvocationRecord(COMMAND, DEFAULT_HOST);
+ long afterTime = System.currentTimeMillis();
+ LOG.info(commandInvocation.toString());
+ assertEquals("Command", COMMAND, commandInvocation.getCommand());
+ assertTrue("time", commandInvocation.getTime().getTime() >= beforeTime
+ && commandInvocation.getTime().getTime() <= afterTime);
+ assertEquals("host", DEFAULT_HOST, commandInvocation.getClientHost());
+ }
+
+ /**
+ * Test the set() method, passing in a null key
+ */
+ public void testSet_NullKey() {
+ try {
+ invocationRecord.set(null, STRING);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the set() method, passing in a null value
+ */
+ public void testSet_NullValue() {
+ invocationRecord.set(KEY1, null);
+ assertNull(KEY1, invocationRecord.getObject(KEY1));
+ }
+
+ /**
+ * Test the containsKey() method
+ */
+ public void testContainsKey() {
+ invocationRecord.set(KEY1, STRING);
+ assertTrue(KEY1, invocationRecord.containsKey(KEY1));
+ assertFalse(KEY2, invocationRecord.containsKey(KEY2));
+ }
+
+ /**
+ * Test the containsKey() method, passing in a null key
+ */
+ public void testContainsKey_Null() {
+ try {
+ invocationRecord.containsKey(null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the getString() method
+ */
+ public void testGetString() {
+ assertNull("undefined", invocationRecord.getString("UNDEFINED"));
+ invocationRecord.set(KEY1, STRING);
+ assertEquals(KEY1, STRING, invocationRecord.getString(KEY1));
+ }
+
+ /**
+ * Test the getString() method, passing in a null key
+ */
+ public void testGetString_Null() {
+ try {
+ invocationRecord.getString(null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the getString() method, when the value for the key is not a String
+ */
+ public void testGetString_NotAString() {
+
+ invocationRecord.set(KEY1, INT);
+ try {
+ invocationRecord.getString(KEY1);
+ fail("Expected ClassCastException");
+ }
+ catch (ClassCastException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the getObject() method
+ */
+ public void testGetObject() {
+ assertNull("undefined", invocationRecord.getObject("UNDEFINED"));
+ invocationRecord.set(KEY1, STRING);
+ assertEquals(KEY1, STRING, invocationRecord.getObject(KEY1));
+ }
+
+ /**
+ * Test the keySet() method
+ */
+ public void testKeySet() {
+ Set set = new HashSet();
+ assertEquals("empty", set, invocationRecord.keySet());
+ invocationRecord.set(KEY1, STRING);
+ invocationRecord.set(KEY2, STRING);
+ set.add(KEY1);
+ set.add(KEY2);
+ assertEquals("2", set, invocationRecord.keySet());
+ }
+
+ /**
+ * Test that the keySet() return value does not allow breaking immutability
+ */
+ public void testKeySet_Immutability() {
+ Set keySet = invocationRecord.keySet();
+ try {
+ keySet.add("abc");
+ fail("Expected UnsupportedOperationException");
+ }
+ catch (UnsupportedOperationException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the getObject() method, passing in a null key
+ */
+ public void testGetObject_Null() {
+ try {
+ invocationRecord.getObject(null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the lock() method
+ */
+ public void testLock() {
+ assertFalse("locked - before", invocationRecord.isLocked());
+ invocationRecord.set(KEY1, STRING);
+ invocationRecord.lock();
+ assertTrue("locked - after", invocationRecord.isLocked());
+ try {
+ invocationRecord.set(KEY2, "abc");
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test that the getTime() return value does not break immutability
+ */
+ public void testGetTime_Immutability() {
+
+ Date timestamp = invocationRecord.getTime();
+ long timeInMillis = timestamp.getTime();
+ timestamp.setTime(12345L);
+ assertEquals("time", timeInMillis, invocationRecord.getTime().getTime());
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.test.AbstractTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ invocationRecord = new InvocationRecord(COMMAND, DEFAULT_HOST);
+ }
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/command/ReplyTextBundleUtilTest.java b/tags/2.5/src/test/java/org/mockftpserver/core/command/ReplyTextBundleUtilTest.java
new file mode 100644
index 0000000..edb6384
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/command/ReplyTextBundleUtilTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.util.AssertFailedException;
+import org.mockftpserver.test.AbstractTestCase;
+
+import java.util.ListResourceBundle;
+import java.util.ResourceBundle;
+
+/**
+ * Tests for the ReplyTextBundleUtil class.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class ReplyTextBundleUtilTest extends AbstractTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ReplyTextBundleUtilTest.class);
+
+ private ResourceBundle resourceBundle1;
+ private ResourceBundle resourceBundle2;
+
+ /**
+ * Test the setReplyTextBundleIfAppropriate() method, when the CommandHandler implements
+ * the ResourceBundleAware interface, and the replyTextBundle has not yet been set.
+ */
+ public void testSetReplyTextBundleIfAppropriate_ReplyTextBundleAware_NotSetYet() {
+ AbstractTrackingCommandHandler commandHandler = new StaticReplyCommandHandler();
+ ReplyTextBundleUtil.setReplyTextBundleIfAppropriate(commandHandler, resourceBundle1);
+ assertSame(resourceBundle1, commandHandler.getReplyTextBundle());
+ }
+
+ /**
+ * Test the setReplyTextBundleIfAppropriate() method, when the CommandHandler implements
+ * the ResourceBundleAware interface, and the replyTextBundle has already been set.
+ */
+ public void testSetReplyTextBundleIfAppropriate_ReplyTextBundleAware_AlreadySet() {
+ AbstractTrackingCommandHandler commandHandler = new StaticReplyCommandHandler();
+ commandHandler.setReplyTextBundle(resourceBundle2);
+ ReplyTextBundleUtil.setReplyTextBundleIfAppropriate(commandHandler, resourceBundle1);
+ assertSame(resourceBundle2, commandHandler.getReplyTextBundle());
+ }
+
+ /**
+ * Test the setReplyTextBundleIfAppropriate() method, when the CommandHandler does not
+ * implement the ResourceBundleAware interface.
+ */
+ public void testSetReplyTextBundleIfAppropriate_NotReplyTextBundleAware() {
+ CommandHandler commandHandler = (CommandHandler) createMock(CommandHandler.class);
+ replay(commandHandler);
+ ReplyTextBundleUtil.setReplyTextBundleIfAppropriate(commandHandler, resourceBundle1);
+ verify(commandHandler); // expect no method calls
+ }
+
+ /**
+ * Test the setReplyTextBundleIfAppropriate() method, when the CommandHandler is null.
+ */
+ public void testSetReplyTextBundleIfAppropriate_NullCommandHandler() {
+ try {
+ ReplyTextBundleUtil.setReplyTextBundleIfAppropriate(null, resourceBundle1);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * @see org.mockftpserver.test.AbstractTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ resourceBundle1 = new ListResourceBundle() {
+ protected Object[][] getContents() {
+ return null;
+ }
+ };
+
+ resourceBundle2 = new ListResourceBundle() {
+ protected Object[][] getContents() {
+ return new Object[][] { { "a", "b" } };
+ }
+ };
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/command/SimpleCompositeCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/core/command/SimpleCompositeCommandHandlerTest.java
new file mode 100644
index 0000000..6635afa
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/command/SimpleCompositeCommandHandlerTest.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.AssertFailedException;
+import org.mockftpserver.test.AbstractTestCase;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListResourceBundle;
+import java.util.ResourceBundle;
+
+/**
+ * Tests for SimpleCompositeCommandHandler
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public class SimpleCompositeCommandHandlerTest extends AbstractTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SimpleCompositeCommandHandlerTest.class);
+
+ private SimpleCompositeCommandHandler simpleCompositeCommandHandler;
+ private Session session;
+ private Command command;
+ private CommandHandler commandHandler1;
+ private CommandHandler commandHandler2;
+ private CommandHandler commandHandler3;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand_OneHandler_OneInvocation() throws Exception {
+ simpleCompositeCommandHandler.addCommandHandler(commandHandler1);
+
+ commandHandler1.handleCommand(command, session);
+ replay(commandHandler1);
+
+ simpleCompositeCommandHandler.handleCommand(command, session);
+ verify(commandHandler1);
+ }
+
+ /**
+ * Test the handleCommand() method, with two CommandHandler defined, but with multiple invocation
+ */
+ public void testHandleCommand_TwoHandlers() throws Exception {
+ simpleCompositeCommandHandler.addCommandHandler(commandHandler1);
+ simpleCompositeCommandHandler.addCommandHandler(commandHandler2);
+
+ commandHandler1.handleCommand(command, session);
+ commandHandler2.handleCommand(command, session);
+ replayAll();
+
+ simpleCompositeCommandHandler.handleCommand(command, session);
+ simpleCompositeCommandHandler.handleCommand(command, session);
+ verifyAll();
+ }
+
+ /**
+ * Test the handleCommand() method, with three CommandHandler defined, and multiple invocation
+ */
+ public void testHandleCommand_ThreeHandlers() throws Exception {
+
+ List list = new ArrayList();
+ list.add(commandHandler1);
+ list.add(commandHandler2);
+ list.add(commandHandler3);
+ simpleCompositeCommandHandler.setCommandHandlers(list);
+
+ commandHandler1.handleCommand(command, session);
+ commandHandler2.handleCommand(command, session);
+ commandHandler3.handleCommand(command, session);
+ replayAll();
+
+ simpleCompositeCommandHandler.handleCommand(command, session);
+ simpleCompositeCommandHandler.handleCommand(command, session);
+ simpleCompositeCommandHandler.handleCommand(command, session);
+ verifyAll();
+ }
+
+ /**
+ * Test the handleCommand() method, with a single CommandHandler defined, but too many invocations
+ */
+ public void testHandleCommand_OneHandler_TooManyInvocations() throws Exception {
+ simpleCompositeCommandHandler.addCommandHandler(commandHandler1);
+
+ commandHandler1.handleCommand(command, session);
+ replay(commandHandler1);
+
+ simpleCompositeCommandHandler.handleCommand(command, session);
+
+ // Second invocation throws an exception
+ try {
+ simpleCompositeCommandHandler.handleCommand(command, session);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the handleCommand_NoHandlersDefined() method
+ */
+ public void testHandleCommand_NoHandlersDefined() throws Exception {
+ try {
+ simpleCompositeCommandHandler.handleCommand(command, session);
+ fail("Expected AssertFailedException");
+ }
+ catch(AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the handleCommand(Command,Session) method, passing in a null Command
+ */
+ public void testHandleCommand_NullCommand() throws Exception {
+ try {
+ simpleCompositeCommandHandler.handleCommand(null, session);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the handleCommand(Command,Session) method, passing in a null Session
+ */
+ public void testHandleCommand_NullSession() throws Exception {
+ try {
+ simpleCompositeCommandHandler.handleCommand(command, null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the addCommandHandler(CommandHandler) method, passing in a null CommandHandler
+ */
+ public void testAddCommandHandler_NullCommandHandler() throws Exception {
+ try {
+ simpleCompositeCommandHandler.addCommandHandler(null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the setCommandHandlers(List) method, passing in a null
+ */
+ public void testSetCommandHandlers_Null() throws Exception {
+ try {
+ simpleCompositeCommandHandler.setCommandHandlers(null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the getCommandHandler(int) method, passing in an index for which no CommandHandler is defined
+ */
+ public void testGetCommandHandler_UndefinedIndex() throws Exception {
+ simpleCompositeCommandHandler.addCommandHandler(commandHandler1);
+ try {
+ simpleCompositeCommandHandler.getCommandHandler(1);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the getCommandHandler(int) method
+ */
+ public void testGetCommandHandler() throws Exception {
+ simpleCompositeCommandHandler.addCommandHandler(commandHandler1);
+ simpleCompositeCommandHandler.addCommandHandler(commandHandler2);
+ assertSame("index 0", commandHandler1, simpleCompositeCommandHandler.getCommandHandler(0));
+ assertSame("index 1", commandHandler2, simpleCompositeCommandHandler.getCommandHandler(1));
+ }
+
+ /**
+ * Test the getCommandHandler(int) method, passing in a negative index
+ */
+ public void testGetCommandHandler_NegativeIndex() throws Exception {
+ simpleCompositeCommandHandler.addCommandHandler(commandHandler1);
+ try {
+ simpleCompositeCommandHandler.getCommandHandler(-1);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the getReplyTextBundle() method
+ */
+ public void testGetReplyTextBundle() {
+ assertNull(simpleCompositeCommandHandler.getReplyTextBundle());
+ }
+
+ /**
+ * Test the setReplyTextBundle() method
+ */
+ public void testSetReplyTextBundle() {
+
+ AbstractTrackingCommandHandler replyTextBundleAwareCommandHandler1 = new StaticReplyCommandHandler();
+ AbstractTrackingCommandHandler replyTextBundleAwareCommandHandler2 = new StaticReplyCommandHandler();
+ simpleCompositeCommandHandler.addCommandHandler(replyTextBundleAwareCommandHandler1);
+ simpleCompositeCommandHandler.addCommandHandler(commandHandler1);
+ simpleCompositeCommandHandler.addCommandHandler(replyTextBundleAwareCommandHandler2);
+
+ ResourceBundle resourceBundle = new ListResourceBundle() {
+ protected Object[][] getContents() {
+ return null;
+ }
+ };
+
+ simpleCompositeCommandHandler.setReplyTextBundle(resourceBundle);
+ assertSame("1", resourceBundle, replyTextBundleAwareCommandHandler1.getReplyTextBundle());
+ assertSame("2", resourceBundle, replyTextBundleAwareCommandHandler1.getReplyTextBundle());
+ }
+
+ //-------------------------------------------------------------------------
+ // Test setup
+ //-------------------------------------------------------------------------
+
+ /**
+ * Perform initialization before each test
+ * @see org.mockftpserver.test.AbstractTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ simpleCompositeCommandHandler = new SimpleCompositeCommandHandler();
+ session = (Session) createMock(Session.class);
+ command = new Command("cmd", EMPTY);
+ commandHandler1 = (CommandHandler) createMock(CommandHandler.class);
+ commandHandler2 = (CommandHandler) createMock(CommandHandler.class);
+ commandHandler3 = (CommandHandler) createMock(CommandHandler.class);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/command/StaticReplyCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/core/command/StaticReplyCommandHandlerTest.java
new file mode 100644
index 0000000..634f182
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/command/StaticReplyCommandHandlerTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.util.AssertFailedException;
+
+/**
+ * Tests for the StaticReplyCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class StaticReplyCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(StaticReplyCommandHandlerTest.class);
+ private static final int REPLY_CODE = 999;
+ private static final String REPLY_TEXT = "some text 123";
+ private static final Command COMMAND = new Command("ANY", EMPTY);
+
+ private StaticReplyCommandHandler commandHandler;
+
+ /**
+ * Test the constructor that takes a replyCode, passing in a null
+ */
+ public void testConstructor_String_InvalidReplyCode() {
+ try {
+ new StaticReplyCommandHandler(-1);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the constructor that takes a replyCode and replyText, passing in a null replyCode
+ */
+ public void testConstructor_StringString_InvalidReplyCode() {
+ try {
+ new StaticReplyCommandHandler(-99, "text");
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the setReplyCode() method, passing in a null
+ */
+ public void testSetReplyCode_Invalid() {
+ try {
+ commandHandler.setReplyCode(-1);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the handleCommand() method when the replyText attribute has not been set.
+ * So, use whatever replyText has been configured in the replyCodeMapping
+ * @throws Exception
+ */
+ public void testHandleCommand_ReplyTextNotSet() throws Exception {
+ commandHandler.setReplyCode(250);
+
+ session.sendReply(250, replyTextFor(250));
+ replay(session);
+
+ commandHandler.handleCommand(COMMAND, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyNoDataElements(commandHandler.getInvocation(0));
+ }
+
+ /**
+ * Test the handleCommand() method, when the replyCode and replyText are both set
+ * @throws Exception
+ */
+ public void testHandleCommand_SetReplyText() throws Exception {
+ commandHandler.setReplyCode(REPLY_CODE);
+ commandHandler.setReplyText(REPLY_TEXT);
+
+ session.sendReply(REPLY_CODE, REPLY_TEXT);
+ replay(session);
+
+ commandHandler.handleCommand(COMMAND, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyNoDataElements(commandHandler.getInvocation(0));
+ }
+
+ /**
+ * Test the handleCommand() method when the replyCode attribute has not been set
+ * @throws Exception
+ */
+ public void testHandleCommand_ReplyCodeNotSet() throws Exception {
+
+ try {
+ commandHandler.handleCommand(COMMAND, session);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyNoDataElements(commandHandler.getInvocation(0));
+ }
+
+ /**
+ * @see AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new StaticReplyCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/command/UnsupportedCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/core/command/UnsupportedCommandHandlerTest.java
new file mode 100644
index 0000000..cd6d84a
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/command/UnsupportedCommandHandlerTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+/**
+ * Tests for the UnsupportedCommandHandler class
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class UnsupportedCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private UnsupportedCommandHandler commandHandler;
+ private Command command1;
+
+ public void testHandleCommand() throws Exception {
+
+ session.sendReply(ReplyCodes.COMMAND_NOT_SUPPORTED, replyTextFor(ReplyCodes.COMMAND_NOT_SUPPORTED));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyNoDataElements(commandHandler.getInvocation(0));
+ }
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new UnsupportedCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ command1 = new Command("XXXX", EMPTY);
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/command/_AbstractCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/core/command/_AbstractCommandHandlerTest.java
new file mode 100644
index 0000000..f356c88
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/command/_AbstractCommandHandlerTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.easymock.MockControl;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.AssertFailedException;
+import org.mockftpserver.stub.command.AbstractStubCommandHandler;
+import org.mockftpserver.test.AbstractTestCase;
+
+import java.util.ListResourceBundle;
+import java.util.ResourceBundle;
+
+/**
+ * Tests for the AbstractCommandHandler class. The class name is prefixed with an
+ * underscore so that it is not filtered out by Maven's Surefire test plugin.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class _AbstractCommandHandlerTest extends AbstractTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(_AbstractTrackingCommandHandlerTest.class);
+ private static final int REPLY_CODE1 = 777;
+ private static final int REPLY_CODE2 = 888;
+ private static final String REPLY_TEXT1 = "reply1 ... abcdef";
+ private static final String REPLY_TEXT2 = "abc {0} def";
+ private static final String MESSAGE_KEY = "key.123";
+ private static final String MESSAGE_TEXT = "message.123";
+
+ private AbstractCommandHandler commandHandler;
+
+ /**
+ * Test the quotes utility method
+ */
+ public void testQuotes() {
+ assertEquals("abc", "\"abc\"", AbstractStubCommandHandler.quotes("abc"));
+ assertEquals("<empty>", "\"\"", AbstractStubCommandHandler.quotes(""));
+ }
+
+ /**
+ * Test the quotes utility method, passing in a null
+ */
+ public void testQuotes_Null() {
+ try {
+ AbstractStubCommandHandler.quotes(null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the assertValidReplyCode() method
+ */
+ public void testAssertValidReplyCode() {
+ // These are valid, so expect no exceptions
+ commandHandler.assertValidReplyCode(1);
+ commandHandler.assertValidReplyCode(100);
+
+ // These are invalid
+ testAssertValidReplyCodeWithInvalid(0);
+ testAssertValidReplyCodeWithInvalid(-1);
+ }
+
+ /**
+ * Test the assertValidReplyCode() method , passing in an invalid replyCode value
+ *
+ * @param invalidReplyCode - a reply code that is expected to be invalid
+ */
+ private void testAssertValidReplyCodeWithInvalid(int invalidReplyCode) {
+ try {
+ commandHandler.assertValidReplyCode(invalidReplyCode);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ //-------------------------------------------------------------------------
+ // Test setup
+ //-------------------------------------------------------------------------
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.test.AbstractTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ Session session = (Session) createMock(Session.class);
+ control(session).setDefaultMatcher(MockControl.ARRAY_MATCHER);
+ commandHandler = new AbstractCommandHandler() {
+ public void handleCommand(Command command, Session session) throws Exception {
+ }
+ };
+ ResourceBundle replyTextBundle = new ListResourceBundle() {
+ protected Object[][] getContents() {
+ return new Object[][]{
+ {Integer.toString(REPLY_CODE1), REPLY_TEXT1},
+ {Integer.toString(REPLY_CODE2), REPLY_TEXT2},
+ {MESSAGE_KEY, MESSAGE_TEXT}
+ };
+ }
+ };
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/command/_AbstractStaticReplyCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/core/command/_AbstractStaticReplyCommandHandlerTest.java
new file mode 100644
index 0000000..c4a8e50
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/command/_AbstractStaticReplyCommandHandlerTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.easymock.MockControl;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.AssertFailedException;
+import org.mockftpserver.test.AbstractTestCase;
+
+import java.util.ListResourceBundle;
+import java.util.ResourceBundle;
+
+/**
+ * Tests for the AbstractStaticReplyCommandHandler class. The class name is prefixed with an underscore
+ * so that it is not filtered out by Maven's Surefire test plugin.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class _AbstractStaticReplyCommandHandlerTest extends AbstractTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(_AbstractStaticReplyCommandHandlerTest.class);
+ private static final int REPLY_CODE1 = 777;
+ private static final int REPLY_CODE2 = 888;
+ private static final String REPLY_TEXT1 = "reply1 ... abcdef";
+ private static final String REPLY_TEXT2 = "abc {0} def";
+ private static final String REPLY_TEXT2_FORMATTED = "abc 123 def";
+ private static final String MESSAGE_KEY = "key.123";
+ private static final String MESSAGE_TEXT = "message.123";
+ private static final String OVERRIDE_REPLY_TEXT = "overridden reply ... abcdef";
+ private static final Object ARG = "123";
+
+ private AbstractStaticReplyCommandHandler commandHandler;
+ private Session session;
+
+ /**
+ * Test the sendReply(Session) method
+ */
+ public void testSendReply() {
+ session.sendReply(REPLY_CODE1, REPLY_TEXT1);
+ replay(session);
+
+ commandHandler.setReplyCode(REPLY_CODE1);
+ commandHandler.sendReply(session);
+ verify(session);
+ }
+
+ /**
+ * Test the sendReply(Session) method, when the replyText has been set
+ */
+ public void testSendReply_SetReplyText() {
+ session.sendReply(REPLY_CODE1, OVERRIDE_REPLY_TEXT);
+ replay(session);
+
+ commandHandler.setReplyCode(REPLY_CODE1);
+ commandHandler.setReplyText(OVERRIDE_REPLY_TEXT);
+ commandHandler.sendReply(session);
+ verify(session);
+ }
+
+ /**
+ * Test the sendReply(Session) method, when the replyMessageKey has been set
+ */
+ public void testSendReply_SetReplyMessageKey() {
+ session.sendReply(REPLY_CODE1, REPLY_TEXT2);
+ replay(session);
+
+ commandHandler.setReplyCode(REPLY_CODE1);
+ commandHandler.setReplyMessageKey(Integer.toString(REPLY_CODE2));
+ commandHandler.sendReply(session);
+ verify(session);
+ }
+
+ /**
+ * Test the sendReply(Session) method, when the replyCode has not been set
+ */
+ public void testSendReply_ReplyCodeNotSet() {
+ try {
+ commandHandler.sendReply(session);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the sendReply(Session,Object) method
+ */
+ public void testSendReply_MessageParameter() {
+ session.sendReply(REPLY_CODE2, REPLY_TEXT2);
+ session.sendReply(REPLY_CODE2, REPLY_TEXT2_FORMATTED);
+ replay(session);
+
+ commandHandler.setReplyCode(REPLY_CODE2);
+ commandHandler.sendReply(session);
+ commandHandler.sendReply(session, ARG);
+ verify(session);
+ }
+
+ /**
+ * Test the setReplyCode() method, passing in an invalid value
+ */
+ public void testSetReplyCode_Invalid() {
+ try {
+ commandHandler.setReplyCode(0);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ //-------------------------------------------------------------------------
+ // Test setup
+ //-------------------------------------------------------------------------
+
+ /**
+ * Perform initialization before each test
+ * @see org.mockftpserver.test.AbstractTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ session = (Session) createMock(Session.class);
+ control(session).setDefaultMatcher(MockControl.ARRAY_MATCHER);
+ commandHandler = new AbstractStaticReplyCommandHandler() {
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) throws Exception {
+ }
+ };
+ ResourceBundle replyTextBundle = new ListResourceBundle() {
+ protected Object[][] getContents() {
+ return new Object[][]{
+ {Integer.toString(REPLY_CODE1), REPLY_TEXT1},
+ {Integer.toString(REPLY_CODE2), REPLY_TEXT2},
+ {MESSAGE_KEY, MESSAGE_TEXT}
+ };
+ }
+ };
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/command/_AbstractTrackingCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/core/command/_AbstractTrackingCommandHandlerTest.java
new file mode 100644
index 0000000..5cc98f2
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/command/_AbstractTrackingCommandHandlerTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.command;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.easymock.MockControl;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.AssertFailedException;
+import org.mockftpserver.test.AbstractTestCase;
+
+import java.util.ListResourceBundle;
+import java.util.ResourceBundle;
+
+/**
+ * Tests for the AbstractTrackingCommandHandler class. The class name is prefixed with an
+ * underscore so that it is not filtered out by Maven's Surefire test plugin.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class _AbstractTrackingCommandHandlerTest extends AbstractTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(_AbstractTrackingCommandHandlerTest.class);
+ private static final String COMMAND_NAME = "abc";
+ private static final Object ARG = "123";
+ private static final Object[] ARGS = {ARG};
+ private static final Command COMMAND = new Command(COMMAND_NAME, EMPTY);
+ private static final Command COMMAND_WITH_ARGS = new Command(COMMAND_NAME, EMPTY);
+ private static final int REPLY_CODE1 = 777;
+ private static final int REPLY_CODE2 = 888;
+ private static final int REPLY_CODE3 = 999;
+ private static final String REPLY_TEXT1 = "reply1 ... abcdef";
+ private static final String REPLY_TEXT2 = "abc {0} def";
+ private static final String REPLY_TEXT2_FORMATTED = "abc 123 def";
+ private static final String OVERRIDE_REPLY_TEXT = "overridden reply ... abcdef";
+ private static final String MESSAGE_KEY = "key.123";
+ private static final String MESSAGE_TEXT = "message.123";
+
+ private AbstractTrackingCommandHandler commandHandler;
+ private Session session;
+
+ /**
+ * Test the handleCommand(Command,Session) method
+ */
+ public void testHandleCommand() throws Exception {
+ assertEquals("before", 0, commandHandler.numberOfInvocations());
+ commandHandler.handleCommand(COMMAND, session);
+ assertEquals("after", 1, commandHandler.numberOfInvocations());
+ assertTrue("locked", commandHandler.getInvocation(0).isLocked());
+ }
+
+ /**
+ * Test the handleCommand(Command,Session) method, passing in a null Command
+ */
+ public void testHandleCommand_NullCommand() throws Exception {
+ try {
+ commandHandler.handleCommand(null, session);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the handleCommand(Command,Session) method, passing in a null Session
+ */
+ public void testHandleCommand_NullSession() throws Exception {
+ try {
+ commandHandler.handleCommand(COMMAND, null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the numberOfInvocations(), addInvocationRecord() and clearInvocationRecord() methods
+ */
+ public void testInvocationHistory() throws Exception {
+ control(session).expectAndDefaultReturn(session.getClientHost(), DEFAULT_HOST);
+ replay(session);
+
+ assertEquals("none", 0, commandHandler.numberOfInvocations());
+ commandHandler.handleCommand(COMMAND, session);
+ assertEquals("1", 1, commandHandler.numberOfInvocations());
+ commandHandler.handleCommand(COMMAND, session);
+ assertEquals("2", 2, commandHandler.numberOfInvocations());
+ commandHandler.clearInvocations();
+ assertEquals("cleared", 0, commandHandler.numberOfInvocations());
+ }
+
+ /**
+ * Test the getInvocation() method
+ *
+ * @throws Exception
+ */
+ public void testGetInvocation() throws Exception {
+ control(session).expectAndDefaultReturn(session.getClientHost(), DEFAULT_HOST);
+ replay(session);
+
+ commandHandler.handleCommand(COMMAND, session);
+ commandHandler.handleCommand(COMMAND_WITH_ARGS, session);
+ assertSame("1", COMMAND, commandHandler.getInvocation(0).getCommand());
+ assertSame("2", COMMAND_WITH_ARGS, commandHandler.getInvocation(1).getCommand());
+ }
+
+ /**
+ * Test the getInvocation() method, passing in an invalid index
+ */
+ public void testGetInvocation_IndexOutOfBounds() throws Exception {
+ commandHandler.handleCommand(COMMAND, session);
+ try {
+ commandHandler.getInvocation(2);
+ fail("Expected IndexOutOfBoundsException");
+ }
+ catch (IndexOutOfBoundsException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the sendReply() method, when no message arguments are specified
+ */
+ public void testSendReply() {
+ session.sendReply(REPLY_CODE1, REPLY_TEXT1);
+ session.sendReply(REPLY_CODE1, MESSAGE_TEXT);
+ session.sendReply(REPLY_CODE1, OVERRIDE_REPLY_TEXT);
+ session.sendReply(REPLY_CODE3, null);
+ replay(session);
+
+ commandHandler.sendReply(session, REPLY_CODE1, null, null, null);
+ commandHandler.sendReply(session, REPLY_CODE1, MESSAGE_KEY, null, null);
+ commandHandler.sendReply(session, REPLY_CODE1, MESSAGE_KEY, OVERRIDE_REPLY_TEXT, null);
+ commandHandler.sendReply(session, REPLY_CODE3, null, null, null);
+
+ verify(session);
+ }
+
+ /**
+ * Test the sendReply() method, passing in message arguments
+ */
+ public void testSendReply_WithMessageArguments() {
+ session.sendReply(REPLY_CODE1, REPLY_TEXT2_FORMATTED);
+ replay(session);
+
+ commandHandler.sendReply(session, REPLY_CODE1, null, REPLY_TEXT2, ARGS);
+
+ verify(session);
+ }
+
+ /**
+ * Test the sendReply() method, passing in a null Session
+ */
+ public void testSendReply_NullSession() {
+ try {
+ commandHandler.sendReply(null, REPLY_CODE1, REPLY_TEXT1, null, null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the sendReply() method, passing in an invalid replyCode
+ */
+ public void testSendReply_InvalidReplyCode() {
+ try {
+ commandHandler.sendReply(session, 0, REPLY_TEXT1, null, null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ //-------------------------------------------------------------------------
+ // Test setup
+ //-------------------------------------------------------------------------
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.test.AbstractTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ session = (Session) createMock(Session.class);
+ control(session).setDefaultMatcher(MockControl.ARRAY_MATCHER);
+ commandHandler = new AbstractTrackingCommandHandler() {
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) throws Exception {
+ }
+ };
+ ResourceBundle replyTextBundle = new ListResourceBundle() {
+ protected Object[][] getContents() {
+ return new Object[][]{
+ {Integer.toString(REPLY_CODE1), REPLY_TEXT1},
+ {Integer.toString(REPLY_CODE2), REPLY_TEXT2},
+ {MESSAGE_KEY, MESSAGE_TEXT}
+ };
+ }
+ };
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/server/AbstractFtpServerTestCase.java b/tags/2.5/src/test/java/org/mockftpserver/core/server/AbstractFtpServerTestCase.java
new file mode 100644
index 0000000..70e6f9a
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/server/AbstractFtpServerTestCase.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.server;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.session.DefaultSession;
+import org.mockftpserver.core.util.AssertFailedException;
+import org.mockftpserver.test.AbstractTestCase;
+
+import java.net.Socket;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Abstract superclass for tests of AbstractFtpServer subclasses.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public abstract class AbstractFtpServerTestCase extends AbstractTestCase {
+
+ protected Logger LOG = LoggerFactory.getLogger(getClass());
+
+ protected AbstractFtpServer ftpServer;
+ private CommandHandler commandHandler;
+ private CommandHandler commandHandler2;
+
+ /**
+ * Test the setCommandHandlers() method
+ */
+ public void testSetCommandHandlers() {
+ Map mapping = new HashMap();
+ mapping.put("AAA", commandHandler);
+ mapping.put("BBB", commandHandler2);
+
+ ftpServer.setCommandHandlers(mapping);
+ assertSame("commandHandler1", commandHandler, ftpServer.getCommandHandler("AAA"));
+ assertSame("commandHandler2", commandHandler2, ftpServer.getCommandHandler("BBB"));
+
+ verifyCommandHandlerInitialized(commandHandler);
+ verifyCommandHandlerInitialized(commandHandler2);
+
+ // Make sure default CommandHandlers are still set
+ assertTrue("ConnectCommandHandler", ftpServer.getCommandHandler(CommandNames.CONNECT) != null);
+ }
+
+ /**
+ * Test the setCommandHandlers() method, when the Map is null
+ */
+ public void testSetCommandHandlers_Null() {
+ try {
+ ftpServer.setCommandHandlers(null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the setCommandHandler() method
+ */
+ public void testSetCommandHandler() {
+ ftpServer.setCommandHandler("ZZZ", commandHandler2);
+ assertSame("commandHandler", commandHandler2, ftpServer.getCommandHandler("ZZZ"));
+ verifyCommandHandlerInitialized(commandHandler2);
+ }
+
+ /**
+ * Test the setCommandHandler() method, when the commandName is null
+ */
+ public void testSetCommandHandler_NullCommandName() {
+ CommandHandler commandHandler = (CommandHandler) createMock(CommandHandler.class);
+ try {
+ ftpServer.setCommandHandler(null, commandHandler);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the setCommandHandler() method, when the commandHandler is null
+ */
+ public void testSetCommandHandler_NullCommandHandler() {
+ try {
+ ftpServer.setCommandHandler("ZZZ", null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ public void testSetServerControlPort() {
+ assertEquals("default", 21, ftpServer.getServerControlPort());
+ ftpServer.setServerControlPort(99);
+ assertEquals("99", 99, ftpServer.getServerControlPort());
+ }
+
+ /**
+ * Test the setCommandHandler() and getCommandHandler() methods for commands in lower case or mixed case
+ */
+ public void testLowerCaseOrMixedCaseCommandNames() {
+ ftpServer.setCommandHandler("XXX", commandHandler);
+ assertSame("ZZZ", commandHandler, ftpServer.getCommandHandler("XXX"));
+ assertSame("Zzz", commandHandler, ftpServer.getCommandHandler("Xxx"));
+ assertSame("zzz", commandHandler, ftpServer.getCommandHandler("xxx"));
+
+ ftpServer.setCommandHandler("YyY", commandHandler);
+ assertSame("ZZZ", commandHandler, ftpServer.getCommandHandler("YYY"));
+ assertSame("Zzz", commandHandler, ftpServer.getCommandHandler("Yyy"));
+ assertSame("zzz", commandHandler, ftpServer.getCommandHandler("yyy"));
+
+ ftpServer.setCommandHandler("zzz", commandHandler);
+ assertSame("ZZZ", commandHandler, ftpServer.getCommandHandler("ZZZ"));
+ assertSame("Zzz", commandHandler, ftpServer.getCommandHandler("zzZ"));
+ assertSame("zzz", commandHandler, ftpServer.getCommandHandler("zzz"));
+ }
+
+ /**
+ * Test calling stop() for a server that was never started.
+ */
+ public void testStopWithoutStart() {
+ ftpServer.stop();
+ }
+
+ public void testCreateSession() {
+ assertEquals(ftpServer.createSession(new Socket()).getClass(), DefaultSession.class);
+ }
+
+ //-------------------------------------------------------------------------
+ // Test setup
+ //-------------------------------------------------------------------------
+
+ /**
+ * @see org.mockftpserver.test.AbstractTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ ftpServer = createFtpServer();
+
+ commandHandler = createCommandHandler();
+ commandHandler2 = createCommandHandler();
+ }
+
+ //-------------------------------------------------------------------------
+ // Abstract method declarations
+ //-------------------------------------------------------------------------
+
+ protected abstract AbstractFtpServer createFtpServer();
+
+ protected abstract CommandHandler createCommandHandler();
+
+ protected abstract void verifyCommandHandlerInitialized(CommandHandler commandHandler);
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/server/AbstractFtpServer_StartTestCase.java b/tags/2.5/src/test/java/org/mockftpserver/core/server/AbstractFtpServer_StartTestCase.java
new file mode 100644
index 0000000..cc5b346
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/server/AbstractFtpServer_StartTestCase.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.server;
+
+import org.apache.commons.net.ftp.FTPClient;
+import org.mockftpserver.test.*;
+import org.mockftpserver.test.AbstractTestCase;
+
+/**
+ * Abstract superclass for tests of AbstractFtpServer subclasses that require the server thread to be started.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public abstract class AbstractFtpServer_StartTestCase extends AbstractTestCase {
+
+ private static final String SERVER = "localhost";
+
+ private AbstractFtpServer ftpServer;
+
+ /**
+ * Test the start() and stop() methods. Start the server and then stop it immediately.
+ */
+ public void testStartAndStop() throws Exception {
+ ftpServer.setServerControlPort(PortTestUtil.getFtpServerControlPort());
+ assertEquals("started - before", false, ftpServer.isStarted());
+
+ ftpServer.start();
+ Thread.sleep(200L); // give it some time to get started
+ assertEquals("started - after start()", true, ftpServer.isStarted());
+ assertEquals("shutdown - after start()", false, ftpServer.isShutdown());
+
+ ftpServer.stop();
+
+ assertEquals("shutdown - after stop()", true, ftpServer.isShutdown());
+ }
+
+ /**
+ * Test setting a non-default port number for the StubFtpServer control connection socket.
+ */
+ public void testCustomServerControlPort() throws Exception {
+ final int SERVER_CONTROL_PORT = 9187;
+
+ ftpServer.setServerControlPort(SERVER_CONTROL_PORT);
+ ftpServer.start();
+
+ try {
+ FTPClient ftpClient = new FTPClient();
+ ftpClient.connect(SERVER, SERVER_CONTROL_PORT);
+ }
+ finally {
+ ftpServer.stop();
+ }
+ }
+
+ //-------------------------------------------------------------------------
+ // Test setup
+ //-------------------------------------------------------------------------
+
+ /**
+ * @see org.mockftpserver.test.AbstractTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ ftpServer = createFtpServer();
+ }
+
+ //-------------------------------------------------------------------------
+ // Abstract method declarations
+ //-------------------------------------------------------------------------
+
+ protected abstract AbstractFtpServer createFtpServer();
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/session/DefaultSessionTest.java b/tags/2.5/src/test/java/org/mockftpserver/core/session/DefaultSessionTest.java
new file mode 100644
index 0000000..5763e5e
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/session/DefaultSessionTest.java
@@ -0,0 +1,482 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.session;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.MockFtpServerException;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.socket.StubServerSocket;
+import org.mockftpserver.core.socket.StubServerSocketFactory;
+import org.mockftpserver.core.socket.StubSocket;
+import org.mockftpserver.core.socket.StubSocketFactory;
+import org.mockftpserver.core.util.AssertFailedException;
+import org.mockftpserver.test.AbstractTestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.SocketTimeoutException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tests for the DefaultSession class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class DefaultSessionTest extends AbstractTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultSessionTest.class);
+ private static final String DATA = "sample data 123";
+ private static final int PORT = 197;
+ private static final String NAME1 = "name1";
+ private static final String NAME2 = "name2";
+ private static final Object VALUE = "value";
+
+ private DefaultSession session;
+ private ByteArrayOutputStream outputStream;
+ private Map commandHandlerMap;
+ private StubSocket stubSocket;
+ private InetAddress clientHost;
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.test.AbstractTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ commandHandlerMap = new HashMap();
+ outputStream = new ByteArrayOutputStream();
+ session = createDefaultSession("");
+ clientHost = InetAddress.getLocalHost();
+ }
+
+ /**
+ * @see org.mockftpserver.test.AbstractTestCase#tearDown()
+ */
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /**
+ * Test the Constructor when the control socket is null
+ */
+ public void testConstructor_NullControlSocket() {
+ try {
+ new DefaultSession(null, commandHandlerMap);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the Constructor when the command handler Map is null
+ */
+ public void testConstructor_NullCommandHandlerMap() {
+ try {
+ new DefaultSession(stubSocket, null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the setClientDataPort() method
+ */
+ public void testSetClientDataPort() {
+ StubSocket stubSocket = createTestSocket("");
+ StubSocketFactory stubSocketFactory = new StubSocketFactory(stubSocket);
+ session.socketFactory = stubSocketFactory;
+ session.setClientDataPort(PORT);
+ session.setClientDataHost(clientHost);
+ session.openDataConnection();
+ assertEquals("data port", PORT, stubSocketFactory.requestedDataPort);
+ }
+
+ /**
+ * Test the setClientDataPort() method after the session was in passive data mode
+ */
+ public void testSetClientDataPort_AfterPassiveConnectionMode() throws IOException {
+ StubServerSocket stubServerSocket = new StubServerSocket(PORT);
+ StubServerSocketFactory stubServerSocketFactory = new StubServerSocketFactory(stubServerSocket);
+ session.serverSocketFactory = stubServerSocketFactory;
+
+ session.switchToPassiveMode();
+ assertFalse("server socket closed", stubServerSocket.isClosed());
+ assertNotNull("passiveModeDataSocket", session.passiveModeDataSocket);
+ session.setClientDataPort(PORT);
+
+ // Make sure that any passive mode connection info is cleared out
+ assertTrue("server socket closed", stubServerSocket.isClosed());
+ assertNull("passiveModeDataSocket should be null", session.passiveModeDataSocket);
+ }
+
+ /**
+ * Test the setClientHost() method
+ */
+ public void testSetClientHost() throws Exception {
+ StubSocket stubSocket = createTestSocket("");
+ StubSocketFactory stubSocketFactory = new StubSocketFactory(stubSocket);
+ session.socketFactory = stubSocketFactory;
+ session.setClientDataHost(clientHost);
+ session.openDataConnection();
+ assertEquals("client host", clientHost, stubSocketFactory.requestedHost);
+ }
+
+ /**
+ * Test the openDataConnection(), setClientDataPort() and setClientDataHost() methods
+ */
+ public void testOpenDataConnection() {
+ StubSocket stubSocket = createTestSocket("");
+ StubSocketFactory stubSocketFactory = new StubSocketFactory(stubSocket);
+ session.socketFactory = stubSocketFactory;
+
+ // Use default client data port
+ session.setClientDataHost(clientHost);
+ session.openDataConnection();
+ assertEquals("data port", DefaultSession.DEFAULT_CLIENT_DATA_PORT, stubSocketFactory.requestedDataPort);
+ assertEquals("client host", clientHost, stubSocketFactory.requestedHost);
+
+ // Set client data port explicitly
+ session.setClientDataPort(PORT);
+ session.setClientDataHost(clientHost);
+ session.openDataConnection();
+ assertEquals("data port", PORT, stubSocketFactory.requestedDataPort);
+ assertEquals("client host", clientHost, stubSocketFactory.requestedHost);
+ }
+
+ /**
+ * Test the OpenDataConnection method, when in passive mode and no incoming connection is
+ * initiated
+ */
+ public void testOpenDataConnection_PassiveMode_NoConnection() throws IOException {
+
+ StubServerSocket stubServerSocket = new StubServerSocket(PORT);
+ StubServerSocketFactory stubServerSocketFactory = new StubServerSocketFactory(stubServerSocket);
+ session.serverSocketFactory = stubServerSocketFactory;
+
+ session.switchToPassiveMode();
+
+ try {
+ session.openDataConnection();
+ fail("Expected MockFtpServerException");
+ }
+ catch (MockFtpServerException expected) {
+ LOG.info("Expected: " + expected);
+ assertSame("cause", SocketTimeoutException.class, expected.getCause().getClass());
+ }
+ }
+
+ /**
+ * Test the OpenDataConnection method, when the clientHost has not been set
+ */
+ public void testOpenDataConnection_NullClientHost() {
+ try {
+ session.openDataConnection();
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the readData() method
+ */
+ public void testReadData() {
+ StubSocket stubSocket = createTestSocket(DATA);
+ session.socketFactory = new StubSocketFactory(stubSocket);
+ session.setClientDataHost(clientHost);
+
+ session.openDataConnection();
+ byte[] data = session.readData();
+ LOG.info("data=[" + new String(data) + "]");
+ assertEquals("data", DATA.getBytes(), data);
+ }
+
+ /**
+ * Test the readData() method after switching to passive mode
+ */
+ public void testReadData_PassiveMode() throws IOException {
+ StubSocket stubSocket = createTestSocket(DATA);
+ StubServerSocket stubServerSocket = new StubServerSocket(PORT, stubSocket);
+ StubServerSocketFactory stubServerSocketFactory = new StubServerSocketFactory(stubServerSocket);
+ session.serverSocketFactory = stubServerSocketFactory;
+
+ session.switchToPassiveMode();
+ session.openDataConnection();
+ byte[] data = session.readData();
+ LOG.info("data=[" + new String(data) + "]");
+ assertEquals("data", DATA.getBytes(), data);
+ }
+
+ /**
+ * Test the readData(int) method
+ */
+ public void testReadData_NumBytes() {
+ final int NUM_BYTES = 5;
+ final String EXPECTED_DATA = DATA.substring(0, NUM_BYTES);
+ StubSocket stubSocket = createTestSocket(DATA);
+ session.socketFactory = new StubSocketFactory(stubSocket);
+ session.setClientDataHost(clientHost);
+
+ session.openDataConnection();
+ byte[] data = session.readData(NUM_BYTES);
+ LOG.info("data=[" + new String(data) + "]");
+ assertEquals("data", EXPECTED_DATA.getBytes(), data);
+ }
+
+ public void testReadData_NumBytes_AskForMoreBytesThanThereAre() {
+ StubSocket stubSocket = createTestSocket(DATA);
+ session.socketFactory = new StubSocketFactory(stubSocket);
+ session.setClientDataHost(clientHost);
+
+ session.openDataConnection();
+ byte[] data = session.readData(10000);
+ LOG.info("data=[" + new String(data) + "]");
+ assertEquals("data", DATA.getBytes(), data);
+ }
+
+ /**
+ * Test the closeDataConnection() method
+ */
+ public void testCloseDataConnection() {
+ StubSocket stubSocket = createTestSocket(DATA);
+ session.socketFactory = new StubSocketFactory(stubSocket);
+
+ session.setClientDataHost(clientHost);
+ session.openDataConnection();
+ session.closeDataConnection();
+ assertTrue("client data socket should be closed", stubSocket.isClosed());
+ }
+
+ /**
+ * Test the switchToPassiveMode() method
+ */
+ public void testSwitchToPassiveMode() throws IOException {
+ StubServerSocket stubServerSocket = new StubServerSocket(PORT);
+ StubServerSocketFactory stubServerSocketFactory = new StubServerSocketFactory(stubServerSocket);
+ session.serverSocketFactory = stubServerSocketFactory;
+
+ assertNull("passiveModeDataSocket starts out null", session.passiveModeDataSocket);
+ int port = session.switchToPassiveMode();
+ assertSame("passiveModeDataSocket", stubServerSocket, session.passiveModeDataSocket);
+ assertEquals("port", PORT, port);
+ }
+
+ /**
+ * Test the getServerHost() method
+ */
+ public void testGetServerHost() {
+ assertEquals("host", DEFAULT_HOST, session.getServerHost());
+ }
+
+ /**
+ * Test the getClientHost() method when the session is not yet started
+ */
+ public void testGetClientHost_NotRunning() {
+ assertNull("null", session.getClientHost());
+ }
+
+ /**
+ * Test the parseCommand() method
+ */
+ public void testParseCommand() {
+ Command command = session.parseCommand("LIST");
+ assertEquals("command name", "LIST", command.getName());
+ assertEquals("command parameters", EMPTY, command.getParameters());
+
+ command = session.parseCommand("USER user123");
+ assertEquals("command name", "USER", command.getName());
+ assertEquals("command parameters", array("user123"), command.getParameters());
+
+ command = session.parseCommand("PORT 127,0,0,1,17,37");
+ assertEquals("command name", "PORT", command.getName());
+ assertEquals("command parameters", new String[] { "127", "0", "0", "1", "17", "37" }, command
+ .getParameters());
+ }
+
+ /**
+ * Test the parseCommand() method, passing in an empty command String
+ */
+ public void testParseCommand_EmptyCommandString() {
+ try {
+ session.parseCommand("");
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the sendData() method, as well as the openDataConnection() and closeDataConnection()
+ */
+ public void testSendData() {
+ StubSocket stubSocket = createTestSocket("1234567890 abcdef");
+ session.socketFactory = new StubSocketFactory(stubSocket);
+
+ session.setClientDataHost(clientHost);
+ session.openDataConnection();
+ session.sendData(DATA.getBytes(), DATA.length());
+ LOG.info("output=[" + outputStream.toString() + "]");
+ assertEquals("output", DATA, outputStream.toString());
+ }
+
+ /**
+ * Test the SendData() method, passing in a null byte[]
+ */
+ public void testSendData_Null() {
+
+ try {
+ session.sendData(null, 1);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the SendReply(int,String) method, passing in an invalid reply code
+ */
+ public void testSendReply_InvalidReplyCode() {
+
+ try {
+ session.sendReply(-66, "text");
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the getAttribute() and setAttribute() methods
+ */
+ public void testGetAndSetAttribute() {
+ assertNull("name does not exist yet", session.getAttribute(NAME1));
+ session.setAttribute(NAME1, VALUE);
+ session.setAttribute(NAME2, null);
+ assertEquals("NAME1", VALUE, session.getAttribute(NAME1));
+ assertNull("NAME2", session.getAttribute(NAME2));
+ assertNull("no such name", session.getAttribute("noSuchName"));
+ }
+
+ /**
+ * Test the getAttribute() method, passing in a null name
+ */
+ public void testGetAttribute_Null() {
+ try {
+ session.getAttribute(null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the setAttribute() method, passing in a null name
+ */
+ public void testSetAttribute_NullName() {
+ try {
+ session.setAttribute(null, VALUE);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the removeAttribute()
+ */
+ public void testRemoveAttribute() {
+ session.removeAttribute("noSuchName"); // do nothing
+ session.setAttribute(NAME1, VALUE);
+ session.removeAttribute(NAME1);
+ assertNull("NAME1", session.getAttribute(NAME1));
+ }
+
+ /**
+ * Test the removeAttribute() method, passing in a null name
+ */
+ public void testRemoveAttribute_Null() {
+ try {
+ session.removeAttribute(null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the getAttributeNames()
+ */
+ public void testGetAttributeNames() {
+ assertEquals("No names yet", Collections.EMPTY_SET, session.getAttributeNames());
+ session.setAttribute(NAME1, VALUE);
+ assertEquals("1", Collections.singleton(NAME1), session.getAttributeNames());
+ session.setAttribute(NAME2, VALUE);
+ assertEquals("2", set(NAME1, NAME2), session.getAttributeNames());
+ }
+
+ // -------------------------------------------------------------------------
+ // Internal Helper Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Create and return a DefaultSession object that reads from an InputStream with the specified
+ * contents and writes to the predefined outputStrean ByteArrayOutputStream. Also, save the
+ * StubSocket being used in the stubSocket attribute.
+ *
+ * @param inputStreamContents - the contents of the input stream
+ * @return the DefaultSession
+ */
+ private DefaultSession createDefaultSession(String inputStreamContents) {
+ stubSocket = createTestSocket(inputStreamContents);
+ return new DefaultSession(stubSocket, commandHandlerMap);
+ }
+
+ /**
+ * Create and return a StubSocket that reads from an InputStream with the specified contents and
+ * writes to the predefined outputStrean ByteArrayOutputStream.
+ *
+ * @param inputStreamContents - the contents of the input stream
+ * @return the StubSocket
+ */
+ private StubSocket createTestSocket(String inputStreamContents) {
+ InputStream inputStream = new ByteArrayInputStream(inputStreamContents.getBytes());
+ StubSocket stubSocket = new StubSocket(inputStream, outputStream);
+ stubSocket._setLocalAddress(DEFAULT_HOST);
+ return stubSocket;
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/session/DefaultSession_RunTest.java b/tags/2.5/src/test/java/org/mockftpserver/core/session/DefaultSession_RunTest.java
new file mode 100644
index 0000000..5c64877
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/session/DefaultSession_RunTest.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.session;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ConnectCommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.socket.StubSocket;
+import org.mockftpserver.stub.command.AbstractStubCommandHandler;
+import org.mockftpserver.test.AbstractTestCase;
+
+import java.io.*;
+import java.util.HashMap;
+import java.util.ListResourceBundle;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+/**
+ * Tests for the DefaultSession class that require the session (thread) to be running/active.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class DefaultSession_RunTest extends AbstractTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultSession_RunTest.class);
+ private static final Command COMMAND = new Command("USER", EMPTY);
+ private static final int REPLY_CODE = 100;
+ private static final String REPLY_TEXT = "sample text description";
+
+ private DefaultSession session;
+ private ByteArrayOutputStream outputStream;
+ private Map commandHandlerMap;
+ private StubSocket stubSocket;
+ private boolean commandHandled = false;
+ private String commandToRegister = COMMAND.getName();
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandlerMap = new HashMap();
+ outputStream = new ByteArrayOutputStream();
+ }
+
+ public void testInvocationOfCommandHandler() throws Exception {
+ AbstractStubCommandHandler commandHandler = new AbstractStubCommandHandler() {
+ public void handleCommand(Command command, Session cmdSession, InvocationRecord invocationRecord) {
+ assertEquals("command", COMMAND, command);
+ assertSame("session", session, cmdSession);
+ assertEquals("InvocationRecord: command", COMMAND, invocationRecord.getCommand());
+ assertEquals("InvocationRecord: clientHost", DEFAULT_HOST, invocationRecord.getClientHost());
+ commandHandled = true;
+ }
+ };
+ runCommandAndVerifyOutput(commandHandler, "");
+ }
+
+ public void testClose() throws Exception {
+ CommandHandler commandHandler = new AbstractStubCommandHandler() {
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ session.close();
+ commandHandled = true;
+ }
+ };
+ runCommandAndVerifyOutput(commandHandler, "");
+ assertFalse("socket should not be closed", stubSocket.isClosed());
+ }
+
+ public void testClose_WithoutCommand() throws Exception {
+ PipedOutputStream pipedOutputStream = new PipedOutputStream();
+ PipedInputStream inputStream = new PipedInputStream(pipedOutputStream);
+ stubSocket = new StubSocket(DEFAULT_HOST, inputStream, outputStream);
+ session = new DefaultSession(stubSocket, commandHandlerMap);
+
+ initializeConnectCommandHandler();
+
+ Thread thread = new Thread(session);
+ thread.start();
+ Thread.sleep(1000L);
+
+ session.close();
+ thread.join();
+ }
+
+ public void testGetClientHost() throws Exception {
+ CommandHandler commandHandler = new AbstractStubCommandHandler() {
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ commandHandled = true;
+ }
+ };
+ runCommandAndVerifyOutput(commandHandler, "");
+ LOG.info("clientHost=" + session.getClientHost());
+ assertEquals("clientHost", DEFAULT_HOST, session.getClientHost());
+ }
+
+ public void testSendReply_NullReplyText() throws Exception {
+ CommandHandler commandHandler = new AbstractStubCommandHandler() {
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ session.sendReply(REPLY_CODE, null);
+ commandHandled = true;
+ }
+ };
+ runCommandAndVerifyOutput(commandHandler, Integer.toString(REPLY_CODE));
+ }
+
+ public void testSendReply_TrimReplyText() throws Exception {
+ CommandHandler commandHandler = new AbstractStubCommandHandler() {
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ session.sendReply(REPLY_CODE, " " + REPLY_TEXT + " ");
+ commandHandled = true;
+ }
+ };
+ runCommandAndVerifyOutput(commandHandler, REPLY_CODE + " " + REPLY_TEXT);
+ }
+
+ public void testSendReply_MultiLineText() throws Exception {
+ final String MULTILINE_REPLY_TEXT = "abc\ndef\nghi\njkl";
+ final String FORMATTED_MULTILINE_REPLY_TEXT = "123-abc\ndef\nghi\n123 jkl";
+
+ CommandHandler commandHandler = new AbstractStubCommandHandler() {
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ session.sendReply(123, MULTILINE_REPLY_TEXT);
+ commandHandled = true;
+ }
+ };
+ runCommandAndVerifyOutput(commandHandler, FORMATTED_MULTILINE_REPLY_TEXT);
+ }
+
+ public void testSendReply_ReplyText() throws Exception {
+ CommandHandler commandHandler = new AbstractStubCommandHandler() {
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ session.sendReply(REPLY_CODE, REPLY_TEXT);
+ commandHandled = true;
+ }
+ };
+ runCommandAndVerifyOutput(commandHandler, REPLY_CODE + " " + REPLY_TEXT);
+ }
+
+ public void testUnrecognizedCommand() throws Exception {
+ // Register a handler for unsupported commands
+ CommandHandler commandHandler = new AbstractStubCommandHandler() {
+ public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
+ session.sendReply(502, "Unsupported");
+ commandHandled = true;
+ }
+ };
+ // Register the UNSUPPORTED command handler instead of the command that will be sent. So when we
+ // send the regular command, it will trigger the handling for unsupported/unrecognized commands.
+ commandToRegister = CommandNames.UNSUPPORTED;
+ runCommandAndVerifyOutput(commandHandler, "502 Unsupported");
+ }
+
+ // -------------------------------------------------------------------------
+ // Internal Helper Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Create and return a DefaultSession and define the specified CommandHandler. Also, save the
+ * StubSocket being used in the stubSocket attribute.
+ *
+ * @param commandHandler - define this CommandHandler within the commandHandlerMap
+ * @return the DefaultSession
+ */
+ private DefaultSession createDefaultSession(CommandHandler commandHandler) {
+ stubSocket = createTestSocket(COMMAND.getName());
+ commandHandlerMap.put(commandToRegister, commandHandler);
+ initializeConnectCommandHandler();
+ return new DefaultSession(stubSocket, commandHandlerMap);
+ }
+
+ private void initializeConnectCommandHandler() {
+ ConnectCommandHandler connectCommandHandler = new ConnectCommandHandler();
+
+ ResourceBundle replyTextBundle = new ListResourceBundle() {
+ protected Object[][] getContents() {
+ return new Object[][]{
+ {"220", "Reply for 220"},
+ };
+ }
+ };
+ connectCommandHandler.setReplyTextBundle(replyTextBundle);
+ commandHandlerMap.put(CommandNames.CONNECT, connectCommandHandler);
+ }
+
+ /**
+ * Create and return a StubSocket that reads from an InputStream with the specified contents and
+ * writes to the predefined outputStrean ByteArrayOutputStream.
+ *
+ * @param inputStreamContents - the contents of the input stream
+ * @return the StubSocket
+ */
+ private StubSocket createTestSocket(String inputStreamContents) {
+ InputStream inputStream = new ByteArrayInputStream(inputStreamContents.getBytes());
+ return new StubSocket(DEFAULT_HOST, inputStream, outputStream);
+ }
+
+ /**
+ * Run the command represented by the CommandHandler and verify that the session output from the
+ * control socket contains the expected output text.
+ *
+ * @param commandHandler - the CommandHandler to invoke
+ * @param expectedOutput - the text expected within the session output
+ * @throws InterruptedException - if the thread sleep is interrupted
+ */
+ private void runCommandAndVerifyOutput(CommandHandler commandHandler, String expectedOutput)
+ throws InterruptedException {
+ session = createDefaultSession(commandHandler);
+
+ Thread thread = new Thread(session);
+ thread.start();
+
+ for (int i = 0; !commandHandled && i < 10; i++) {
+ Thread.sleep(50L);
+ }
+
+ session.close();
+ thread.join();
+
+ assertEquals("commandHandled", true, commandHandled);
+
+ String output = outputStream.toString();
+ LOG.info("output=[" + output.trim() + "]");
+ assertTrue("line ends with \\r\\n",
+ output.charAt(output.length() - 2) == '\r' && output.charAt(output.length() - 1) == '\n');
+ assertTrue("output: expected [" + expectedOutput + "]", output.indexOf(expectedOutput) != -1);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/socket/StubServerSocket.java b/tags/2.5/src/test/java/org/mockftpserver/core/socket/StubServerSocket.java
new file mode 100644
index 0000000..e3a9686
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/socket/StubServerSocket.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.socket;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+
+/**
+ * Test (fake) subclass of ServerSocket that performs no network access and allows setting the
+ * Socket returned by accept(), and the local port for the ServerSocket.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public class StubServerSocket extends ServerSocket {
+ private int localPort;
+ private Socket socket;
+
+ /**
+ * Construct a new instance with the specified local port.
+ * @param localPort - the local port to be returned from getLocalPort()
+ * @throws IOException
+ */
+ public StubServerSocket(int localPort) throws IOException {
+ this(localPort, null);
+ }
+
+ /**
+ * Construct a new instance with specified local port and accept() socket.
+ * @param localPort - the local port to be returned from getLocalPort()
+ * @param socket - the socket to be returned from accept(); if null, then accept() throws SocketTimeoutException.
+ * @throws IOException
+ */
+ public StubServerSocket(int localPort, Socket socket) throws IOException {
+ super(0);
+ this.localPort = localPort;
+ this.socket = socket;
+ }
+
+ /**
+ * Return the predefined local port
+ * @see java.net.ServerSocket#getLocalPort()
+ */
+ public int getLocalPort() {
+ return localPort;
+ }
+
+ /**
+ * If a socket was specified on the constructor, then return that; otherwise, throw a SocketTimeoutException.
+ * @see java.net.ServerSocket#accept()
+ */
+ public Socket accept() throws IOException {
+ if (socket != null) {
+ return socket;
+ }
+ throw new SocketTimeoutException();
+ }
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/socket/StubServerSocketFactory.java b/tags/2.5/src/test/java/org/mockftpserver/core/socket/StubServerSocketFactory.java
new file mode 100644
index 0000000..c58caa4
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/socket/StubServerSocketFactory.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.socket;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+
+/**
+ * Test-only implementation of ServerSocketFactory. It always returns the predefined
+ * ServerSocket instance specified on the constructor.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public class StubServerSocketFactory implements ServerSocketFactory {
+ private StubServerSocket stubServerSocket;
+
+ /**
+ * Construct a new factory instance that always returns the specified
+ * ServerSocket instance.
+ * @param serverSocket - the ServerSocket instance to be returned by this factory
+ */
+ public StubServerSocketFactory(StubServerSocket serverSocket) {
+ this.stubServerSocket = serverSocket;
+ }
+
+ /**
+ * Return the predefined ServerSocket instance.
+ * @see org.mockftpserver.core.socket.ServerSocketFactory#createServerSocket(int)
+ */
+ public ServerSocket createServerSocket(int port) throws IOException {
+ return stubServerSocket;
+ }
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/socket/StubSocket.java b/tags/2.5/src/test/java/org/mockftpserver/core/socket/StubSocket.java
new file mode 100644
index 0000000..3816bf0
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/socket/StubSocket.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.socket;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+
+/**
+ * Test (fake) subclass of Socket that performs no network access and allows setting the
+ * inputStream and OutputStream for the socket.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class StubSocket extends Socket {
+
+ private InetAddress inetAddress;
+ private InetAddress localAddress;
+ private InputStream inputStream;
+ private OutputStream outputStream;
+
+ /**
+ * Construct a new instance using the specified InputStream and OutputStream
+ * @param inputStream - the InputStream to use
+ * @param outputStream - the OutputStream to use
+ */
+ public StubSocket(InputStream inputStream, OutputStream outputStream) {
+ this(null, inputStream, outputStream);
+ }
+
+ /**
+ * Construct a new instance using the specified host, InputStream and OutputStream
+ * @param inetAddress - the InetAddress for this socket
+ * @param inputStream - the InputStream to use
+ * @param outputStream - the OutputStream to use
+ */
+ public StubSocket(InetAddress inetAddress, InputStream inputStream, OutputStream outputStream) {
+ this.inetAddress = inetAddress;
+ this.inputStream = inputStream;
+ this.outputStream = outputStream;
+ }
+
+ /**
+ * Override the superclass implementation. If the local inetAddress is not null,
+ * return that. Otherwise return super.getInetAddress().
+ * @see java.net.Socket#getInetAddress()
+ */
+ public InetAddress getInetAddress() {
+ return (inetAddress != null) ? inetAddress : super.getInetAddress();
+ }
+
+ /**
+ * Override the superclass implementation. If the local localAddress is not
+ * null, return that. Otherwise return super.getLocalAddress();
+ * @see java.net.Socket#getLocalAddress()
+ */
+ public InetAddress getLocalAddress() {
+ return (localAddress != null) ? localAddress : super.getLocalAddress();
+ }
+
+ /**
+ * Override the superclass implementation to provide the predefined InputStream
+ * @see java.net.Socket#getInputStream()
+ */
+ public InputStream getInputStream() throws IOException {
+ return inputStream;
+ }
+
+ /**
+ * Override the superclass implementation to provide the predefined OutputStream
+ * @see java.net.Socket#getOutputStream()
+ */
+ public OutputStream getOutputStream() throws IOException {
+ return outputStream;
+ }
+
+ //-------------------------------------------------------------------------
+ // Test-specific helper methods
+ //-------------------------------------------------------------------------
+
+ public void _setLocalAddress(InetAddress localAddress) {
+ this.localAddress = localAddress;
+ }
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/socket/StubSocketFactory.java b/tags/2.5/src/test/java/org/mockftpserver/core/socket/StubSocketFactory.java
new file mode 100644
index 0000000..724399c
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/socket/StubSocketFactory.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.socket;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+
+/**
+ * Test-only implementation of SocketFactory. It always returns the predefined
+ * StubSocket instance specified on the constructor. It allows direct access to the
+ * requested host address and port number.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public class StubSocketFactory implements SocketFactory {
+ private StubSocket stubSocket;
+ public int requestedDataPort;
+ public InetAddress requestedHost;
+
+ /**
+ * Create a new instance that always returns the specified StubSocket instance.
+ * @param stubSocket - the StubSocket to be returned by this factory
+ */
+ public StubSocketFactory(StubSocket stubSocket) {
+ this.stubSocket = stubSocket;
+ }
+
+ /**
+ * Return the predefined StubSocket instance
+ * @see org.mockftpserver.core.socket.SocketFactory#createSocket(java.net.InetAddress, int)
+ */
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ this.requestedHost = host;
+ this.requestedDataPort = port;
+ return stubSocket;
+ }
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/java/org/mockftpserver/core/util/AssertTest.java b/tags/2.5/src/test/java/org/mockftpserver/core/util/AssertTest.java
new file mode 100644
index 0000000..0cdf72f
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/core/util/AssertTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.core.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.test.AbstractTestCase;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tests for the Assert class
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class AssertTest extends AbstractTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AssertTest.class);
+
+ /**
+ * This interface defines a generic closure (a generic wrapper for a block of code).
+ */
+ private static interface ExceptionClosure {
+ /**
+ * Execute arbitrary logic that can throw any type of Exception
+ *
+ * @throws Exception
+ */
+ public void execute() throws Exception;
+ }
+
+
+ private static final String MESSAGE = "exception message";
+
+ /**
+ * Test the assertNull() method
+ */
+ public void testAssertNull() {
+
+ Assert.isNull(null, MESSAGE);
+
+ try {
+ Assert.isNull("OK", MESSAGE);
+ fail("Expected IllegalArumentException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ assertExceptionMessageContains(expected, MESSAGE);
+ }
+ }
+
+
+ /**
+ * Test the assertNotNull() method
+ */
+ public void testAssertNotNull() {
+
+ Assert.notNull("OK", MESSAGE);
+
+ try {
+ Assert.notNull(null, MESSAGE);
+ fail("Expected IllegalArumentException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ assertExceptionMessageContains(expected, MESSAGE);
+ }
+ }
+
+ /**
+ * Test the assertTrue() method
+ */
+ public void testAssertTrue() throws Exception {
+
+ Assert.isTrue(true, MESSAGE);
+
+ verifyThrowsAssertFailedException(true, new ExceptionClosure() {
+ public void execute() throws Exception {
+ Assert.isTrue(false, MESSAGE);
+ }
+ });
+ }
+
+ /**
+ * Test the assertFalse() method
+ */
+ public void testAssertFalse() throws Exception {
+
+ Assert.isFalse(false, MESSAGE);
+
+ verifyThrowsAssertFailedException(true, new ExceptionClosure() {
+ public void execute() throws Exception {
+ Assert.isFalse(true, MESSAGE);
+ }
+ });
+ }
+
+ /**
+ * Test the assertNotEmpty(Collection,String) method
+ */
+ public void testAssertNotNullOrEmpty_Collection() throws Exception {
+
+ final Collection COLLECTION = Collections.singletonList("item");
+ Assert.notNullOrEmpty(COLLECTION, MESSAGE);
+
+ verifyThrowsAssertFailedException(true, new ExceptionClosure() {
+ public void execute() throws Exception {
+ Assert.notNullOrEmpty((Collection) null, MESSAGE);
+ }
+ });
+
+ verifyThrowsAssertFailedException(true, new ExceptionClosure() {
+ public void execute() throws Exception {
+ Assert.notNullOrEmpty(new ArrayList(), MESSAGE);
+ }
+ });
+ }
+
+ /**
+ * Test the assertNotEmpty(Map,String) method
+ */
+ public void testAssertNotNullOrEmpty_Map() throws Exception {
+
+ final Map MAP = Collections.singletonMap("key", "value");
+ Assert.notNullOrEmpty(MAP, MESSAGE);
+
+ verifyThrowsAssertFailedException(true, new ExceptionClosure() {
+ public void execute() throws Exception {
+ Assert.notNullOrEmpty((Map) null, MESSAGE);
+ }
+ });
+
+ verifyThrowsAssertFailedException(true, new ExceptionClosure() {
+ public void execute() throws Exception {
+ Assert.notNullOrEmpty(new HashMap(), MESSAGE);
+ }
+ });
+ }
+
+ /**
+ * Test the assertNotEmpty(Objecct[],String) method
+ */
+ public void testAssertNotNullOrEmpty_array() throws Exception {
+
+ final Object[] ARRAY = {"1", "2"};
+ Assert.notNullOrEmpty(ARRAY, MESSAGE);
+
+ verifyThrowsAssertFailedException(true, new ExceptionClosure() {
+ public void execute() throws Exception {
+ Assert.notNullOrEmpty((Object[]) null, MESSAGE);
+ }
+ });
+
+ verifyThrowsAssertFailedException(true, new ExceptionClosure() {
+ public void execute() throws Exception {
+ Assert.notNullOrEmpty(new String[]{}, MESSAGE);
+ }
+ });
+ }
+
+ /**
+ * Test the assertNotEmpty(String,String) method
+ */
+ public void testAssertNotNullOrEmpty_String() throws Exception {
+
+ Assert.notNullOrEmpty("OK", MESSAGE);
+
+ verifyThrowsAssertFailedException(true, new ExceptionClosure() {
+ public void execute() throws Exception {
+ Assert.notNullOrEmpty((String) null, MESSAGE);
+ }
+ });
+
+ verifyThrowsAssertFailedException(true, new ExceptionClosure() {
+ public void execute() throws Exception {
+ Assert.notNullOrEmpty("", MESSAGE);
+ }
+ });
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper Methods
+ //-------------------------------------------------------------------------
+
+ private void assertExceptionMessageContains(Throwable exception, String text) {
+ String message = exception.getMessage();
+ assertTrue("Exception message [" + message + "] does not contain [" + text + "]", message.indexOf(text) != -1);
+ }
+
+ /**
+ * Verify that execution of the ExceptionClosure (code block) results in an
+ * AssertFailedException being thrown with the constant MESSAGE as its message.
+ *
+ * @param closure - the ExceptionClosure encapsulating the code to execute
+ */
+ private void verifyThrowsAssertFailedException(boolean checkMessage, ExceptionClosure closure)
+ throws Exception {
+
+ try {
+ closure.execute();
+ fail("Expected IllegalArumentException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ if (checkMessage) {
+ assertExceptionMessageContains(expected, MESSAGE);
+ }
+ }
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/fake/example/RemoteFileTest.java b/tags/2.5/src/test/java/org/mockftpserver/fake/example/RemoteFileTest.java
new file mode 100644
index 0000000..8c7e5ea
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/fake/example/RemoteFileTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.example;
+
+import org.mockftpserver.fake.FakeFtpServer;
+import org.mockftpserver.fake.UserAccount;
+import org.mockftpserver.fake.filesystem.FileEntry;
+import org.mockftpserver.fake.filesystem.FileSystem;
+import org.mockftpserver.fake.filesystem.UnixFakeFileSystem;
+import org.mockftpserver.stub.example.RemoteFile;
+import org.mockftpserver.test.*;
+import org.mockftpserver.test.AbstractTestCase;
+
+import java.io.IOException;
+
+/**
+ * Example test using FakeFtpServer, with programmatic configuration.
+ */
+public class RemoteFileTest extends AbstractTestCase implements IntegrationTest {
+
+ private static final String HOME_DIR = "/";
+ private static final String FILE = "/dir/sample.txt";
+ private static final String CONTENTS = "abcdef 1234567890";
+
+ private RemoteFile remoteFile;
+ private FakeFtpServer fakeFtpServer;
+
+ public void testReadFile() throws Exception {
+ String contents = remoteFile.readFile(FILE);
+ assertEquals("contents", CONTENTS, contents);
+ }
+
+ public void testReadFileThrowsException() {
+ try {
+ remoteFile.readFile("NoSuchFile.txt");
+ fail("Expected IOException");
+ }
+ catch (IOException expected) {
+ // Expected this
+ }
+ }
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ fakeFtpServer = new FakeFtpServer();
+ fakeFtpServer.setServerControlPort(0); // use any free port
+
+ FileSystem fileSystem = new UnixFakeFileSystem();
+ fileSystem.add(new FileEntry(FILE, CONTENTS));
+ fakeFtpServer.setFileSystem(fileSystem);
+
+ UserAccount userAccount = new UserAccount(RemoteFile.USERNAME, RemoteFile.PASSWORD, HOME_DIR);
+ fakeFtpServer.addUserAccount(userAccount);
+
+ fakeFtpServer.start();
+ int port = fakeFtpServer.getServerControlPort();
+
+ remoteFile = new RemoteFile();
+ remoteFile.setServer("localhost");
+ remoteFile.setPort(port);
+ }
+
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ fakeFtpServer.stop();
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/java/org/mockftpserver/fake/example/SimpleUnixFakeFtpServerTest.java b/tags/2.5/src/test/java/org/mockftpserver/fake/example/SimpleUnixFakeFtpServerTest.java
new file mode 100644
index 0000000..5f04957
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/fake/example/SimpleUnixFakeFtpServerTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.example;
+
+import org.mockftpserver.fake.FakeFtpServer;
+import org.mockftpserver.fake.UserAccount;
+import org.mockftpserver.fake.filesystem.DirectoryEntry;
+import org.mockftpserver.fake.filesystem.FileEntry;
+import org.mockftpserver.fake.filesystem.FileSystem;
+import org.mockftpserver.fake.filesystem.UnixFakeFileSystem;
+import org.mockftpserver.test.AbstractTestCase;
+import org.mockftpserver.test.IntegrationTest;
+
+/**
+ * Example code illustrating how to programmatically configure a FakeFtpServer with a (simulated) Unix
+ * filesystem.
+ */
+public class SimpleUnixFakeFtpServerTest extends AbstractTestCase implements IntegrationTest {
+
+ public void testConfigureAndStart() throws Exception {
+ FakeFtpServer fakeFtpServer = new FakeFtpServer();
+ fakeFtpServer.setServerControlPort(9981);
+ fakeFtpServer.addUserAccount(new UserAccount("user", "password", "c:\\data"));
+
+ FileSystem fileSystem = new UnixFakeFileSystem();
+ fileSystem.add(new DirectoryEntry("/data"));
+ fileSystem.add(new FileEntry("/data/file1.txt", "abcdef 1234567890"));
+ fileSystem.add(new FileEntry("/data/run.exe"));
+ fakeFtpServer.setFileSystem(fileSystem);
+
+ fakeFtpServer.start();
+
+ fakeFtpServer.stop();
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/java/org/mockftpserver/fake/example/SimpleWindowsFakeFtpServerTest.java b/tags/2.5/src/test/java/org/mockftpserver/fake/example/SimpleWindowsFakeFtpServerTest.java
new file mode 100644
index 0000000..e1a595f
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/fake/example/SimpleWindowsFakeFtpServerTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.example;
+
+import org.mockftpserver.fake.FakeFtpServer;
+import org.mockftpserver.fake.UserAccount;
+import org.mockftpserver.fake.filesystem.DirectoryEntry;
+import org.mockftpserver.fake.filesystem.FileEntry;
+import org.mockftpserver.fake.filesystem.FileSystem;
+import org.mockftpserver.fake.filesystem.WindowsFakeFileSystem;
+import org.mockftpserver.test.*;
+import org.mockftpserver.test.AbstractTestCase;
+
+/**
+ * Example code illustrating how to programmatically configure a FakeFtpServer with a (simulated) Windows
+ * filesystem.
+ */
+public class SimpleWindowsFakeFtpServerTest extends AbstractTestCase implements IntegrationTest {
+
+ public void testConfigureAndStart() throws Exception {
+ FakeFtpServer fakeFtpServer = new FakeFtpServer();
+ fakeFtpServer.setServerControlPort(9981);
+ fakeFtpServer.addUserAccount(new UserAccount("user", "password", "c:\\data"));
+
+ FileSystem fileSystem = new WindowsFakeFileSystem();
+ fileSystem.add(new DirectoryEntry("c:\\data"));
+ fileSystem.add(new FileEntry("c:\\data\\file1.txt", "abcdef 1234567890"));
+ fileSystem.add(new FileEntry("c:\\data\\run.exe"));
+ fakeFtpServer.setFileSystem(fileSystem);
+
+ fakeFtpServer.start();
+
+ fakeFtpServer.stop();
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/java/org/mockftpserver/fake/example/WindowsFakeFileSystemPermissionsTest.java b/tags/2.5/src/test/java/org/mockftpserver/fake/example/WindowsFakeFileSystemPermissionsTest.java
new file mode 100644
index 0000000..54630d9
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/fake/example/WindowsFakeFileSystemPermissionsTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.example;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.fake.FakeFtpServer;
+import org.mockftpserver.fake.filesystem.DirectoryEntry;
+import org.mockftpserver.fake.filesystem.FileEntry;
+import org.mockftpserver.fake.filesystem.FileSystem;
+import org.mockftpserver.fake.filesystem.Permissions;
+import org.mockftpserver.fake.filesystem.WindowsFakeFileSystem;
+import org.mockftpserver.test.*;
+import org.mockftpserver.test.AbstractTestCase;
+
+/**
+ * Example code illustrating how to programmatically configure a FakeFtpServer with a (simulated) Windows
+ * filesystem, and including file/directory permissions.
+ */
+public class WindowsFakeFileSystemPermissionsTest extends AbstractTestCase implements IntegrationTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(WindowsFakeFileSystemPermissionsTest.class);
+
+ public void testFilesystemWithPermissions() throws Exception {
+
+ final String USER1 = "joe";
+ final String USER2 = "mary";
+ final String GROUP = "dev";
+ final String CONTENTS = "abcdef 1234567890";
+
+ FileSystem fileSystem = new WindowsFakeFileSystem();
+ DirectoryEntry directoryEntry1 = new DirectoryEntry("c:\\");
+ directoryEntry1.setPermissions(new Permissions("rwxrwx---"));
+ directoryEntry1.setOwner(USER1);
+ directoryEntry1.setGroup(GROUP);
+
+ DirectoryEntry directoryEntry2 = new DirectoryEntry("c:\\data");
+ directoryEntry2.setPermissions(Permissions.ALL);
+ directoryEntry2.setOwner(USER1);
+ directoryEntry2.setGroup(GROUP);
+
+ FileEntry fileEntry1 = new FileEntry("c:\\data\\file1.txt", CONTENTS);
+ fileEntry1.setPermissionsFromString("rw-rw-rw-");
+ fileEntry1.setOwner(USER1);
+ fileEntry1.setGroup(GROUP);
+
+ FileEntry fileEntry2 = new FileEntry("c:\\data\\run.exe");
+ fileEntry2.setPermissionsFromString("rwxrwx---");
+ fileEntry2.setOwner(USER2);
+ fileEntry2.setGroup(GROUP);
+
+ fileSystem.add(directoryEntry1);
+ fileSystem.add(directoryEntry2);
+ fileSystem.add(fileEntry1);
+ fileSystem.add(fileEntry2);
+
+ FakeFtpServer fakeFtpServer = new FakeFtpServer();
+ fakeFtpServer.setFileSystem(fileSystem);
+
+ LOG.info(fileSystem.toString());
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/StubFtpServerIntegrationTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/StubFtpServerIntegrationTest.java
new file mode 100644
index 0000000..2949a14
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/StubFtpServerIntegrationTest.java
@@ -0,0 +1,591 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub;
+
+import org.apache.commons.net.ftp.FTP;
+import org.apache.commons.net.ftp.FTPClient;
+import org.apache.commons.net.ftp.FTPFile;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.command.SimpleCompositeCommandHandler;
+import org.mockftpserver.core.command.StaticReplyCommandHandler;
+import org.mockftpserver.stub.command.*;
+import org.mockftpserver.test.*;
+import org.mockftpserver.test.AbstractTestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Tests for StubFtpServer using the Apache Jakarta Commons Net FTP client.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class StubFtpServerIntegrationTest extends AbstractTestCase implements IntegrationTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(StubFtpServerIntegrationTest.class);
+ private static final String SERVER = "localhost";
+ private static final String USERNAME = "user123";
+ private static final String PASSWORD = "password";
+ private static final String FILENAME = "abc.txt";
+ private static final String ASCII_CONTENTS = "abcdef\tghijklmnopqr";
+ private static final byte[] BINARY_CONTENTS = new byte[256];
+
+ private StubFtpServer stubFtpServer;
+ private FTPClient ftpClient;
+ private RetrCommandHandler retrCommandHandler;
+ private StorCommandHandler storCommandHandler;
+
+ //-------------------------------------------------------------------------
+ // Tests
+ //-------------------------------------------------------------------------
+
+ public void testLogin() throws Exception {
+ // Connect
+ LOG.info("Conecting to " + SERVER);
+ ftpClientConnect();
+ verifyReplyCode("connect", 220);
+
+ // Login
+ String userAndPassword = USERNAME + "/" + PASSWORD;
+ LOG.info("Logging in as " + userAndPassword);
+ boolean success = ftpClient.login(USERNAME, PASSWORD);
+ assertTrue("Unable to login with " + userAndPassword, success);
+ verifyReplyCode("login with " + userAndPassword, 230);
+
+ assertTrue("isStarted", stubFtpServer.isStarted());
+ assertFalse("isShutdown", stubFtpServer.isShutdown());
+
+ // Quit
+ LOG.info("Quit");
+ ftpClient.quit();
+ verifyReplyCode("quit", 221);
+ }
+
+ public void testAcct() throws Exception {
+ ftpClientConnect();
+
+ // ACCT
+ int replyCode = ftpClient.acct("123456");
+ assertEquals("acct", 230, replyCode);
+ }
+
+ /**
+ * Test the stop() method when no session has ever been started
+ */
+ public void testStop_NoSessionEverStarted() throws Exception {
+ LOG.info("Testing a stop() when no session has ever been started");
+ }
+
+ public void testHelp() throws Exception {
+ // Modify HELP CommandHandler to return a predefined help message
+ final String HELP = "help message";
+ HelpCommandHandler helpCommandHandler = (HelpCommandHandler) stubFtpServer.getCommandHandler(CommandNames.HELP);
+ helpCommandHandler.setHelpMessage(HELP);
+
+ ftpClientConnect();
+
+ // HELP
+ String help = ftpClient.listHelp();
+ assertTrue("Wrong response", help.indexOf(HELP) != -1);
+ verifyReplyCode("listHelp", 214);
+ }
+
+ /**
+ * Test the LIST and SYST commands.
+ */
+ public void testList() throws Exception {
+ ftpClientConnect();
+
+ // Set directory listing
+ ListCommandHandler listCommandHandler = (ListCommandHandler) stubFtpServer.getCommandHandler(CommandNames.LIST);
+ listCommandHandler.setDirectoryListing("11-09-01 12:30PM 406348 File2350.log\n"
+ + "11-01-01 1:30PM <DIR> archive");
+
+ // LIST
+ FTPFile[] files = ftpClient.listFiles();
+ assertEquals("number of files", 2, files.length);
+ verifyFTPFile(files[0], FTPFile.FILE_TYPE, "File2350.log", 406348L);
+ verifyFTPFile(files[1], FTPFile.DIRECTORY_TYPE, "archive", 0L);
+ verifyReplyCode("list", 226);
+ }
+
+ /**
+ * Test the LIST, PASV and SYST commands, transferring a directory listing in passive mode
+ */
+ public void testList_PassiveMode() throws Exception {
+ ftpClientConnect();
+
+ ftpClient.enterLocalPassiveMode();
+
+ // Set directory listing
+ ListCommandHandler listCommandHandler = (ListCommandHandler) stubFtpServer.getCommandHandler(CommandNames.LIST);
+ listCommandHandler.setDirectoryListing("11-09-01 12:30PM 406348 File2350.log");
+
+ // LIST
+ FTPFile[] files = ftpClient.listFiles();
+ assertEquals("number of files", 1, files.length);
+ verifyReplyCode("list", 226);
+ }
+
+ public void testNlst() throws Exception {
+ ftpClientConnect();
+
+ // Set directory listing
+ NlstCommandHandler nlstCommandHandler = (NlstCommandHandler) stubFtpServer.getCommandHandler(CommandNames.NLST);
+ nlstCommandHandler.setDirectoryListing("File1.txt\nfile2.data");
+
+ // NLST
+ String[] filenames = ftpClient.listNames();
+ assertEquals("number of files", 2, filenames.length);
+ assertEquals(filenames[0], "File1.txt");
+ assertEquals(filenames[1], "file2.data");
+ verifyReplyCode("listNames", 226);
+ }
+
+ /**
+ * Test printing the current working directory (PWD)
+ */
+ public void testPwd() throws Exception {
+ // Modify PWD CommandHandler to return a predefined directory
+ final String DIR = "some/dir";
+ PwdCommandHandler pwdCommandHandler = (PwdCommandHandler) stubFtpServer.getCommandHandler(CommandNames.PWD);
+ pwdCommandHandler.setDirectory(DIR);
+
+ ftpClientConnect();
+
+ // PWD
+ String dir = ftpClient.printWorkingDirectory();
+ assertEquals("Unable to PWD", DIR, dir);
+ verifyReplyCode("printWorkingDirectory", 257);
+ }
+
+ public void testStat() throws Exception {
+ // Modify Stat CommandHandler to return predefined text
+ final String STATUS = "some information 123";
+ StatCommandHandler statCommandHandler = (StatCommandHandler) stubFtpServer.getCommandHandler(CommandNames.STAT);
+ statCommandHandler.setStatus(STATUS);
+
+ ftpClientConnect();
+
+ // STAT
+ String status = ftpClient.getStatus();
+ assertEquals("STAT reply", "211 " + STATUS + ".", status.trim());
+ verifyReplyCode("getStatus", 211);
+ }
+
+ /**
+ * Test getting the status (STAT), when the reply text contains multiple lines
+ */
+ public void testStat_MultilineReplyText() throws Exception {
+ // Modify Stat CommandHandler to return predefined text
+ final String STATUS = "System name: abc.def\nVersion 3.5.7\nNumber of failed logins: 2";
+ final String FORMATTED_REPLY_STATUS = "211-System name: abc.def\r\nVersion 3.5.7\r\n211 Number of failed logins: 2.";
+ StatCommandHandler statCommandHandler = (StatCommandHandler) stubFtpServer.getCommandHandler(CommandNames.STAT);
+ statCommandHandler.setStatus(STATUS);
+
+ ftpClientConnect();
+
+ // STAT
+ String status = ftpClient.getStatus();
+ assertEquals("STAT reply", FORMATTED_REPLY_STATUS, status.trim());
+ verifyReplyCode("getStatus", 211);
+ }
+
+ public void testSyst() throws Exception {
+ ftpClientConnect();
+
+ // SYST
+ assertEquals("getSystemName()", "\"WINDOWS\" system type.", ftpClient.getSystemName());
+ verifyReplyCode("syst", 215);
+ }
+
+ public void testCwd() throws Exception {
+ // Connect
+ LOG.info("Conecting to " + SERVER);
+ ftpClientConnect();
+ verifyReplyCode("connect", 220);
+
+ // CWD
+ boolean success = ftpClient.changeWorkingDirectory("dir1/dir2");
+ assertTrue("Unable to CWD", success);
+ verifyReplyCode("changeWorkingDirectory", 250);
+ }
+
+ /**
+ * Test changing the current working directory (CWD), when it causes a remote error
+ */
+ public void testCwd_Error() throws Exception {
+ // Override CWD CommandHandler to return error reply code
+ final int REPLY_CODE = 500;
+ StaticReplyCommandHandler cwdCommandHandler = new StaticReplyCommandHandler(REPLY_CODE);
+ stubFtpServer.setCommandHandler("CWD", cwdCommandHandler);
+
+ ftpClientConnect();
+
+ // CWD
+ boolean success = ftpClient.changeWorkingDirectory("dir1/dir2");
+ assertFalse("Expected failure", success);
+ verifyReplyCode("changeWorkingDirectory", REPLY_CODE);
+ }
+
+ public void testCdup() throws Exception {
+ ftpClientConnect();
+
+ // CDUP
+ boolean success = ftpClient.changeToParentDirectory();
+ assertTrue("Unable to CDUP", success);
+ verifyReplyCode("changeToParentDirectory", 200);
+ }
+
+ public void testDele() throws Exception {
+ ftpClientConnect();
+
+ // DELE
+ boolean success = ftpClient.deleteFile(FILENAME);
+ assertTrue("Unable to DELE", success);
+ verifyReplyCode("deleteFile", 250);
+ }
+
+ public void testEprt() throws Exception {
+ LOG.info("Skipping...");
+// ftpClientConnect();
+// ftpClient.sendCommand("EPRT", "|2|1080::8:800:200C:417A|5282|");
+// verifyReplyCode("EPRT", 200);
+ }
+
+ public void testEpsv() throws Exception {
+ ftpClientConnect();
+ ftpClient.sendCommand("EPSV");
+ verifyReplyCode("EPSV", 229);
+ }
+
+ public void testFeat_UseStaticReplyCommandHandler() throws IOException {
+ // The FEAT command is not supported out of the box
+ final String FEAT_TEXT = "Extensions supported:\n" +
+ "MLST size*;create;modify*;perm;media-type\n" +
+ "SIZE\n" +
+ "COMPRESSION\n" +
+ "END";
+ StaticReplyCommandHandler featCommandHandler = new StaticReplyCommandHandler(211, FEAT_TEXT);
+ stubFtpServer.setCommandHandler("FEAT", featCommandHandler);
+
+ ftpClientConnect();
+ assertEquals(ftpClient.sendCommand("FEAT"), 211);
+ LOG.info(ftpClient.getReplyString());
+ }
+
+ public void testMkd() throws Exception {
+ ftpClientConnect();
+
+ // MKD
+ boolean success = ftpClient.makeDirectory("dir1/dir2");
+ assertTrue("Unable to CWD", success);
+ verifyReplyCode("makeDirectory", 257);
+ }
+
+ public void testNoop() throws Exception {
+ ftpClientConnect();
+
+ // NOOP
+ boolean success = ftpClient.sendNoOp();
+ assertTrue("Unable to NOOP", success);
+ verifyReplyCode("NOOP", 200);
+ }
+
+ public void testRest() throws Exception {
+ ftpClientConnect();
+
+ // REST
+ int replyCode = ftpClient.rest("marker");
+ assertEquals("Unable to REST", 350, replyCode);
+ }
+
+ public void testRmd() throws Exception {
+ ftpClientConnect();
+
+ // RMD
+ boolean success = ftpClient.removeDirectory("dir1/dir2");
+ assertTrue("Unable to RMD", success);
+ verifyReplyCode("removeDirectory", 250);
+ }
+
+ public void testRename() throws Exception {
+ ftpClientConnect();
+
+ // Rename (RNFR, RNTO)
+ boolean success = ftpClient.rename(FILENAME, "new_" + FILENAME);
+ assertTrue("Unable to RENAME", success);
+ verifyReplyCode("rename", 250);
+ }
+
+ public void testAllo() throws Exception {
+ ftpClientConnect();
+
+ // ALLO
+ assertTrue("ALLO", ftpClient.allocate(1024));
+ assertTrue("ALLO with recordSize", ftpClient.allocate(1024, 64));
+ }
+
+ /**
+ * Test GET and PUT of ASCII files
+ */
+ public void testTransferAsciiFile() throws Exception {
+ retrCommandHandler.setFileContents(ASCII_CONTENTS);
+
+ ftpClientConnect();
+
+ // Get File
+ LOG.info("Get File for remotePath [" + FILENAME + "]");
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ assertTrue(ftpClient.retrieveFile(FILENAME, outputStream));
+ LOG.info("File contents=[" + outputStream.toString());
+ assertEquals("File contents", ASCII_CONTENTS, outputStream.toString());
+
+ // Put File
+ LOG.info("Put File for local path [" + FILENAME + "]");
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(ASCII_CONTENTS.getBytes());
+ assertTrue(ftpClient.storeFile(FILENAME, inputStream));
+ InvocationRecord invocationRecord = storCommandHandler.getInvocation(0);
+ byte[] contents = (byte[]) invocationRecord.getObject(StorCommandHandler.FILE_CONTENTS_KEY);
+ LOG.info("File contents=[" + contents + "]");
+ assertEquals("File contents", ASCII_CONTENTS.getBytes(), contents);
+ }
+
+ /**
+ * Test GET and PUT of binary files
+ */
+ public void testTransferBinaryFiles() throws Exception {
+ retrCommandHandler.setFileContents(BINARY_CONTENTS);
+
+ ftpClientConnect();
+ ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
+
+ // Get File
+ LOG.info("Get File for remotePath [" + FILENAME + "]");
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ assertTrue("GET", ftpClient.retrieveFile(FILENAME, outputStream));
+ LOG.info("GET File length=" + outputStream.size());
+ assertEquals("File contents", BINARY_CONTENTS, outputStream.toByteArray());
+
+ // Put File
+ LOG.info("Put File for local path [" + FILENAME + "]");
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(BINARY_CONTENTS);
+ assertTrue("PUT", ftpClient.storeFile(FILENAME, inputStream));
+ InvocationRecord invocationRecord = storCommandHandler.getInvocation(0);
+ byte[] contents = (byte[]) invocationRecord.getObject(StorCommandHandler.FILE_CONTENTS_KEY);
+ LOG.info("PUT File length=" + contents.length);
+ assertEquals("File contents", BINARY_CONTENTS, contents);
+ }
+
+ public void testStou() throws Exception {
+ StouCommandHandler stouCommandHandler = (StouCommandHandler) stubFtpServer.getCommandHandler(CommandNames.STOU);
+ stouCommandHandler.setFilename(FILENAME);
+
+ ftpClientConnect();
+
+ // Stor a File (STOU)
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(ASCII_CONTENTS.getBytes());
+ assertTrue(ftpClient.storeUniqueFile(FILENAME, inputStream));
+ InvocationRecord invocationRecord = stouCommandHandler.getInvocation(0);
+ byte[] contents = (byte[]) invocationRecord.getObject(StorCommandHandler.FILE_CONTENTS_KEY);
+ LOG.info("File contents=[" + contents + "]");
+ assertEquals("File contents", ASCII_CONTENTS.getBytes(), contents);
+ }
+
+ public void testAppe() throws Exception {
+ AppeCommandHandler appeCommandHandler = (AppeCommandHandler) stubFtpServer.getCommandHandler(CommandNames.APPE);
+
+ ftpClientConnect();
+
+ // Append a File (APPE)
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(ASCII_CONTENTS.getBytes());
+ assertTrue(ftpClient.appendFile(FILENAME, inputStream));
+ InvocationRecord invocationRecord = appeCommandHandler.getInvocation(0);
+ byte[] contents = (byte[]) invocationRecord.getObject(AppeCommandHandler.FILE_CONTENTS_KEY);
+ LOG.info("File contents=[" + contents + "]");
+ assertEquals("File contents", ASCII_CONTENTS.getBytes(), contents);
+ }
+
+ public void testAbor() throws Exception {
+ ftpClientConnect();
+
+ // ABOR
+ assertTrue("ABOR", ftpClient.abort());
+ }
+
+ public void testPasv() throws Exception {
+ ftpClientConnect();
+
+ // PASV
+ ftpClient.enterLocalPassiveMode();
+ // no reply code; the PASV command is sent only when the data connection is opened
+ }
+
+ public void testMode() throws Exception {
+ ftpClientConnect();
+
+ // MODE
+ boolean success = ftpClient.setFileTransferMode(FTP.STREAM_TRANSFER_MODE);
+ assertTrue("Unable to MODE", success);
+ verifyReplyCode("setFileTransferMode", 200);
+ }
+
+ public void testStru() throws Exception {
+ ftpClientConnect();
+
+ // STRU
+ boolean success = ftpClient.setFileStructure(FTP.FILE_STRUCTURE);
+ assertTrue("Unable to STRU", success);
+ verifyReplyCode("setFileStructure", 200);
+ }
+
+ public void testSimpleCompositeCommandHandler() throws Exception {
+ // Replace CWD CommandHandler with a SimpleCompositeCommandHandler
+ CommandHandler commandHandler1 = new StaticReplyCommandHandler(500);
+ CommandHandler commandHandler2 = new CwdCommandHandler();
+ SimpleCompositeCommandHandler simpleCompositeCommandHandler = new SimpleCompositeCommandHandler();
+ simpleCompositeCommandHandler.addCommandHandler(commandHandler1);
+ simpleCompositeCommandHandler.addCommandHandler(commandHandler2);
+ stubFtpServer.setCommandHandler("CWD", simpleCompositeCommandHandler);
+
+ // Connect
+ ftpClientConnect();
+
+ // CWD
+ assertFalse("first", ftpClient.changeWorkingDirectory("dir1/dir2"));
+ assertTrue("first", ftpClient.changeWorkingDirectory("dir1/dir2"));
+ }
+
+ public void testSite() throws Exception {
+ ftpClientConnect();
+
+ // SITE
+ int replyCode = ftpClient.site("parameters,1,2,3");
+ assertEquals("SITE", 200, replyCode);
+ }
+
+ public void testSmnt() throws Exception {
+ ftpClientConnect();
+
+ // SMNT
+ assertTrue("SMNT", ftpClient.structureMount("dir1/dir2"));
+ verifyReplyCode("structureMount", 250);
+ }
+
+ public void testRein() throws Exception {
+ ftpClientConnect();
+
+ // REIN
+ assertEquals("REIN", 220, ftpClient.rein());
+ }
+
+ /**
+ * Test that command names in lowercase or mixed upper/lower case are accepted
+ */
+ public void testCommandNamesInLowerOrMixedCase() throws Exception {
+ ftpClientConnect();
+
+ assertEquals("rein", 220, ftpClient.sendCommand("rein"));
+ assertEquals("rEIn", 220, ftpClient.sendCommand("rEIn"));
+ assertEquals("reiN", 220, ftpClient.sendCommand("reiN"));
+ assertEquals("Rein", 220, ftpClient.sendCommand("Rein"));
+ }
+
+ public void testUnrecognizedCommand() throws Exception {
+ ftpClientConnect();
+
+ assertEquals("Unrecognized:XXXX", 502, ftpClient.sendCommand("XXXX"));
+ }
+
+ // -------------------------------------------------------------------------
+ // Test setup and tear-down
+ // -------------------------------------------------------------------------
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.test.AbstractTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ for (int i = 0; i < BINARY_CONTENTS.length; i++) {
+ BINARY_CONTENTS[i] = (byte) i;
+ }
+
+ stubFtpServer = new StubFtpServer();
+ stubFtpServer.setServerControlPort(PortTestUtil.getFtpServerControlPort());
+ stubFtpServer.start();
+ ftpClient = new FTPClient();
+ retrCommandHandler = (RetrCommandHandler) stubFtpServer.getCommandHandler(CommandNames.RETR);
+ storCommandHandler = (StorCommandHandler) stubFtpServer.getCommandHandler(CommandNames.STOR);
+ }
+
+ /**
+ * Perform cleanup after each test
+ *
+ * @see org.mockftpserver.test.AbstractTestCase#tearDown()
+ */
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ stubFtpServer.stop();
+ }
+
+ // -------------------------------------------------------------------------
+ // Internal Helper Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Connect to the server from the FTPClient
+ */
+ private void ftpClientConnect() throws IOException {
+ ftpClient.connect(SERVER, PortTestUtil.getFtpServerControlPort());
+ }
+
+ /**
+ * Assert that the FtpClient reply code is equal to the expected value
+ *
+ * @param operation - the description of the operation performed; used in the error message
+ * @param expectedReplyCode - the expected FtpClient reply code
+ */
+ private void verifyReplyCode(String operation, int expectedReplyCode) {
+ int replyCode = ftpClient.getReplyCode();
+ LOG.info("Reply: operation=\"" + operation + "\" replyCode=" + replyCode);
+ assertEquals("Unexpected replyCode for " + operation, expectedReplyCode, replyCode);
+ }
+
+ /**
+ * Verify that the FTPFile has the specified properties
+ *
+ * @param ftpFile - the FTPFile to verify
+ * @param type - the expected file type
+ * @param name - the expected file name
+ * @param size - the expected file size (will be zero for a directory)
+ */
+ private void verifyFTPFile(FTPFile ftpFile, int type, String name, long size) {
+ LOG.info(ftpFile.toString());
+ assertEquals("type: " + ftpFile, type, ftpFile.getType());
+ assertEquals("name: " + ftpFile, name, ftpFile.getName());
+ assertEquals("size: " + ftpFile, size, ftpFile.getSize());
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/StubFtpServerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/StubFtpServerTest.java
new file mode 100644
index 0000000..797d806
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/StubFtpServerTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub;
+
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandHandler;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.server.*;
+import org.mockftpserver.core.server.AbstractFtpServerTestCase;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.stub.command.AbstractStubCommandHandler;
+import org.mockftpserver.stub.command.CwdCommandHandler;
+
+import java.util.ResourceBundle;
+
+/**
+ * Unit tests for StubFtpServer. Also see {@link StubFtpServer_StartTest}
+ * and {@link StubFtpServerIntegrationTest}.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class StubFtpServerTest extends AbstractFtpServerTestCase {
+
+ private StubFtpServer stubFtpServer;
+ private AbstractStubCommandHandler commandHandler;
+ private CommandHandler commandHandler_NoReplyTextBundle;
+
+ //-------------------------------------------------------------------------
+ // Extra tests (Standard tests defined in superclass)
+ //-------------------------------------------------------------------------
+
+ /**
+ * Test the setCommandHandler() method, for a CommandHandler that does not implement ResourceBundleAware
+ */
+ public void testSetCommandHandler_NotReplyTextBundleAware() {
+ stubFtpServer.setCommandHandler("ZZZ", commandHandler_NoReplyTextBundle);
+ assertSame("commandHandler", commandHandler_NoReplyTextBundle, stubFtpServer.getCommandHandler("ZZZ"));
+ }
+
+ /**
+ * Test the setCommandHandler() method, for a CommandHandler that implements ReplyTextBundleAware,
+ * and whose replyTextBundle attribute is null.
+ */
+ public void testSetCommandHandler_NullReplyTextBundle() {
+ stubFtpServer.setCommandHandler("ZZZ", commandHandler);
+ assertSame("commandHandler", commandHandler, stubFtpServer.getCommandHandler("ZZZ"));
+ assertSame("replyTextBundle", stubFtpServer.getReplyTextBundle(), commandHandler.getReplyTextBundle());
+ }
+
+ /**
+ * Test setReplyTextBaseName() method
+ */
+ public void testSetReplyTextBaseName() {
+ stubFtpServer.setReplyTextBaseName("SampleReplyText");
+ CwdCommandHandler commandHandler = new CwdCommandHandler();
+
+ // The resource bundle is passed along to new CommandHandlers (if they don't already have one)
+ stubFtpServer.setCommandHandler("CWD", commandHandler);
+ ResourceBundle resourceBundle = commandHandler.getReplyTextBundle();
+ assertEquals("110", "Testing123", resourceBundle.getString("110"));
+ }
+
+ //-------------------------------------------------------------------------
+ // Test setup
+ //-------------------------------------------------------------------------
+
+ /**
+ * @see org.mockftpserver.test.AbstractTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ stubFtpServer = (StubFtpServer) ftpServer;
+
+ // Create a CommandHandler instance that also implements ResourceBundleAware
+ commandHandler = new AbstractStubCommandHandler() {
+ protected void handleCommand(Command command, Session session, InvocationRecord invocationRecord) throws Exception {
+ }
+ };
+
+ // Create a CommandHandler instance that does NOT implement ResourceBundleAware
+ commandHandler_NoReplyTextBundle = new CommandHandler() {
+ public void handleCommand(Command command, Session session) throws Exception {
+ }
+ };
+ }
+
+ //-------------------------------------------------------------------------
+ // Abstract method implementations
+ //-------------------------------------------------------------------------
+
+ protected AbstractFtpServer createFtpServer() {
+ return new StubFtpServer();
+ }
+
+ protected CommandHandler createCommandHandler() {
+ return new AbstractStubCommandHandler() {
+ protected void handleCommand(Command command, Session session, InvocationRecord invocationRecord) throws Exception {
+ }
+ };
+ }
+
+ protected void verifyCommandHandlerInitialized(CommandHandler commandHandler) {
+ AbstractStubCommandHandler stubCommandHandler = (AbstractStubCommandHandler) commandHandler;
+ assertSame("replyTextBundle", stubFtpServer.getReplyTextBundle(), stubCommandHandler.getReplyTextBundle());
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/StubFtpServer_MultipleClientsIntegrationTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/StubFtpServer_MultipleClientsIntegrationTest.java
new file mode 100644
index 0000000..87ad2dd
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/StubFtpServer_MultipleClientsIntegrationTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub;
+
+import org.apache.commons.net.ftp.FTPClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.stub.command.AbstractStubCommandHandler;
+import org.mockftpserver.test.AbstractTestCase;
+import org.mockftpserver.test.IntegrationTest;
+import org.mockftpserver.test.PortTestUtil;
+
+/**
+ * StubFtpServer tests for multiple FTP clients using the Apache Jakarta Commons Net FTP client.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class StubFtpServer_MultipleClientsIntegrationTest extends AbstractTestCase implements
+ IntegrationTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(StubFtpServer_MultipleClientsIntegrationTest.class);
+ private static final String SERVER = "localhost";
+
+ // Custom CommandHandler for PWD so that we can verify unique session-specific responses.
+ // Send back the hashCode for the Session as the reply text.
+ private static class CustomPwdCommandHandler extends AbstractStubCommandHandler {
+ protected void handleCommand(Command command, Session session, InvocationRecord invocationRecord) throws Exception {
+ String replyText = quotes(Integer.toString(session.hashCode()));
+ sendReply(session, 257, null, replyText, null);
+ }
+ }
+
+ private StubFtpServer stubFtpServer;
+ private FTPClient ftpClient1;
+ private FTPClient ftpClient2;
+ private FTPClient ftpClient3;
+
+ /**
+ * Test that multiple simultaneous clients can connect and establish sessions.
+ */
+ public void testMultipleClients() throws Exception {
+
+ // Connect from client 1
+ LOG.info("connect() to ftpClient1");
+ ftpClient1.connect(SERVER, PortTestUtil.getFtpServerControlPort());
+ String sessionId1 = ftpClient1.printWorkingDirectory();
+ LOG.info("PWD(1) reply =[" + sessionId1 + "]");
+
+ // Connect from client 2
+ LOG.info("connect() to ftpClient2");
+ ftpClient2.connect(SERVER, PortTestUtil.getFtpServerControlPort());
+ String sessionId2 = ftpClient2.printWorkingDirectory();
+ LOG.info("PWD(2) reply =[" + sessionId2 + "]");
+
+ // Connect from client 3
+ LOG.info("connect() to ftpClient3");
+ ftpClient3.connect(SERVER, PortTestUtil.getFtpServerControlPort());
+ String sessionId3 = ftpClient3.printWorkingDirectory();
+ LOG.info("PWD(3) reply =[" + sessionId3 + "]");
+
+ // Make sure all session ids are unique
+ assertNotSame("sessionId1 vs sessionId2", sessionId1, sessionId2);
+ assertNotSame("sessionId2 vs sessionId3", sessionId2, sessionId3);
+ assertNotSame("sessionId1 vs sessionId3", sessionId1, sessionId3);
+
+ // Now make sure that the replies from the existing sessions remain consistent
+ assertEquals("reply from session1", sessionId1, ftpClient1.printWorkingDirectory());
+ assertEquals("reply from session2", sessionId2, ftpClient2.printWorkingDirectory());
+ assertEquals("reply from session3", sessionId3, ftpClient3.printWorkingDirectory());
+ }
+
+ // -------------------------------------------------------------------------
+ // Test setup and tear-down
+ // -------------------------------------------------------------------------
+
+ /**
+ * Perform initialization before each test
+ * @see org.mockftpserver.test.AbstractTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ stubFtpServer = new StubFtpServer();
+ stubFtpServer.setServerControlPort(PortTestUtil.getFtpServerControlPort());
+ stubFtpServer.setCommandHandler(CommandNames.PWD, new CustomPwdCommandHandler());
+ stubFtpServer.start();
+
+ ftpClient1 = new FTPClient();
+ ftpClient2 = new FTPClient();
+ ftpClient3 = new FTPClient();
+
+ ftpClient1.setDefaultTimeout(1000);
+ ftpClient2.setDefaultTimeout(1000);
+ ftpClient3.setDefaultTimeout(1000);
+ }
+
+ /**
+ * Perform cleanup after each test
+ * @see org.mockftpserver.test.AbstractTestCase#tearDown()
+ */
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ LOG.info("Cleaning up...");
+ if (ftpClient1.isConnected()) {
+ ftpClient1.disconnect();
+ }
+ if (ftpClient2.isConnected()) {
+ ftpClient2.disconnect();
+ }
+ if (ftpClient3.isConnected()) {
+ ftpClient3.disconnect();
+ }
+
+ stubFtpServer.stop();
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/StubFtpServer_StartTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/StubFtpServer_StartTest.java
new file mode 100644
index 0000000..e67b289
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/StubFtpServer_StartTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub;
+
+import org.mockftpserver.core.server.AbstractFtpServer;
+import org.mockftpserver.core.server.AbstractFtpServer_StartTestCase;
+
+/**
+ * Tests for StubFtpServer that require the StubFtpServer thread to be started.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class StubFtpServer_StartTest extends AbstractFtpServer_StartTestCase {
+
+ //-------------------------------------------------------------------------
+ // Abstract method implementations
+ //-------------------------------------------------------------------------
+
+ protected AbstractFtpServer createFtpServer() {
+ return new StubFtpServer();
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/AborCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/AborCommandHandlerTest.java
new file mode 100644
index 0000000..6aab009
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/AborCommandHandlerTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+
+/**
+ * Tests for the AborCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class AborCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private AborCommandHandler commandHandler;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+ final Command COMMAND = new Command(CommandNames.ABOR, EMPTY);
+
+ session.sendReply(ReplyCodes.ABOR_OK, replyTextFor(ReplyCodes.ABOR_OK));
+ replay(session);
+
+ commandHandler.handleCommand(COMMAND, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyNoDataElements(commandHandler.getInvocation(0));
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new AborCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/AcctCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/AcctCommandHandlerTest.java
new file mode 100644
index 0000000..0960f97
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/AcctCommandHandlerTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+
+/**
+ * Tests for the AcctCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class AcctCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private static final String ACCOUNT1 = "account1";
+ private static final String ACCOUNT2 = "account2";
+
+ private AcctCommandHandler commandHandler;
+ private Command command1;
+ private Command command2;
+
+ /**
+ * Test the handleCommand() method
+ * @throws Exception
+ */
+ public void testHandleCommand() throws Exception {
+
+ session.sendReply(ReplyCodes.ACCT_OK, replyTextFor(ReplyCodes.ACCT_OK));
+ session.sendReply(ReplyCodes.ACCT_OK, replyTextFor(ReplyCodes.ACCT_OK));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyOneDataElement(commandHandler.getInvocation(0), AcctCommandHandler.ACCOUNT_KEY, ACCOUNT1);
+ verifyOneDataElement(commandHandler.getInvocation(1), AcctCommandHandler.ACCOUNT_KEY, ACCOUNT2);
+ }
+
+ /**
+ * Test the handleCommand() method, when no password parameter has been specified
+ */
+ public void testHandleCommand_MissingPasswordParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.ACCT, EMPTY);
+ }
+
+ /**
+ * Perform initialization before each test
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new AcctCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ command1 = new Command(CommandNames.ACCT, array(ACCOUNT1));
+ command2 = new Command(CommandNames.ACCT, array(ACCOUNT2));
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/AlloCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/AlloCommandHandlerTest.java
new file mode 100644
index 0000000..3d555b0
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/AlloCommandHandlerTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.util.AssertFailedException;
+
+/**
+ * Tests for the AlloCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class AlloCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AlloCommandHandlerTest.class);
+ private static final int BYTES1 = 64;
+ private static final int BYTES2 = 555;
+ private static final int RECORD_SIZE = 77;
+
+ private AlloCommandHandler commandHandler;
+ private Command command1;
+ private Command command2;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+
+ session.sendReply(ReplyCodes.ALLO_OK, replyTextFor(ReplyCodes.ALLO_OK));
+ session.sendReply(ReplyCodes.ALLO_OK, replyTextFor(ReplyCodes.ALLO_OK));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyOneDataElement(commandHandler.getInvocation(0), AlloCommandHandler.NUMBER_OF_BYTES_KEY, new Integer(
+ BYTES1));
+ verifyTwoDataElements(commandHandler.getInvocation(1), AlloCommandHandler.NUMBER_OF_BYTES_KEY, new Integer(
+ BYTES2), AlloCommandHandler.RECORD_SIZE_KEY, new Integer(RECORD_SIZE));
+ }
+
+ /**
+ * Test the handleCommand() method, when no numberOfBytes parameter has been specified
+ */
+ public void testHandleCommand_MissingNumberOfBytesParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.ALLO, EMPTY);
+ }
+
+ /**
+ * Test the handleCommand() method, when the recordSize delimiter ("R") parameter is specified,
+ * but is not followed by the recordSize value.
+ */
+ public void testHandleCommand_RecordSizeDelimiterWithoutValue() throws Exception {
+ try {
+ commandHandler.handleCommand(new Command(CommandNames.ALLO, array("123 R ")), session);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the handleCommand() method, when a non-numeric numberOfBytes parameter has been
+ * specified
+ */
+ public void testHandleCommand_InvalidNumberOfBytesParameter() throws Exception {
+ try {
+ commandHandler.handleCommand(new Command(CommandNames.ALLO, array("xx")), session);
+ fail("Expected NumberFormatException");
+ }
+ catch (NumberFormatException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the handleCommand() method, when a non-numeric recordSize parameter has been specified
+ */
+ public void testHandleCommand_InvalidRecordSizeParameter() throws Exception {
+ try {
+ commandHandler.handleCommand(new Command(CommandNames.ALLO, array("123 R xx")), session);
+ fail("Expected NumberFormatException");
+ }
+ catch (NumberFormatException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new AlloCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ command1 = new Command(CommandNames.ALLO, array(Integer.toString(BYTES1)));
+ command2 = new Command(CommandNames.ALLO, array(Integer.toString(BYTES2) + " R " + Integer.toString(RECORD_SIZE)));
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/AppeCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/AppeCommandHandlerTest.java
new file mode 100644
index 0000000..8b288d4
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/AppeCommandHandlerTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+
+/**
+ * Tests for the AppeCommandHandler class
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class AppeCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private AppeCommandHandler commandHandler;
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new AppeCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+ /**
+ * Test the handleCommand() method, as well as the getFileContents() and clearFileContents() methods
+ */
+ public void testHandleCommand() throws Exception {
+ final String DATA = "ABC";
+
+ session.sendReply(ReplyCodes.TRANSFER_DATA_INITIAL_OK, replyTextFor(ReplyCodes.TRANSFER_DATA_INITIAL_OK));
+ session.openDataConnection();
+ session.readData();
+ control(session).setReturnValue(DATA.getBytes());
+ session.closeDataConnection();
+ session.sendReply(ReplyCodes.TRANSFER_DATA_FINAL_OK, replyTextFor(ReplyCodes.TRANSFER_DATA_FINAL_OK));
+ replay(session);
+
+ Command command = new Command(CommandNames.APPE, array(FILENAME1));
+ commandHandler.handleCommand(command, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyTwoDataElements(commandHandler.getInvocation(0), AppeCommandHandler.PATHNAME_KEY, FILENAME1,
+ AppeCommandHandler.FILE_CONTENTS_KEY, DATA.getBytes());
+ }
+
+ /**
+ * Test the handleCommand() method, when no pathname parameter has been specified
+ */
+ public void testHandleCommand_MissingPathnameParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.APPE, EMPTY);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/CdupCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/CdupCommandHandlerTest.java
new file mode 100644
index 0000000..ea449dd
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/CdupCommandHandlerTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ReplyCodes;
+
+/**
+ * Tests for the CdupCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class CdupCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private CdupCommandHandler commandHandler;
+ private Command command1;
+ private Command command2;
+
+ /**
+ * Test the handleCommand(Command,Session) method
+ * @throws Exception
+ */
+ public void testHandleCommand() throws Exception {
+ session.sendReply(ReplyCodes.CDUP_OK, replyTextFor(ReplyCodes.CDUP_OK));
+ session.sendReply(ReplyCodes.CDUP_OK, replyTextFor(ReplyCodes.CDUP_OK));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyNoDataElements(commandHandler.getInvocation(0));
+ verifyNoDataElements(commandHandler.getInvocation(1));
+ }
+
+ /**
+ * Perform initialization before each test
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new CdupCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ command1 = new Command(CommandNames.CDUP, EMPTY);
+ command2 = new Command(CommandNames.CDUP, EMPTY);
+ }
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/CwdCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/CwdCommandHandlerTest.java
new file mode 100644
index 0000000..1ca9707
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/CwdCommandHandlerTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ReplyCodes;
+
+/**
+ * Tests for the CwdCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class CwdCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private CwdCommandHandler commandHandler;
+ private Command command1;
+ private Command command2;
+
+ /**
+ * Test the handleCommand(Command,Session) method
+ * @throws Exception
+ */
+ public void testHandleCommand() throws Exception {
+ session.sendReply(ReplyCodes.CWD_OK, replyTextFor(ReplyCodes.CWD_OK));
+ session.sendReply(ReplyCodes.CWD_OK, replyTextFor(ReplyCodes.CWD_OK));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyOneDataElement(commandHandler.getInvocation(0), CwdCommandHandler.PATHNAME_KEY, DIR1);
+ verifyOneDataElement(commandHandler.getInvocation(1), CwdCommandHandler.PATHNAME_KEY, DIR2);
+ }
+
+ /**
+ * Test the handleCommand() method, when no pathname parameter has been specified
+ */
+ public void testHandleCommand_MissingPathnameParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.CWD, EMPTY);
+ }
+
+ /**
+ * Perform initialization before each test
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new CwdCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ command1 = new Command(CommandNames.CWD, array(DIR1));
+ command2 = new Command(CommandNames.CWD, array(DIR2));
+ }
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/DeleCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/DeleCommandHandlerTest.java
new file mode 100644
index 0000000..f7fcad0
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/DeleCommandHandlerTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ReplyCodes;
+
+/**
+ * Tests for the DeleCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class DeleCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private DeleCommandHandler commandHandler;
+ private Command command1;
+ private Command command2;
+
+ /**
+ * Test the handleCommand(Command,Session) method
+ * @throws Exception
+ */
+ public void testHandleCommand() throws Exception {
+ session.sendReply(ReplyCodes.DELE_OK, replyTextFor(ReplyCodes.DELE_OK));
+ session.sendReply(ReplyCodes.DELE_OK, replyTextFor(ReplyCodes.DELE_OK));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyOneDataElement(commandHandler.getInvocation(0), DeleCommandHandler.PATHNAME_KEY, FILENAME1);
+ verifyOneDataElement(commandHandler.getInvocation(1), DeleCommandHandler.PATHNAME_KEY, FILENAME2);
+ }
+
+ /**
+ * Test the handleCommand() method, when no pathname parameter has been specified
+ */
+ public void testHandleCommand_MissingPathnameParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.DELE, EMPTY);
+ }
+
+ /**
+ * Perform initialization before each test
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new DeleCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ command1 = new Command(CommandNames.DELE, array(FILENAME1));
+ command2 = new Command(CommandNames.DELE, array(FILENAME2));
+ }
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/EprtCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/EprtCommandHandlerTest.java
new file mode 100644
index 0000000..632fa59
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/EprtCommandHandlerTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+
+import java.net.InetAddress;
+
+/**
+ * Tests for the EprtCommandHandler class
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class EprtCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private static final String[] PARAMETERS_INSUFFICIENT = EMPTY;
+ private static final String[] PARAMETERS_IPV4 = {"|1|132.235.1.2|6275|"};
+ private static final InetAddress HOST_IPV4 = inetAddress("132.235.1.2");
+ private static final String[] PARAMETERS_IPV6 = {"|2|1080::8:800:200C:417A|6275|"};
+ private static final InetAddress HOST_IPV6 = inetAddress("1080::8:800:200C:417A");
+ private static final int PORT = 6275;
+
+ private EprtCommandHandler commandHandler;
+
+ public void testHandleCommand_IPv4() throws Exception {
+ final Command COMMAND = new Command(CommandNames.EPRT, PARAMETERS_IPV4);
+
+ session.setClientDataPort(PORT);
+ session.setClientDataHost(HOST_IPV4);
+ session.sendReply(ReplyCodes.EPRT_OK, replyTextFor(ReplyCodes.EPRT_OK));
+ replay(session);
+
+ commandHandler.handleCommand(COMMAND, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyTwoDataElements(commandHandler.getInvocation(0),
+ PortCommandHandler.HOST_KEY, HOST_IPV4,
+ PortCommandHandler.PORT_KEY, new Integer(PORT));
+ }
+
+ public void testHandleCommand_IPv6() throws Exception {
+ final Command COMMAND = new Command(CommandNames.EPRT, PARAMETERS_IPV6);
+
+ session.setClientDataPort(PORT);
+ session.setClientDataHost(HOST_IPV6);
+ session.sendReply(ReplyCodes.EPRT_OK, replyTextFor(ReplyCodes.EPRT_OK));
+ replay(session);
+
+ commandHandler.handleCommand(COMMAND, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyTwoDataElements(commandHandler.getInvocation(0),
+ PortCommandHandler.HOST_KEY, HOST_IPV6,
+ PortCommandHandler.PORT_KEY, new Integer(PORT));
+ }
+
+ public void testHandleCommand_MissingRequiredParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.EPRT, PARAMETERS_INSUFFICIENT);
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new EprtCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/EpsvCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/EpsvCommandHandlerTest.java
new file mode 100644
index 0000000..e995a8f
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/EpsvCommandHandlerTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+
+import java.net.InetAddress;
+
+/**
+ * Tests for the EpsvCommandHandler class
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class EpsvCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private static final InetAddress SERVER = inetAddress("1080::8:800:200C:417A");
+ private static final int PORT = 6275;
+
+ private EpsvCommandHandler commandHandler;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+ session.switchToPassiveMode();
+ control(session).setReturnValue(PORT);
+ session.getServerHost();
+ control(session).setReturnValue(SERVER);
+ session.sendReply(ReplyCodes.EPSV_OK, formattedReplyTextFor(ReplyCodes.EPSV_OK, Integer.toString(PORT)));
+ replay(session);
+
+ final Command COMMAND = new Command(CommandNames.EPSV, EMPTY);
+
+ commandHandler.handleCommand(COMMAND, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyNoDataElements(commandHandler.getInvocation(0));
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new EpsvCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+} \ No newline at end of file
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/FileRetrCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/FileRetrCommandHandlerTest.java
new file mode 100644
index 0000000..2e02f6e
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/FileRetrCommandHandlerTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.easymock.ArgumentsMatcher;
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.util.AssertFailedException;
+
+import java.util.Arrays;
+
+/**
+ * Tests for the FileRetrCommandHandler class
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class FileRetrCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(FileRetrCommandHandlerTest.class);
+ private static final byte BYTE1 = (byte) 7;
+ private static final byte BYTE2 = (byte) 21;
+
+ private FileRetrCommandHandler commandHandler;
+
+ /**
+ * Test the constructor that takes a String, passing in a null
+ */
+ public void testConstructor_String_Null() {
+ try {
+ new FileRetrCommandHandler(null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the setFile(String) method, passing in a null
+ */
+ public void testSetFile_Null() {
+ try {
+ commandHandler.setFile(null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the handleCommand(Command,Session) method. Create a temporary (binary) file, and
+ * make sure its contents are written back
+ *
+ * @throws Exception
+ */
+ public void testHandleCommand() throws Exception {
+
+ final byte[] BUFFER = new byte[FileRetrCommandHandler.BUFFER_SIZE];
+ Arrays.fill(BUFFER, BYTE1);
+
+ session.sendReply(ReplyCodes.TRANSFER_DATA_INITIAL_OK, replyTextFor(ReplyCodes.TRANSFER_DATA_INITIAL_OK));
+ session.openDataConnection();
+
+ ArgumentsMatcher matcher = new ArgumentsMatcher() {
+ int counter = -1; // will increment for each invocation
+
+ public boolean matches(Object[] expected, Object[] actual) {
+ counter++;
+ byte[] buffer = (byte[]) actual[0];
+ int expectedLength = ((Integer) expected[1]).intValue();
+ int actualLength = ((Integer) actual[1]).intValue();
+ LOG.info("invocation #" + counter + " expected=" + expectedLength + " actualLength=" + actualLength);
+ if (counter < 5) {
+ assertEquals("buffer for invocation #" + counter, BUFFER, buffer);
+ } else {
+ // TODO Got two invocations here; only expected one
+ //assertEquals("length for invocation #" + counter, expectedLength, actualLength);
+ assertEquals("buffer[0]", BYTE2, buffer[0]);
+ assertEquals("buffer[1]", BYTE2, buffer[1]);
+ assertEquals("buffer[2]", BYTE2, buffer[2]);
+ }
+ return true;
+ }
+
+ public String toString(Object[] args) {
+ return args[0].getClass().getName() + " " + args[1].toString();
+ }
+ };
+
+ session.sendData(BUFFER, 512);
+ control(session).setMatcher(matcher);
+ session.sendData(BUFFER, 512);
+ session.sendData(BUFFER, 512);
+ session.sendData(BUFFER, 512);
+ session.sendData(BUFFER, 512);
+ session.sendData(BUFFER, 3);
+
+ session.closeDataConnection();
+ session.sendReply(ReplyCodes.TRANSFER_DATA_FINAL_OK, replyTextFor(ReplyCodes.TRANSFER_DATA_FINAL_OK));
+ replay(session);
+
+ commandHandler.setFile("Sample.jpg");
+ Command command = new Command(CommandNames.RETR, array(FILENAME1));
+ commandHandler.handleCommand(command, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyOneDataElement(commandHandler.getInvocation(0), FileRetrCommandHandler.PATHNAME_KEY, FILENAME1);
+ }
+
+ /**
+ * Test the handleCommand() method, when no pathname parameter has been specified
+ */
+ public void testHandleCommand_MissingPathnameParameter() throws Exception {
+ commandHandler.setFile("abc.txt"); // this property must be set
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.RETR, EMPTY);
+ }
+
+ /**
+ * Test the HandleCommand method, when the file property has not been set
+ */
+ public void testHandleCommand_FileNotSet() throws Exception {
+ try {
+ commandHandler.handleCommand(new Command(CommandNames.RETR, EMPTY), session);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new FileRetrCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+// /**
+// * Create a sample binary file; 5 buffers full plus 3 extra bytes
+// */
+// private void createSampleFile() {
+// final String FILE_PATH = "test/org.mockftpserver/command/Sample.jpg";
+// final byte[] BUFFER = new byte[FileRetrCommandHandler.BUFFER_SIZE];
+// Arrays.fill(BUFFER, BYTE1);
+//
+// File file = new File(FILE_PATH);
+// FileOutputStream out = new FileOutputStream(file);
+// for (int i = 0; i < 5; i++) {
+// out.write(BUFFER);
+// }
+// Arrays.fill(BUFFER, BYTE2);
+// out.write(BUFFER, 0, 3);
+// out.close();
+// LOG.info("Created temporary file [" + FILE_PATH + "]: length=" + file.length());
+// }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/HelpCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/HelpCommandHandlerTest.java
new file mode 100644
index 0000000..3605fdb
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/HelpCommandHandlerTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ReplyCodes;
+
+/**
+ * Tests for the HelpCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class HelpCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private HelpCommandHandler commandHandler;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+
+ final String RESPONSE_DATA = "help for ABC...";
+ commandHandler.setHelpMessage(RESPONSE_DATA);
+
+ session.sendReply(ReplyCodes.HELP_OK, formattedReplyTextFor(ReplyCodes.HELP_OK, RESPONSE_DATA));
+ session.sendReply(ReplyCodes.HELP_OK, formattedReplyTextFor(ReplyCodes.HELP_OK, RESPONSE_DATA));
+ replay(session);
+
+ final Command COMMAND1 = new Command(CommandNames.HELP, EMPTY);
+ final Command COMMAND2 = new Command(CommandNames.HELP, array("abc"));
+
+ commandHandler.handleCommand(COMMAND1, session);
+ commandHandler.handleCommand(COMMAND2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyOneDataElement(commandHandler.getInvocation(0), HelpCommandHandler.COMMAND_NAME_KEY, null);
+ verifyOneDataElement(commandHandler.getInvocation(1), HelpCommandHandler.COMMAND_NAME_KEY, "abc");
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new HelpCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/ListCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/ListCommandHandlerTest.java
new file mode 100644
index 0000000..7301798
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/ListCommandHandlerTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.easymock.MockControl;
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+
+/**
+ * Tests for the ListCommandHandler class
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class ListCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private ListCommandHandler commandHandler;
+
+ /**
+ * Test the handleCommand() method
+ *
+ * @throws Exception
+ */
+ public void testHandleCommand() throws Exception {
+ final String DIR_LISTING = " directory listing\nabc.txt\ndef.log\n";
+ final String DIR_LISTING_TRIMMED = DIR_LISTING.trim();
+ ((ListCommandHandler) commandHandler).setDirectoryListing(DIR_LISTING);
+
+ for (int i = 0; i < 2; i++) {
+ session.sendReply(ReplyCodes.TRANSFER_DATA_INITIAL_OK, replyTextFor(ReplyCodes.TRANSFER_DATA_INITIAL_OK));
+ session.openDataConnection();
+ byte[] bytes = DIR_LISTING_TRIMMED.getBytes();
+ session.sendData(bytes, bytes.length);
+ control(session).setMatcher(MockControl.ARRAY_MATCHER);
+ session.closeDataConnection();
+ session.sendReply(ReplyCodes.TRANSFER_DATA_FINAL_OK, replyTextFor(ReplyCodes.TRANSFER_DATA_FINAL_OK));
+ }
+ replay(session);
+
+ Command command1 = new Command(CommandNames.LIST, array(DIR1));
+ Command command2 = new Command(CommandNames.LIST, EMPTY);
+ commandHandler.handleCommand(command1, session);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyOneDataElement(commandHandler.getInvocation(0), ListCommandHandler.PATHNAME_KEY, DIR1);
+ verifyOneDataElement(commandHandler.getInvocation(1), ListCommandHandler.PATHNAME_KEY, null);
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new ListCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/MkdCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/MkdCommandHandlerTest.java
new file mode 100644
index 0000000..acb432c
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/MkdCommandHandlerTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+
+/**
+ * Tests for the MkdCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class MkdCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private MkdCommandHandler commandHandler;
+ private Command command1;
+ private Command command2;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+
+ session.sendReply(ReplyCodes.MKD_OK, formattedReplyTextFor(ReplyCodes.MKD_OK, DIR1));
+ session.sendReply(ReplyCodes.MKD_OK, formattedReplyTextFor(ReplyCodes.MKD_OK, DIR2));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyOneDataElement(commandHandler.getInvocation(0), MkdCommandHandler.PATHNAME_KEY, DIR1);
+ verifyOneDataElement(commandHandler.getInvocation(1), MkdCommandHandler.PATHNAME_KEY, DIR2);
+ }
+
+ /**
+ * Test the handleCommand() method, when no pathname parameter has been specified
+ */
+ public void testHandleCommand_MissingPathnameParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.MKD, EMPTY);
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new MkdCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ command1 = new Command(CommandNames.MKD, array(DIR1));
+ command2 = new Command(CommandNames.MKD, array(DIR2));
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/ModeCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/ModeCommandHandlerTest.java
new file mode 100644
index 0000000..bc38036
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/ModeCommandHandlerTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+
+/**
+ * Tests for the ModeCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class ModeCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private static final String CODE1 = "S";
+ private static final String CODE2 = "B";
+
+ private ModeCommandHandler commandHandler;
+ private Command command1;
+ private Command command2;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+
+ session.sendReply(ReplyCodes.MODE_OK, replyTextFor(ReplyCodes.MODE_OK));
+ session.sendReply(ReplyCodes.MODE_OK, replyTextFor(ReplyCodes.MODE_OK));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyOneDataElement(commandHandler.getInvocation(0), ModeCommandHandler.MODE_KEY, CODE1);
+ verifyOneDataElement(commandHandler.getInvocation(1), ModeCommandHandler.MODE_KEY, CODE2);
+ }
+
+ /**
+ * Test the handleCommand() method, when no pathname parameter has been specified
+ */
+ public void testHandleCommand_MissingPathnameParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.MODE, EMPTY);
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new ModeCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ command1 = new Command(CommandNames.MODE, array(CODE1));
+ command2 = new Command(CommandNames.MODE, array(CODE2));
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/NlstCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/NlstCommandHandlerTest.java
new file mode 100644
index 0000000..3cc6b3c
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/NlstCommandHandlerTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.easymock.MockControl;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ReplyCodes;
+
+/**
+ * Tests for the NlstCommandHandler class
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class NlstCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private NlstCommandHandler commandHandler;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+ final String DIR_LISTING = " directory listing\nabc.txt\ndef.log\n";
+ final String DIR_LISTING_TRIMMED = DIR_LISTING.trim();
+ ((NlstCommandHandler) commandHandler).setDirectoryListing(DIR_LISTING);
+
+ for (int i = 0; i < 2; i++) {
+ session.sendReply(ReplyCodes.TRANSFER_DATA_INITIAL_OK, replyTextFor(ReplyCodes.TRANSFER_DATA_INITIAL_OK));
+ session.openDataConnection();
+ byte[] bytes = DIR_LISTING_TRIMMED.getBytes();
+ session.sendData(bytes, bytes.length);
+ control(session).setMatcher(MockControl.ARRAY_MATCHER);
+ session.closeDataConnection();
+ session.sendReply(ReplyCodes.TRANSFER_DATA_FINAL_OK, replyTextFor(ReplyCodes.TRANSFER_DATA_FINAL_OK));
+ }
+ replay(session);
+
+ Command command1 = new Command(CommandNames.LIST, array(DIR1));
+ Command command2 = new Command(CommandNames.LIST, EMPTY);
+ commandHandler.handleCommand(command1, session);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyOneDataElement(commandHandler.getInvocation(0), NlstCommandHandler.PATHNAME_KEY, DIR1);
+ verifyOneDataElement(commandHandler.getInvocation(1), NlstCommandHandler.PATHNAME_KEY, null);
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new NlstCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/NoopCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/NoopCommandHandlerTest.java
new file mode 100644
index 0000000..65422bf
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/NoopCommandHandlerTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ReplyCodes;
+
+/**
+ * Tests for the NoopCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class NoopCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private NoopCommandHandler commandHandler;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+ final Command COMMAND = new Command(CommandNames.NOOP, EMPTY);
+
+ session.sendReply(ReplyCodes.NOOP_OK, replyTextFor(ReplyCodes.NOOP_OK));
+ replay(session);
+
+ commandHandler.handleCommand(COMMAND, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyNoDataElements(commandHandler.getInvocation(0));
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new NoopCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/PassCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/PassCommandHandlerTest.java
new file mode 100644
index 0000000..750d259
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/PassCommandHandlerTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ReplyCodes;
+
+/**
+ * Tests for the PassCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class PassCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private static final String PASSWORD1 = "password1";
+ private static final String PASSWORD2 = "password2";
+
+ private PassCommandHandler commandHandler;
+ private Command command1;
+ private Command command2;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+
+ session.sendReply(ReplyCodes.PASS_OK, replyTextFor(ReplyCodes.PASS_OK));
+ session.sendReply(ReplyCodes.PASS_OK, replyTextFor(ReplyCodes.PASS_OK));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyOneDataElement(commandHandler.getInvocation(0), PassCommandHandler.PASSWORD_KEY, PASSWORD1);
+ verifyOneDataElement(commandHandler.getInvocation(1), PassCommandHandler.PASSWORD_KEY, PASSWORD2);
+ }
+
+ /**
+ * Test the handleCommand() method, when no password parameter has been specified
+ */
+ public void testHandleCommand_MissingPasswordParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.PASS, EMPTY);
+ }
+
+ /**
+ * Perform initialization before each test
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new PassCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ command1 = new Command(CommandNames.PASS, array(PASSWORD1));
+ command2 = new Command(CommandNames.PASS, array(PASSWORD2));
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/PasvCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/PasvCommandHandlerTest.java
new file mode 100644
index 0000000..82ee289
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/PasvCommandHandlerTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ReplyCodes;
+
+import java.net.InetAddress;
+
+/**
+ * Tests for the PasvCommandHandler class
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class PasvCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(PasvCommandHandlerTest.class);
+ private static final int PORT = (23 << 8) + 77;
+
+ private PasvCommandHandler commandHandler;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+
+ final InetAddress SERVER = inetAddress("192.168.0.2");
+ session.switchToPassiveMode();
+ control(session).setReturnValue(PORT);
+ session.getServerHost();
+ control(session).setReturnValue(SERVER);
+ session.sendReply(ReplyCodes.PASV_OK, formattedReplyTextFor(227, "(192,168,0,2,23,77)"));
+ replay(session);
+
+ final Command COMMAND = new Command(CommandNames.PASV, EMPTY);
+
+ commandHandler.handleCommand(COMMAND, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyNoDataElements(commandHandler.getInvocation(0));
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new PasvCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/PortCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/PortCommandHandlerTest.java
new file mode 100644
index 0000000..6f5f4e0
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/PortCommandHandlerTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ReplyCodes;
+
+import java.net.InetAddress;
+
+/**
+ * Tests for the PortCommandHandler class
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class PortCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private static final String[] PARAMETERS = new String[]{"11", "22", "33", "44", "1", "206"};
+ private static final String[] PARAMETERS_INSUFFICIENT = new String[]{"7", "29", "99", "11", "77"};
+ private static final int PORT = (1 << 8) + 206;
+ private static final InetAddress HOST = inetAddress("11.22.33.44");
+
+ private PortCommandHandler commandHandler;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+ final Command COMMAND = new Command(CommandNames.PORT, PARAMETERS);
+
+ session.setClientDataPort(PORT);
+ session.setClientDataHost(HOST);
+ session.sendReply(ReplyCodes.PORT_OK, replyTextFor(ReplyCodes.PORT_OK));
+ replay(session);
+
+ commandHandler.handleCommand(COMMAND, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyTwoDataElements(commandHandler.getInvocation(0),
+ PortCommandHandler.HOST_KEY, HOST,
+ PortCommandHandler.PORT_KEY, new Integer(PORT));
+ }
+
+ /**
+ * Test the handleCommand() method, when not enough parameters have been specified
+ */
+ public void testHandleCommand_InsufficientParameters() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.PORT, PARAMETERS_INSUFFICIENT);
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new PortCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/PwdCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/PwdCommandHandlerTest.java
new file mode 100644
index 0000000..92d8cd4
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/PwdCommandHandlerTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ReplyCodes;
+
+/**
+ * Tests for the PwdCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class PwdCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private PwdCommandHandler commandHandler;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+
+ final String RESPONSE_DATA = "current dir 1";
+ commandHandler.setDirectory(RESPONSE_DATA);
+
+ session.sendReply(ReplyCodes.PWD_OK, formattedReplyTextFor(ReplyCodes.PWD_OK, "\"" + RESPONSE_DATA + "\""));
+ replay(session);
+
+ final Command COMMAND = new Command(CommandNames.PWD, EMPTY);
+
+ commandHandler.handleCommand(COMMAND, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyNoDataElements(commandHandler.getInvocation(0));
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new PwdCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/QuitCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/QuitCommandHandlerTest.java
new file mode 100644
index 0000000..8392dcc
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/QuitCommandHandlerTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+
+/**
+ * Tests for the QuitCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class QuitCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private QuitCommandHandler commandHandler;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+ final Command COMMAND = new Command(CommandNames.QUIT, EMPTY);
+
+ session.sendReply(ReplyCodes.QUIT_OK, replyTextFor(ReplyCodes.QUIT_OK));
+ session.close();
+ replay(session);
+
+ commandHandler.handleCommand(COMMAND, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyNoDataElements(commandHandler.getInvocation(0));
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new QuitCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/ReinCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/ReinCommandHandlerTest.java
new file mode 100644
index 0000000..9fd6501
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/ReinCommandHandlerTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ReplyCodes;
+
+/**
+ * Tests for the ReinCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class ReinCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private ReinCommandHandler commandHandler;
+ private Command command1;
+ private Command command2;
+
+ /**
+ * Test the handleCommand(Command,Session) method
+ * @throws Exception
+ */
+ public void testHandleCommand() throws Exception {
+ session.sendReply(ReplyCodes.REIN_OK, replyTextFor(ReplyCodes.REIN_OK));
+ session.sendReply(ReplyCodes.REIN_OK, replyTextFor(ReplyCodes.REIN_OK));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyNoDataElements(commandHandler.getInvocation(0));
+ verifyNoDataElements(commandHandler.getInvocation(1));
+ }
+
+ /**
+ * Perform initialization before each test
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new ReinCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ command1 = new Command(CommandNames.REIN, EMPTY);
+ command2 = new Command(CommandNames.REIN, EMPTY);
+ }
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/RestCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/RestCommandHandlerTest.java
new file mode 100644
index 0000000..09d378c
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/RestCommandHandlerTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ReplyCodes;
+
+/**
+ * Tests for the RestCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class RestCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private static final String MARKER1 = "marker1";
+ private static final String MARKER2 = "marker2";
+
+ private RestCommandHandler commandHandler;
+ private Command command1;
+ private Command command2;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+
+ session.sendReply(ReplyCodes.REST_OK, replyTextFor(ReplyCodes.REST_OK));
+ session.sendReply(ReplyCodes.REST_OK, replyTextFor(ReplyCodes.REST_OK));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyOneDataElement(commandHandler.getInvocation(0), RestCommandHandler.MARKER_KEY, MARKER1);
+ verifyOneDataElement(commandHandler.getInvocation(1), RestCommandHandler.MARKER_KEY, MARKER2);
+ }
+
+ /**
+ * Test the handleCommand() method, when no marker parameter has been specified
+ */
+ public void testHandleCommand_MissingPathnameParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.REST, EMPTY);
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new RestCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ command1 = new Command(CommandNames.REST, array(MARKER1));
+ command2 = new Command(CommandNames.REST, array(MARKER2));
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/RetrCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/RetrCommandHandlerTest.java
new file mode 100644
index 0000000..c3e6c2d
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/RetrCommandHandlerTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.easymock.MockControl;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.util.AssertFailedException;
+
+/**
+ * Tests for the RetrCommandHandler class
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class RetrCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RetrCommandHandlerTest.class);
+
+ private RetrCommandHandler commandHandler;
+
+ /**
+ * Test the constructor that takes a String, passing in a null
+ */
+ public void testConstructor_String_Null() {
+ try {
+ new RetrCommandHandler((String) null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the constructor that takes a byte[], passing in a null
+ */
+ public void testConstructor_ByteArray_Null() {
+ try {
+ new RetrCommandHandler((byte[]) null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the setFileContents(String) method, passing in a null
+ */
+ public void testSetFileContents_String_Null() {
+ try {
+ commandHandler.setFileContents((String) null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the setFileContents(byte[]) method, passing in a null
+ */
+ public void testSetFileContents_ByteArray_Null() {
+ try {
+ commandHandler.setFileContents((byte[]) null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the handleCommand() method
+ *
+ * @throws Exception
+ */
+ public void testHandleCommand() throws Exception {
+ final String FILE_CONTENTS = "abc_123 456";
+ commandHandler.setFileContents(FILE_CONTENTS);
+
+ session.sendReply(ReplyCodes.TRANSFER_DATA_INITIAL_OK, replyTextFor(ReplyCodes.TRANSFER_DATA_INITIAL_OK));
+ session.openDataConnection();
+ session.sendData(FILE_CONTENTS.getBytes(), FILE_CONTENTS.length());
+ control(session).setMatcher(MockControl.ARRAY_MATCHER);
+ session.closeDataConnection();
+ session.sendReply(ReplyCodes.TRANSFER_DATA_FINAL_OK, replyTextFor(ReplyCodes.TRANSFER_DATA_FINAL_OK));
+ replay(session);
+
+ Command command = new Command(CommandNames.RETR, array(FILENAME1));
+ commandHandler.handleCommand(command, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyOneDataElement(commandHandler.getInvocation(0), RetrCommandHandler.PATHNAME_KEY, FILENAME1);
+ }
+
+ /**
+ * Test the handleCommand() method, when no pathname parameter has been specified
+ */
+ public void testHandleCommand_MissingPathnameParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.RETR, EMPTY);
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new RetrCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/RmdCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/RmdCommandHandlerTest.java
new file mode 100644
index 0000000..a549514
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/RmdCommandHandlerTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ReplyCodes;
+
+/**
+ * Tests for the RmdCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class RmdCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private RmdCommandHandler commandHandler;
+ private Command command1;
+ private Command command2;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+ session.sendReply(ReplyCodes.RMD_OK, replyTextFor(ReplyCodes.RMD_OK));
+ session.sendReply(ReplyCodes.RMD_OK, replyTextFor(ReplyCodes.RMD_OK));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyOneDataElement(commandHandler.getInvocation(0), RmdCommandHandler.PATHNAME_KEY, DIR1);
+ verifyOneDataElement(commandHandler.getInvocation(1), RmdCommandHandler.PATHNAME_KEY, DIR2);
+ }
+
+ /**
+ * Test the handleCommand() method, when no pathname parameter has been specified
+ */
+ public void testHandleCommand_MissingPathnameParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.RMD, EMPTY);
+ }
+
+ /**
+ * Perform initialization before each test
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new RmdCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ command1 = new Command(CommandNames.RMD, array(DIR1));
+ command2 = new Command(CommandNames.RMD, array(DIR2));
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/RnfrCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/RnfrCommandHandlerTest.java
new file mode 100644
index 0000000..f494dd5
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/RnfrCommandHandlerTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+
+/**
+ * Tests for the RnfrCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class RnfrCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private RnfrCommandHandler commandHandler;
+ private Command command1;
+ private Command command2;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+
+ session.sendReply(ReplyCodes.RNFR_OK, replyTextFor(ReplyCodes.RNFR_OK));
+ session.sendReply(ReplyCodes.RNFR_OK, replyTextFor(ReplyCodes.RNFR_OK));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyOneDataElement(commandHandler.getInvocation(0), RnfrCommandHandler.PATHNAME_KEY, FILENAME1);
+ verifyOneDataElement(commandHandler.getInvocation(1), RnfrCommandHandler.PATHNAME_KEY, FILENAME2);
+ }
+
+ /**
+ * Test the handleCommand() method, when no pathname parameter has been specified
+ */
+ public void testHandleCommand_MissingPathnameParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.RNFR, EMPTY);
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new RnfrCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ command1 = new Command(CommandNames.RNFR, array(FILENAME1));
+ command2 = new Command(CommandNames.RNFR, array(FILENAME2));
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/RntoCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/RntoCommandHandlerTest.java
new file mode 100644
index 0000000..12217d1
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/RntoCommandHandlerTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+
+/**
+ * Tests for the RntoCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class RntoCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private RntoCommandHandler commandHandler;
+ private Command command1;
+ private Command command2;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+
+ session.sendReply(ReplyCodes.RNTO_OK, replyTextFor(ReplyCodes.RNTO_OK));
+ session.sendReply(ReplyCodes.RNTO_OK, replyTextFor(ReplyCodes.RNTO_OK));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyOneDataElement(commandHandler.getInvocation(0), RntoCommandHandler.PATHNAME_KEY, FILENAME1);
+ verifyOneDataElement(commandHandler.getInvocation(1), RntoCommandHandler.PATHNAME_KEY, FILENAME2);
+ }
+
+ /**
+ * Test the handleCommand() method, when no pathname parameter has been specified
+ */
+ public void testHandleCommand_MissingPathnameParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.RNTO, EMPTY);
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new RntoCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ command1 = new Command(CommandNames.RNTO, array(FILENAME1));
+ command2 = new Command(CommandNames.RNTO, array(FILENAME2));
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/SiteCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/SiteCommandHandlerTest.java
new file mode 100644
index 0000000..6351cd5
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/SiteCommandHandlerTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+
+/**
+ * Tests for the SiteCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class SiteCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private static final String PARAMETERS1 = "abc def";
+ private static final String PARAMETERS2 = "abc,23,def";
+
+ private SiteCommandHandler commandHandler;
+ private Command command1;
+ private Command command2;
+
+ /**
+ * Test the handleCommand(Command,Session) method
+ * @throws Exception
+ */
+ public void testHandleCommand() throws Exception {
+ session.sendReply(ReplyCodes.SITE_OK, replyTextFor(ReplyCodes.SITE_OK));
+ session.sendReply(ReplyCodes.SITE_OK, replyTextFor(ReplyCodes.SITE_OK));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyOneDataElement(commandHandler.getInvocation(0), SiteCommandHandler.PARAMETERS_KEY, PARAMETERS1);
+ verifyOneDataElement(commandHandler.getInvocation(1), SiteCommandHandler.PARAMETERS_KEY, PARAMETERS2);
+ }
+
+ /**
+ * Test the handleCommand() method, when no "parameters" parameter has been specified
+ */
+ public void testHandleCommand_MissingParameters() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.SITE, EMPTY);
+ }
+
+ /**
+ * Perform initialization before each test
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new SiteCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ command1 = new Command(CommandNames.SITE, array(PARAMETERS1));
+ command2 = new Command(CommandNames.SITE, array(PARAMETERS2));
+ }
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/SmntCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/SmntCommandHandlerTest.java
new file mode 100644
index 0000000..65cea91
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/SmntCommandHandlerTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+
+/**
+ * Tests for the SmntCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class SmntCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private SmntCommandHandler commandHandler;
+ private Command command1;
+ private Command command2;
+
+ /**
+ * Test the handleCommand(Command,Session) method
+ * @throws Exception
+ */
+ public void testHandleCommand() throws Exception {
+ session.sendReply(ReplyCodes.SMNT_OK, replyTextFor(ReplyCodes.SMNT_OK));
+ session.sendReply(ReplyCodes.SMNT_OK, replyTextFor(ReplyCodes.SMNT_OK));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyOneDataElement(commandHandler.getInvocation(0), SmntCommandHandler.PATHNAME_KEY, DIR1);
+ verifyOneDataElement(commandHandler.getInvocation(1), SmntCommandHandler.PATHNAME_KEY, DIR2);
+ }
+
+ /**
+ * Test the handleCommand() method, when no pathname parameter has been specified
+ */
+ public void testHandleCommand_MissingPathnameParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.SMNT, EMPTY);
+ }
+
+ /**
+ * Perform initialization before each test
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new SmntCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ command1 = new Command(CommandNames.SMNT, array(DIR1));
+ command2 = new Command(CommandNames.SMNT, array(DIR2));
+ }
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/StatCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/StatCommandHandlerTest.java
new file mode 100644
index 0000000..5596685
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/StatCommandHandlerTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+
+/**
+ * Tests for the StatCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class StatCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private static final String RESPONSE_DATA = "status info 123.456";
+ private static final String PATHNAME = "dir/file";
+
+ private StatCommandHandler commandHandler;
+
+ /**
+ * Test the handleCommand() method, when no pathname parameter is specified
+ */
+ public void testHandleCommand_NoPathname() throws Exception {
+
+ session.sendReply(ReplyCodes.STAT_SYSTEM_OK, formattedReplyTextFor(ReplyCodes.STAT_SYSTEM_OK, RESPONSE_DATA));
+ replay(session);
+
+ final Command COMMAND = new Command(CommandNames.STAT, EMPTY);
+ commandHandler.setStatus(RESPONSE_DATA);
+ commandHandler.handleCommand(COMMAND, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyOneDataElement(commandHandler.getInvocation(0), StatCommandHandler.PATHNAME_KEY, null);
+ }
+
+ /**
+ * Test the handleCommand() method, specifying a pathname parameter
+ * @throws Exception
+ */
+ public void testHandleCommand_Pathname() throws Exception {
+
+ session.sendReply(ReplyCodes.STAT_FILE_OK, formattedReplyTextFor(ReplyCodes.STAT_FILE_OK, RESPONSE_DATA));
+ replay(session);
+
+ final Command COMMAND = new Command(CommandNames.STAT, array(PATHNAME));
+
+ commandHandler.setStatus(RESPONSE_DATA);
+ commandHandler.handleCommand(COMMAND, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyOneDataElement(commandHandler.getInvocation(0), StatCommandHandler.PATHNAME_KEY, PATHNAME);
+ }
+
+ /**
+ * Test the handleCommand() method, when the replyCode is explicitly set
+ */
+ public void testHandleCommand_OverrideReplyCode() throws Exception {
+
+ session.sendReply(200, replyTextFor(200));
+ replay(session);
+
+ final Command COMMAND = new Command(CommandNames.STAT, EMPTY);
+ commandHandler.setStatus(RESPONSE_DATA);
+ commandHandler.setReplyCode(200);
+ commandHandler.handleCommand(COMMAND, session);
+
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyOneDataElement(commandHandler.getInvocation(0), StatCommandHandler.PATHNAME_KEY, null);
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new StatCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/StorCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/StorCommandHandlerTest.java
new file mode 100644
index 0000000..8f9c51f
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/StorCommandHandlerTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+
+/**
+ * Tests for the StorCommandHandler class
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class StorCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private StorCommandHandler commandHandler;
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new StorCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+ /**
+ * Test the handleCommand() method, as well as the getFileContents() and clearFileContents() methods
+ */
+ public void testHandleCommand() throws Exception {
+ final String DATA = "ABC";
+
+ session.sendReply(ReplyCodes.TRANSFER_DATA_INITIAL_OK, replyTextFor(ReplyCodes.TRANSFER_DATA_INITIAL_OK));
+ session.openDataConnection();
+ session.readData();
+ control(session).setReturnValue(DATA.getBytes());
+ session.closeDataConnection();
+ session.sendReply(ReplyCodes.TRANSFER_DATA_FINAL_OK, replyTextFor(ReplyCodes.TRANSFER_DATA_FINAL_OK));
+ replay(session);
+
+ Command command = new Command(CommandNames.STOR, array(FILENAME1));
+ commandHandler.handleCommand(command, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyTwoDataElements(commandHandler.getInvocation(0), StorCommandHandler.PATHNAME_KEY, FILENAME1,
+ StorCommandHandler.FILE_CONTENTS_KEY, DATA.getBytes());
+ }
+
+ /**
+ * Test the handleCommand() method, when no pathname parameter has been specified
+ */
+ public void testHandleCommand_MissingPathnameParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.STOR, EMPTY);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/StouCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/StouCommandHandlerTest.java
new file mode 100644
index 0000000..4aeef23
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/StouCommandHandlerTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ReplyCodes;
+
+/**
+ * Tests for the StouCommandHandler class
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public final class StouCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private StouCommandHandler commandHandler;
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new StouCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+ /**
+ * Test the handleCommand() method, as well as the getFileContents() and clearFileContents() methods
+ */
+ public void testHandleCommand() throws Exception {
+ final String DATA = "ABC";
+ final String FILENAME = "abc.txt";
+
+ session.sendReply(ReplyCodes.TRANSFER_DATA_INITIAL_OK, replyTextFor(ReplyCodes.TRANSFER_DATA_INITIAL_OK));
+ session.openDataConnection();
+ session.readData();
+ control(session).setReturnValue(DATA.getBytes());
+ session.closeDataConnection();
+ session.sendReply(ReplyCodes.TRANSFER_DATA_FINAL_OK, formattedReplyTextFor("226.WithFilename", FILENAME));
+ replay(session);
+
+ Command command = new Command(CommandNames.STOU, array(FILENAME1));
+ commandHandler.setFilename(FILENAME);
+ commandHandler.handleCommand(command, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyOneDataElement(commandHandler.getInvocation(0), StouCommandHandler.FILE_CONTENTS_KEY, DATA.getBytes());
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/StruCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/StruCommandHandlerTest.java
new file mode 100644
index 0000000..2c5ba1e
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/StruCommandHandlerTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+
+/**
+ * Tests for the StruCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class StruCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private static final String CODE1 = "F";
+ private static final String CODE2 = "R";
+
+ private StruCommandHandler commandHandler;
+ private Command command1;
+ private Command command2;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+
+ session.sendReply(ReplyCodes.STRU_OK, replyTextFor(ReplyCodes.STRU_OK));
+ session.sendReply(ReplyCodes.STRU_OK, replyTextFor(ReplyCodes.STRU_OK));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyOneDataElement(commandHandler.getInvocation(0), StruCommandHandler.FILE_STRUCTURE_KEY, CODE1);
+ verifyOneDataElement(commandHandler.getInvocation(1), StruCommandHandler.FILE_STRUCTURE_KEY, CODE2);
+ }
+
+ /**
+ * Test the handleCommand() method, when no pathname parameter has been specified
+ */
+ public void testHandleCommand_MissingPathnameParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.STRU, EMPTY);
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new StruCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ command1 = new Command(CommandNames.STRU, array(CODE1));
+ command2 = new Command(CommandNames.STRU, array(CODE2));
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/SystCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/SystCommandHandlerTest.java
new file mode 100644
index 0000000..e7991c0
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/SystCommandHandlerTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.util.AssertFailedException;
+
+/**
+ * Tests for the SystCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class SystCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SystCommandHandlerTest.class);
+
+ private SystCommandHandler commandHandler;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+
+ final String SYSTEM_NAME = "UNIX";
+ commandHandler.setSystemName(SYSTEM_NAME);
+
+ session.sendReply(ReplyCodes.SYST_OK, formattedReplyTextFor(ReplyCodes.SYST_OK, "\"" + SYSTEM_NAME + "\""));
+ replay(session);
+
+ final Command COMMAND = new Command(CommandNames.SYST, EMPTY);
+
+ commandHandler.handleCommand(COMMAND, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 1);
+ verifyNoDataElements(commandHandler.getInvocation(0));
+ }
+
+ /**
+ * Test the SetSystemName method, passing in a null
+ */
+ public void testSetSystemName_Null() {
+ try {
+ commandHandler.setSystemName(null);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Perform initialization before each test
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new SystCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/TypeCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/TypeCommandHandlerTest.java
new file mode 100644
index 0000000..3a1e0f2
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/TypeCommandHandlerTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.ReplyCodes;
+
+/**
+ * Tests for the TypeCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class TypeCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private TypeCommandHandler commandHandler;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+ final Command COMMAND1 = new Command("TYPE", array("A"));
+ final Command COMMAND2 = new Command("TYPE", array("B"));
+ final Command COMMAND3 = new Command("TYPE", array("L", "8"));
+
+ session.sendReply(ReplyCodes.TYPE_OK, replyTextFor(ReplyCodes.TYPE_OK));
+ session.sendReply(ReplyCodes.TYPE_OK, replyTextFor(ReplyCodes.TYPE_OK));
+ session.sendReply(ReplyCodes.TYPE_OK, replyTextFor(ReplyCodes.TYPE_OK));
+ replay(session);
+
+ commandHandler.handleCommand(COMMAND1, session);
+ commandHandler.handleCommand(COMMAND2, session);
+ commandHandler.handleCommand(COMMAND3, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 3);
+ verifyOneDataElement(commandHandler.getInvocation(0), TypeCommandHandler.TYPE_INFO_KEY, new String[] {"A", null});
+ verifyOneDataElement(commandHandler.getInvocation(1), TypeCommandHandler.TYPE_INFO_KEY, new String[] {"B", null});
+ verifyOneDataElement(commandHandler.getInvocation(2), TypeCommandHandler.TYPE_INFO_KEY, new String[] {"L", "8"});
+ }
+
+ /**
+ * Test the handleCommand() method, when no type parameter has been specified
+ */
+ public void testHandleCommand_MissingTypeParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.TYPE, EMPTY);
+ }
+
+ /**
+ * Perform initialization before each test
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new TypeCommandHandler();
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/UserCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/UserCommandHandlerTest.java
new file mode 100644
index 0000000..f63b847
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/UserCommandHandlerTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.mockftpserver.core.command.*;
+import org.mockftpserver.core.command.AbstractCommandHandlerTestCase;
+
+/**
+ * Tests for the UserCommandHandler class
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class UserCommandHandlerTest extends AbstractCommandHandlerTestCase {
+
+ private static final String USERNAME1 = "user1";
+ private static final String USERNAME2 = "user2";
+
+ private UserCommandHandler commandHandler;
+ private Command command1;
+ private Command command2;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+
+ session.sendReply(ReplyCodes.USER_NEED_PASSWORD_OK, replyTextFor(ReplyCodes.USER_NEED_PASSWORD_OK));
+ session.sendReply(ReplyCodes.USER_LOGGED_IN_OK, replyTextFor(ReplyCodes.USER_LOGGED_IN_OK));
+ replay(session);
+
+ commandHandler.handleCommand(command1, session);
+ commandHandler.setPasswordRequired(false);
+ commandHandler.handleCommand(command2, session);
+ verify(session);
+
+ verifyNumberOfInvocations(commandHandler, 2);
+ verifyOneDataElement(commandHandler.getInvocation(0), UserCommandHandler.USERNAME_KEY, USERNAME1);
+ verifyOneDataElement(commandHandler.getInvocation(1), UserCommandHandler.USERNAME_KEY, USERNAME2);
+ }
+
+ /**
+ * Test the handleCommand() method, when no username parameter has been specified
+ */
+ public void testHandleCommand_MissingUsernameParameter() throws Exception {
+ testHandleCommand_InvalidParameters(commandHandler, CommandNames.USER, EMPTY);
+ }
+
+ /**
+ * Test the setPasswordRequired() and isPasswordRequired() methods
+ */
+ public void testSetPasswordRequired() {
+ assertTrue("initial state", commandHandler.isPasswordRequired());
+ commandHandler.setPasswordRequired(false);
+ assertFalse("after set false", commandHandler.isPasswordRequired());
+ }
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.core.command.AbstractCommandHandlerTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ commandHandler = new UserCommandHandler();
+ command1 = new Command(CommandNames.USER, array(USERNAME1));
+ command2 = new Command(CommandNames.USER, array(USERNAME2));
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/command/_AbstractStubDataCommandHandlerTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/command/_AbstractStubDataCommandHandlerTest.java
new file mode 100644
index 0000000..0d2e979
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/command/_AbstractStubDataCommandHandlerTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.command;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.util.AssertFailedException;
+import org.mockftpserver.test.AbstractTestCase;
+
+import java.util.ListResourceBundle;
+import java.util.ResourceBundle;
+
+/**
+ * Tests for AbstractStubDataCommandHandler. The class name is prefixed with an underscore
+ * so that it is not filtered out by Maven's Surefire test plugin.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class _AbstractStubDataCommandHandlerTest extends AbstractTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(_AbstractStubDataCommandHandlerTest.class);
+ private static final Command COMMAND = new Command("command", EMPTY);
+ private static final InvocationRecord INVOCATION_RECORD = new InvocationRecord(COMMAND, DEFAULT_HOST);
+
+ private static final String REPLY_TEXT150 = "reply 150 ... abcdef";
+ private static final String REPLY_TEXT226 = "reply 226 ... abcdef";
+ private static final String REPLY_TEXT222 = "reply 222 ... abcdef";
+ private static final String REPLY_TEXT333 = "reply 333 ... abcdef";
+ private static final String REPLY_TEXT444 = "reply 444 ... abcdef";
+
+ private Session session;
+ private ResourceBundle replyTextBundle;
+ private AbstractStubDataCommandHandler commandHandler;
+
+ /**
+ * Test the handleCommand() method
+ */
+ public void testHandleCommand() throws Exception {
+
+ session.sendReply(150, REPLY_TEXT150);
+ session.openDataConnection();
+ session.sendReply(222, REPLY_TEXT222);
+ session.sendReply(333, REPLY_TEXT333);
+ session.sendReply(444, REPLY_TEXT444);
+ session.closeDataConnection();
+ session.sendReply(226, REPLY_TEXT226);
+ replay(session);
+
+ // Define CommandHandler test subclass
+ commandHandler = new AbstractStubDataCommandHandler() {
+ protected void beforeProcessData(Command c, Session s, InvocationRecord ir) {
+ verifyParameters(c, s, ir);
+ // Send unique reply code so that we can verify proper method invocation and ordering
+ session.sendReply(222, REPLY_TEXT222);
+ }
+
+ protected void processData(Command c, Session s, InvocationRecord ir) {
+ verifyParameters(c, s, ir);
+ // Send unique reply code so that we can verify proper method invocation and ordering
+ session.sendReply(333, REPLY_TEXT333);
+ }
+
+ protected void afterProcessData(Command c, Session s, InvocationRecord ir) {
+ verifyParameters(c, s, ir);
+ // Send unique reply code so that we can verify proper method invocation and ordering
+ session.sendReply(444, REPLY_TEXT444);
+ }
+
+ private void verifyParameters(Command c, Session s, InvocationRecord ir) {
+ assertSame("command", COMMAND, c);
+ assertSame("session", session, s);
+ assertSame("invocationRecord", INVOCATION_RECORD, ir);
+ }
+ };
+
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ commandHandler.handleCommand(COMMAND, session, INVOCATION_RECORD);
+
+ verify(session);
+ }
+
+ /**
+ * Test the handleCommand() method, overriding the initial reply code and text
+ */
+ public void testHandleCommand_OverrideInitialReplyCodeAndText() throws Exception {
+
+ final int OVERRIDE_REPLY_CODE = 333;
+ final String OVERRIDE_REPLY_TEXT = "reply text";
+
+ session.sendReply(OVERRIDE_REPLY_CODE, OVERRIDE_REPLY_TEXT);
+ session.openDataConnection();
+ session.closeDataConnection();
+ session.sendReply(226, REPLY_TEXT226);
+ replay(session);
+
+ commandHandler.setPreliminaryReplyCode(OVERRIDE_REPLY_CODE);
+ commandHandler.setPreliminaryReplyText(OVERRIDE_REPLY_TEXT);
+ commandHandler.setReplyTextBundle(replyTextBundle);
+ commandHandler.handleCommand(COMMAND, session, INVOCATION_RECORD);
+
+ verify(session);
+ }
+
+ /**
+ * Test the setPreliminaryReplyCode() method, passing in an invalid value
+ */
+ public void testSetPreliminaryReplyCode_Invalid() {
+ try {
+ commandHandler.setPreliminaryReplyCode(0);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ /**
+ * Test the setFinalReplyCode() method, passing in an invalid value
+ */
+ public void testSetFinalReplyCode_Invalid() {
+ try {
+ commandHandler.setFinalReplyCode(0);
+ fail("Expected AssertFailedException");
+ }
+ catch (AssertFailedException expected) {
+ LOG.info("Expected: " + expected);
+ }
+ }
+
+ //-------------------------------------------------------------------------
+ // Test setup
+ //-------------------------------------------------------------------------
+
+ /**
+ * Perform initialization before each test
+ *
+ * @see org.mockftpserver.test.AbstractTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ session = (Session) createMock(Session.class);
+ replyTextBundle = new ListResourceBundle() {
+ protected Object[][] getContents() {
+ return new Object[][] {
+ { Integer.toString(150), REPLY_TEXT150 },
+ { Integer.toString(222), REPLY_TEXT222 },
+ { Integer.toString(226), REPLY_TEXT226 },
+ { Integer.toString(333), REPLY_TEXT333 },
+ { Integer.toString(444), REPLY_TEXT444 },
+ };
+ }
+ };
+ commandHandler = new AbstractStubDataCommandHandler() {
+ protected void processData(Command c, Session s, InvocationRecord ir) {
+ }
+ };
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/example/FtpWorkingDirectory.java b/tags/2.5/src/test/java/org/mockftpserver/stub/example/FtpWorkingDirectory.java
new file mode 100644
index 0000000..dab841f
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/example/FtpWorkingDirectory.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.example;
+
+import org.apache.commons.net.ftp.FTPClient;
+
+import java.io.IOException;
+import java.net.SocketException;
+
+/**
+ * Simple FTP client code example.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public class FtpWorkingDirectory {
+
+ private String server;
+ private int port;
+
+ /**
+ * Return the current working directory for the FTP account on the server
+ * @return the current working directory
+ * @throws SocketException
+ * @throws IOException
+ */
+ public String getWorkingDirectory() throws SocketException, IOException {
+ FTPClient ftpClient = new FTPClient();
+ ftpClient.connect(server, port);
+ return ftpClient.printWorkingDirectory();
+ }
+
+ /**
+ * Set the hostname of the FTP server
+ * @param server - the hostname of the FTP server
+ */
+ public void setServer(String server) {
+ this.server = server;
+ }
+
+ /**
+ * Set the port number for the FTP server
+ * @param port - the port number
+ */
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/example/FtpWorkingDirectoryTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/example/FtpWorkingDirectoryTest.java
new file mode 100644
index 0000000..b3a7d47
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/example/FtpWorkingDirectoryTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.example;
+
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.stub.StubFtpServer;
+import org.mockftpserver.stub.command.PwdCommandHandler;
+import org.mockftpserver.test.AbstractTestCase;
+import org.mockftpserver.test.IntegrationTest;
+
+/**
+ * Example test using StubFtpServer, with programmatic configuration.
+ */
+public class FtpWorkingDirectoryTest extends AbstractTestCase implements IntegrationTest {
+
+ private static final int PORT = 9981;
+ private FtpWorkingDirectory ftpWorkingDirectory;
+ private StubFtpServer stubFtpServer;
+
+ /**
+ * Test FtpWorkingDirectory getWorkingDirectory() method
+ */
+ public void testGetWorkingDirectory() throws Exception {
+
+ // Replace the existing (default) CommandHandler; customize returned directory pathname
+ final String DIR = "some/dir";
+ PwdCommandHandler pwdCommandHandler = new PwdCommandHandler();
+ pwdCommandHandler.setDirectory(DIR);
+ stubFtpServer.setCommandHandler(CommandNames.PWD, pwdCommandHandler);
+
+ stubFtpServer.start();
+
+ String workingDir = ftpWorkingDirectory.getWorkingDirectory();
+
+ assertEquals("workingDirectory", DIR, workingDir);
+ }
+
+ /**
+ * @see org.mockftpserver.test.AbstractTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ ftpWorkingDirectory = new FtpWorkingDirectory();
+ ftpWorkingDirectory.setPort(PORT);
+ stubFtpServer = new StubFtpServer();
+ stubFtpServer.setServerControlPort(PORT);
+ }
+
+ /**
+ * @see org.mockftpserver.test.AbstractTestCase#tearDown()
+ */
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ stubFtpServer.stop();
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/example/RemoteFile.java b/tags/2.5/src/test/java/org/mockftpserver/stub/example/RemoteFile.java
new file mode 100644
index 0000000..833cf56
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/example/RemoteFile.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.example;
+
+import org.apache.commons.net.ftp.FTPClient;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Simple FTP client code example.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public class RemoteFile {
+
+ public static final String USERNAME = "user";
+ public static final String PASSWORD = "password";
+
+ private String server;
+ private int port;
+
+ public String readFile(String filename) throws IOException {
+
+ FTPClient ftpClient = new FTPClient();
+ ftpClient.connect(server, port);
+ ftpClient.login(USERNAME, PASSWORD);
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ boolean success = ftpClient.retrieveFile(filename, outputStream);
+ ftpClient.disconnect();
+
+ if (!success) {
+ throw new IOException("Retrieve file failed: " + filename);
+ }
+ return outputStream.toString();
+ }
+
+ /**
+ * Set the hostname of the FTP server
+ *
+ * @param server - the hostname of the FTP server
+ */
+ public void setServer(String server) {
+ this.server = server;
+ }
+
+ /**
+ * Set the port number for the FTP server
+ *
+ * @param port - the port number
+ */
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ // Other methods ...
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/example/RemoteFileTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/example/RemoteFileTest.java
new file mode 100644
index 0000000..e7e5769
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/example/RemoteFileTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.example;
+
+import org.mockftpserver.core.command.CommandNames;
+import org.mockftpserver.core.command.InvocationRecord;
+import org.mockftpserver.stub.StubFtpServer;
+import org.mockftpserver.stub.command.RetrCommandHandler;
+import org.mockftpserver.test.AbstractTestCase;
+import org.mockftpserver.test.IntegrationTest;
+
+import java.io.IOException;
+
+/**
+ * Example test using StubFtpServer, with programmatic configuration.
+ */
+public class RemoteFileTest extends AbstractTestCase implements IntegrationTest {
+
+ private static final int PORT = 9981;
+ private static final String FILENAME = "dir/sample.txt";
+
+ private RemoteFile remoteFile;
+ private StubFtpServer stubFtpServer;
+
+ /**
+ * Test readFile() method
+ */
+ public void testReadFile() throws Exception {
+
+ final String CONTENTS = "abcdef 1234567890";
+
+ // Replace the default RETR CommandHandler; customize returned file contents
+ RetrCommandHandler retrCommandHandler = new RetrCommandHandler();
+ retrCommandHandler.setFileContents(CONTENTS);
+ stubFtpServer.setCommandHandler(CommandNames.RETR, retrCommandHandler);
+
+ stubFtpServer.start();
+
+ String contents = remoteFile.readFile(FILENAME);
+
+ // Verify returned file contents
+ assertEquals("contents", CONTENTS, contents);
+
+ // Verify the submitted filename
+ InvocationRecord invocationRecord = retrCommandHandler.getInvocation(0);
+ String filename = invocationRecord.getString(RetrCommandHandler.PATHNAME_KEY);
+ assertEquals("filename", FILENAME, filename);
+ }
+
+ /**
+ * Test the readFile() method when the FTP transfer fails (returns a non-success reply code)
+ */
+ public void testReadFileThrowsException() {
+
+ // Replace the default RETR CommandHandler; return failure reply code
+ RetrCommandHandler retrCommandHandler = new RetrCommandHandler();
+ retrCommandHandler.setFinalReplyCode(550);
+ stubFtpServer.setCommandHandler(CommandNames.RETR, retrCommandHandler);
+
+ stubFtpServer.start();
+
+ try {
+ remoteFile.readFile(FILENAME);
+ fail("Expected IOException");
+ }
+ catch (IOException expected) {
+ // Expected this
+ }
+ }
+
+ /**
+ * @see org.mockftpserver.test.AbstractTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ remoteFile = new RemoteFile();
+ remoteFile.setServer("localhost");
+ remoteFile.setPort(PORT);
+ stubFtpServer = new StubFtpServer();
+ stubFtpServer.setServerControlPort(PORT);
+ }
+
+ /**
+ * @see org.mockftpserver.test.AbstractTestCase#tearDown()
+ */
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ stubFtpServer.stop();
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/stub/example/SpringConfigurationTest.java b/tags/2.5/src/test/java/org/mockftpserver/stub/example/SpringConfigurationTest.java
new file mode 100644
index 0000000..acb10c1
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/stub/example/SpringConfigurationTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.stub.example;
+
+import org.apache.commons.net.ftp.FTPClient;
+import org.apache.commons.net.ftp.FTPFile;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.mockftpserver.stub.StubFtpServer;
+import org.mockftpserver.test.AbstractTestCase;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * Example test for StubFtpServer, using the Spring Framework ({@link http://www.springframework.org/})
+ * for configuration.
+ */
+public class SpringConfigurationTest extends AbstractTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SpringConfigurationTest.class);
+ private static final String SERVER = "localhost";
+ private static final int PORT = 9981;
+
+ private StubFtpServer stubFtpServer;
+ private FTPClient ftpClient;
+
+ /**
+ * Test starting the StubFtpServer configured within the example Spring configuration file
+ */
+ public void testStubFtpServer() throws Exception {
+ stubFtpServer.start();
+
+ ftpClient.connect(SERVER, PORT);
+
+ // PWD
+ String dir = ftpClient.printWorkingDirectory();
+ assertEquals("PWD", "foo/bar", dir);
+
+ // LIST
+ FTPFile[] files = ftpClient.listFiles();
+ LOG.info("FTPFile[0]=" + files[0]);
+ LOG.info("FTPFile[1]=" + files[1]);
+ assertEquals("number of files from LIST", 2, files.length);
+
+ // DELE
+ assertFalse("DELE", ftpClient.deleteFile("AnyFile.txt"));
+
+ // RETR
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ assertTrue(ftpClient.retrieveFile("SomeFile.txt", outputStream));
+ LOG.info("File contents=[" + outputStream.toString() + "]");
+ }
+
+ /**
+ * @see org.mockftpserver.test.AbstractTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ ApplicationContext context = new ClassPathXmlApplicationContext("stubftpserver-beans.xml");
+ stubFtpServer = (StubFtpServer) context.getBean("stubFtpServer");
+
+ ftpClient = new FTPClient();
+ }
+
+ /**
+ * @see org.mockftpserver.test.AbstractTestCase#tearDown()
+ */
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ stubFtpServer.stop();
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/test/AbstractTestCase.java b/tags/2.5/src/test/java/org/mockftpserver/test/AbstractTestCase.java
new file mode 100644
index 0000000..a3e5110
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/test/AbstractTestCase.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.test;
+
+import junit.framework.TestCase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.easymock.MockControl;
+import org.mockftpserver.core.MockFtpServerException;
+import org.mockftpserver.core.util.Assert;
+import org.mockftpserver.core.util.AssertFailedException;
+
+import java.io.File;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Abstract superclass for all project test classes
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public abstract class AbstractTestCase extends TestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractTestCase.class);
+ protected static final List EMPTY_LIST = Collections.EMPTY_LIST;
+ protected static final String[] EMPTY = new String[0];
+ protected static final InetAddress DEFAULT_HOST = inetAddress(null);
+
+ /**
+ * Constructor
+ */
+ public AbstractTestCase() {
+ super();
+ }
+
+ //-------------------------------------------------------------------------
+ // Manage EasyMock Control objects under the covers, and provide a syntax
+ // somewhat similar to EasyMock 2.2 for createMock, verify and replay.
+ //-------------------------------------------------------------------------
+
+ private Map mocks = new HashMap();
+
+ /**
+ * Create a new mock for the specified interface. Keep track of the associated control object
+ * under the covers to support the associated method.
+ *
+ * @param interfaceToMock - the Class of the interface to be mocked
+ * @return the new mock
+ */
+ protected Object createMock(Class interfaceToMock) {
+ MockControl control = MockControl.createControl(interfaceToMock);
+ Object mock = control.getMock();
+ mocks.put(mock, control);
+ return mock;
+ }
+
+ /**
+ * Put the mock object into replay mode
+ *
+ * @param mock - the mock to set in replay mode
+ * @throws AssertFailedException - if mock is null
+ * @throws AssertFailedException - if mock is not a mock object created using {@link #createMock(Class)}
+ */
+ protected void replay(Object mock) {
+ control(mock).replay();
+ }
+
+ /**
+ * Put all mocks created with createMock() into replay mode.
+ */
+ protected void replayAll() {
+ for (Iterator iter = mocks.keySet().iterator(); iter.hasNext();) {
+ Object mock = iter.next();
+ replay(mock);
+ }
+ }
+
+ /**
+ * Verify the mock object
+ *
+ * @param mock - the mock to verify
+ * @throws AssertFailedException - if mock is null
+ * @throws AssertFailedException - if mock is not a mock object created using {@link #createMock(Class)}
+ */
+ protected void verify(Object mock) {
+ control(mock).verify();
+ }
+
+ /**
+ * Verify all mocks created with createMock() into replay mode.
+ */
+ protected void verifyAll() {
+ for (Iterator iter = mocks.keySet().iterator(); iter.hasNext();) {
+ Object mock = iter.next();
+ verify(mock);
+ }
+ }
+
+ /**
+ * Return the mock control associated with the mock
+ *
+ * @param mock - the mock
+ * @return the associated MockControl
+ * @throws AssertFailedException - if mock is null
+ * @throws AssertFailedException - if mock is not a mock object created using {@link #createMock(Class)}
+ */
+ protected MockControl control(Object mock) {
+ Assert.notNull(mock, "mock");
+ MockControl control = (MockControl) mocks.get(mock);
+ Assert.notNull(control, "control");
+ return control;
+ }
+
+ //-------------------------------------------------------------------------
+ // Other Helper Methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * Assert that the two objects are not equal
+ *
+ * @param object1 - the first object
+ * @param object2 - the second object
+ */
+ protected void assertNotEquals(String message, Object object1, Object object2) {
+ assertFalse(message, object1.equals(object2));
+ }
+
+ /**
+ * Assert that the two byte arrays have the same length and content
+ *
+ * @param array1 - the first array
+ * @param array2 - the second array
+ */
+ protected void assertEquals(String message, byte[] array1, byte[] array2) {
+ assertTrue("Arrays not equal: " + message, Arrays.equals(array1, array2));
+ }
+
+ /**
+ * Assert that the two Object arrays have the same length and content
+ *
+ * @param array1 - the first array
+ * @param array2 - the second array
+ */
+ protected void assertEquals(String message, Object[] array1, Object[] array2) {
+ assertTrue("Arrays not equal: " + message, Arrays.equals(array1, array2));
+ }
+
+ /**
+ * Create and return a one-element Object[] containing the specified Object
+ *
+ * @param o - the object
+ * @return the Object array, of length 1, containing o
+ */
+ protected static Object[] objArray(Object o) {
+ return new Object[]{o};
+ }
+
+ /**
+ * Create and return a one-element String[] containing the specified String
+ *
+ * @param s - the String
+ * @return the String array, of length 1, containing s
+ */
+ protected static String[] array(String s) {
+ return new String[]{s};
+ }
+
+ /**
+ * Create and return a two-element String[] containing the specified Strings
+ *
+ * @param s1 - the first String
+ * @param s2 - the second String
+ * @return the String array, of length 2, containing s1 and s2
+ */
+ protected static String[] array(String s1, String s2) {
+ return new String[]{s1, s2};
+ }
+
+ /**
+ * Create a new InetAddress from the specified host String, using the
+ * {@link InetAddress#getByName(String)} method, wrapping any checked
+ * exception within a unchecked MockFtpServerException.
+ *
+ * @param host
+ * @return an InetAddress for the specified host
+ * @throws MockFtpServerException - if an UnknownHostException is thrown
+ */
+ protected static InetAddress inetAddress(String host) {
+ try {
+ return InetAddress.getByName(host);
+ }
+ catch (UnknownHostException e) {
+ throw new MockFtpServerException(e);
+ }
+ }
+
+ /**
+ * Create and return a List containing the Objects passed as arguments to this method
+ *
+ * @param e1- the first element to add
+ * @param e2- the second element to add
+ * @return the List containing the specified elements
+ */
+ protected static List list(Object e1, Object e2) {
+ List list = new ArrayList();
+ list.add(e1);
+ list.add(e2);
+ return list;
+ }
+
+ /**
+ * Create and return a List containing the single Object passed as an argument to this method
+ *
+ * @param element- the element to add
+ * @return the List containing the specified element
+ */
+ protected static List list(Object element) {
+ return Collections.singletonList(element);
+ }
+
+ /**
+ * Create and return a Set containing the Objects passed as arguments to this method
+ *
+ * @param e1 - the first element to add
+ * @param e2 - the second element to add
+ * @return the Set containing the specified elements
+ */
+ protected static Set set(Object e1, Object e2) {
+ Set set = new HashSet();
+ set.add(e1);
+ set.add(e2);
+ return set;
+ }
+
+ /**
+ * Create and return a Set containing the Objects passed as arguments to this method
+ *
+ * @param e1 - the first element to add
+ * @param e2 - the second element to add
+ * @param e3 - the third element to add
+ * @return the Set containing the specified elements
+ */
+ protected static Set set(Object e1, Object e2, Object e3) {
+ Set set = set(e1, e2);
+ set.add(e3);
+ return set;
+ }
+
+ /**
+ * Override the default test run behavior to write out the current test name
+ * and handle Errors and Exceptions in a standard way.
+ *
+ * @see junit.framework.TestCase#runBare()
+ */
+ public void runBare() throws Throwable {
+
+ LoggingUtil loggingUtil = null;
+ try {
+ loggingUtil = LoggingUtil.getTestCaseLogger(this);
+ loggingUtil.logStartOfTest();
+ super.runBare();
+ }
+ catch (Exception e) {
+ handleException(e);
+ }
+ catch (Error e) {
+ handleError(e);
+ }
+ finally {
+ if (loggingUtil != null) {
+ loggingUtil.logEndOfTest();
+ }
+ }
+ }
+
+ /**
+ * Setup before each test.
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ /**
+ * Cleanup after each test.
+ */
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ //-----------------------------------------------------------
+ // Private Internal Methods
+ //-----------------------------------------------------------
+
+ /**
+ * Handle an exception
+ *
+ * @param e the Exception
+ * @throws Exception
+ */
+ private void handleException(Exception e) throws Exception {
+
+ LOG.error("EXCEPTION: ", e);
+ throw e;
+ }
+
+ /**
+ * Handle an Error
+ *
+ * @param e the Error
+ * @throws Exception
+ */
+ private void handleError(Error e) throws Exception {
+ LOG.error("ERROR: ", e);
+ throw e;
+ }
+
+ //-------------------------------------------------------------------------
+ // Helper methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * Delete the named file if it exists
+ *
+ * @param filename - the full pathname of the file
+ */
+ protected void deleteFile(String filename) {
+ File keyFile = new File(filename);
+ boolean deleted = keyFile.delete();
+ LOG.info("Deleted [" + filename + "]: " + deleted);
+ }
+
+ //-------------------------------------------------------------------------
+ // Common validation helper methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * Verify that the named file exists
+ *
+ * @param filename - the full pathname of the file
+ */
+ protected void verifyFileExists(String filename) {
+ File keyFile = new File(filename);
+ assertTrue("File does not exist [" + filename + "]", keyFile.exists());
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/test/IntegrationTest.java b/tags/2.5/src/test/java/org/mockftpserver/test/IntegrationTest.java
new file mode 100644
index 0000000..0f6e3ae
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/test/IntegrationTest.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.test;
+
+/**
+ * Marker interface for integration test
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public interface IntegrationTest {
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/test/LoggingUtil.java b/tags/2.5/src/test/java/org/mockftpserver/test/LoggingUtil.java
new file mode 100644
index 0000000..d7347ca
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/test/LoggingUtil.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.test;
+
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Provides facilities to log the start and end of a test run.
+ *
+ * May want to refactor this and create two subclasses: TestCaseLogger
+ * and TestSuiteLogger.
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class LoggingUtil {
+
+ private static final String TEST_CASE_SEPARATOR = "---------------";
+ private static final String TEST_SUITE_SEPARATOR = "####################";
+
+ private String testTitle;
+ private String separator;
+ private long startTime;
+
+
+ //-------------------------------------------------------------------------
+ // General-purpose API to log messages
+ //-------------------------------------------------------------------------
+
+ /**
+ * Log the specified message from the caller object.
+ * @param caller the calling object
+ * @param message the message to log
+ */
+ public static void log(Object caller, Object message) {
+
+ String classNameNoPackage = getClassName(caller);
+ String messageStr = (message==null) ? "null" : message.toString();
+ String formattedMessage = "[" + classNameNoPackage + "] " + messageStr;
+ writeLogMessage(formattedMessage);
+ }
+
+
+ //-------------------------------------------------------------------------
+ // Factory Methods to get instance for TestCase or TestSuite
+ //-------------------------------------------------------------------------
+
+ /**
+ * Return a LoggingUtil instance suitable for logging TestCase start and end
+ * @param testCase the TestCase
+ * @return a LoggingUtil
+ */
+ public static LoggingUtil getTestCaseLogger(TestCase testCase) {
+
+ String title = getClassName(testCase) + "." + testCase.getName();
+ return new LoggingUtil(title, TEST_CASE_SEPARATOR);
+ }
+
+
+ /**
+ * Return a LoggingUtil instance suitable for logging TestSuite start and end
+ * @param testSuite the TestSuite
+ * @return a LoggingUtil
+ */
+ public static LoggingUtil getTestSuiteLogger(TestSuite testCase) {
+
+ String title = "SUITE " + getClassName(testCase);
+ return new LoggingUtil(title, TEST_SUITE_SEPARATOR);
+ }
+
+
+ /**
+ * Constructor. Private to force access through the factory method(s)
+ */
+ private LoggingUtil(String title, String separator) {
+ this.startTime = System.currentTimeMillis();
+ this.testTitle = title;
+ this.separator = separator;
+ }
+
+
+ /**
+ * Write out the the name of the test class and test name to the log
+ */
+ public void logStartOfTest() {
+
+ writeLogMessage(separator + " [ START: " + testTitle + " ] " + separator);
+ }
+
+
+ /**
+ * Write out the the name of the test class and test name to the log
+ */
+ public void logEndOfTest() {
+
+ long elapsedTime = System.currentTimeMillis() - startTime;
+ writeLogMessage(separator + " [ END: "
+ + testTitle
+ + " Time=" + elapsedTime
+ + "ms ] "+ separator + "\n");
+ }
+
+
+ /**
+ * Return the name of the class for the specified object, stripping off the package name
+ * @return the name of the class, stripping off the package name
+ */
+ private static String getClassName(Object object) {
+
+ // If it's already a class, then use as is
+ Class theClass = (object instanceof Class) ? ((Class)object) : object.getClass();
+ String className = theClass.getName();
+
+ int index = className.lastIndexOf(".");
+ if (index != -1) {
+ className = className.substring(index+1);
+ }
+ return className;
+ }
+
+
+ /**
+ * Write the specified message out to the log
+ * @param message the message to write
+ */
+ private static void writeLogMessage(String message) {
+ // Don't want to use Trace -- it requires initialization of the system configuration
+ //Trace.trace(message);
+ System.out.println(message);
+ }
+
+}
diff --git a/tags/2.5/src/test/java/org/mockftpserver/test/PortTestUtil.java b/tags/2.5/src/test/java/org/mockftpserver/test/PortTestUtil.java
new file mode 100644
index 0000000..72017bf
--- /dev/null
+++ b/tags/2.5/src/test/java/org/mockftpserver/test/PortTestUtil.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.mockftpserver.test;
+
+/**
+ * Contains static test utility method to determine FTP server port number to use for tests
+ *
+ * @version $Revision$ - $Date$
+ *
+ * @author Chris Mair
+ */
+public final class PortTestUtil {
+
+ private static final int DEFAULT_SERVER_CONTROL_PORT = 21;
+ private static final String FTP_SERVER_PORT_PROPERTY = "ftp.server.port";
+
+ /**
+ * Return the port number to use for the FTP server control port. If the "ftp.server.port"
+ * system property is defined, then use that value (converted to an integer), otherwise
+ * return the default port number of 21.
+ *
+ * @return the port number to use for the FTP server control port
+ */
+ public static int getFtpServerControlPort() {
+ String systemProperty = System.getProperty(FTP_SERVER_PORT_PROPERTY);
+ return (systemProperty == null) ? DEFAULT_SERVER_CONTROL_PORT : Integer.parseInt(systemProperty);
+ }
+
+}
diff --git a/tags/2.5/src/test/resources/Sample.jpg b/tags/2.5/src/test/resources/Sample.jpg
new file mode 100644
index 0000000..628a3cd
--- /dev/null
+++ b/tags/2.5/src/test/resources/Sample.jpg
@@ -0,0 +1 @@
+ \ No newline at end of file
diff --git a/tags/2.5/src/test/resources/SampleReplyText.properties b/tags/2.5/src/test/resources/SampleReplyText.properties
new file mode 100644
index 0000000..7a19a33
--- /dev/null
+++ b/tags/2.5/src/test/resources/SampleReplyText.properties
@@ -0,0 +1,17 @@
+# Copyright 2007 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Test-specific mapping of reply code -> reply text
+# Tests are dependent on one or more values within this file
+110=Testing123
diff --git a/tags/2.5/src/test/resources/fakeftpserver-beans.xml b/tags/2.5/src/test/resources/fakeftpserver-beans.xml
new file mode 100644
index 0000000..e5d8793
--- /dev/null
+++ b/tags/2.5/src/test/resources/fakeftpserver-beans.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2008 the original author or authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Spring Framework configuration for FakeFtpServer -->
+<!-- The FakeFtpServerSpringCofigurationTest class has dependencies on
+ several of the bean names and values configured within this file -->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
+
+ <bean id="fakeFtpServer" class="org.mockftpserver.fake.FakeFtpServer">
+ <property name="serverControlPort" value="9981"/>
+ <property name="systemName" value="UNIX"/>
+ <property name="userAccounts">
+ <list>
+ <bean class="org.mockftpserver.fake.UserAccount">
+ <property name="username" value="joe"/>
+ <property name="password" value="password"/>
+ <property name="homeDirectory" value="/"/>
+ </bean>
+ </list>
+ </property>
+
+ <property name="fileSystem">
+ <bean class="org.mockftpserver.fake.filesystem.UnixFakeFileSystem">
+ <property name="createParentDirectoriesAutomatically" value="false"/>
+ <property name="entries">
+ <list>
+ <bean class="org.mockftpserver.fake.filesystem.DirectoryEntry">
+ <property name="path" value="/"/>
+ </bean>
+ <bean class="org.mockftpserver.fake.filesystem.FileEntry">
+ <property name="path" value="/File.txt"/>
+ <property name="contents" value="abcdefghijklmnopqrstuvwxyz"/>
+ </bean>
+ </list>
+ </property>
+ </bean>
+ </property>
+
+ </bean>
+
+</beans> \ No newline at end of file
diff --git a/tags/2.5/src/test/resources/fakeftpserver-permissions-beans.xml b/tags/2.5/src/test/resources/fakeftpserver-permissions-beans.xml
new file mode 100644
index 0000000..a506a95
--- /dev/null
+++ b/tags/2.5/src/test/resources/fakeftpserver-permissions-beans.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2008 the original author or authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Spring Framework configuration for FakeFtpServer -->
+<!-- The FakeFtpServerSpringCofigurationTest class has dependencies on
+ several of the bean names and values configured within this file -->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
+
+ <bean id="fakeFtpServer" class="org.mockftpserver.fake.FakeFtpServer">
+ <property name="serverControlPort" value="9981"/>
+ <property name="userAccounts">
+ <list>
+ <bean class="org.mockftpserver.fake.UserAccount">
+ <property name="username" value="joe"/>
+ <property name="password" value="password"/>
+ <property name="homeDirectory" value="c:\"/>
+ </bean>
+ </list>
+ </property>
+
+ <property name="fileSystem">
+ <bean class="org.mockftpserver.fake.filesystem.WindowsFakeFileSystem">
+ <property name="createParentDirectoriesAutomatically" value="false"/>
+ <property name="entries">
+ <list>
+ <bean class="org.mockftpserver.fake.filesystem.DirectoryEntry">
+ <property name="path" value="c:\"/>
+ <property name="permissionsFromString" value="rwxrwxrwx"/>
+ <property name="owner" value="joe"/>
+ <property name="group" value="users"/>
+ </bean>
+ <bean class="org.mockftpserver.fake.filesystem.FileEntry">
+ <property name="path" value="c:\File1.txt"/>
+ <property name="contents" value="1234567890"/>
+ <property name="permissionsFromString" value="rwxrwxrwx"/>
+ <property name="owner" value="peter"/>
+ <property name="group" value="users"/>
+ </bean>
+ <bean class="org.mockftpserver.fake.filesystem.FileEntry">
+ <property name="path" value="c:\File2.txt"/>
+ <property name="contents" value="abcdefghijklmnopqrstuvwxyz"/>
+ <property name="permissions">
+ <bean class="org.mockftpserver.fake.filesystem.Permissions">
+ <constructor-arg value="rwx------"/>
+ </bean>
+ </property>
+ <property name="owner" value="peter"/>
+ <property name="group" value="users"/>
+ </bean>
+ </list>
+ </property>
+ </bean>
+ </property>
+
+ </bean>
+
+</beans> \ No newline at end of file
diff --git a/tags/2.5/src/test/resources/log4j.properties b/tags/2.5/src/test/resources/log4j.properties
new file mode 100644
index 0000000..b7465ac
--- /dev/null
+++ b/tags/2.5/src/test/resources/log4j.properties
@@ -0,0 +1,6 @@
+# Set root category priority to INFO and set its only appender to CONSOLE
+log4j.rootCategory=INFO, CONSOLE
+
+log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
+log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
+log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %c{1} [%t] %p - %m%n
diff --git a/tags/2.5/src/test/resources/stubftpserver-beans.xml b/tags/2.5/src/test/resources/stubftpserver-beans.xml
new file mode 100644
index 0000000..89918f5
--- /dev/null
+++ b/tags/2.5/src/test/resources/stubftpserver-beans.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2007 the original author or authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Spring Framework configuration for StubFtpServer -->
+<!-- The SpringConfigurationTest class has dependencies on
+ several of the bean names and values configured within this file -->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
+
+ <bean id="stubFtpServer" class="org.mockftpserver.stub.StubFtpServer">
+ <property name="serverControlPort" value="9981" />
+ <property name="commandHandlers">
+ <map>
+
+ <entry key="LIST">
+ <bean class="org.mockftpserver.stub.command.ListCommandHandler">
+ <property name="directoryListing">
+ <value>
+ 11-09-01 12:30PM 406348 File2350.log
+ 11-01-01 1:30PM &lt;DIR&gt; 0 archive
+ </value>
+ </property>
+ </bean>
+ </entry>
+
+ <entry key="PWD">
+ <bean class="org.mockftpserver.stub.command.PwdCommandHandler">
+ <property name="directory" value="foo/bar" />
+ </bean>
+ </entry>
+
+ <entry key="DELE">
+ <bean class="org.mockftpserver.stub.command.DeleCommandHandler">
+ <property name="replyCode" value="450" />
+ </bean>
+ </entry>
+
+ <entry key="RETR">
+ <bean class="org.mockftpserver.stub.command.RetrCommandHandler">
+ <property name="fileContents"
+ value="Sample file contents line 1&#10;Line 2&#10;Line 3"/>
+ </bean>
+ </entry>
+
+ </map>
+ </property>
+ </bean>
+
+</beans> \ No newline at end of file