From d66dbd59c48091f184d7930decece03124332359 Mon Sep 17 00:00:00 2001 From: Markus Koschany <apo@debian.org> Date: Tue, 27 Dec 2022 12:20:39 +0100 Subject: [PATCH] New upstream version 3.9.0 --- .asf.yaml | 25 + .travis.yml => .github/GH-ROBOTS.txt | 43 +- .github/dependabot.yml | 29 + .github/workflows/codeql-analysis.yml | 85 + .github/workflows/coverage.yml | 52 + .github/workflows/maven.yml | 62 + .github/workflows/maven_adhoc.yml | 41 + .github/workflows/scorecards-analysis.yml | 69 + .gitignore | 8 + BUILDING.txt | 2 +- CODE_OF_CONDUCT.md | 17 + CONTRIBUTING.md | 8 +- NOTICE.txt | 4 +- README.md | 76 +- RELEASE-NOTES.txt | 263 +- SECURITY.md | 17 + checkstyle-suppressions.xml | 4 +- checkstyle.xml | 39 +- findbugs-exclude-filter.xml | 5 +- pom.xml | 609 +- src/assembly/bin.xml | 6 +- src/assembly/src.xml | 9 +- src/changes/changes.xml | 2829 +++++---- src/changes/release-notes.vm | 17 +- .../examples/cidr/SubnetUtilsExample.java | 71 - .../examples/telnet/TelnetClientExample.java | 348 -- src/main/java/examples/util/IOUtil.java | 106 - .../commons/net/DatagramSocketClient.java | 358 +- .../commons/net/DatagramSocketFactory.java | 46 +- .../net/DefaultDatagramSocketFactory.java | 44 +- .../commons/net/DefaultSocketFactory.java | 219 +- .../net/MalformedServerReplyException.java | 34 +- .../commons/net/PrintCommandListener.java | 156 +- .../commons/net/ProtocolCommandEvent.java | 161 +- .../commons/net/ProtocolCommandListener.java | 41 +- .../commons/net/ProtocolCommandSupport.java | 151 +- .../org/apache/commons/net/SocketClient.java | 996 ++- .../commons/net/bsd/RCommandClient.java | 501 +- .../apache/commons/net/bsd/RExecClient.java | 324 +- .../apache/commons/net/bsd/RLoginClient.java | 138 +- .../commons/net/chargen/CharGenTCPClient.java | 67 +- .../commons/net/chargen/CharGenUDPClient.java | 138 +- .../commons/net/daytime/DaytimeTCPClient.java | 53 +- .../commons/net/daytime/DaytimeUDPClient.java | 65 +- .../commons/net/discard/DiscardTCPClient.java | 46 +- .../commons/net/discard/DiscardUDPClient.java | 101 +- .../commons/net/echo/EchoTCPClient.java | 51 +- .../commons/net/echo/EchoUDPClient.java | 106 +- .../apache/commons/net}/examples/Main.java | 73 +- .../net/examples/cidr/SubnetUtilsExample.java | 63 + .../net}/examples/ftp/FTPClientExample.java | 412 +- .../net}/examples/ftp/ServerToServerFTP.java | 159 +- .../net}/examples/ftp/TFTPExample.java | 279 +- .../net}/examples/mail/IMAPExportMbox.java | 418 +- .../net}/examples/mail/IMAPImportMbox.java | 134 +- .../commons/net}/examples/mail/IMAPMail.java | 28 +- .../commons/net}/examples/mail/IMAPUtils.java | 15 +- .../net/examples/mail/POP3ExportMbox.java | 193 + .../commons/net}/examples/mail/POP3Mail.java | 105 +- .../commons/net}/examples/mail/SMTPMail.java | 82 +- .../commons/net}/examples/mail/Utils.java | 37 +- .../net}/examples/nntp/ArticleReader.java | 32 +- .../net}/examples/nntp/ExtendedNNTPOps.java | 62 +- .../net}/examples/nntp/ListNewsgroups.java | 54 +- .../net}/examples/nntp/MessageThreading.java | 40 +- .../commons/net}/examples/nntp/NNTPUtils.java | 20 +- .../net}/examples/nntp/PostMessage.java | 85 +- .../commons/net}/examples/ntp/NTPClient.java | 143 +- .../net}/examples/ntp/SimpleNTPServer.java | 225 +- .../commons/net}/examples/ntp/TimeClient.java | 96 +- .../commons/net/examples/package-info.java | 23 + .../examples/telnet/TelnetClientExample.java | 263 + .../net}/examples/telnet/WeatherTelnet.java | 50 +- .../commons/net}/examples/unix/chargen.java | 101 +- .../commons/net}/examples/unix/daytime.java | 60 +- .../commons/net}/examples/unix/echo.java | 107 +- .../commons/net}/examples/unix/finger.java | 80 +- .../commons/net}/examples/unix/fwhois.java | 48 +- .../commons/net}/examples/unix/rdate.java | 89 +- .../commons/net}/examples/unix/rexec.java | 74 +- .../commons/net}/examples/unix/rlogin.java | 88 +- .../commons/net}/examples/unix/rshell.java | 78 +- .../commons/net/examples/util/IOUtil.java | 76 + .../commons/net/finger/FingerClient.java | 216 +- .../apache/commons/net/ftp/Configurable.java | 11 +- .../apache/commons/net/ftp/DurationUtils.java | 43 + .../java/org/apache/commons/net/ftp/FTP.java | 2497 ++++---- .../org/apache/commons/net/ftp/FTPClient.java | 5434 ++++++++--------- .../commons/net/ftp/FTPClientConfig.java | 833 ++- .../org/apache/commons/net/ftp/FTPCmd.java | 56 +- .../apache/commons/net/ftp/FTPCommand.java | 61 +- .../net/ftp/FTPConnectionClosedException.java | 33 +- .../org/apache/commons/net/ftp/FTPFile.java | 685 +-- .../commons/net/ftp/FTPFileEntryParser.java | 113 +- .../net/ftp/FTPFileEntryParserImpl.java | 59 +- .../apache/commons/net/ftp/FTPFileFilter.java | 10 +- .../commons/net/ftp/FTPFileFilters.java | 24 +- .../apache/commons/net/ftp/FTPHTTPClient.java | 120 +- .../commons/net/ftp/FTPListParseEngine.java | 337 +- .../org/apache/commons/net/ftp/FTPReply.java | 138 +- .../apache/commons/net/ftp/FTPSClient.java | 1211 ++-- .../apache/commons/net/ftp/FTPSCommand.java | 13 +- .../net/ftp/FTPSServerSocketFactory.java | 41 +- .../commons/net/ftp/FTPSSocketFactory.java | 99 +- .../commons/net/ftp/FTPSTrustManager.java | 28 +- .../ftp/parser/CompositeFileEntryParser.java | 42 +- .../ConfigurableFTPFileEntryParserImpl.java | 93 +- .../DefaultFTPFileEntryParserFactory.java | 273 +- .../parser/EnterpriseUnixFTPEntryParser.java | 130 +- .../ftp/parser/FTPFileEntryParserFactory.java | 53 +- .../net/ftp/parser/FTPTimestampParser.java | 24 +- .../ftp/parser/FTPTimestampParserImpl.java | 360 +- .../net/ftp/parser/MLSxEntryParser.java | 302 +- .../net/ftp/parser/MVSFTPEntryParser.java | 424 +- .../ftp/parser/MacOsPeterFTPEntryParser.java | 225 +- .../net/ftp/parser/NTFTPEntryParser.java | 135 +- .../net/ftp/parser/NetwareFTPEntryParser.java | 117 +- .../net/ftp/parser/OS2FTPEntryParser.java | 113 +- .../net/ftp/parser/OS400FTPEntryParser.java | 180 +- .../parser/ParserInitializationException.java | 17 +- .../parser/RegexFTPFileEntryParserImpl.java | 148 +- .../net/ftp/parser/UnixFTPEntryParser.java | 320 +- .../net/ftp/parser/VMSFTPEntryParser.java | 275 +- .../parser/VMSVersioningFTPEntryParser.java | 130 +- .../net/imap/AuthenticatingIMAPClient.java | 302 +- .../org/apache/commons/net/imap/IMAP.java | 547 +- .../apache/commons/net/imap/IMAPClient.java | 784 ++- .../apache/commons/net/imap/IMAPCommand.java | 78 +- .../apache/commons/net/imap/IMAPReply.java | 140 +- .../apache/commons/net/imap/IMAPSClient.java | 376 +- .../apache/commons/net/io/CRLFLineReader.java | 43 +- .../commons/net/io/CopyStreamAdapter.java | 96 +- .../commons/net/io/CopyStreamEvent.java | 71 +- .../commons/net/io/CopyStreamException.java | 46 +- .../commons/net/io/CopyStreamListener.java | 56 +- .../net/io/DotTerminatedMessageReader.java | 211 +- .../net/io/DotTerminatedMessageWriter.java | 265 +- .../net/io/FromNetASCIIInputStream.java | 224 +- .../net/io/FromNetASCIIOutputStream.java | 193 +- .../commons/net/io/SocketInputStream.java | 52 +- .../commons/net/io/SocketOutputStream.java | 78 +- .../commons/net/io/ToNetASCIIInputStream.java | 181 +- .../net/io/ToNetASCIIOutputStream.java | 122 +- .../java/org/apache/commons/net/io/Util.java | 477 +- .../org/apache/commons/net/nntp/Article.java | 360 +- .../apache/commons/net/nntp/ArticleInfo.java | 5 +- .../commons/net/nntp/ArticleIterator.java | 27 +- .../commons/net/nntp/ArticlePointer.java | 16 +- .../org/apache/commons/net/nntp/NNTP.java | 1341 ++-- .../apache/commons/net/nntp/NNTPClient.java | 2518 ++++---- .../apache/commons/net/nntp/NNTPCommand.java | 88 +- .../nntp/NNTPConnectionClosedException.java | 33 +- .../apache/commons/net/nntp/NNTPReply.java | 204 +- .../net/nntp/NewGroupsOrNewsQuery.java | 225 +- .../commons/net/nntp/NewsgroupInfo.java | 187 +- .../commons/net/nntp/NewsgroupIterator.java | 23 +- .../commons/net/nntp/ReplyIterator.java | 34 +- .../commons/net/nntp/SimpleNNTPHeader.java | 180 +- .../commons/net/nntp/ThreadContainer.java | 24 +- .../apache/commons/net/nntp/Threadable.java | 26 +- .../org/apache/commons/net/nntp/Threader.java | 348 +- .../apache/commons/net/ntp/NTPUDPClient.java | 134 +- .../org/apache/commons/net/ntp/NtpUtils.java | 120 +- .../org/apache/commons/net/ntp/NtpV3Impl.java | 741 +-- .../apache/commons/net/ntp/NtpV3Packet.java | 239 +- .../org/apache/commons/net/ntp/TimeInfo.java | 331 +- .../org/apache/commons/net/ntp/TimeStamp.java | 557 +- .../commons/net/pop3/ExtendedPOP3Client.java | 165 +- .../org/apache/commons/net/pop3/POP3.java | 426 +- .../apache/commons/net/pop3/POP3Client.java | 613 +- .../apache/commons/net/pop3/POP3Command.java | 60 +- .../commons/net/pop3/POP3MessageInfo.java | 69 +- .../apache/commons/net/pop3/POP3Reply.java | 16 +- .../apache/commons/net/pop3/POP3SClient.java | 384 +- .../net/smtp/AuthenticatingSMTPClient.java | 380 +- .../apache/commons/net/smtp/RelayPath.java | 68 +- .../org/apache/commons/net/smtp/SMTP.java | 991 ++- .../apache/commons/net/smtp/SMTPClient.java | 706 +-- .../apache/commons/net/smtp/SMTPCommand.java | 55 +- .../smtp/SMTPConnectionClosedException.java | 32 +- .../apache/commons/net/smtp/SMTPReply.java | 138 +- .../apache/commons/net/smtp/SMTPSClient.java | 388 +- .../commons/net/smtp/SimpleSMTPHeader.java | 165 +- .../commons/net/telnet/EchoOptionHandler.java | 47 +- .../telnet/InvalidTelnetOptionException.java | 38 +- .../net/telnet/SimpleOptionHandler.java | 54 +- .../net/telnet/SuppressGAOptionHandler.java | 47 +- .../org/apache/commons/net/telnet/Telnet.java | 1499 ++--- .../commons/net/telnet/TelnetClient.java | 529 +- .../commons/net/telnet/TelnetCommand.java | 93 +- .../net/telnet/TelnetInputListener.java | 19 +- .../commons/net/telnet/TelnetInputStream.java | 850 ++- .../net/telnet/TelnetNotificationHandler.java | 61 +- .../commons/net/telnet/TelnetOption.java | 101 +- .../net/telnet/TelnetOptionHandler.java | 346 +- .../net/telnet/TelnetOutputStream.java | 180 +- .../net/telnet/TerminalTypeOptionHandler.java | 103 +- .../net/telnet/WindowSizeOptionHandler.java | 143 +- .../org/apache/commons/net/tftp/TFTP.java | 369 +- .../commons/net/tftp/TFTPAckPacket.java | 176 +- .../apache/commons/net/tftp/TFTPClient.java | 428 +- .../commons/net/tftp/TFTPDataPacket.java | 321 +- .../commons/net/tftp/TFTPErrorPacket.java | 237 +- .../apache/commons/net/tftp/TFTPPacket.java | 267 +- .../commons/net/tftp/TFTPPacketException.java | 38 +- .../net/tftp/TFTPReadRequestPacket.java | 67 +- .../commons/net/tftp/TFTPRequestPacket.java | 274 +- .../net/tftp/TFTPWriteRequestPacket.java | 67 +- .../commons/net/time/TimeTCPClient.java | 100 +- .../commons/net/time/TimeUDPClient.java | 139 +- .../org/apache/commons/net/util/Base64.java | 1244 ++-- .../org/apache/commons/net/util/Charsets.java | 12 +- .../commons/net/util/KeyManagerUtils.java | 248 +- .../apache/commons/net/util/ListenerList.java | 48 +- .../apache/commons/net/util/NetConstants.java | 55 + .../commons/net/util/SSLContextUtils.java | 45 +- .../commons/net/util/SSLSocketUtils.java | 41 +- .../apache/commons/net/util/SubnetUtils.java | 481 +- .../commons/net/util/TrustManagerUtils.java | 59 +- .../apache/commons/net/whois/WhoisClient.java | 108 +- .../resources/examples/examples.properties | 51 - .../commons/net/examples/examples.properties | 51 + src/site/site.xml | 20 +- src/site/xdoc/code-standards.xml | 6 +- src/site/xdoc/download_net.xml | 78 +- src/site/xdoc/index.xml | 6 +- src/site/xdoc/issue-tracking.xml | 6 +- src/site/xdoc/mail-lists.xml | 58 +- src/test/java/examples/MainTest.java | 124 - .../net/SocketClientFunctionalTest.java | 22 +- .../apache/commons/net/SocketClientTest.java | 14 +- .../apache/commons/net/SubnetUtilsTest.java | 252 +- .../apache/commons/net/examples/MainTest.java | 130 + .../commons/net/ftp/AbstractFtpsTest.java | 211 + .../ftp/FTPClientConfigFunctionalTest.java | 164 +- .../commons/net/ftp/FTPClientConfigTest.java | 168 +- .../apache/commons/net/ftp/FTPClientTest.java | 256 +- .../commons/net/ftp/FTPCommandTest.java | 2 +- .../commons/net/ftp/FTPSClientTest.java | 198 + .../net/ftp/ListingFunctionalTest.java | 211 +- .../ftp/NoProtocolSslConfigurationProxy.java | 69 + .../commons/net/ftp/TestConnectTimeout.java | 11 +- .../CompositeFTPParseTestFramework.java | 100 +- .../DefaultFTPFileEntryParserFactoryTest.java | 60 +- .../net/ftp/parser/DownloadListings.java | 124 +- .../EnterpriseUnixFTPEntryParserTest.java | 227 +- .../ftp/parser/FTPConfigEntryParserTest.java | 147 +- .../net/ftp/parser/FTPParseTestFramework.java | 191 +- .../parser/FTPTimestampParserImplTest.java | 597 +- .../net/ftp/parser/MLSDComparison.java | 233 +- .../net/ftp/parser/MLSxEntryParserTest.java | 66 +- .../net/ftp/parser/MVSFTPEntryParserTest.java | 167 +- .../parser/MacOsPeterFTPEntryParserTest.java | 111 +- .../net/ftp/parser/NTFTPEntryParserTest.java | 419 +- .../ftp/parser/NetwareFTPEntryParserTest.java | 55 +- .../net/ftp/parser/OS2FTPEntryParserTest.java | 105 +- .../OS400FTPEntryParserAdditionalTest.java | 141 +- .../ftp/parser/OS400FTPEntryParserTest.java | 214 +- .../ftp/parser/UnixFTPEntryParserTest.java | 410 +- .../net/ftp/parser/VMSFTPEntryParserTest.java | 362 +- .../org/apache/commons/net/imap/IMAPTest.java | 16 +- .../io/DotTerminatedMessageReaderTest.java | 106 +- .../net/io/ToNetASCIIInputStreamTest.java | 102 +- .../apache/commons/net/nntp/TestThreader.java | 54 +- .../apache/commons/net/ntp/TestNtpClient.java | 46 +- .../apache/commons/net/ntp/TestNtpPacket.java | 110 +- .../apache/commons/net/ntp/TestTimeInfo.java | 120 +- .../apache/commons/net/ntp/TimeStampTest.java | 67 +- .../net/pop3/POP3ClientCommandsTest.java | 581 +- .../commons/net/pop3/POP3ClientTest.java | 113 +- .../commons/net/pop3/POP3Constants.java | 26 +- .../commons/net/pop3/POP3ConstructorTest.java | 92 +- .../net/smtp/SimpleSMTPHeaderTestCase.java | 119 +- .../net/telnet/EchoOptionHandlerTest.java | 69 +- .../InvalidTelnetOptionExceptionTest.java | 23 +- .../net/telnet/SimpleOptionHandlerTest.java | 69 +- .../telnet/SuppressGAOptionHandlerTest.java | 69 +- .../telnet/TelnetClientFunctionalTest.java | 113 +- .../commons/net/telnet/TelnetClientTest.java | 1113 ++-- .../TelnetOptionHandlerTestAbstract.java | 106 +- .../commons/net/telnet/TelnetOptionTest.java | 35 +- .../net/telnet/TelnetTestResponder.java | 74 +- .../net/telnet/TelnetTestSimpleServer.java | 157 +- .../telnet/TerminalTypeOptionHandlerTest.java | 102 +- .../telnet/WindowSizeOptionHandlerTest.java | 111 +- .../apache/commons/net/tftp/TFTPServer.java | 1150 ++-- .../commons/net/tftp/TFTPServerMain.java | 149 +- .../commons/net/tftp/TFTPServerPathTest.java | 92 +- .../org/apache/commons/net/tftp/TFTPTest.java | 226 +- .../commons/net/time/TimeTCPClientTest.java | 97 +- .../net/time/TimeTestSimpleServer.java | 132 +- .../apache/commons/net/util/Base64Test.java | 162 +- .../org/apache/commons/net/util/UtilTest.java | 221 +- .../commons/net/ftpsserver/ftpserver.jks | Bin 0 -> 3051 bytes .../commons/net/ftpsserver/users.properties | 43 + .../org/apache/commons/net/test-data/file.txt | 20 + 296 files changed, 29691 insertions(+), 35679 deletions(-) create mode 100644 .asf.yaml rename .travis.yml => .github/GH-ROBOTS.txt (78%) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 .github/workflows/coverage.yml create mode 100644 .github/workflows/maven.yml create mode 100644 .github/workflows/maven_adhoc.yml create mode 100644 .github/workflows/scorecards-analysis.yml create mode 100644 .gitignore create mode 100644 CODE_OF_CONDUCT.md mode change 100755 => 100644 CONTRIBUTING.md create mode 100644 SECURITY.md delete mode 100644 src/main/java/examples/cidr/SubnetUtilsExample.java delete mode 100644 src/main/java/examples/telnet/TelnetClientExample.java delete mode 100644 src/main/java/examples/util/IOUtil.java rename src/main/java/{ => org/apache/commons/net}/examples/Main.java (60%) create mode 100644 src/main/java/org/apache/commons/net/examples/cidr/SubnetUtilsExample.java rename src/main/java/{ => org/apache/commons/net}/examples/ftp/FTPClientExample.java (55%) rename src/main/java/{ => org/apache/commons/net}/examples/ftp/ServerToServerFTP.java (61%) rename src/main/java/{ => org/apache/commons/net}/examples/ftp/TFTPExample.java (52%) rename src/main/java/{ => org/apache/commons/net}/examples/mail/IMAPExportMbox.java (60%) rename src/main/java/{ => org/apache/commons/net}/examples/mail/IMAPImportMbox.java (62%) rename src/main/java/{ => org/apache/commons/net}/examples/mail/IMAPMail.java (77%) rename src/main/java/{ => org/apache/commons/net}/examples/mail/IMAPUtils.java (85%) create mode 100644 src/main/java/org/apache/commons/net/examples/mail/POP3ExportMbox.java rename src/main/java/{ => org/apache/commons/net}/examples/mail/POP3Mail.java (68%) rename src/main/java/{ => org/apache/commons/net}/examples/mail/SMTPMail.java (74%) rename src/main/java/{ => org/apache/commons/net}/examples/mail/Utils.java (68%) rename src/main/java/{ => org/apache/commons/net}/examples/nntp/ArticleReader.java (75%) rename src/main/java/{ => org/apache/commons/net}/examples/nntp/ExtendedNNTPOps.java (67%) rename src/main/java/{ => org/apache/commons/net}/examples/nntp/ListNewsgroups.java (66%) rename src/main/java/{ => org/apache/commons/net}/examples/nntp/MessageThreading.java (71%) rename src/main/java/{ => org/apache/commons/net}/examples/nntp/NNTPUtils.java (70%) rename src/main/java/{ => org/apache/commons/net}/examples/nntp/PostMessage.java (74%) rename src/main/java/{ => org/apache/commons/net}/examples/ntp/NTPClient.java (67%) rename src/main/java/{ => org/apache/commons/net}/examples/ntp/SimpleNTPServer.java (66%) rename src/main/java/{ => org/apache/commons/net}/examples/ntp/TimeClient.java (58%) create mode 100644 src/main/java/org/apache/commons/net/examples/package-info.java create mode 100644 src/main/java/org/apache/commons/net/examples/telnet/TelnetClientExample.java rename src/main/java/{ => org/apache/commons/net}/examples/telnet/WeatherTelnet.java (59%) rename src/main/java/{ => org/apache/commons/net}/examples/unix/chargen.java (59%) rename src/main/java/{ => org/apache/commons/net}/examples/unix/daytime.java (65%) rename src/main/java/{ => org/apache/commons/net}/examples/unix/echo.java (63%) rename src/main/java/{ => org/apache/commons/net}/examples/unix/finger.java (70%) rename src/main/java/{ => org/apache/commons/net}/examples/unix/fwhois.java (77%) rename src/main/java/{ => org/apache/commons/net}/examples/unix/rdate.java (62%) rename src/main/java/{ => org/apache/commons/net}/examples/unix/rexec.java (61%) rename src/main/java/{ => org/apache/commons/net}/examples/unix/rlogin.java (55%) rename src/main/java/{ => org/apache/commons/net}/examples/unix/rshell.java (60%) create mode 100644 src/main/java/org/apache/commons/net/examples/util/IOUtil.java create mode 100644 src/main/java/org/apache/commons/net/ftp/DurationUtils.java create mode 100644 src/main/java/org/apache/commons/net/util/NetConstants.java delete mode 100644 src/main/resources/examples/examples.properties create mode 100644 src/main/resources/org/apache/commons/net/examples/examples.properties delete mode 100644 src/test/java/examples/MainTest.java create mode 100644 src/test/java/org/apache/commons/net/examples/MainTest.java create mode 100644 src/test/java/org/apache/commons/net/ftp/AbstractFtpsTest.java create mode 100644 src/test/java/org/apache/commons/net/ftp/FTPSClientTest.java create mode 100644 src/test/java/org/apache/commons/net/ftp/NoProtocolSslConfigurationProxy.java create mode 100644 src/test/resources/org/apache/commons/net/ftpsserver/ftpserver.jks create mode 100644 src/test/resources/org/apache/commons/net/ftpsserver/users.properties create mode 100644 src/test/resources/org/apache/commons/net/test-data/file.txt diff --git a/.asf.yaml b/.asf.yaml new file mode 100644 index 0000000..6dc0263 --- /dev/null +++ b/.asf.yaml @@ -0,0 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +github: + description: "Apache Commons Net" + homepage: https://commons.apache.org/net/ + +notifications: + commits: commits@commons.apache.org + issues: issues@commons.apache.org + pullrequests: issues@commons.apache.org + jira_options: link label + jobs: notifications@commons.apache.org diff --git a/.travis.yml b/.github/GH-ROBOTS.txt similarity index 78% rename from .travis.yml rename to .github/GH-ROBOTS.txt index fc2a03a..e3329e5 100644 --- a/.travis.yml +++ b/.github/GH-ROBOTS.txt @@ -1,24 +1,19 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -language: java -sudo: false - -jdk: - - openjdk7 - - oraclejdk8 - -after_success: - - mvn clean cobertura:cobertura coveralls:report +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Keeps on creating FUD PRs in test code +# Does not follow Apache disclosure policies +User-agent: JLLeitschuh/security-research +Disallow: * diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..da0ca5b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "weekly" + day: "friday" + ignore: + - dependency-name: "org.slf4j:slf4j-simple" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "friday" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..73b5dc9 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,85 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '33 9 * * 4' + +permissions: + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3.1.0 + with: + persist-credentials: false + - uses: actions/cache@v3.0.11 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..d8e2327 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,52 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Coverage + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + java: [ 8 ] + + steps: + - uses: actions/checkout@v3.1.0 + with: + persist-credentials: false + - uses: actions/cache@v3.0.11 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v3.6.0 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + - name: Build with Maven + run: mvn -V test jacoco:report --file pom.xml --no-transfer-progress + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + files: ./target/site/jacoco/jacoco.xml diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000..f4684fa --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,62 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Java CI + +on: [push, pull_request, workflow_dispatch] + +permissions: + contents: read + +jobs: + build: + timeout-minutes: 7 + continue-on-error: ${{ matrix.experimental }} + strategy: + matrix: + java: [ 8, 11, 17 ] + os: [ubuntu-latest] + experimental: [false] + # Don't need + include: + - java: 8 + os: macos-latest + experimental: false + - java: 8 + os: windows-latest + experimental: false +# include: +# - java: 18-ea +# experimental: true + + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3.1.0 + with: + persist-credentials: false + - uses: actions/cache@v3.0.11 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v3.6.0 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + - name: Build with Maven + run: mvn -V --batch-mode -Ddoclint=all --file pom.xml --no-transfer-progress +# N.B. Add -Pslf4j-simple to enable logging above diff --git a/.github/workflows/maven_adhoc.yml b/.github/workflows/maven_adhoc.yml new file mode 100644 index 0000000..00b0c6a --- /dev/null +++ b/.github/workflows/maven_adhoc.yml @@ -0,0 +1,41 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Java CI adhoc testing + +on: workflow_dispatch + +permissions: + contents: read + +jobs: + build: + timeout-minutes: 7 + runs-on: windows-latest + steps: + - uses: actions/checkout@v3.1.0 + with: + persist-credentials: false + - name: Set up JDK + uses: actions/setup-java@v3.6.0 + with: + distribution: 'temurin' + java-version: 8 + - name: Build with Maven + run: mvn -V --batch-mode --file pom.xml --no-transfer-progress -Dtest=MainTest + - name: Test exec function + run: | + mvn -q exec:java + mvn -q exec:java -D"exec.arguments=FTPClientExample" diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml new file mode 100644 index 0000000..d223bf4 --- /dev/null +++ b/.github/workflows/scorecards-analysis.yml @@ -0,0 +1,69 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache license, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the license for the specific language governing permissions and +# limitations under the license. + +name: "Scorecards supply-chain security" + +on: + branch_protection_rule: + schedule: + - cron: "30 1 * * 6" # Weekly on Saturdays + push: + branches: [ "master" ] + +permissions: read-all + +jobs: + + analysis: + + name: "Scorecards analysis" + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to the code-scanning dashboard. + security-events: write + actions: read + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout + + steps: + + - name: "Checkout code" + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # 3.1.0 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@99c53751e09b9529366343771cc321ec74e9bd3d # 2.0.6 + with: + results_file: results.sarif + results_format: sarif + # A read-only PAT token, which is sufficient for the action to function. + # The relevant discussion: https://github.com/ossf/scorecard-action/issues/188 + repo_token: ${{ secrets.GITHUB_TOKEN }} + # Publish the results for public repositories to enable scorecard badges. + # For more details: https://github.com/ossf/scorecard-action#publishing-results + publish_results: true + + - name: "Upload artifact" + uses: actions/upload-artifact@83fd05a356d7e2593de66fc9913b3002723633cb # 3.1.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@b398f525a5587552e573b247ac661067fafa920b # 2.1.22 + with: + sarif_file: results.sarif diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..689c402 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/.classpath +/.project +/.settings/ +/target/ +/.vscode/ +.idea/ +*.iml +/site-content/ diff --git a/BUILDING.txt b/BUILDING.txt index 840417b..2eb2e4a 100644 --- a/BUILDING.txt +++ b/BUILDING.txt @@ -8,7 +8,7 @@ $ mvn clean test -Pjava-1.6 For setting up your Maven installation to enable the use of the profile, please see: -http://commons.apache.org/commons-parent-pom.html#Testing_with_different_Java_versions +https://commons.apache.org/commons-parent-pom.html#Testing_with_different_Java_versions The latest version of Maven that runs under Java 1.6 is 3.2.5 [1] diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..3ed5015 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,17 @@ +<!--- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +The Apache code of conduct page is [https://www.apache.org/foundation/policies/conduct.html](https://www.apache.org/foundation/policies/conduct.html). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md old mode 100755 new mode 100644 index 856747c..71f0edc --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,7 +25,7 @@ | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates | +======================================================================+ | | - | 1) Re-generate using: mvn commons:contributing-md | + | 1) Re-generate using: mvn commons-build:contributing-md | | | | 2) Set the following properties in the component's pom: | | - commons.jira.id (required, alphabetic, upper case) | @@ -50,7 +50,7 @@ Getting Started + Make sure you have a [JIRA account](https://issues.apache.org/jira/). + Make sure you have a [GitHub account](https://github.com/signup/free). -+ If you're planning to implement a new feature it makes sense to discuss you're changes on the [dev list](https://commons.apache.org/mail-lists.html) first. This way you can make sure you're not wasting your time on something that isn't considered to be in Apache Commons Net's scope. ++ If you're planning to implement a new feature it makes sense to discuss your changes on the [dev list](https://commons.apache.org/mail-lists.html) first. This way you can make sure you're not wasting your time on something that isn't considered to be in Apache Commons Net's scope. + Submit a [Jira Ticket][jira] for your issue, assuming one does not already exist. + Clearly describe the issue including steps to reproduce when it is a bug. + Make sure you fill in the earliest version that you know has the issue. @@ -61,7 +61,7 @@ Making Changes -------------- + Create a _topic branch_ for your isolated work. - * Usually you should base your branch on the `master` or `trunk` branch. + * Usually you should base your branch on the `master` branch. * A good topic branch name can be the JIRA bug id plus a keyword, e.g. `NET-123-InputStream`. * If you have submitted multiple JIRA issues, try to maintain separate branches and pull requests. + Make commits of logical units. @@ -107,7 +107,7 @@ Additional Resources + [Apache Commons Net JIRA project page][jira] + [Contributor License Agreement][cla] + [General GitHub documentation](https://help.github.com/) -+ [GitHub pull request documentation](https://help.github.com/send-pull-requests/) ++ [GitHub pull request documentation](https://help.github.com/articles/creating-a-pull-request/) + [Apache Commons Twitter Account](https://twitter.com/ApacheCommons) + `#apache-commons` IRC channel on `irc.freenode.net` diff --git a/NOTICE.txt b/NOTICE.txt index a1c6705..a617e25 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,5 +1,5 @@ Apache Commons Net -Copyright 2001-2017 The Apache Software Foundation +Copyright 2001-2022 The Apache Software Foundation This product includes software developed at -The Apache Software Foundation (http://www.apache.org/). +The Apache Software Foundation (https://www.apache.org/). diff --git a/README.md b/README.md index 15f0f7b..cbef9d9 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates | +======================================================================+ | | - | 1) Re-generate using: mvn commons:readme-md | + | 1) Re-generate using: mvn commons-build:readme-md | | | | 2) Set the following properties in the component's pom: | | - commons.componentid (required, alphabetic, lower case) | @@ -43,10 +43,12 @@ Apache Commons Net =================== -[](https://travis-ci.org/apache/commons-net) -[](https://coveralls.io/r/apache/commons-net) -[](https://maven-badges.herokuapp.com/maven-central/commons-net/commons-net/) -[](http://www.apache.org/licenses/LICENSE-2.0.html) +[](https://github.com/apache/commons-net/actions) +[](https://app.codecov.io/gh/apache/commons-net) +[](https://maven-badges.herokuapp.com/maven-central/commons-net/commons-net/?gav=true) +[](https://javadoc.io/doc/commons-net/commons-net/3.9.0) +[](hhttps://github.com/apache/commons-net/actions/workflows/codeql-analysis.yml?query=workflow%3ACodeQL) +[](https://api.securityscorecards.dev/projects/github.com/apache/commons-text) Apache Commons Net library contains a collection of network utilities and protocol implementations. Supported protocols include: Echo, Finger, FTP, NNTP, NTP, POP3(S), SMTP(S), Telnet, Whois @@ -55,7 +57,7 @@ Documentation ------------- More information can be found on the [Apache Commons Net homepage](https://commons.apache.org/proper/commons-net). -The [JavaDoc](https://commons.apache.org/proper/commons-net/javadocs/api-release) can be browsed. +The [Javadoc](https://commons.apache.org/proper/commons-net/apidocs) can be browsed. Questions related to the usage of Apache Commons Net should be posted to the [user mailing list][ml]. Where can I get the latest release? @@ -68,19 +70,19 @@ Alternatively you can pull it from the central Maven repositories: <dependency> <groupId>commons-net</groupId> <artifactId>commons-net</artifactId> - <version>3.6</version> + <version>3.9.0</version> </dependency> ``` Contributing ------------ -We accept Pull Requests via GitHub. The [developer mailing list][ml] is the main channel of communication for contributors. +We accept Pull Requests via GitHub. The [developer mailing list](https://commons.apache.org/mail-lists.html) is the main channel of communication for contributors. There are some guidelines which will make applying PRs easier for us: + No tabs! Please use spaces for indentation. + Respect the code style. + Create minimal diffs - disable on save actions like reformat source code or organize imports. If you feel the source code should be reformatted create a separate PR for this change. -+ Provide JUnit tests for your changes and make sure your changes don't break any existing tests by running ```mvn clean test```. ++ Provide JUnit tests for your changes and make sure your changes don't break any existing tests by running ```mvn```. If you plan to contribute on a regular basis, please consider filing a [contributor license agreement](https://www.apache.org/licenses/#clas). You can learn more about contributing via GitHub in our [contribution guidelines](CONTRIBUTING.md). @@ -100,7 +102,61 @@ Additional Resources + [Apache Commons Homepage](https://commons.apache.org/) + [Apache Issue Tracker (JIRA)](https://issues.apache.org/jira/browse/NET) ++ [Apache Commons Slack Channel](https://the-asf.slack.com/archives/C60NVB8AD) + [Apache Commons Twitter Account](https://twitter.com/ApacheCommons) + `#apache-commons` IRC channel on `irc.freenode.org` -[ml]:https://commons.apache.org/mail-lists.html +Apache Commons Components +------------------------- + +| Component | GitHub Repository | Apache Homepage | +| --------- | ----------------- | ----------------| +| Apache Commons BCEL | [commons-bcel](https://github.com/apache/commons-bcel) | [commons-bcel](https://commons.apache.org/proper/commons-bcel) | +| Apache Commons Beanutils | [commons-beanutils](https://github.com/apache/commons-beanutils) | [commons-beanutils](https://commons.apache.org/proper/commons-beanutils) | +| Apache Commons BSF | [commons-bsf](https://github.com/apache/commons-bsf) | [commons-bsf](https://commons.apache.org/proper/commons-bsf) | +| Apache Commons Build-plugin | [commons-build-plugin](https://github.com/apache/commons-build-plugin) | [commons-build-plugin](https://commons.apache.org/proper/commons-build-plugin) | +| Apache Commons Chain | [commons-chain](https://github.com/apache/commons-chain) | [commons-chain](https://commons.apache.org/proper/commons-chain) | +| Apache Commons CLI | [commons-cli](https://github.com/apache/commons-cli) | [commons-cli](https://commons.apache.org/proper/commons-cli) | +| Apache Commons Codec | [commons-codec](https://github.com/apache/commons-codec) | [commons-codec](https://commons.apache.org/proper/commons-codec) | +| Apache Commons Collections | [commons-collections](https://github.com/apache/commons-collections) | [commons-collections](https://commons.apache.org/proper/commons-collections) | +| Apache Commons Compress | [commons-compress](https://github.com/apache/commons-compress) | [commons-compress](https://commons.apache.org/proper/commons-compress) | +| Apache Commons Configuration | [commons-configuration](https://github.com/apache/commons-configuration) | [commons-configuration](https://commons.apache.org/proper/commons-configuration) | +| Apache Commons Crypto | [commons-crypto](https://github.com/apache/commons-crypto) | [commons-crypto](https://commons.apache.org/proper/commons-crypto) | +| Apache Commons CSV | [commons-csv](https://github.com/apache/commons-csv) | [commons-csv](https://commons.apache.org/proper/commons-csv) | +| Apache Commons Daemon | [commons-daemon](https://github.com/apache/commons-daemon) | [commons-daemon](https://commons.apache.org/proper/commons-daemon) | +| Apache Commons DBCP | [commons-dbcp](https://github.com/apache/commons-dbcp) | [commons-dbcp](https://commons.apache.org/proper/commons-dbcp) | +| Apache Commons Dbutils | [commons-dbutils](https://github.com/apache/commons-dbutils) | [commons-dbutils](https://commons.apache.org/proper/commons-dbutils) | +| Apache Commons Digester | [commons-digester](https://github.com/apache/commons-digester) | [commons-digester](https://commons.apache.org/proper/commons-digester) | +| Apache Commons Email | [commons-email](https://github.com/apache/commons-email) | [commons-email](https://commons.apache.org/proper/commons-email) | +| Apache Commons Exec | [commons-exec](https://github.com/apache/commons-exec) | [commons-exec](https://commons.apache.org/proper/commons-exec) | +| Apache Commons Fileupload | [commons-fileupload](https://github.com/apache/commons-fileupload) | [commons-fileupload](https://commons.apache.org/proper/commons-fileupload) | +| Apache Commons Functor | [commons-functor](https://github.com/apache/commons-functor) | [commons-functor](https://commons.apache.org/proper/commons-functor) | +| Apache Commons Geometry | [commons-geometry](https://github.com/apache/commons-geometry) | [commons-geometry](https://commons.apache.org/proper/commons-geometry) | +| Apache Commons Graph | [commons-graph](https://github.com/apache/commons-graph) | [commons-graph](https://commons.apache.org/proper/commons-graph) | +| Apache Commons Imaging | [commons-imaging](https://github.com/apache/commons-imaging) | [commons-imaging](https://commons.apache.org/proper/commons-imaging) | +| Apache Commons IO | [commons-io](https://github.com/apache/commons-io) | [commons-io](https://commons.apache.org/proper/commons-io) | +| Apache Commons JCI | [commons-jci](https://github.com/apache/commons-jci) | [commons-jci](https://commons.apache.org/proper/commons-jci) | +| Apache Commons JCS | [commons-jcs](https://github.com/apache/commons-jcs) | [commons-jcs](https://commons.apache.org/proper/commons-jcs) | +| Apache Commons Jelly | [commons-jelly](https://github.com/apache/commons-jelly) | [commons-jelly](https://commons.apache.org/proper/commons-jelly) | +| Apache Commons Jexl | [commons-jexl](https://github.com/apache/commons-jexl) | [commons-jexl](https://commons.apache.org/proper/commons-jexl) | +| Apache Commons Jxpath | [commons-jxpath](https://github.com/apache/commons-jxpath) | [commons-jxpath](https://commons.apache.org/proper/commons-jxpath) | +| Apache Commons Lang | [commons-lang](https://github.com/apache/commons-lang) | [commons-lang](https://commons.apache.org/proper/commons-lang) | +| Apache Commons Logging | [commons-logging](https://github.com/apache/commons-logging) | [commons-logging](https://commons.apache.org/proper/commons-logging) | +| Apache Commons Math | [commons-math](https://github.com/apache/commons-math) | [commons-math](https://commons.apache.org/proper/commons-math) | +| Apache Commons Net | [commons-net](https://github.com/apache/commons-net) | [commons-net](https://commons.apache.org/proper/commons-net) | +| Apache Commons Numbers | [commons-numbers](https://github.com/apache/commons-numbers) | [commons-numbers](https://commons.apache.org/proper/commons-numbers) | +| Apache Commons Parent | [commons-parent](https://github.com/apache/commons-parent) | [commons-parent](https://commons.apache.org/proper/commons-parent) | +| Apache Commons Pool | [commons-pool](https://github.com/apache/commons-pool) | [commons-pool](https://commons.apache.org/proper/commons-pool) | +| Apache Commons Proxy | [commons-proxy](https://github.com/apache/commons-proxy) | [commons-proxy](https://commons.apache.org/proper/commons-proxy) | +| Apache Commons RDF | [commons-rdf](https://github.com/apache/commons-rdf) | [commons-rdf](https://commons.apache.org/proper/commons-rdf) | +| Apache Commons Release-plugin | [commons-release-plugin](https://github.com/apache/commons-release-plugin) | [commons-release-plugin](https://commons.apache.org/proper/commons-release-plugin) | +| Apache Commons Rng | [commons-rng](https://github.com/apache/commons-rng) | [commons-rng](https://commons.apache.org/proper/commons-rng) | +| Apache Commons Scxml | [commons-scxml](https://github.com/apache/commons-scxml) | [commons-scxml](https://commons.apache.org/proper/commons-scxml) | +| Apache Commons Signing | [commons-signing](https://github.com/apache/commons-signing) | [commons-signing](https://commons.apache.org/proper/commons-signing) | +| Apache Commons Skin | [commons-skin](https://github.com/apache/commons-skin) | [commons-skin](https://commons.apache.org/proper/commons-skin) | +| Apache Commons Statistics | [commons-statistics](https://github.com/apache/commons-statistics) | [commons-statistics](https://commons.apache.org/proper/commons-statistics) | +| Apache Commons Testing | [commons-testing](https://github.com/apache/commons-testing) | [commons-testing](https://commons.apache.org/proper/commons-testing) | +| Apache Commons Text | [commons-text](https://github.com/apache/commons-text) | [commons-text](https://commons.apache.org/proper/commons-text) | +| Apache Commons Validator | [commons-validator](https://github.com/apache/commons-validator) | [commons-validator](https://commons.apache.org/proper/commons-validator) | +| Apache Commons VFS | [commons-vfs](https://github.com/apache/commons-vfs) | [commons-vfs](https://commons.apache.org/proper/commons-vfs) | +| Apache Commons Weaver | [commons-weaver](https://github.com/apache/commons-weaver) | [commons-weaver](https://commons.apache.org/proper/commons-weaver) | diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index e7d8c9c..a839670 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,59 +1,248 @@ - Apache Commons Net 3.6 + Apache Commons Net 3.9.0 RELEASE NOTES -The Apache Commons Net team is pleased to announce the release of Apache Commons Net 3.6 +The Apache Commons Net team is pleased to announce the release of Apache Commons Net 3.9.0. Apache Commons Net library contains a collection of network utilities and protocol implementations. Supported protocols include: Echo, Finger, FTP, NNTP, NTP, POP3(S), SMTP(S), Telnet, Whois -This is mainly a bug-fix release. See further details below. -This release is binary compatible with previous releases. -However it is not source compatible with releases before 3.4, as some methods were added to the interface NtpV3Packet in 3.4 +Maintenance and bug fix release (Java 8). + +For complete information on Apache Commons Net, including instructions on how to submit bug reports, +patches, or suggestions for improvement, see the Apache Apache Commons Net website: + +https://commons.apache.org/proper/commons-net/ + +Download page: https://commons.apache.org/proper/commons-net/download_net.cgi + +Changes in this version include: +=============================== + +New features: +o [FTP] Add FTPClient.mdtmInstant(String). Thanks to Gary Gregory. +o [FTP] Add MLSxEntryParser.parseGmtInstant(String). Thanks to Gary Gregory. +o [FTP] Add FTPClient.getControlKeepAliveReplyTimeoutDuration(). Thanks to Gary Gregory. +o [FTP] Add FTPClient.setControlKeepAliveReplyTimeout(Duration). Thanks to Gary Gregory. +o [FTP] Add FTPClient.getControlKeepAliveTimeoutDuration(). Thanks to Gary Gregory. +o [FTP] Add FTPClient.setControlKeepAliveTimeout(Duration). Thanks to Gary Gregory. +o [FTP] Add FTPClient.getDataTimeout(). Thanks to Gary Gregory. +o [FTP] Add FTPClient.setDataTimeout(Duration). Thanks to Gary Gregory. +o [FTP] Add FTPFile.getTimestampInstant(). Thanks to Gary Gregory. +o Add github/codeql-action. Thanks to Gary Gregory. + +Fixed Bugs: +o NET-708: Use yyyy instead of YYYY in SimpleDateFormat #97. Thanks to XenoAmess. +o Prevent serialization of the 4 classes that implement Serializable. + It is not useful and is unlikely to work properly. +o Use Math.min and Math.max method instead of manual calculations. #104. Thanks to Arturo Bernal. +o NET-711: Add FTP option to toggle use of return host like CURL. Thanks to Jochen Wiedmann, Gary Gregory. +o NET-642: FTPSClient execPROT removes proxy settings #90. Thanks to Yani Mihaylov, Gary Gregory. +o JUnit5 assertThrows SimpleSMTPHeaderTestCase #121. Thanks to John Patrick, Gary Gregory. +o JUnit5 assertThrows TestTimeInfo #120. Thanks to John Patrick, Gary Gregory. +o Simplify conditions avoiding extra operations #88. Thanks to Arturo Bernal, Gary Gregory. +o Remove reflection from SSLSocketUtils. Thanks to Gary Gregory. +o NET-707: Process files with spaces in name for OS400 #95. Thanks to Dmytro Sylaiev, sebbASF, Gary Gregory. + +Changes: +o Bump actions/cache from 2.1.6 to 3.0.11 #93, #102, #115, #116. Thanks to Dependabot, Gary Gregory. +o Bump actions/checkout from 2.3.4 to 3.1.0 #89, #91, #100, #114. Thanks to Dependabot, Gary Gregory. +o Bump actions/upload-artifact from 3.1.0 to 3.1.1 #124. Thanks to Dependabot. +o Bump junit from 4.13.1 to 5.9.1 Vintage #74. Thanks to Dependabot. +o Bump commons-io from 2.6 to 2.11.0 #60. Thanks to Dependabot, Gary Gregory. +o Bump commons.jacoco.version from 0.8.6 to 0.8.8. Thanks to Gary Gregory. +o Bump commons.japicmp.version from 0.14.3 to 0.17.1. Thanks to Gary Gregory. +o Bump commons.surefire.version from 2.22.2 to 3.0.0-M7. Thanks to Gary Gregory. +o Bump ftpserver-core from 1.1.1 to 1.2.0 #96. Thanks to XenoAmess, Gary Gregory. +o Bump exec-maven-plugin from 3.0.0 to 3.1.0 #109. Thanks to Dependabot. +o Bump commons-parent from 53 to 54 #112. Thanks to Dependabot. + + +Historical list of changes: https://commons.apache.org/proper/commons-net/changes-report.html + +Enjoy! +-Apache Commons Net team + +----------------------------------------------------------------------------- + + Apache Commons Net 3.8.0 + RELEASE NOTES + +The Apache Commons Net team is pleased to announce the release of Apache Commons Net 3.8.0. + +Apache Commons Net library contains a collection of network utilities and protocol implementations. +Supported protocols include: Echo, Finger, FTP, NNTP, NTP, POP3(S), SMTP(S), Telnet, Whois + + +Maintenance and bug fix release (Java 7). + +For complete information on Apache Commons Net, including instructions on how to submit bug reports, +patches, or suggestions for improvement, see the Apache Apache Commons Net website: + +https://commons.apache.org/proper/commons-net/ + +Download page: https://commons.apache.org/proper/commons-net/download_net.cgi + +Changes in this version include: +=============================== + +New features: +o Add and use NetConstants. Thanks to Arturo Bernal, Gary Gregory. +o Add and use SocketClient.applySocketAttributes(). Thanks to Gary Gregory. +o Add FTPClient.hasFeature(FTPCmd). Thanks to Gary Gregory. +o Add FTPClient.mdtmCalendar(String). Thanks to Gary Gregory. + +Fixed Bugs: +o Fix concurrent counting of chunks in IMAPExportMbox. Thanks to Gary Gregory. +o Fix possible if rare NPEs in tests. Thanks to Gary Gregory. + +Changes: +o Bump actions/checkout from v2.3.3 to v2.3.4 #69. Thanks to Dependabot. +o NET-685: Update SocketClient default connect timeout from ? to 60 seconds #51. Thanks to Simo385. +o NET-695: Apply SocketClient timeout after connection but before SSL negotiation. Thanks to Gary Gregory, Possibly Cott. +o Minor Improvements #71, #72. Thanks to Arturo Bernal, Gary Gregory. +o Bump actions/cache from v2 to v2.1.4 #73. Thanks to Dependabot. + + +Historical list of changes: https://commons.apache.org/proper/commons-net/changes-report.html + +Enjoy! +-Apache Commons Net team + +----------------------------------------------------------------------------- + + Apache Commons Net 3.7.2 + RELEASE NOTES + +The Apache Commons Net team is pleased to announce the release of Apache Commons Net 3.7.2. + +Apache Commons Net library contains a collection of network utilities and protocol implementations. +Supported protocols include: Echo, Finger, FTP, NNTP, NTP, POP3(S), SMTP(S), Telnet, Whois + + +Maintenance and bug fix release. -The code now requires a minimum of Java 1.6. +For complete information on Apache Commons Net, including instructions on how to submit bug reports, +patches, or suggestions for improvement, see the Apache Apache Commons Net website: -Changes to functionality: -* The FTP client now performs stricter checks on non-multiline command replies. - The 3 digit code must now be followed by a space and some text, as per RFC 959. - To suppress this stricter checking, call FTP#setStrictReplyParsing(false). This should not be needed with a well-behaved server. - Note also that if strict checking is disabled, some functions may unconditionally strip the next character after the code, without checking it if is a space. -* The FTP client mlistFile() method now checks for a leading space before removing it. - If the space is missing, a MalformedServerReplyException is thrown. - This will only happen if the FTP server is not compliant with RFC 3659. +https://commons.apache.org/proper/commons-net/ -Notable additions: -* The POP3Mail examples can now get password from console, stdin or an environment variable. -* TFTPClient code has been rewritten to improve error handling and retries. +Download page: https://commons.apache.org/proper/commons-net/download_net.cgi Changes in this version include: +=============================== Fixed Bugs: -o NET-613: TFTPClient assumes that lastBlock == 0 only once -o NET-320: Allow TFTPServer.java to bind to a specific network adapter Thanks to Kevin Bulebush. -o NET-414: Apache Commons TFTP does not reject request replies that originate from a control port. Thanks to Chuck Wolber. -o NET-477: TFTP sendFile retry broken Thanks to John Walton. -o NET-596: NullPointerException when disconnecting TelnetClient twice with JDK 7 Thanks to Vincent Bories-Azeau. -o NET-602: Failure to parse times from SYST_L8 systems that report as "WINDOWS Type: L8" Thanks to Ross Braithwaite. -o NET-604: TFTP send and receive don't have progress indication Thanks to Frank Delporte. -o NET-588: FTPClient.setPassiveNatWorkaround assumes host is outside site local range Thanks to Dave Nice / Thai H. -o NET-610: FTPClient.mlistFile incorrectly handles MLST reply Thanks to Sergey Yanzin. -o NET-611: FTP does not validate command reply syntax fully -o NET-609: DefaultUnixFTPFileEntryParserFactory Issue (leading spaces removal configuration) Thanks to Tqup3. -o NET-597: FTP fails to parse listings for Solaris 10 FTPd in Japanese Thanks to Hiroki Taniura. -o NET-593: HostnameVerifier is called with ip addess instead of the provided hostname Thanks to J�rg Weule. -o NET-594: TelnetClient._closeOutputStream unhandled exception from FilterOutputStream.close Thanks to Brad Worrral. -o NET-592: plainSocket in FTPSClient is never closed Thanks to Mark Ford. +o NET-689: Host name is not set on the SSLSocket causing isEndpointCheckingEnabled to fail. Thanks to Charlie, Gary Gregory. +o Fix possible socket and input stream leak on socket exception in org.apache.commons.net.ftp.FTPClient._retrieveFile(String, String, OutputStream). Thanks to Dependabot. +o NET-690: Performance issue when using the FTPClient to retrieve files #65. Thanks to payal-meh, Gary Gregory. Changes: -o NET-612: Allow TFTP socket IO tracing -o POP3Mail example: support host port; allow reading password from Console/stdin/environment -o NET-599: Add shorthand FTPClientConfig constructor +o NET-691: Improve Javadoc for IMAPSClient #68. Thanks to Lewis John McGibbney. +o Bump actions/setup-java from v1.4.2 to v1.4.3 #62. Thanks to Dependabot. +o Bump junit from 4.13 to 4.13.1 #67. Thanks to Dependabot. + + +Historical list of changes: https://commons.apache.org/proper/commons-net/changes-report.html + +Enjoy! +-Apache Commons Net team + +----------------------------------------------------------------------------- + + Apache Commons Net 3.7.1 + RELEASE NOTES + +The Apache Commons Net team is pleased to announce the release of Apache Commons Net 3.7.1 + +Apache Commons Net library contains a collection of network utilities and protocol implementations. +Supported protocols include: Echo, Finger, FTP, NNTP, NTP, POP3(S), SMTP(S), Telnet, Whois -Historical list of changes: http://commons.apache.org/proper/commons-net/changes-report.html +Maintenance and bug fix release. For complete information on Apache Commons Net, including instructions on how to submit bug reports, patches, or suggestions for improvement, see the Apache Apache Commons Net website: -http://commons.apache.org/proper/commons-net/ +https://commons.apache.org/proper/commons-net/ + +Download page: https://commons.apache.org/proper/commons-net/download_net.cgi + +Changes in this version include: +=============================== + +Fixed Bugs: +o NET-687: [FTPS] javax.net.ssl.SSLException: Unsupported or unrecognized SSL message, #59. Thanks to Gary Gregory, Mikael, j-verse. +o NET-673: Update actions/checkout from v2.3.1 to v2.3.3 #56, #61. Thanks to Dependabot. +o NET-673: Update actions/setup-java from v1.4.0 to v1.4.2 #58. Thanks to Dependabot. + + +Historical list of changes: https://commons.apache.org/proper/commons-net/changes-report.html + +Enjoy! +-Apache Commons Net team + +----------------------------------------------------------------------------- + + Apache Commons Net 3.7 + RELEASE NOTES + +The Apache Commons Net team is pleased to announce the release of Apache Commons Net 3.7 + +Apache Commons Net library contains a collection of network utilities and protocol implementations. +Supported protocols include: Echo, Finger, FTP, NNTP, NTP, POP3(S), SMTP(S), Telnet, Whois + +This is mainly a bug-fix release. See further details below. + This release requires a minimum of Java 7. + This release is binary compatible with previous releases. +However it is not source compatible with releases before 3.4, as some methods were added to the interface NtpV3Packet in 3.4 + Note that the examples packages were moved under org/apache/commons/net/examples. +The examples are not part of the public API, so this does not affect compatibility. + +Changes in this version include: + +New features: +o NET-646: ALLO FTP Command for files >2GB +o NET-615: IMAPClient could simplify using empty arguments +o NET-614: IMAP fails to quote/encode mailbox names +o NET-648: Add Automatic-Module-Name MANIFEST entry for Java 9 compatibility +o NET-638: Telnet subnegotiations hard-limited to 512 bytes - allow override Thanks to Daniel Leong. +o NET-634: Add SIZE command support Thanks to Mauro Molinari. +o Add POP3ExportMbox example code +o NET-674: FTPListParseEngine should support listing via MLSD Thanks to Chris Steingen. +o NET-660: Next and Previous IP Address in SubnetUtils.SubnetInfo Thanks to Nagabhushan S N. + +Fixed Bugs: +o NET-673: IMAPClient.APPEND does not always calculate the correct length +o NET-643: NPE when closing telnet stream Thanks to Vasily. +o NET-641: SubnetUtils.SubnetInfo.isInRange("0.0.0.0") returns true for CIDR/31, 32 Thanks to pin_ptr. +o NET-639: MVSFTPEntryParser.preParse - MVS, z/OS - allow for merged Ext/Used fields Thanks to Alexander Eller. +o NET-636: examples should be in org.apache.commons.net subpackage +o NET-631: Bug in MVSFTPEntryParser.parseUnixList (FindBugs) +o NET-584: Error when using org.apache.commons.net.ftp.FTPClient setControlKeepAliveTimeout Thanks to Kazantsev Andrey Sergeevich/Nick Manley. +o NET-624: SubnetInfo#toCidrNotation: A wrong format subnet mask is allowed Thanks to Makoto Sakaguchi. +o NET-623: SubnetUtils - fixed spelling errors Thanks to Makoto Sakaguchi. +o NET-613: System Information Leak in ftp parser Thanks to Donald Kwakkel. +o NET-663: NullPointerException when FTPClient remote verification fails Thanks to Max Shenfield. +o NET-649: 227 Entering Passive Mode Thanks to Filipe Bojikian Rissi. +o NET-682: MVSFTPEntryParser doesn't support Record Formats of U Thanks to richard. + +Changes: +o NET-633: Add XOAUTH2 to IMAP and SMTP Thanks to n0rm1e. +o NET-632: FTPHTTPClient - support for encoding other than UTF-8 Thanks to prakapenka. +o NET-626: SubnetUtils#SubnetUtils - improved comment Thanks to Makoto Sakaguchi. +o NET-625: SubnetUtils - improve construction +o NET-624: SubnetInfo#getCidrSignature - improve functions Thanks to Makoto Sakaguchi. +o NET-621: SubnetUtils#SubnetInfo - remove unnecessary accessors Thanks to Makoto Sakaguchi. +o NET-619: SubnetUtils - improve binary netmask algorithm Thanks to Makoto Sakaguchi. +o NET-678: VMS ftp LIST parsing results in empty file list Thanks to Roman Grigoriadi. + + +Historical list of changes: https://commons.apache.org/proper/commons-net/changes-report.html + +For complete information on Apache Commons Net, including instructions on how to submit bug reports, +patches, or suggestions for improvement, see the Apache Apache Commons Net website: + +https://commons.apache.org/proper/commons-net/ + +Download page: https://commons.apache.org/proper/commons-net/download_net.cgi diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..51943ba --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,17 @@ +<!--- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +The Apache Commons security page is [https://commons.apache.org/security.html](https://commons.apache.org/security.html). diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml index 448c654..0a54532 100644 --- a/checkstyle-suppressions.xml +++ b/checkstyle-suppressions.xml @@ -17,8 +17,8 @@ limitations under the License. --> <!DOCTYPE suppressions PUBLIC - "-//Puppy Crawl//DTD Suppressions 1.1//EN" - "http://www.puppycrawl.com/dtds/suppressions_1_1.dtd"> + "-//Checkstyle//DTD SuppressionFilter Configuration 1.1//EN" + "https://checkstyle.org/dtds/suppressions_1_1.dtd"> <suppressions> <!-- On Windows, it appears that Checkstyle matches files using \ delims --> diff --git a/checkstyle.xml b/checkstyle.xml index 987b86e..a34cbfc 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -17,8 +17,8 @@ limitations under the License. --> <!DOCTYPE module PUBLIC - "-//Puppy Crawl//DTD Check Configuration 1.1//EN" - "http://www.puppycrawl.com/dtds/configuration_1_1.dtd"> + "-//Checkstyle//DTD Checkstyle Configuration 1.1//EN" + "https://checkstyle.org/dtds/configuration_1_1.dtd"> <!-- Commons Net customization of default Checkstyle behavior --> <module name="Checker"> @@ -48,17 +48,17 @@ limitations under the License. <property name="fileExtensions" value="java" /> </module> + <module name="LineLength"> + <property name="max" value="160"/> + </module> + <module name="TreeWalker"> - <property name="cacheFile" value="target/cachefile"/> <module name="AvoidStarImport"> <property name="excludes" value="org.junit.Assert"/> </module> <module name="RedundantImport"/> <module name="UnusedImports"/> <module name="NeedBraces"/> - <module name="LineLength"> - <property name="max" value="132"/> - </module> <!-- Modifier Checks --> <!-- See http://checkstyle.sf.net/config_modifiers.html --> @@ -75,31 +75,28 @@ limitations under the License. <module name="EmptyCatchBlock"></module> <module name="JavadocMethod"> - <property name="scope" value="public"/> - <property name="allowUndeclaredRTE" value="true"/> - <property name="allowMissingJavadoc" value="true"/> + <property name="accessModifiers" value="public"/> <property name="allowMissingParamTags" value="true"/> - <property name="allowMissingThrowsTags" value="true"/> </module> - </module> + <module name="SuppressionCommentFilter"/> - <module name="SuppressionCommentFilter"/> + <!-- + Allow comment to suppress checkstyle for a single line + e.g. // CHECKSTYLE IGNORE MagicNumber + --> + <module name="SuppressWithNearbyCommentFilter"> + <property name="commentFormat" value="CHECKSTYLE IGNORE (\w+)"/> + <property name="checkFormat" value="$1"/> + </module> + + </module> <module name="SuppressionFilter"> <!-- config_loc is used by Eclipse plugin --> <property name="file" value="${config_loc}/checkstyle-suppressions.xml"/> </module> - <!-- - Allow comment to suppress checkstyle for a single line - e.g. // CHECKSTYLE IGNORE MagicNumber - --> - <module name="SuppressWithNearbyCommentFilter"> - <property name="commentFormat" value="CHECKSTYLE IGNORE (\w+)"/> - <property name="checkFormat" value="$1"/> - </module> - </module> diff --git a/findbugs-exclude-filter.xml b/findbugs-exclude-filter.xml index 43aab16..19a7b11 100644 --- a/findbugs-exclude-filter.xml +++ b/findbugs-exclude-filter.xml @@ -21,7 +21,10 @@ false positive nature has been analyzed individually and they have been put here to instruct Findbugs it must ignore them. --> -<FindBugsFilter> +<FindBugsFilter + xmlns="https://github.com/spotbugs/filter/3.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd"> <!-- Unchecked cast is deliberate --> <Match> diff --git a/pom.xml b/pom.xml index 2ee8713..9ee1f19 100644 --- a/pom.xml +++ b/pom.xml @@ -18,217 +18,136 @@ --> -<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"> +<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> <parent> <groupId>org.apache.commons</groupId> <artifactId>commons-parent</artifactId> - <version>42</version> + <version>54</version> </parent> - <modelVersion>4.0.0</modelVersion> + <groupId>commons-net</groupId> <artifactId>commons-net</artifactId> - <version>3.6</version> + <version>3.9.0</version> <name>Apache Commons Net</name> - <!-- N.B. the description content is deliberately not indented - ! to improve the layout of the Release Notes generated by mvn changes:announcement-generate - --> + <!-- N.B. the description content is deliberately not indented ! to improve the layout of the Release Notes generated + by mvn changes:announcement-generate --> <description> Apache Commons Net library contains a collection of network utilities and protocol implementations. Supported protocols include: Echo, Finger, FTP, NNTP, NTP, POP3(S), SMTP(S), Telnet, Whois </description> - <url>http://commons.apache.org/proper/commons-net/</url> - <issueManagement> - <system>jira</system> - <url>http://issues.apache.org/jira/browse/NET</url> - </issueManagement> + <url>https://commons.apache.org/proper/commons-net/</url> <inceptionYear>2001</inceptionYear> - <scm> - <connection>scm:svn:http://svn.apache.org/repos/asf/commons/proper/net/tags/NET_3_6</connection> - <developerConnection>scm:svn:https://svn.apache.org/repos/asf/commons/proper/net/tags/NET_3_6</developerConnection> - <url>http://svn.apache.org/viewvc/commons/proper/net/tags/NET_3_6</url> - </scm> - - <developers> - <developer> - <name>Jeffrey D. Brekke</name> - <id>brekke</id> - <email>Jeff.Brekke@qg.com</email> - <organization>Quad/Graphics, Inc.</organization> - </developer> - <developer> - <name>Steve Cohen</name> - <id>scohen</id> - <email>scohen@apache.org</email> - <organization>javactivity.org</organization> - </developer> - <developer> - <name>Bruno D'Avanzo</name> - <id>brudav</id> - <email>bruno.davanzo@hp.com</email> - <organization>Hewlett-Packard</organization> - </developer> - <developer> - <name>Daniel F. Savarese</name> - <id>dfs</id> - <email>dfs@apache.org</email> - <organization> - <a href="http://www.savarese.com/">Savarese Software Research</a> - </organization> - </developer> - <developer> - <name>Rory Winston</name> - <id>rwinston</id> - <email>rwinston@apache.org</email> - <organization /> - </developer> - <developer> - <name>Rory Winston</name> - <email>rwinston@checkfree.com</email> - <organization /> - </developer> - </developers> - - <contributors> - <contributor> - <name>Henrik Sorensen</name> - <email>henrik.sorensen@balcab.ch</email> - </contributor> - <contributor> - <name>Jeff Nadler</name> - <email>jnadler@srcginc.com</email> - </contributor> - <contributor> - <name>William Noto</name> - <email>wnoto@openfinance.com</email> - </contributor> - <contributor> - <name>Stephane ESTE-GRACIAS</name> - <email>sestegra@free.fr</email> - </contributor> - <contributor> - <name>Dan Armbrust</name> - <email>daniel.armbrust.list@gmail.com</email> - </contributor> - <contributor> - <name>Yuval Kashtan</name> - </contributor> - <contributor> - <name>Joseph Hindsley</name> - </contributor> - <contributor> - <name>Rob Hasselbaum</name> - <email>rhasselbaum@alumni.ithaca.edu</email> - </contributor> - <contributor> - <name>Mario Ivankovits</name> - <email>mario@ops.co.at</email> - </contributor> - <contributor> - <name>Naz Irizarry</name> - <organization>MITRE Corp</organization> - </contributor> - <contributor> - <name>Tapan Karecha</name> - <email>tapan@india.hp.com</email> - </contributor> - <contributor> - <name>Jason Mathews</name> - <organization>MITRE Corp</organization> - </contributor> - <contributor> - <name>Winston Ojeda</name> - <email>Winston.Ojeda@qg.com</email> - <organization>Quad/Graphics, Inc.</organization> - </contributor> - <contributor> - <name>Ted Wise</name> - <email>ctwise@bellsouth.net</email> - </contributor> - <contributor> - <name>Bogdan Drozdowski</name> - <email>bogdandr # op dot pl</email> - </contributor> - </contributors> - - <dependencies> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <version>4.12</version> - <scope>test</scope> - </dependency> - </dependencies> - - <distributionManagement> - <site> - <id>apache.website</id> - <name>Apache Commons Site</name> - <url>scm:svn:https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-net/</url> - </site> - </distributionManagement> <properties> <!-- Environment --> - <maven.compiler.source>1.6</maven.compiler.source> - <maven.compiler.target>1.6</maven.compiler.target> - <commons.javadoc.java.link>http://download.oracle.com/javase/1.6.0/docs/api/</commons.javadoc.java.link> + <maven.compiler.source>1.8</maven.compiler.source> + <maven.compiler.target>1.8</maven.compiler.target> + <commons.javadoc.java.link>${commons.javadoc7.java.link}</commons.javadoc.java.link> <commons.componentid>net</commons.componentid> + <commons.module.name>org.apache.commons.net</commons.module.name> <commons.jira.id>NET</commons.jira.id> <commons.jira.pid>12310487</commons.jira.pid> + <commons.scmPubCheckoutDirectory>site-content</commons.scmPubCheckoutDirectory> + <commons.scmPubUrl>https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-net</commons.scmPubUrl> + <japicmp.skip>false</japicmp.skip> + <jacoco.skip>false</jacoco.skip> + <!-- Current release --> - <commons.release.version>3.6</commons.release.version> + <commons.release.version>3.9.0</commons.release.version> <commons.rc.version>RC1</commons.rc.version> <commons.release.desc>(Requires Java ${maven.compiler.target} or later)</commons.release.desc> - <!-- Previous release --> - <commons.release.2.version>1.4.1</commons.release.2.version> - <!-- Override Commons parent, as version 1.4.1 does not use the -bin suffix --> - <commons.release.2.binary.suffix /> - <commons.release.2.desc>(Requires Java 1.3 or later)</commons.release.2.desc> - + <!-- Release plugin defines --> + <commons.bc.version>3.8.0</commons.bc.version> + <commons.release.isDistModule>true</commons.release.isDistModule> + <commons.releaseManagerName>Gary Gregory</commons.releaseManagerName> + <commons.releaseManagerKey>86fdc7e2a11262cb</commons.releaseManagerKey> <!-- Local version defines --> - <checkstyle.plugin.version>2.17</checkstyle.plugin.version> - <checkstyle.tool.version>6.9</checkstyle.tool.version> <commons.changes.onlyCurrentVersion>true</commons.changes.onlyCurrentVersion> + <commons.junit.version>5.9.1</commons.junit.version> + <commons.jacoco.version>0.8.8</commons.jacoco.version> + <commons.japicmp.version>0.17.1</commons.japicmp.version> + <commons.surefire.version>3.0.0-M7</commons.surefire.version> + <!-- for debugging FTPSClientTest --> + <commons.net.trace_calls>false</commons.net.trace_calls> + <commons.net.add_listener>false</commons.net.add_listener> </properties> + <scm> + <connection>scm:git:https://gitbox.apache.org/repos/asf/commons-net</connection> + <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/commons-net</developerConnection> + <url>https://gitbox.apache.org/repos/asf/commons-net</url> + </scm> + <issueManagement> + <system>jira</system> + <url>https://issues.apache.org/jira/browse/NET</url> + </issueManagement> + + <distributionManagement> + <site> + <id>apache.website</id> + <name>Apache Commons Site</name> + <url>scm:svn:https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-net/</url> + </site> + </distributionManagement> + + <dependencies> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.vintage</groupId> + <artifactId>junit-vintage-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.ftpserver</groupId> + <artifactId>ftpserver-core</artifactId> + <version>1.2.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + <version>2.11.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <version>3.12.0</version> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <defaultGoal>clean apache-rat:check javadoc:javadoc checkstyle:check package japicmp:cmp</defaultGoal> <pluginManagement> - <plugins> - <!-- Allow CLI usage --> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>findbugs-maven-plugin</artifactId> - <version>${commons.findbugs.version}</version> - <configuration> - <excludeFilterFile>findbugs-exclude-filter.xml</excludeFilterFile> - </configuration> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-compiler-plugin</artifactId> - <version>${commons.compiler.version}</version> - <configuration> - <!-- Fix incremental compiler bug, see https://jira.codehaus.org/browse/MCOMPILER-205 etc. --> - <excludes> - <exclude>**/package-info.java</exclude> - </excludes> - </configuration> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-checkstyle-plugin</artifactId> - <version>${checkstyle.plugin.version}</version> - <dependencies> - <dependency> - <groupId>com.puppycrawl.tools</groupId> - <artifactId>checkstyle</artifactId> - <version>${checkstyle.tool.version}</version> - </dependency> - </dependencies> - </plugin> - </plugins> + <plugins> + <!-- Allow CLI usage --> + <plugin> + <groupId>com.github.spotbugs</groupId> + <artifactId>spotbugs-maven-plugin</artifactId> + <configuration> + <excludeFilterFile>findbugs-exclude-filter.xml</excludeFilterFile> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-checkstyle-plugin</artifactId> + <version>${commons.checkstyle-plugin.version}</version> + </plugin> + </plugins> </pluginManagement> <plugins> @@ -238,8 +157,13 @@ Supported protocols include: Echo, Finger, FTP, NNTP, NTP, POP3(S), SMTP(S), Tel <artifactId>maven-jar-plugin</artifactId> <configuration> <excludes> - <exclude>examples/**</exclude> + <exclude>**/examples/**</exclude> </excludes> + <archive combine.children="append"> + <manifestEntries> + <Automatic-Module-Name>${commons.module.name}</Automatic-Module-Name> + </manifestEntries> + </archive> </configuration> </plugin> @@ -249,7 +173,7 @@ Supported protocols include: Echo, Finger, FTP, NNTP, NTP, POP3(S), SMTP(S), Tel <artifactId>maven-source-plugin</artifactId> <configuration> <excludes> - <exclude>examples/**</exclude> + <exclude>**/examples/**</exclude> </excludes> </configuration> </plugin> @@ -262,6 +186,10 @@ Supported protocols include: Echo, Finger, FTP, NNTP, NTP, POP3(S), SMTP(S), Tel <exclude>**/*FunctionalTest.java</exclude> <exclude>**/POP3*Test.java</exclude> </excludes> + <environmentVariables> + <TRACE_CALLS>${commons.net.trace_calls}</TRACE_CALLS> + <ADD_LISTENER>${commons.net.add_listener}</ADD_LISTENER> + </environmentVariables> </configuration> </plugin> @@ -289,37 +217,44 @@ Supported protocols include: Echo, Finger, FTP, NNTP, NTP, POP3(S), SMTP(S), Tel <attribute name="Extension-Name" value="org.apache.commons.net" /> <attribute name="Specification-Title" value="${project.name}" /> <attribute name="Implementation-Title" value="${project.name}" /> - <attribute name="Implementation-Vendor" value="${project.organization.name}" /> + <attribute name="Implementation-Vendor" + value="${project.organization.name}" /> <attribute name="Implementation-Version" value="${project.version}" /> <attribute name="Implementation-Vendor-Id" value="org.apache" /> - <attribute name="Implementation-Build" value="${implementation.build}"/> - <attribute name="X-Compile-Source-JDK" value="${maven.compiler.source}" /> - <attribute name="X-Compile-Target-JDK" value="${maven.compiler.target}" /> + <attribute name="Implementation-Build" value="${implementation.build}" /> + <attribute name="X-Compile-Source-JDK" + value="${maven.compiler.source}" /> + <attribute name="X-Compile-Target-JDK" + value="${maven.compiler.target}" /> </manifest> - <fileset dir="target/classes" includes="org/apache/commons/net/ftp/**,org/apache/commons/net/*,org/apache/commons/net/io/*,org/apache/commons/net/util/*" /> + <fileset dir="target/classes" + includes="org/apache/commons/net/ftp/**,org/apache/commons/net/*,org/apache/commons/net/io/*,org/apache/commons/net/util/*" /> </jar> - <!-- - Create the binary examples jar, which will be added to the binary zip/tgz, - but not deployed independently to Maven - --> + <!-- Create the binary examples jar, which will be added to the binary zip/tgz, but not deployed + independently to Maven --> <jar destfile="target/commons-net-examples-${project.version}.jar"> <metainf dir="${basedir}" includes="NOTICE.txt,LICENSE.txt" /> <manifest> <attribute name="Extension-Name" value="org.apache.commons.net" /> <attribute name="Specification-Title" value="${project.name}" /> <attribute name="Implementation-Title" value="${project.name}" /> - <attribute name="Implementation-Vendor" value="${project.organization.name}" /> + <attribute name="Implementation-Vendor" + value="${project.organization.name}" /> <attribute name="Implementation-Version" value="${project.version}" /> <attribute name="Implementation-Vendor-Id" value="org.apache" /> - <attribute name="Implementation-Build" value="${implementation.build}"/> - <attribute name="X-Compile-Source-JDK" value="${maven.compiler.source}" /> - <attribute name="X-Compile-Target-JDK" value="${maven.compiler.target}" /> + <attribute name="Implementation-Build" value="${implementation.build}" /> + <attribute name="X-Compile-Source-JDK" + value="${maven.compiler.source}" /> + <attribute name="X-Compile-Target-JDK" + value="${maven.compiler.target}" /> <!-- Helper application --> - <attribute name="Main-Class" value="examples/Main" /> + <attribute name="Main-Class" + value="org.apache.commons.net.examples.Main" /> <!-- Allow java -jar examples.jar to work --> - <attribute name="Class-Path" value="commons-net-${project.version}.jar" /> + <attribute name="Class-Path" + value="commons-net-${project.version}.jar" /> </manifest> - <fileset dir="target/classes" includes="examples/**" /> + <fileset dir="target/classes" includes="**/examples/**" /> </jar> </target> </configuration> @@ -329,8 +264,8 @@ Supported protocols include: Echo, Finger, FTP, NNTP, NTP, POP3(S), SMTP(S), Tel </execution> </executions> </plugin> - <!-- Attaches the commons-net-ftp and examples JARs to the Maven lifecycle - to ensure they will be signed and deployed as normal --> + <!-- Attaches the commons-net-ftp and examples JARs to the Maven lifecycle to ensure they will be signed and + deployed as normal --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>build-helper-maven-plugin</artifactId> @@ -364,83 +299,67 @@ Supported protocols include: Echo, Finger, FTP, NNTP, NTP, POP3(S), SMTP(S), Tel <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <configuration> - <excludePackageNames>examples.*</excludePackageNames> + <excludePackageNames>*.examples.*</excludePackageNames> </configuration> </plugin> <!-- Copy the examples sources --> <plugin> - <artifactId>maven-resources-plugin</artifactId> - <executions> - <execution> - <id>copy-resources</id> - <phase>pre-site</phase> - <goals> - <goal>copy-resources</goal> - </goals> - <configuration> - <outputDirectory>${basedir}/target/site/examples</outputDirectory> - <resources> - <resource> - <directory>src/main/java/examples</directory> - <excludes> - <exclude>**/Main.java</exclude> - </excludes> - <filtering>false</filtering> - </resource> - </resources> - </configuration> - </execution> - </executions> + <artifactId>maven-resources-plugin</artifactId> + <executions> + <execution> + <id>copy-resources</id> + <phase>pre-site</phase> + <goals> + <goal>copy-resources</goal> + </goals> + <configuration> + <outputDirectory>${basedir}/target/site/examples</outputDirectory> + <resources> + <resource> + <directory>src/main/java/org/apache/commons/net/examples</directory> + <excludes> + <exclude>**/Main.java</exclude> + </excludes> + <filtering>false</filtering> + </resource> + </resources> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-scm-publish-plugin</artifactId> + <configuration> + <ignorePathsToDelete> + <ignorePathToDelete>javadocs</ignorePathToDelete> + </ignorePathsToDelete> + </configuration> + </plugin> + + <!-- Allow exec:java to launch examples from the classpath For example: mvn -q exec:java -Dexec.arguments=FTPClientExample,-A,-l,hostname --> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <version>3.1.0</version> + <executions> + <execution> + <goals> + <goal>java</goal> + </goals> + </execution> + </executions> + <configuration> + <mainClass>org.apache.commons.net.examples.Main</mainClass> + </configuration> </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-scm-publish-plugin</artifactId> - <configuration> - <ignorePathsToDelete> - <ignorePathToDelete>javadocs</ignorePathToDelete> - </ignorePathsToDelete> - </configuration> - </plugin> - - <!-- drop examples from CLI invocations --> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>clirr-maven-plugin</artifactId> - <configuration> - <excludes> - <exclude>examples/**</exclude> - </excludes> - </configuration> - </plugin> - - <!-- - Allow exec:java to launch examples from the classpath - For example: - - mvn -q exec:java -Dexec.arguments=FTPClientExample,-A,-l,hostname - --> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>exec-maven-plugin</artifactId> - <version>1.5.0</version> - <executions> - <execution> - <goals> - <goal>java</goal> - </goals> - </execution> - </executions> - <configuration> - <mainClass>examples.Main</mainClass> - </configuration> - </plugin> <!-- Allow checkstyle use from command line --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> - <version>${checkstyle.plugin.version}</version> + <version>${commons.checkstyle-plugin.version}</version> <configuration> <configLocation>${basedir}/checkstyle.xml</configLocation> <!-- Needed to define config_loc for use by Eclipse --> @@ -457,36 +376,25 @@ Supported protocols include: Echo, Finger, FTP, NNTP, NTP, POP3(S), SMTP(S), Tel <plugins> <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>findbugs-maven-plugin</artifactId> - <version>${commons.findbugs.version}</version> + <groupId>com.github.spotbugs</groupId> + <artifactId>spotbugs-maven-plugin</artifactId> <configuration> <excludeFilterFile>findbugs-exclude-filter.xml</excludeFilterFile> </configuration> </plugin> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>clirr-maven-plugin</artifactId> - <configuration> - <excludes> - <exclude>examples/**</exclude> - </excludes> - </configuration> - </plugin> - <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <configuration> - <excludePackageNames>examples.*</excludePackageNames> + <excludePackageNames>*.examples.*</excludePackageNames> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> - <version>${checkstyle.plugin.version}</version> + <version>${commons.checkstyle-plugin.version}</version> <configuration> <configLocation>${basedir}/checkstyle.xml</configLocation> <!-- Needed to define config_loc for use by Eclipse --> @@ -499,4 +407,139 @@ Supported protocols include: Echo, Finger, FTP, NNTP, NTP, POP3(S), SMTP(S), Tel </plugins> </reporting> + <profiles> + <profile> + <id>slf4j-simple</id> + <properties> + <commons.net.trace_calls>true</commons.net.trace_calls> + <commons.net.add_listener>true</commons.net.add_listener> + </properties> + <dependencies> + <!-- adds logging for MINA ftpserver as used by FTPSClientTest --> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <version>1.7.21</version> + <scope>test</scope> + </dependency> + </dependencies> + </profile> + </profiles> + + <developers> + <developer> + <name>Jeffrey D. Brekke</name> + <id>brekke</id> + <email>Jeff.Brekke@qg.com</email> + <organization>Quad/Graphics, Inc.</organization> + </developer> + <developer> + <name>Steve Cohen</name> + <id>scohen</id> + <email>scohen@apache.org</email> + <organization>javactivity.org</organization> + </developer> + <developer> + <name>Bruno D'Avanzo</name> + <id>brudav</id> + <email>bruno.davanzo@hp.com</email> + <organization>Hewlett-Packard</organization> + </developer> + <developer> + <name>Daniel F. Savarese</name> + <id>dfs</id> + <email>dfs@apache.org</email> + <organization> + <a href="http://www.savarese.com/">Savarese Software Research</a> </organization> + </developer> + <developer> + <name>Rory Winston</name> + <id>rwinston</id> + <email>rwinston@apache.org</email> + <organization /> + </developer> + <developer> + <name>Rory Winston</name> + <email>rwinston@checkfree.com</email> + <organization /> + </developer> + <developer> + <id>ggregory</id> + <name>Gary Gregory</name> + <email>ggregory at apache.org</email> + <url>https://www.garygregory.com</url> + <organization>The Apache Software Foundation</organization> + <organizationUrl>https://www.apache.org/</organizationUrl> + <roles> + <role>PMC Member</role> + </roles> + <timezone>America/New_York</timezone> + <properties> + <picUrl>https://people.apache.org/~ggregory/img/garydgregory80.png</picUrl> + </properties> + </developer> + </developers> + + <contributors> + <contributor> + <name>Henrik Sorensen</name> + <email>henrik.sorensen@balcab.ch</email> + </contributor> + <contributor> + <name>Jeff Nadler</name> + <email>jnadler@srcginc.com</email> + </contributor> + <contributor> + <name>William Noto</name> + <email>wnoto@openfinance.com</email> + </contributor> + <contributor> + <name>Stephane ESTE-GRACIAS</name> + <email>sestegra@free.fr</email> + </contributor> + <contributor> + <name>Dan Armbrust</name> + <email>daniel.armbrust.list@gmail.com</email> + </contributor> + <contributor> + <name>Yuval Kashtan</name> + </contributor> + <contributor> + <name>Joseph Hindsley</name> + </contributor> + <contributor> + <name>Rob Hasselbaum</name> + <email>rhasselbaum@alumni.ithaca.edu</email> + </contributor> + <contributor> + <name>Mario Ivankovits</name> + <email>mario@ops.co.at</email> + </contributor> + <contributor> + <name>Naz Irizarry</name> + <organization>MITRE Corp</organization> + </contributor> + <contributor> + <name>Tapan Karecha</name> + <email>tapan@india.hp.com</email> + </contributor> + <contributor> + <name>Jason Mathews</name> + <organization>MITRE Corp</organization> + </contributor> + <contributor> + <name>Winston Ojeda</name> + <email>Winston.Ojeda@qg.com</email> + <organization>Quad/Graphics, Inc.</organization> + </contributor> + <contributor> + <name>Ted Wise</name> + <email>ctwise@bellsouth.net</email> + </contributor> + <contributor> + <name>Bogdan Drozdowski</name> + <email>bogdandr # op dot pl</email> + </contributor> + </contributors> + </project> diff --git a/src/assembly/bin.xml b/src/assembly/bin.xml index 3c114dd..2576fe1 100644 --- a/src/assembly/bin.xml +++ b/src/assembly/bin.xml @@ -27,9 +27,9 @@ Licensed to the Apache Software Foundation (ASF) under one or more <fileSets> <fileSet> <includes> - <include>README*</include> <include>LICENSE*</include> <include>NOTICE*</include> + <include>README*</include> <include>RELEASE-NOTES.txt</include> </includes> </fileSet> @@ -65,8 +65,8 @@ Licensed to the Apache Software Foundation (ASF) under one or more </fileSet> <!-- Include example sources for developers --> <fileSet> - <directory>src/main/java/examples</directory> - <outputDirectory>examples</outputDirectory> + <directory>src/main/java/org/apache/commons/net/examples</directory> + <outputDirectory>org/apache/commons/net/examples</outputDirectory> <includes> <include>**/*</include> </includes> diff --git a/src/assembly/src.xml b/src/assembly/src.xml index 289c552..670b968 100644 --- a/src/assembly/src.xml +++ b/src/assembly/src.xml @@ -28,15 +28,16 @@ Licensed to the Apache Software Foundation (ASF) under one or more <fileSets> <fileSet> <includes> + <include>.travis.yml</include> <include>BUILDING.txt</include> + <include>checkstyle*.xml</include> <include>CONTRIBUTING.md</include> - <include>README*</include> + <include>findbugs-exclude-filter.xml</include> <include>LICENSE*</include> <include>NOTICE*</include> - <include>RELEASE-NOTES.txt</include> <include>pom.xml</include> - <include>findbugs-exclude-filter.xml</include> - <include>checkstyle*.xml</include> + <include>README*</include> + <include>RELEASE-NOTES.txt</include> </includes> </fileSet> <fileSet> diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 4de6617..e0319c4 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -1,4 +1,3 @@ -<?xml version="1.0"?> <!-- Licensed to the Apache Software Foundation (ASF) under one or more @@ -41,10 +40,10 @@ The <action> type attribute can be add,update,fix,remove. --> <document> - <properties> - <title>Changes</title> - <author email="dev@commons.apache.org">Apache Commons developers</author> - </properties> + <properties> + <title>Apache Commons Net Release Notes</title> + <author email="dev@commons.apache.org">Apache Commons developers</author> + </properties> <!-- NOTE: The description below is specially formatted so as to improve the layout of the generated release notes: @@ -62,15 +61,288 @@ The <action> type attribute can be add,update,fix,remove. Defining changes.version allows one to create the RN without first removing the SNAPSHOT suffix. --> - <body> - <release version="3.6" date="TBA" description=" -This is mainly a bug-fix release. See further details below. + <body> + <release version="3.9.0" date="2021-MM-DD" description="Maintenance and bug fix release (Java 8)."> + <!-- FIX --> + <action type="fix" issue="NET-708" dev="ggregory" due-to="XenoAmess"> + Use yyyy instead of YYYY in SimpleDateFormat #97. + </action> + <action type="fix" dev="sebb"> + Prevent serialization of the 4 classes that implement Serializable. + It is not useful and is unlikely to work properly. + </action> + <action type="fix" dev="ggregory" due-to="Arturo Bernal"> + Use Math.min and Math.max method instead of manual calculations. #104. + </action> + <action issue="NET-711" type="fix" dev="ggregory" due-to="Jochen Wiedmann, Gary Gregory"> + Add FTP option to toggle use of return host like CURL. + </action> + <action issue="NET-642" type="fix" dev="ggregory" due-to="Yani Mihaylov, Gary Gregory"> + FTPSClient execPROT removes proxy settings #90. + </action> + <action type="fix" dev="ggregory" due-to="John Patrick, Gary Gregory"> + JUnit5 assertThrows SimpleSMTPHeaderTestCase #121. + </action> + <action type="fix" dev="ggregory" due-to="John Patrick, Gary Gregory"> + JUnit5 assertThrows TestTimeInfo #120. + </action> + <action type="fix" dev="ggregory" due-to="Arturo Bernal, Gary Gregory"> + Simplify conditions avoiding extra operations #88. + </action> + <action type="fix" dev="ggregory" due-to="Gary Gregory"> + Remove reflection from SSLSocketUtils. + </action> + <action type="fix" issue="NET-707" dev="ggregory" due-to="Dmytro Sylaiev, sebbASF, Gary Gregory"> + Process files with spaces in name for OS400 #95. + </action> + <!-- ADD --> + <action type="add" dev="ggregory" due-to="Gary Gregory"> + [FTP] Add FTPClient.mdtmInstant(String). + </action> + <action type="add" dev="ggregory" due-to="Gary Gregory"> + [FTP] Add MLSxEntryParser.parseGmtInstant(String). + </action> + <action type="add" dev="ggregory" due-to="Gary Gregory"> + [FTP] Add FTPClient.getControlKeepAliveReplyTimeoutDuration(). + </action> + <action type="add" dev="ggregory" due-to="Gary Gregory"> + [FTP] Add FTPClient.setControlKeepAliveReplyTimeout(Duration). + </action> + <action type="add" dev="ggregory" due-to="Gary Gregory"> + [FTP] Add FTPClient.getControlKeepAliveTimeoutDuration(). + </action> + <action type="add" dev="ggregory" due-to="Gary Gregory"> + [FTP] Add FTPClient.setControlKeepAliveTimeout(Duration). + </action> + <action type="add" dev="ggregory" due-to="Gary Gregory"> + [FTP] Add FTPClient.getDataTimeout(). + </action> + <action type="add" dev="ggregory" due-to="Gary Gregory"> + [FTP] Add FTPClient.setDataTimeout(Duration). + </action> + <action type="add" dev="ggregory" due-to="Gary Gregory"> + [FTP] Add FTPFile.getTimestampInstant(). + </action> + <action type="add" dev="ggregory" due-to="Gary Gregory"> + Add github/codeql-action. + </action> + <!-- UPDATE --> + <action type="update" dev="ggregory" due-to="Dependabot, Gary Gregory"> + Bump actions/cache from 2.1.6 to 3.0.11 #93, #102, #115, #116. + </action> + <action type="update" dev="ggregory" due-to="Dependabot, Gary Gregory"> + Bump actions/checkout from 2.3.4 to 3.1.0 #89, #91, #100, #114. + </action> + <action dev="ggregory" type="update" due-to="Dependabot"> + Bump actions/upload-artifact from 3.1.0 to 3.1.1 #124. + </action> + <action type="update" dev="ggregory" due-to="Dependabot"> + Bump junit from 4.13.1 to 5.9.1 Vintage #74. + </action> + <action type="update" dev="ggregory" due-to="Dependabot, Gary Gregory"> + Bump commons-io from 2.6 to 2.11.0 #60. + </action> + <action dev="ggregory" type="update" due-to="Gary Gregory"> + Bump commons.jacoco.version from 0.8.6 to 0.8.8. + </action> + <action dev="ggregory" type="update" due-to="Gary Gregory"> + Bump commons.japicmp.version from 0.14.3 to 0.17.1. + </action> + <action dev="ggregory" type="update" due-to="Gary Gregory"> + Bump commons.surefire.version from 2.22.2 to 3.0.0-M7. + </action> + <action type="update" dev="ggregory" due-to="XenoAmess, Gary Gregory"> + Bump ftpserver-core from 1.1.1 to 1.2.0 #96. + </action> + <action type="update" dev="ggregory" due-to="Dependabot"> + Bump exec-maven-plugin from 3.0.0 to 3.1.0 #109. + </action> + <action type="update" dev="ggregory" due-to="Dependabot"> + Bump commons-parent from 53 to 54 #112. + </action> + </release> + <release version="3.8.0" date="2021-02-13" description="Maintenance and bug fix release (Java 7)."> + <!-- ADD --> + <action type="add" dev="ggregory" due-to="Arturo Bernal, Gary Gregory"> + Add and use NetConstants. + </action> + <action type="add" dev="ggregory" due-to="Gary Gregory"> + Add and use SocketClient.applySocketAttributes(). + </action> + <action type="add" dev="ggregory" due-to="Gary Gregory"> + [FTP] Add FTPClient.hasFeature(FTPCmd). + </action> + <action type="add" dev="ggregory" due-to="Gary Gregory"> + [FTP] Add FTPClient.mdtmCalendar(String). + </action> + <!-- FIX --> + <action type="fix" dev="ggregory" due-to="Gary Gregory"> + [IMAP] Fix concurrent counting of chunks in IMAPExportMbox. + </action> + <action type="fix" dev="ggregory" due-to="Gary Gregory"> + Fix possible if rare NPEs in tests. + </action> + <!-- UPDATE --> + <action type="update" dev="ggregory" due-to="Dependabot"> + Bump actions/checkout from v2.3.3 to v2.3.4 #69. + </action> + <action issue="NET-685" type="update" dev="ggregory" due-to="Simo385"> + Update SocketClient default connect timeout from ∞ to 60 seconds #51. + </action> + <action issue="NET-695" type="update" dev="ggregory" due-to="Gary Gregory, Possibly Cott"> + Apply SocketClient timeout after connection but before SSL negotiation. + </action> + <action type="update" dev="ggregory" due-to="Arturo Bernal, Gary Gregory"> + Minor Improvements #71, #72. + </action> + <action type="update" dev="ggregory" due-to="Dependabot"> + Bump actions/cache from v2 to v2.1.4 #73. + </action> + </release> + <release version="3.7.2" date="2020-10-14" description="Maintenance and bug fix release (Java 7)."> + <action issue="NET-689" type="fix" dev="ggregory" due-to="Charlie, Gary Gregory"> + Host name is not set on the SSLSocket causing isEndpointCheckingEnabled to fail. + </action> + <action type="fix" dev="ggregory" due-to="Dependabot"> + Fix possible socket and input stream leak on socket exception in + org.apache.commons.net.ftp.FTPClient._retrieveFile(String, String, OutputStream). + </action> + <action issue="NET-690" type="fix" dev="ggregory" due-to="payal-meh, Gary Gregory"> + Performance issue when using the FTPClient to retrieve files #65. + </action> + <action issue="NET-691" type="update" dev="ggregory" due-to="Lewis John McGibbney"> + Improve Javadoc for IMAPSClient #68. + </action> + <!-- UPDATES --> + <action type="update" dev="ggregory" due-to="Dependabot"> + Bump actions/setup-java from v1.4.2 to v1.4.3 #62. + </action> + <action type="update" dev="ggregory" due-to="Dependabot"> + Bump junit from 4.13 to 4.13.1 #67. + </action> + </release> + <release version="3.7.1" date="2020-09-30" description="Maintenance and bug fix release (Java 7)."> + <action issue="NET-687" type="fix" dev="ggregory" due-to="Gary Gregory, Mikael, j-verse"> + [FTPS] javax.net.ssl.SSLException: Unsupported or unrecognized SSL message, #59. + </action> + <!-- UPDATES --> + <action type="update" dev="ggregory" due-to="Dependabot"> + Update actions/checkout from v2.3.1 to v2.3.3 #56, #61. + </action> + </release> + <release version="3.7" date="2020-08-05" + description=" +This is mainly a bug-fix release (Java 7). See further details below. + This release requires a minimum of Java 7. This release is binary compatible with previous releases. However it is not source compatible with releases before 3.4, as some methods were added to the interface NtpV3Packet in 3.4 - The code now requires a minimum of Java 1.6. + Note that the examples packages were moved under org/apache/commons/net/examples. + The examples are not part of the public API, so this does not affect compatibility. + +"> + <action issue="NET-673" type="fix" dev="sebb"> + IMAPClient.APPEND does not always calculate the correct length + </action> + <action issue="NET-646" type="add" dev="sebb"> + ALLO FTP Command for files >2GB + </action> + <action issue="NET-615" type="add" dev="sebb"> + IMAPClient could simplify using empty arguments + </action> + <action issue="NET-614" type="add" dev="sebb"> + IMAP fails to quote/encode mailbox names + </action> + <action issue="NET-643" type="fix" dev="sebb" due-to="Vasily"> + NPE when closing telnet stream + </action> + <action issue="NET-648" type="add" dev="pschumacher"> + Add Automatic-Module-Name MANIFEST entry for Java 9 compatibility + </action> + <action issue="NET-641" type="fix" dev="sebb" due-to="pin_ptr"> + SubnetUtils.SubnetInfo.isInRange("0.0.0.0") returns true for CIDR/31, 32 + </action> + <action issue="NET-638" type="add" dev="sebb" due-to="Daniel Leong"> + Telnet subnegotiations hard-limited to 512 bytes - allow override + </action> + <action issue="NET-639" type="fix" dev="sebb" due-to=" Alexander Eller"> + MVSFTPEntryParser.preParse - MVS, z/OS - allow for merged Ext/Used fields + </action> + <action issue="NET-636" type="fix" dev="sebb"> + examples should be in org.apache.commons.net subpackage + </action> + <action issue="NET-634" type="add" dev="sebb" due-to="Mauro Molinari"> + Add SIZE command support + </action> + <action type="add" dev="sebb"> + Add POP3ExportMbox example code + </action> + <action issue="NET-633" type="update" dev="sebb" due-to="n0rm1e"> + Add XOAUTH2 to IMAP and SMTP + </action> + <action issue="NET-632" type="update" dev="sebb" due-to="prakapenka"> + FTPHTTPClient - support for encoding other than UTF-8 + </action> + <action issue="NET-631" type="fix" dev="sebb"> + Bug in MVSFTPEntryParser.parseUnixList (FindBugs) + </action> + <action issue="NET-584" type="fix" dev="sebb" due-to="Kazantsev Andrey Sergeevich/Nick Manley"> + Error when using org.apache.commons.net.ftp.FTPClient setControlKeepAliveTimeout + </action> + <action issue="NET-626" type="update" dev="sebb" due-to="Makoto Sakaguchi"> + SubnetUtils#SubnetUtils - improved comment + </action> + <action issue="NET-625" type="update" dev="sebb"> + SubnetUtils - improve construction + </action> + <action issue="NET-624" type="update" dev="sebb" due-to="Makoto Sakaguchi"> + SubnetInfo#getCidrSignature - improve functions + </action> + <action issue="NET-624" type="fix" dev="sebb" due-to="Makoto Sakaguchi"> + SubnetInfo#toCidrNotation: A wrong format subnet mask is allowed + </action> + <action issue="NET-623" type="fix" dev="sebb" due-to="Makoto Sakaguchi"> + SubnetUtils - fixed spelling errors + </action> + <action issue="NET-621" type="update" dev="sebb" due-to="Makoto Sakaguchi"> + SubnetUtils#SubnetInfo - remove unnecessary accessors + </action> + <action issue="NET-619" type="update" dev="sebb" due-to="Makoto Sakaguchi"> + SubnetUtils - improve binary netmask algorithm + </action> + <action issue="NET-613" type="fix" dev="sebb" due-to="Donald Kwakkel"> + System Information Leak in ftp parser + </action> + <action issue="NET-678" type="update" dev="sebb" due-to="Roman Grigoriadi"> + VMS ftp LIST parsing results in empty file list + </action> + <action issue="NET-674" type="add" dev="sebb" due-to="Chris Steingen"> + FTPListParseEngine should support listing via MLSD + </action> + <action issue="NET-663" type="fix" dev="sebb" due-to="Max Shenfield"> + NullPointerException when FTPClient remote verification fails + </action> + <action issue="NET-649" type="fix" dev="sebb" due-to="Filipe Bojikian Rissi"> + 227 Entering Passive Mode + </action> + <action issue="NET-660" type="add" dev="sebb" due-to="Nagabhushan S N"> + Next and Previous IP Address in SubnetUtils.SubnetInfo + </action> + <action issue="NET-682" type="fix" dev="sebb" due-to="richard"> + MVSFTPEntryParser doesn't support Record Formats of U + </action> + </release> + <release version="3.6" date="2017-02-15" + description=" +This is mainly a bug-fix release (Java 6). See further details below. + + + This release is binary compatible with previous releases. + However it is not source compatible with releases before 3.4, as some methods were added to the interface NtpV3Packet in 3.4 + + The code now requires a minimum of Java 6. Changes to functionality: * The FTP client now performs stricter checks on non-multiline command replies. @@ -89,90 +361,92 @@ without checking it if is a space. * TFTPClient code has been rewritten to improve error handling and retries. "> - <action issue="NET-613" type="fix" dev="sebb"> - TFTPClient assumes that lastBlock == 0 only once - </action> - <action issue="NET-320" type="fix" dev="sebb" due-to="Kevin Bulebush"> - Allow TFTPServer.java to bind to a specific network adapter - </action> - <action issue="NET-414" type="fix" dev="sebb" due-to="Chuck Wolber"> - Apache Commons TFTP does not reject request replies that originate from a control port. - </action> - <action issue="NET-477" type="fix" dev="sebb" due-to="John Walton"> - TFTP sendFile retry broken - </action> - <action issue="NET-612" type="update" dev="sebb"> - Allow TFTP socket IO tracing - </action> - <action issue="NET-596" type="fix" dev="sebb" due-to="Vincent Bories-Azeau"> - NullPointerException when disconnecting TelnetClient twice with JDK 7 - </action> - <action issue="NET-602" type="fix" dev="sebb" due-to="Ross Braithwaite"> - Failure to parse times from SYST_L8 systems that report as "WINDOWS Type: L8" - </action> - <action issue="NET-604" type="fix" dev="sebb" due-to="Frank Delporte"> - TFTP send and receive don't have progress indication - </action> - <action issue="NET-588" type="fix" dev="sebb" due-to="Dave Nice / Thai H"> - FTPClient.setPassiveNatWorkaround assumes host is outside site local range - </action> - <action issue="NET-610" type="fix" dev="sebb" due-to="Sergey Yanzin"> - FTPClient.mlistFile incorrectly handles MLST reply - </action> - <action issue="NET-611" type="fix" dev="sebb"> - FTP does not validate command reply syntax fully - </action> - <action issue="NET-609" type="fix" dev="sebb" due-to="Tqup3"> - DefaultUnixFTPFileEntryParserFactory Issue (leading spaces removal configuration) - </action> - <action type="update" dev="sebb"> - POP3Mail example: support host port; allow reading password from Console/stdin/environment - </action> - <action issue="NET-597" type="fix" dev="sebb" due-to="Hiroki Taniura"> - FTP fails to parse listings for Solaris 10 FTPd in Japanese - </action> - <action issue="NET-599" type="update" dev="sebb"> - Add shorthand FTPClientConfig constructor - </action> - <action issue="NET-593" type="fix" dev="sebb" due-to="Joerg Weule"> - HostnameVerifier is called with ip addess instead of the provided hostname - </action> - <action issue="NET-594" type="fix" dev="sebb" due-to="Brad Worrral"> - TelnetClient._closeOutputStream unhandled exception from FilterOutputStream.close - </action> - <action issue="NET-592" type="fix" dev="sebb" due-to="Mark Ford"> - plainSocket in FTPSClient is never closed - </action> - </release> - <release version="3.5" date="2016-05-05" description=" -This is mainly a bug-fix release. See further details below. + <action issue="NET-613" type="fix" dev="sebb"> + TFTPClient assumes that lastBlock == 0 only once + </action> + <action issue="NET-320" type="fix" dev="sebb" due-to="Kevin Bulebush"> + Allow TFTPServer.java to bind to a specific network adapter + </action> + <action issue="NET-414" type="fix" dev="sebb" due-to="Chuck Wolber"> + Apache Commons TFTP does not reject request replies that originate from a control port. + </action> + <action issue="NET-477" type="fix" dev="sebb" due-to="John Walton"> + TFTP sendFile retry broken + </action> + <action issue="NET-612" type="update" dev="sebb"> + Allow TFTP socket IO tracing + </action> + <action issue="NET-596" type="fix" dev="sebb" due-to="Vincent Bories-Azeau"> + NullPointerException when disconnecting TelnetClient twice with JDK 7 + </action> + <action issue="NET-602" type="fix" dev="sebb" due-to="Ross Braithwaite"> + Failure to parse times from SYST_L8 systems that report as "WINDOWS Type: L8" + </action> + <action issue="NET-604" type="fix" dev="sebb" due-to="Frank Delporte"> + TFTP send and receive don't have progress indication + </action> + <action issue="NET-588" type="fix" dev="sebb" due-to="Dave Nice / Thai H"> + FTPClient.setPassiveNatWorkaround assumes host is outside site local range + </action> + <action issue="NET-610" type="fix" dev="sebb" due-to="Sergey Yanzin"> + FTPClient.mlistFile incorrectly handles MLST reply + </action> + <action issue="NET-611" type="fix" dev="sebb"> + FTP does not validate command reply syntax fully + </action> + <action issue="NET-609" type="fix" dev="sebb" due-to="Tqup3"> + DefaultUnixFTPFileEntryParserFactory Issue (leading spaces removal configuration) + </action> + <action type="update" dev="sebb"> + POP3Mail example: support host port; allow reading password from Console/stdin/environment + </action> + <action issue="NET-597" type="fix" dev="sebb" due-to="Hiroki Taniura"> + FTP fails to parse listings for Solaris 10 FTPd in Japanese + </action> + <action issue="NET-599" type="update" dev="sebb"> + Add shorthand FTPClientConfig constructor + </action> + <action issue="NET-593" type="fix" dev="sebb" due-to="Joerg Weule"> + HostnameVerifier is called with ip addess instead of the provided hostname + </action> + <action issue="NET-594" type="fix" dev="sebb" due-to="Brad Worrral"> + TelnetClient._closeOutputStream unhandled exception from FilterOutputStream.close + </action> + <action issue="NET-592" type="fix" dev="sebb" due-to="Mark Ford"> + plainSocket in FTPSClient is never closed + </action> + </release> + <release version="3.5" date="2016-05-05" + description=" +This is mainly a bug-fix release (Java 6). See further details below. This release is binary compatible with previous releases. However it is not source compatible with releases before 3.4, as some methods were added to the interface NtpV3Packet in 3.4 - The code now requires a minimum of Java 1.6. + The code now requires a minimum of Java 6. Notable additions: The IMAP examples can now get password from console, stdin or an environment variable. "> - <action issue="NET-583" type="fix" dev="sebb" due-to="Holger Rehn"> - FTPClient.getReplyString() returns wrong value after connect() - </action> - <action type="add" dev="sebb"> - Alternative password input methods for IMAP examples - </action> - <action type="add" dev="sebb"> - More tests for Feb 29 handling. - </action> - <action issue="NET-586" type="fix" dev="sebb"> - Don't use Feb 29 for short future date tests - </action> - <action type="fix" dev="sebb"> - Documentation tweaks - </action> - </release> - <release version="3.4" date="2015-11-26" description=" + <action issue="NET-583" type="fix" dev="sebb" due-to="Holger Rehn"> + FTPClient.getReplyString() returns wrong value after connect() + </action> + <action type="add" dev="sebb"> + Alternative password input methods for IMAP examples + </action> + <action type="add" dev="sebb"> + More tests for Feb 29 handling. + </action> + <action issue="NET-586" type="fix" dev="sebb"> + Don't use Feb 29 for short future date tests + </action> + <action type="fix" dev="sebb"> + Documentation tweaks + </action> + </release> + <release version="3.4" date="2015-11-26" + description=" This is mainly a bug-fix release. See further details below. This release is binary compatible with previous releases. @@ -182,344 +456,349 @@ This is mainly a bug-fix release. See further details below. IMAPExportMbox (example app) allows IMAP folders to be exported into an mbox file. This is the inverse of the IMAPImportMbox example added previously "> - <action issue="NET-581" type="fix" dev="sebb"> - SimpleSMTPHeader fails to supply the required Date: header - </action> - <action issue="NET-582" type="fix" dev="sebb"> - SimpleSMTPHeader does not allow for missing To: field - </action> - <action issue="NET-580" type="fix" dev="sebb" due-to="Simon Arlott"> - SMTPClient.sendSimpleMessage() silently ignores failed recipients - Update Javadoc - </action> - <action issue="NET-579" type="fix" dev="sebb" due-to="Simon Arlott"> - SSL/TLS SocketClients do not verify the hostname against the certificate - </action> - <action issue="NET-576" type="update" dev="sebb"> - Allow FTPClient to use SYST response if system type is not specified in configuration - </action> - <action issue="NET-575" type="update" dev="sebb"> - FTPClientExample should support setting the date format - </action> - <action issue="NET-538" type="fix" dev="sebb" due-to="Dzmitry"> - FTPHTTPClient should use socket factory to create sockets - </action> - <action issue="NET-566" type="fix" dev="sebb" due-to="Gary Russell"> - UnixFTPEntryParser Drops Leading Spaces from File Names - </action> - <action type="update" dev="sebb"> - examples/Main now uses a property file to define aliases instead of scanning class files - </action> - <action issue="NET-552" type="fix" dev="sebb" due-to="Quentin Devriendt"> - SocketTimeoutException connecting a FTP server via an HTTP Proxy - </action> - <action issue="NET-528" type="add" dev="sebb"> - FTPListParseEngine does not provide access to raw responses - </action> - <action issue="NET-565" type="add" dev="sebb"> - Add FTPClient method to return an FTPFile from an MDTM command - </action> - <action issue="NET-564" type="update" dev="sebb"> - FTPFile.toFormattedString - allow specification of TimeZone for display - </action> - <action issue="NET-562" type="update" dev="sebb"> - FTPFile.toFormattedString should print only signficant parts of the parsed date - </action> - <action issue="NET-563" type="fix" dev="sebb"> - MLSxEntryParser needs test cases; parsing is too lax - </action> - <action issue="NET-561" type="fix" dev="sebb"> - FTPFile.toFormattedString prints user and group in wrong order - </action> - <action issue="NET-544" type="fix" dev="sebb" due-to="Olivier Queyrut "> - FTPClient.initiateListParsing does not correctly check if parserKey was cached - </action> - <action issue="NET-554" type="update" dev="sebb"> - Simplify TelnetOptionHandler class hierarchy - </action> - <action issue="NET-558" type="fix" dev="sebb" due-to="Ralph Becker"> - FTPClient.getModificationTime(filename) returns complete received line including response code and EOL - Strip the response code and EOL - </action> - <action issue="NET-556" type="update" dev="sebb" due-to="Andy Rosa"> - Make SubnetInfo.isInRange(int) public - </action> - <action issue="NET-550" type="fix" dev="sebb" due-to="Geoffrey Hardy"> - Default FTPClient bufferSize results in very slow retrieve transfers - Fix code in Util#copyStream (also copyReader) that failed to use the proper default for buffer size 0 - </action> - <action issue="NET-551" type="fix" dev="sebb"> - Util copyReader calls CopyStreamListener.bytesTransferred with the incorrect value for bytesTransferred - </action> - <action type="update" dev="sebb"> - Added control character processing to TelnetClientExample - </action> - <action issue="NET-547" type="update" dev="sebb" due-to="Fabio Scippacercola"> - There is a lack of documentation regarding setControlKeepAliveTimeout - </action> - <action issue="NET-549" type="fix" dev="sebb" due-to="Pradeep Natarajan"> - Telnet does not convert LF to CRLF in ASCII mode - </action> - <action issue="NET-543" type="fix" dev="sebb" due-to="Ferry Huberts"> - telnet: spy read EOL is reversed - </action> - <action issue="NET-540" type="add" dev="sebb"> - Article#printThread should have option to use any PrintStream - </action> - <action issue="NET-539" type="fix" dev="sebb"> - NPE if Threader.thread invoked with empty list or with null array - </action> - <action issue="NET-536" type="add" dev="sebb"> - IMAP FETCH example - IMAPExportMbox can export selected nessages from an IMAP folder - </action> - <action issue="NET-535" type="add" dev="sebb"> - IMAP FETCH can overflow reply buffer; provide for partial responses - </action> - <action issue="NET-534" type="update" dev="sebb"> - Unnecesssary call to getReplyString() if no listeners configured - </action> - <action issue="NET-530" type="fix" dev="sebb" due-to="fish ship"> - input parameter of org.apache.commons.net.ftp.FTP.__getReply(boolean) is not used - </action> - <action issue="NET-529" type="fix" dev="sebb" due-to="Putinas Piliponis"> - SubnetUtils throws exception on valid input - </action> - <action issue="NET-527" type="add" dev="sebb" due-to="jason mathews"> - Add SimpleNTPServer as example and for testing - </action> - <action issue="NET-516" type="fix" dev="sebb" due-to="Asha K S & pavan"> - parser problem occurs if the filename contains one or more characters of which the second byte of Shift-JIS code is 0x85 - Fix NT parser - </action> - <action type="update" dev="sebb"> - Added control encoding option to FTPClientExample - </action> - <action issue="NET-526" type="update" dev="sebb" due-to="Jason Mathews, MITRE Corp"> - Added missing set methods on NTP class and interface - </action> - <action issue="NET-526" type="update" dev="sebb"> - Avoid greedy matches within a regex - </action> - <action issue="NET-520" type="fix" dev="sebb"> - SubnetUtils("0.0.0.0/0") does not behave as expected - Fixed range checking so network and broadcast addresses are treated as unsigned ints - </action> - <action issue="NET-521" type="fix" dev="sebb"> - SubnetUtils.SubnetInfo.getAddressCount() can overflow as it returns an int - </action> - <action issue="NET-515" type="fix" due-to="Sebastian Ritter"> - FTPClient sample in class javadoc "bug" - </action> - <action issue="NET-519" type="fix"> - Apache Commons Net 3.3 has a performance issue - </action> - <action issue="NET-517" dev="sebb" type="fix" due-to="David Kocher"> - FTPClient#reinitialize is package protected - </action> - <action issue="NET-512" dev="sebb" type="add" due-to="Thomas Raddatz"> - Downloading files or members from the AS400 QSYS file system is not supported - </action> - <action issue="NET-518" dev="sebb" type="fix" due-to="David Kocher"> - FTPClient#initFeatureMap should not initialize empty map if reply code is 530 - </action> - <action issue="NET-514" dev="sebb" type="fix"> - IMAP APPEND multiple issues in IMapClient. - Deprecated unusable append methods. - Added new append method, as well as example IMapImportMbox class to make use of it. - </action> - <action issue="NET-511" dev="ggregory" type="fix" due-to="Kyriacos Elia, Daniel Scott"> - Exception for new SubnetUtils("0.0.0.0/0"). - </action> - </release> - <release version="3.3" date="2013-06-11" description=" + <action issue="NET-581" type="fix" dev="sebb"> + SimpleSMTPHeader fails to supply the required Date: header + </action> + <action issue="NET-582" type="fix" dev="sebb"> + SimpleSMTPHeader does not allow for missing To: field + </action> + <action issue="NET-580" type="fix" dev="sebb" due-to="Simon Arlott"> + SMTPClient.sendSimpleMessage() silently ignores failed recipients + Update Javadoc + </action> + <action issue="NET-579" type="fix" dev="sebb" due-to="Simon Arlott"> + SSL/TLS SocketClients do not verify the hostname against the certificate + </action> + <action issue="NET-576" type="update" dev="sebb"> + Allow FTPClient to use SYST response if system type is not specified in configuration + </action> + <action issue="NET-575" type="update" dev="sebb"> + FTPClientExample should support setting the date format + </action> + <action issue="NET-538" type="fix" dev="sebb" due-to="Dzmitry"> + FTPHTTPClient should use socket factory to create sockets + </action> + <action issue="NET-566" type="fix" dev="sebb" due-to="Gary Russell"> + UnixFTPEntryParser Drops Leading Spaces from File Names + </action> + <action type="update" dev="sebb"> + examples/Main now uses a property file to define aliases instead of scanning class files + </action> + <action issue="NET-552" type="fix" dev="sebb" due-to="Quentin Devriendt"> + SocketTimeoutException connecting a FTP server via an HTTP Proxy + </action> + <action issue="NET-528" type="add" dev="sebb"> + FTPListParseEngine does not provide access to raw responses + </action> + <action issue="NET-565" type="add" dev="sebb"> + Add FTPClient method to return an FTPFile from an MDTM command + </action> + <action issue="NET-564" type="update" dev="sebb"> + FTPFile.toFormattedString - allow specification of TimeZone for display + </action> + <action issue="NET-562" type="update" dev="sebb"> + FTPFile.toFormattedString should print only signficant parts of the parsed date + </action> + <action issue="NET-563" type="fix" dev="sebb"> + MLSxEntryParser needs test cases; parsing is too lax + </action> + <action issue="NET-561" type="fix" dev="sebb"> + FTPFile.toFormattedString prints user and group in wrong order + </action> + <action issue="NET-544" type="fix" dev="sebb" due-to="Olivier Queyrut "> + FTPClient.initiateListParsing does not correctly check if parserKey was cached + </action> + <action issue="NET-554" type="update" dev="sebb"> + Simplify TelnetOptionHandler class hierarchy + </action> + <action issue="NET-558" type="fix" dev="sebb" due-to="Ralph Becker"> + FTPClient.getModificationTime(filename) returns complete received line including response code and EOL + Strip the response code and EOL + </action> + <action issue="NET-556" type="update" dev="sebb" due-to="Andy Rosa"> + Make SubnetInfo.isInRange(int) public + </action> + <action issue="NET-550" type="fix" dev="sebb" due-to="Geoffrey Hardy"> + Default FTPClient bufferSize results in very slow retrieve transfers + Fix code in Util#copyStream (also copyReader) that failed to use the proper default for buffer size 0 + </action> + <action issue="NET-551" type="fix" dev="sebb"> + Util copyReader calls CopyStreamListener.bytesTransferred with the incorrect value for bytesTransferred + </action> + <action type="update" dev="sebb"> + Added control character processing to TelnetClientExample + </action> + <action issue="NET-547" type="update" dev="sebb" due-to="Fabio Scippacercola"> + There is a lack of documentation regarding setControlKeepAliveTimeout + </action> + <action issue="NET-549" type="fix" dev="sebb" due-to="Pradeep Natarajan"> + Telnet does not convert LF to CRLF in ASCII mode + </action> + <action issue="NET-543" type="fix" dev="sebb" due-to="Ferry Huberts"> + telnet: spy read EOL is reversed + </action> + <action issue="NET-540" type="add" dev="sebb"> + Article#printThread should have option to use any PrintStream + </action> + <action issue="NET-539" type="fix" dev="sebb"> + NPE if Threader.thread invoked with empty list or with null array + </action> + <action issue="NET-536" type="add" dev="sebb"> + IMAP FETCH example + IMAPExportMbox can export selected nessages from an IMAP folder + </action> + <action issue="NET-535" type="add" dev="sebb"> + IMAP FETCH can overflow reply buffer; provide for partial responses + </action> + <action issue="NET-534" type="update" dev="sebb"> + Unnecesssary call to getReplyString() if no listeners configured + </action> + <action issue="NET-530" type="fix" dev="sebb" due-to="fish ship"> + input parameter of org.apache.commons.net.ftp.FTP.__getReply(boolean) is not used + </action> + <action issue="NET-529" type="fix" dev="sebb" due-to="Putinas Piliponis"> + SubnetUtils throws exception on valid input + </action> + <action issue="NET-527" type="add" dev="sebb" due-to="jason mathews"> + Add SimpleNTPServer as example and for testing + </action> + <action issue="NET-516" type="fix" dev="sebb" due-to="Asha K S & pavan"> + parser problem occurs if the filename contains one or more characters of which the second byte of Shift-JIS code is 0x85 + Fix NT parser + </action> + <action type="update" dev="sebb"> + Added control encoding option to FTPClientExample + </action> + <action issue="NET-526" type="update" dev="sebb" due-to="Jason Mathews, MITRE Corp"> + Added missing set methods on NTP class and interface + </action> + <action issue="NET-526" type="update" dev="sebb"> + Avoid greedy matches within a regex + </action> + <action issue="NET-520" type="fix" dev="sebb"> + SubnetUtils("0.0.0.0/0") does not behave as expected + Fixed range checking so network and broadcast addresses are treated as unsigned ints + </action> + <action issue="NET-521" type="fix" dev="sebb"> + SubnetUtils.SubnetInfo.getAddressCount() can overflow as it returns an int + </action> + <action issue="NET-515" type="fix" due-to="Sebastian Ritter"> + FTPClient sample in class javadoc "bug" + </action> + <action issue="NET-519" type="fix"> + Apache Commons Net 3.3 has a performance issue + </action> + <action issue="NET-517" dev="sebb" type="fix" due-to="David Kocher"> + FTPClient#reinitialize is package protected + </action> + <action issue="NET-512" dev="sebb" type="add" due-to="Thomas Raddatz"> + Downloading files or members from the AS400 QSYS file system is not supported + </action> + <action issue="NET-518" dev="sebb" type="fix" due-to="David Kocher"> + FTPClient#initFeatureMap should not initialize empty map if reply code is 530 + </action> + <action issue="NET-514" dev="sebb" type="fix"> + IMAP APPEND multiple issues in IMapClient. + Deprecated unusable append methods. + Added new append method, as well as example IMapImportMbox class to make use of it. + </action> + <action issue="NET-511" dev="ggregory" type="fix" due-to="Kyriacos Elia, Daniel Scott"> + Exception for new SubnetUtils("0.0.0.0/0"). + </action> + </release> + <release version="3.3" date="2013-06-11" + description=" This is mainly a bug-fix release. See further details below. "> - <action issue="NET-509" dev="sebb" due-to="Anthony Dahanne" type="update"> - AuthenticatingSMTPClient needs a constructor with the isImplicit argument for SSL - </action> - <action issue="NET-501" dev="sebb" due-to="Julián Lastiri" type="fix"> - Race Condition on TelnetClient.disconnect() and TelnetInputStream.run() - java.lang.IllegalStateException: Queue is full! Cannot process another character. - </action> - <action issue="NET-505" dev="sebb" due-to="Sean Kelley" type="update"> - User specified bufferSize reset to default when FTPClient is disconnected or reinitialized resulting in performance degradation. - </action> - <action issue="NET-507" dev="sebb" due-to="Jiri Netolicky" type="update"> - Option to disable private IP replacement in FTP passive mode. - </action> - <action issue="NET-503" dev="sebb" due-to="Ofer Regev" type="add"> - AuthenticatingSMTPClient does not support non-default encoding - </action> - <action issue="NET-500" dev="sebb" due-to="Michael Frick" type="fix"> - Always call FTPClient#setFileType after connection. - Not all servers default to ASCII. - </action> - <action issue="NET-465" dev="sebb" due-to="Jim Kerwood" type="fix"> - FTPClient setSendBufferSize and setReceiveBufferSize on data socket. - The previous fix caused performance problems. - Added new getters and setters for the SO_SNDBUF and SO_RCVBUF values to be used on the data socket. - </action> - <action issue="NET-496" dev="sebb" type="add"> - Util copyReader/copyStream classes should use default buffer size for non-positive buffer size parameters. - </action> - <action issue="NET-310" dev="sebb" type="add"> - FTPCommand conversion to use enum; added FTPCmd emum and deprecated FTPCommand. - </action> - <action issue="NET-480" dev="sebb" due-to="Peter Naber" type="fix"> - Wrong passivHost when using FTPHTTPClient with EPSV - </action> - <action issue="NET-494" dev="sebb" type="fix"> - FTPClient.CSL.cleanUp() fails to restore timeout value on exception - </action> - <action issue="NET-492" dev="sebb" due-to="Tomasz Jedrzejewski" type="fix"> - FTPClient.printWorkingDirectory() incorrectly parses certain valid PWD command results - </action> - </release> - <release version="3.2" date="2012-12-03" description=" + <action issue="NET-509" dev="sebb" due-to="Anthony Dahanne" type="update"> + AuthenticatingSMTPClient needs a constructor with the isImplicit argument for SSL + </action> + <action issue="NET-501" dev="sebb" due-to="Julián Lastiri" type="fix"> + Race Condition on TelnetClient.disconnect() and TelnetInputStream.run() + java.lang.IllegalStateException: Queue is full! Cannot process another character. + </action> + <action issue="NET-505" dev="sebb" due-to="Sean Kelley" type="update"> + User specified bufferSize reset to default when FTPClient is disconnected or reinitialized resulting in performance + degradation. + </action> + <action issue="NET-507" dev="sebb" due-to="Jiri Netolicky" type="update"> + Option to disable private IP replacement in FTP passive mode. + </action> + <action issue="NET-503" dev="sebb" due-to="Ofer Regev" type="add"> + AuthenticatingSMTPClient does not support non-default encoding + </action> + <action issue="NET-500" dev="sebb" due-to="Michael Frick" type="fix"> + Always call FTPClient#setFileType after connection. + Not all servers default to ASCII. + </action> + <action issue="NET-465" dev="sebb" due-to="Jim Kerwood" type="fix"> + FTPClient setSendBufferSize and setReceiveBufferSize on data socket. + The previous fix caused performance problems. + Added new getters and setters for the SO_SNDBUF and SO_RCVBUF values to be used on the data socket. + </action> + <action issue="NET-496" dev="sebb" type="add"> + Util copyReader/copyStream classes should use default buffer size for non-positive buffer size parameters. + </action> + <action issue="NET-310" dev="sebb" type="add"> + FTPCommand conversion to use enum; added FTPCmd emum and deprecated FTPCommand. + </action> + <action issue="NET-480" dev="sebb" due-to="Peter Naber" type="fix"> + Wrong passivHost when using FTPHTTPClient with EPSV + </action> + <action issue="NET-494" dev="sebb" type="fix"> + FTPClient.CSL.cleanUp() fails to restore timeout value on exception + </action> + <action issue="NET-492" dev="sebb" due-to="Tomasz Jedrzejewski" type="fix"> + FTPClient.printWorkingDirectory() incorrectly parses certain valid PWD command results + </action> + </release> + <release version="3.2" date="2012-12-03" + description=" This release fixes bugs and adds some new functionality (see below). It is binary compatible with previous releases. Note that Clirr shows that two public methods have been removed (NET-485). These are not used within NET. "> - <action issue="NET-46" dev="sebb" type="fix"> - retrieveFileStream fails randomly or hangs - </action> - <action issue="NET-485" dev="sebb" type="fix"> - Remove unnecessary Base64 methods. - </action> - <action issue="NET-482" dev="sebb" type="update" due-to="Houman Atashbar"> - Support XOAUTH. - </action> - <action issue="NET-484" dev="sebb" type="fix"> - Base64.CHUNK_SEPARATOR should be private - </action> - <action issue="NET-483" dev="sebb" type="fix"> - Base64.encodeBase64(byte[], boolean, boolean, int) does not calculate output size correctly for unchunked output. - </action> - <action issue="NET-466" dev="sebb" type="fix" due-to="Martin Oberhuber"> - Regression: TelnetInputStream#available() blocks. - </action> - <action issue="NET-426" dev="sebb" type="fix" due-to="Ketan"> - FTPS: Hook to customize _openDataConnection_ SSLSocket before startHandshake() is called. - Implement _openDataConnection(String, String) method to properly - interface with FTPClient.openDataConnection(String, String) - </action> - <action issue="NET-456" dev="sebb" type="fix"> - TelnetClient hangs when reader-thread startup is delayed. - </action> - <action issue="NET-449" dev="sebb" type="fix"> - listFiles bug with folder that begins with "-". Clarify Javadoc. - </action> - <action issue="NET-473" dev="sebb" type="fix"> - FTPClient setSoTimeout (int time) will result in NullPointerException. Clarify Javadoc. - </action> - <action issue="NET-468" dev="sebb" type="add" due-to="Bogdan Drozdowski"> - Request for native support for socks proxy routing with Commons net FTP. - </action> - <action issue="NET-475" dev="sebb" type="fix"> - FtpClient sends REST when calling listFiles. Clarified Javadoc. - </action> - <action issue="NET-465" dev="sebb" type="add" due-to="Bogdan Drozdowski"> - FTPClient setSendBufferSize and setReceiveBufferSize on data socket. - </action> - <action issue="NET-462" dev="sebb" type="add" due-to="Bogdan Drozdowski"> - FTPClient in PASSIVE_LOCAL_DATA_CONNECTION_MODE cannot work when host have several different IP. - </action> - <action issue="NET-467" dev="sebb" type="fix"> - IMAPClient#fetch() does not handle literal strings. - </action> - <action issue="NET-458" dev="sebb" type="fix" due-to="Denis Molony"> - MVSFTPEntryParser.parseSimpleEntry - ArrayIndexOutOfBoundsException. - </action> - <action issue="NET-450" dev="sebb" type="fix" due-to="Roger Hardiman"> - Bug in documentation for FTPClient. - </action> - <action dev="sebb" type="add"> - The examples can now be run using "java -jar commons-net-examples-m.n.jar". - This will automatically include the main net jar in the classpath. - See documentation. - FTPClientExample now supports "-A" for anonymous login - </action> - <action issue="NET-442" dev="sebb" type="fix"> - StringIndexOutOfBoundsException: String index out of range: -1 if server respond with root is current directory. - </action> - <action issue="NET-444" dev="sebb" type="fix"> - FTPTimestampParserImpl fails to parse future dates correctly on Feb 28th in a leap year. - </action> - </release> - <release version="3.1" date="Feb 20, 2012" description=" + <action issue="NET-46" dev="sebb" type="fix"> + retrieveFileStream fails randomly or hangs + </action> + <action issue="NET-485" dev="sebb" type="fix"> + Remove unnecessary Base64 methods. + </action> + <action issue="NET-482" dev="sebb" type="update" due-to="Houman Atashbar"> + Support XOAUTH. + </action> + <action issue="NET-484" dev="sebb" type="fix"> + Base64.CHUNK_SEPARATOR should be private + </action> + <action issue="NET-483" dev="sebb" type="fix"> + Base64.encodeBase64(byte[], boolean, boolean, int) does not calculate output size correctly for unchunked output. + </action> + <action issue="NET-466" dev="sebb" type="fix" due-to="Martin Oberhuber"> + Regression: TelnetInputStream#available() blocks. + </action> + <action issue="NET-426" dev="sebb" type="fix" due-to="Ketan"> + FTPS: Hook to customize _openDataConnection_ SSLSocket before startHandshake() is called. + Implement _openDataConnection(String, String) method to properly + interface with FTPClient.openDataConnection(String, String) + </action> + <action issue="NET-456" dev="sebb" type="fix"> + TelnetClient hangs when reader-thread startup is delayed. + </action> + <action issue="NET-449" dev="sebb" type="fix"> + listFiles bug with folder that begins with "-". Clarify Javadoc. + </action> + <action issue="NET-473" dev="sebb" type="fix"> + FTPClient setSoTimeout (int time) will result in NullPointerException. Clarify Javadoc. + </action> + <action issue="NET-468" dev="sebb" type="add" due-to="Bogdan Drozdowski"> + Request for native support for socks proxy routing with Commons net FTP. + </action> + <action issue="NET-475" dev="sebb" type="fix"> + FtpClient sends REST when calling listFiles. Clarified Javadoc. + </action> + <action issue="NET-465" dev="sebb" type="add" due-to="Bogdan Drozdowski"> + FTPClient setSendBufferSize and setReceiveBufferSize on data socket. + </action> + <action issue="NET-462" dev="sebb" type="add" due-to="Bogdan Drozdowski"> + FTPClient in PASSIVE_LOCAL_DATA_CONNECTION_MODE cannot work when host have several different IP. + </action> + <action issue="NET-467" dev="sebb" type="fix"> + IMAPClient#fetch() does not handle literal strings. + </action> + <action issue="NET-458" dev="sebb" type="fix" due-to="Denis Molony"> + MVSFTPEntryParser.parseSimpleEntry - ArrayIndexOutOfBoundsException. + </action> + <action issue="NET-450" dev="sebb" type="fix" due-to="Roger Hardiman"> + Bug in documentation for FTPClient. + </action> + <action dev="sebb" type="add"> + The examples can now be run using "java -jar commons-net-examples-m.n.jar". + This will automatically include the main net jar in the classpath. + See documentation. + FTPClientExample now supports "-A" for anonymous login + </action> + <action issue="NET-442" dev="sebb" type="fix"> + StringIndexOutOfBoundsException: String index out of range: -1 if server respond with root is current directory. + </action> + <action issue="NET-444" dev="sebb" type="fix"> + FTPTimestampParserImpl fails to parse future dates correctly on Feb 28th in a leap year. + </action> + </release> + <release version="3.1" date="Feb 20, 2012" + description=" This release fixes a few bugs and adds some new functionality (see below). It is binary compatible with previous releases "> - <action issue="NET-441" dev="sebb" type="fix" due-to="consiliens"> - [FTP] mlistDir doc should be "MLSD" not "MSLD". - </action> - <action issue="NET-440" dev="sebb" type="add"> - [FTP] Allow user to provide default value in case SYST command fails. - </action> - <action issue="NET-438" dev="sebb" type="fix" due-to="Norman Maurer"> - POP3Client.capa() should call POP3Client.getAdditionalReply() - </action> - <action issue="NET-412" dev="sebb" type="fix" due-to="Chuck Wolber"> - TFTP implementation subject to Sorcerer's Apprentice Syndrome - </action> - <action issue="NET-410" dev="sebb" type="fix" due-to="Chuck Wolber"> - TFTP does not handle RFC 783 retransmits - </action> - <action issue="NET-437" dev="sebb" type="fix" due-to="Gavin Camp"> - TelnetInputStream doesn't support non-blocking IO when reader thread is not enabled - </action> - <action issue="NET-346" dev="sebb" type="add" due-to="Kevin Samuel"> - FTP should support reporting NATed external IP address - </action> - <action issue="NET-433" dev="sebb" type="add"> - Commons NET site should link to the examples - </action> - <action issue="NET-422" dev="sebb" type="fix" due-to="Tomas Mysik / Magnus Johansson"> - FTP using HTTP proxy not working - </action> - <action issue="NET-423" dev="sebb" type="fix" due-to="Jens Koch"> - FTPClient.storeFIle might fail when ControlKeepAliveTimeout is set (ditto for FTPCLient.retrieveFile) - </action> - <action issue="NET-426" dev="sebb" type="add" due-to="Ketan"> - FTPS: Hook to customize _openDataConnection_ SSLSocket before startHandshake() is called - </action> - <action issue="NET-430" dev="sebb" type="fix" due-to="Thomas Mathis"> - Can't login to POP3S Server using explicit mode - </action> - <action issue="NET-434" dev="sebb" type="fix" due-to="zhangyong"> - FTPClient fails to close local listener socket when command socket channel encounter "ReadTimeoutException" - </action> - <action issue="NET-436" dev="sebb" type="add" due-to="Jürgen Jung"> - [FTP] Support for SYST "Mac OS" listing - "MACOS Peter's Server" - </action> - <action issue="NET-425" dev="sebb" type="update" due-to="Steven Jardine"> - [FTP] _openDataConnection_, __storeFile, and __storeFileStream should be protected and take String for FTP command. - Likewise for receiveFile and receiveFileStream. - </action> - <action issue="NET-416" dev="sebb" type="update" due-to="Abhijeet Gaikwad"> - [Telnet] Increasing sub-negotiation message holder array size - </action> - <action issue="NET-428" dev="sebb" type="fix" due-to="sebb"> - SubnetUtils throws ArrayIndexOutOfBoundsException for new SubnetUtils( "1.2.3.4/32" ).getInfo().getAllAddresses() - </action> - <action issue="NET-421" dev="sebb" type="fix" due-to="Oliver Saggau"> - Problem connecting to TLS/SSL SMTP server using explicit mode. - </action> - <action issue="NET-415" dev="sebb" type="fix" due-to="george thomas"> - [Site] typo in migration how-to. - </action> - </release> + <action issue="NET-441" dev="sebb" type="fix" due-to="consiliens"> + [FTP] mlistDir doc should be "MLSD" not "MSLD". + </action> + <action issue="NET-440" dev="sebb" type="add"> + [FTP] Allow user to provide default value in case SYST command fails. + </action> + <action issue="NET-438" dev="sebb" type="fix" due-to="Norman Maurer"> + POP3Client.capa() should call POP3Client.getAdditionalReply() + </action> + <action issue="NET-412" dev="sebb" type="fix" due-to="Chuck Wolber"> + TFTP implementation subject to Sorcerer's Apprentice Syndrome + </action> + <action issue="NET-410" dev="sebb" type="fix" due-to="Chuck Wolber"> + TFTP does not handle RFC 783 retransmits + </action> + <action issue="NET-437" dev="sebb" type="fix" due-to="Gavin Camp"> + TelnetInputStream doesn't support non-blocking IO when reader thread is not enabled + </action> + <action issue="NET-346" dev="sebb" type="add" due-to="Kevin Samuel"> + FTP should support reporting NATed external IP address + </action> + <action issue="NET-433" dev="sebb" type="add"> + Commons NET site should link to the examples + </action> + <action issue="NET-422" dev="sebb" type="fix" due-to="Tomas Mysik / Magnus Johansson"> + FTP using HTTP proxy not working + </action> + <action issue="NET-423" dev="sebb" type="fix" due-to="Jens Koch"> + FTPClient.storeFIle might fail when ControlKeepAliveTimeout is set (ditto for FTPCLient.retrieveFile) + </action> + <action issue="NET-426" dev="sebb" type="add" due-to="Ketan"> + FTPS: Hook to customize _openDataConnection_ SSLSocket before startHandshake() is called + </action> + <action issue="NET-430" dev="sebb" type="fix" due-to="Thomas Mathis"> + Can't login to POP3S Server using explicit mode + </action> + <action issue="NET-434" dev="sebb" type="fix" due-to="zhangyong"> + FTPClient fails to close local listener socket when command socket channel encounter "ReadTimeoutException" + </action> + <action issue="NET-436" dev="sebb" type="add" due-to="Jürgen Jung"> + [FTP] Support for SYST "Mac OS" listing - "MACOS Peter's Server" + </action> + <action issue="NET-425" dev="sebb" type="update" due-to="Steven Jardine"> + [FTP] _openDataConnection_, __storeFile, and __storeFileStream should be protected and take String for FTP command. + Likewise for receiveFile and receiveFileStream. + </action> + <action issue="NET-416" dev="sebb" type="update" due-to="Abhijeet Gaikwad"> + [Telnet] Increasing sub-negotiation message holder array size + </action> + <action issue="NET-428" dev="sebb" type="fix" due-to="sebb"> + SubnetUtils throws ArrayIndexOutOfBoundsException for new SubnetUtils( "1.2.3.4/32" ).getInfo().getAllAddresses() + </action> + <action issue="NET-421" dev="sebb" type="fix" due-to="Oliver Saggau"> + Problem connecting to TLS/SSL SMTP server using explicit mode. + </action> + <action issue="NET-415" dev="sebb" type="fix" due-to="george thomas"> + [Site] typo in migration how-to. + </action> + </release> - <release version="3.0.1" date="June 6, 2011" description=" + <release version="3.0.1" date="June 6, 2011" description=" This is a bug-fix release. "> - <action issue="NET-409" dev="sebb" type="fix"> - FTPClient truncates file (storeFile method). - Fix bug introduced in release 3.0. - </action> - </release> - <release version="3.0" date="May 16, 2011" description=" + <action issue="NET-409" dev="sebb" type="fix"> + FTPClient truncates file (storeFile method). + Fix bug introduced in release 3.0. + </action> + </release> + <release version="3.0" date="May 16, 2011" + description=" This release fixes many bugs (see below), and adds new functionality: - basic support for IMAP and IMAPS - support for SMTPS and POP3S @@ -549,252 +828,254 @@ KeyManagerUtils can be used to provide client certificates. All users are recommended to upgrade. "> - <action issue="NET-407" dev="sebb" type="update"> - Change lenientFutureDates to default to true. - This means short dates will be parsed as the current year when the host clock is up to 1 day ahead of the client clock. - </action> - <action issue="NET-404" dev="sebb" type="fix"> - FTPSSocketFactory does not override createSocket(); causes java.net.SocketException: Unconnected sockets not implemented. - </action> - <action issue="NET-399" dev="sebb" type="fix" due-to="Noah Levitt"> - ftp data connection does not use connectTimeout. - </action> - <action issue="NET-400" dev="sebb" type="update" due-to="David Kocher"> - Option to override SSL negotiation. Make FTPSClient#execAuth() and FTPSClient#sslNegotiation() protected - </action> - <action issue="NET-402" dev="sebb" type="fix"> - IMAP, NNTP, POP3 and SMTP classes uses BufferedReader for control channel, which does not follow the standard. - Changed reader to CRLFLineReader. - </action> - <action issue="NET-401" dev="sebb" type="fix"> - FTP class uses BufferedReader for control channel, which does not follow the standard. - Changed reader to CRLFLineReader. - </action> - <action issue="NET-331" dev="sebb" type="update"> - AS400 file timestamp format is wrong. Workround exists. - </action> - <action issue="NET-269" dev="sebb" type="update"> - Remove semi-redundant check in SubnetUtils.calculate(). - </action> - <action issue="NET-219" dev="sebb" type="update"> - Should Telnet class Exception blocks write to System.err? - Catch blocks removed, and throws clauses added to allow caller to more easily detect and recover. - </action> - <action issue="NET-397" dev="sebb" type="update" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> - FTPSClient does not handle AUTH or ADAT and only partially handles PBSZ. FTPSCommand should be deprecated. - </action> - <action issue="NET-268" dev="sebb" type="fix"> - Better handling of CIDR/31 and CIDR/32 where isInclusive = false. - Return 0 for address count, and 0.0.0.0 for each of the addresses - </action> - <action issue="NET-395" dev="sebb" type="update"> - Move ProtocolCommandSupport to SocketClient. - </action> - <action issue="NET-393" dev="sebb" type="update"> - Should the sendCommandWithID() methods be public? - Made methods private, and deleted currently unused ones. - </action> - <action issue="NET-394" dev="sebb" type="update"> - Are the sendUntaggedCommand() methods needed? - Renamed the method as sendData(), as it's not a command. - </action> - <action issue="NET-392" dev="sebb" type="update"> - Use enum for IMAPCommand. - </action> - <action issue="NET-333" dev="sebb" type="add" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> - Added basic IMAP/IMAPS implementation. - </action> - <action issue="NET-389" dev="sebb" type="fix"> - Unix parser should ignore "total nnn" lines. - </action> - <action issue="NET-369" dev="sebb" type="remove"> - Article.addHeaderField() is currently write-only - there is no way to retrieve the headers - is it needed? - Method was removed, along with the field. - </action> - <action issue="NET-367" dev="sebb" type="fix"> - ntp.TimeStamp uses incorrect lazy initialisation of static fields simpleFormatter and utcFormatter. - </action> - <action issue="NET-381" dev="sebb" type="update"> - Parsing is inefficient, as it parses everything twice. - </action> - <action issue="NET-388" dev="sebb" type="fix"> - VMSVersioningFTPEntryParser#preParse should not call super.preParse(). - </action> - <action issue="NET-362" dev="sebb" type="fix"> - TelnetInputStream has various threading bugs. - </action> - <action issue="NET-89" dev="sebb" type="fix"> - TelnetClient use of FromNetASCIIInputStream and ToNetASCIIOutputStream breaks binary mode. - See also NET-387. - </action> - <action issue="NET-385" dev="sebb" type="update"> - FTP does not apply timeout to initial responses. - </action> - <action issue="NET-384" dev="sebb" type="update"> - KeyManagerUtils - the KeyManager is not efficient. - </action> - <action issue="NET-383" dev="sebb" type="update"> - KeyManagerUtils - allow alias to be omitted when there is only one private key in the store - </action> - <action issue="NET-326" dev="sebb" type="add"> - A KeyManager is required when the protection level is set to 'P' with FTPSClient on active mode. - Added KeyManagerUtils class to simplify provision of client certificates. - </action> - <action issue="NET-273" dev="sebb" type="add"> - FEAT response parsing. Added FTPClient methods: boolean hasFeature(feature [,option]), - String fetaureValue(feature), String[] featureValues(feature) - </action> - <action issue="NET-379" dev="sebb" type="add"> - FTPClient - support for processing arbitrary commands that only use the control channel - </action> - <action issue="NET-378" dev="sebb" type="add"> - FTP listing should support MLST and MLSD. - </action> - <action issue="NET-377" dev="sebb" type="update"> - NLST does not take notice of HiddenFiles setting. - </action> - <action issue="NET-373" dev="sebb" type="update"> - NNTP Listgroups not working - broken server implementation. - </action> - <action issue="NET-375" dev="sebb" type="update"> - DotTerminatedMessageReader should extend BufferedReader, rather than Reader. - </action> - <action issue="NET-374" dev="sebb" type="update"> - ParserInitializationException doesn't use standard JDK exception chaining - </action> - <action issue="NET-372" dev="sebb" type="add"> - FTPSClient: java.security.cert.CertificateException: No X509TrustManager implementation available if trustManager == null - </action> - <action issue="NET-371" dev="sebb" type="add"> - Create TrustManagerFactory to provide custom TrustManagers. - </action> - <action issue="NET-354" dev="sebb" type="fix" due-to="Leif John Korshavn"> - FTPSClient not properly supporting CCC and PROT P. - </action> - <action issue="NET-368" dev="sebb" type="update"> - Threader.thread should accept an Iterable rather than a List. - </action> - <action issue="NET-327" dev="sebb" type="add" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> - "Unconnected sockets not implemented" when using FTPSClient - Added disconnect() override which resets the socket factories to their defaults - </action> - <action issue="NET-350" dev="sebb" type="add" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> - "java.net.SocketException: Broken pipe" when calling "TelnetClient.sendAYT()" - Added SocketClient#isAvailable() method to perform additional checks on a socket. - </action> - <action issue="NET-237" dev="sebb" type="add"> - Add streaming methods (corresponding to array methods) to NNTPClient. - </action> - <action issue="NET-365" dev="sebb" type="fix"> - FTPClient.listFiles() does not work properly, if remote server speaks German. - Match non-space{3} instead of A-Za-z{3} - </action> - <action issue="NET-366" dev="sebb" type="fix"> - FTPClientConfig: setServerLanguageCode and setShortMonthNames do not work. - Ensure that config is passed to all parsers that can use it. - </action> - <action issue="NET-276" dev="sebb" type="fix"> - NNTPClient has problems with group listings for large groups. - </action> - <action issue="NET-185" dev="sebb" type="fix"> - Possible NPE in Threader.java - </action> - <action issue="NET-364" dev="sebb" type="fix"> - nntp.Article is very inefficient and incorrect. - </action> - <action issue="NET-314" dev="sebb" type="add" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> - The FTP client should autodetect the control encoding. - </action> - <action issue="NET-363" dev="sebb" type="fix" due-to="daniel damon"> - Can't connect to a server behind firewall in passive mode. - </action> - <action issue="NET-348" dev="sebb" type="fix"> - Queue is full TelnetInputStream. - </action> - <action issue="NET-361" dev="sebb" type="add"> - Implement Telnet Command sender. - </action> - <action issue="NET-345" dev="sebb" type="fix" due-to="Archie Cobbs"> - Telnet client: not properly handling IAC bytes within subnegotiation messages: - - failing to double IACs on output - - failing to de-double IACs in input - </action> - <action issue="NET-343" dev="sebb" type="add" due-to="Archie Cobbs"> - Telnet client: Support Client-initiated Subnegotiation Messages. - </action> - <action issue="NET-344" dev="sebb" type="add" due-to="Archie Cobbs"> - Telnet client: Support Listener Notification of Incoming Data. - </action> - <action issue="NET-270" dev="sebb" type="fix"> - Incorrect error handling in method initiateListParsing of FTPClient. - </action> - <action issue="NET-352" dev="sebb" type="add" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> - SASL PLAIN and CRAM-MD5 authentication. - </action> - <action issue="NET-357" dev="sebb" type="add" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> - The POP3 client does not support SSL/TLS connections. - </action> - <action dev="sebb" type="remove"> - Removed deprecated unused fields from FTPSClient: - - KEYSTORE_ALGORITHM, PROVIDER, STORE_TYPE, TRUSTSTORE_ALGORITHM - </action> - <action issue="NET-258" dev="sebb" type="fix"> - Implement A Keepalive Mechanism. Control channel keepalive implemented for the following methods: - appendFile, storeFile, storeUniqueFile, retrieveFile. - </action> - <action issue="NET-289" dev="sebb" type="fix" due-to="Luc Claes"> - StackOverflowError in Threader. - </action> - <action issue="NET-317" dev="sebb" type="fix"> - POP3MessageInfo fields should be final. - </action> - <action issue="NET-252" dev="sebb" type="fix"> - Get rid of using deprecated API in VMSFTPEntryParser. - </action> - <action issue="NET-330" dev="sebb" type="remove"> - The method VMSFTPEntryParser.parseFileList(InputStream listStream) should not be present. - </action> - <action issue="NET-303" dev="sebb" type="fix"> - FTPFileEntryParser API samples are wrong. - </action> - <action issue="NET-229" dev="sebb" type="add"> - Use properties file (/systemType.properties) to handle new OS-type auto-detection. - </action> - <action issue="NET-332" dev="sebb" type="add"> - Commons net ftp cannot handle unknown type parser and should allow override of parser through vm argument. - The system property "org.apache.commons.net.ftp.systemType" can be used to provide the system type. - </action> - <action issue="NET-286" dev="sebb" type="fix"> - Unhandled SecurityException in DefaultFTPFileEntryParserFactory.createFileEntryParser when using applets. - </action> - <action issue="NET-360" dev="sebb" type="fix"> - DefaultFTPFileEntryParserFactory.createFileEntryParser(String key) always tries to load a class. - </action> - <action issue="NET-156" dev="sebb" type="add"> - New FTPClient method to retrieve all directory names in the current working directory. - Added methods listDirectories(), listDirectories(String path). - </action> - <action issue="NET-353" dev="sebb" type="add" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> - The SMTPClient does not support authentication. - </action> - <action issue="NET-356" dev="sebb" type="add" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> - The SMTP client does not support SSL/TLS connections. - </action> - <action issue="NET-358" dev="sebb" type="add"> - Implement copy Listener in FTPClient file operations. - </action> - <action issue="NET-359" dev="sebb" type="fix"> - CopyStreamAdapter unconditionally resets the CopyStreamEvent source and is inefficient. - </action> - <action issue="NET-355" dev="sebb" type="fix"> - examples.nntp.NNTPUtils does not compile - </action> - <action issue="NET-351" dev="sebb" type="add" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> - APOP authentication fails most of the time. - Fix by adding leading 0 if necessary. - </action> - </release> - <release version="2.2" date="Nov 22, 2010" description=" + <action issue="NET-407" dev="sebb" type="update"> + Change lenientFutureDates to default to true. + This means short dates will be parsed as the current year when the host clock is up to 1 day ahead of the client clock. + </action> + <action issue="NET-404" dev="sebb" type="fix"> + FTPSSocketFactory does not override createSocket(); causes java.net.SocketException: Unconnected sockets not implemented. + </action> + <action issue="NET-399" dev="sebb" type="fix" due-to="Noah Levitt"> + ftp data connection does not use connectTimeout. + </action> + <action issue="NET-400" dev="sebb" type="update" due-to="David Kocher"> + Option to override SSL negotiation. Make FTPSClient#execAuth() and FTPSClient#sslNegotiation() protected + </action> + <action issue="NET-402" dev="sebb" type="fix"> + IMAP, NNTP, POP3 and SMTP classes uses BufferedReader for control channel, which does not follow the standard. + Changed reader to CRLFLineReader. + </action> + <action issue="NET-401" dev="sebb" type="fix"> + FTP class uses BufferedReader for control channel, which does not follow the standard. + Changed reader to CRLFLineReader. + </action> + <action issue="NET-331" dev="sebb" type="update"> + AS400 file timestamp format is wrong. Workround exists. + </action> + <action issue="NET-269" dev="sebb" type="update"> + Remove semi-redundant check in SubnetUtils.calculate(). + </action> + <action issue="NET-219" dev="sebb" type="update"> + Should Telnet class Exception blocks write to System.err? + Catch blocks removed, and throws clauses added to allow caller to more easily detect and recover. + </action> + <action issue="NET-397" dev="sebb" type="update" due-to="Bogdan Drozdowski" + due-to-email="bogdandr # op . pl"> + FTPSClient does not handle AUTH or ADAT and only partially handles PBSZ. FTPSCommand should be deprecated. + </action> + <action issue="NET-268" dev="sebb" type="fix"> + Better handling of CIDR/31 and CIDR/32 where isInclusive = false. + Return 0 for address count, and 0.0.0.0 for each of the addresses + </action> + <action issue="NET-395" dev="sebb" type="update"> + Move ProtocolCommandSupport to SocketClient. + </action> + <action issue="NET-393" dev="sebb" type="update"> + Should the sendCommandWithID() methods be public? + Made methods private, and deleted currently unused ones. + </action> + <action issue="NET-394" dev="sebb" type="update"> + Are the sendUntaggedCommand() methods needed? + Renamed the method as sendData(), as it's not a command. + </action> + <action issue="NET-392" dev="sebb" type="update"> + Use enum for IMAPCommand. + </action> + <action issue="NET-333" dev="sebb" type="add" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> + Added basic IMAP/IMAPS implementation. + </action> + <action issue="NET-389" dev="sebb" type="fix"> + Unix parser should ignore "total nnn" lines. + </action> + <action issue="NET-369" dev="sebb" type="remove"> + Article.addHeaderField() is currently write-only - there is no way to retrieve the headers - is it needed? + Method was removed, along with the field. + </action> + <action issue="NET-367" dev="sebb" type="fix"> + ntp.TimeStamp uses incorrect lazy initialisation of static fields simpleFormatter and utcFormatter. + </action> + <action issue="NET-381" dev="sebb" type="update"> + Parsing is inefficient, as it parses everything twice. + </action> + <action issue="NET-388" dev="sebb" type="fix"> + VMSVersioningFTPEntryParser#preParse should not call super.preParse(). + </action> + <action issue="NET-362" dev="sebb" type="fix"> + TelnetInputStream has various threading bugs. + </action> + <action issue="NET-89" dev="sebb" type="fix"> + TelnetClient use of FromNetASCIIInputStream and ToNetASCIIOutputStream breaks binary mode. + See also NET-387. + </action> + <action issue="NET-385" dev="sebb" type="update"> + FTP does not apply timeout to initial responses. + </action> + <action issue="NET-384" dev="sebb" type="update"> + KeyManagerUtils - the KeyManager is not efficient. + </action> + <action issue="NET-383" dev="sebb" type="update"> + KeyManagerUtils - allow alias to be omitted when there is only one private key in the store + </action> + <action issue="NET-326" dev="sebb" type="add"> + A KeyManager is required when the protection level is set to 'P' with FTPSClient on active mode. + Added KeyManagerUtils class to simplify provision of client certificates. + </action> + <action issue="NET-273" dev="sebb" type="add"> + FEAT response parsing. Added FTPClient methods: boolean hasFeature(feature [,option]), + String fetaureValue(feature), String[] featureValues(feature) + </action> + <action issue="NET-379" dev="sebb" type="add"> + FTPClient - support for processing arbitrary commands that only use the control channel + </action> + <action issue="NET-378" dev="sebb" type="add"> + FTP listing should support MLST and MLSD. + </action> + <action issue="NET-377" dev="sebb" type="update"> + NLST does not take notice of HiddenFiles setting. + </action> + <action issue="NET-373" dev="sebb" type="update"> + NNTP Listgroups not working - broken server implementation. + </action> + <action issue="NET-375" dev="sebb" type="update"> + DotTerminatedMessageReader should extend BufferedReader, rather than Reader. + </action> + <action issue="NET-374" dev="sebb" type="update"> + ParserInitializationException doesn't use standard JDK exception chaining + </action> + <action issue="NET-372" dev="sebb" type="add"> + FTPSClient: java.security.cert.CertificateException: No X509TrustManager implementation available if trustManager == null + </action> + <action issue="NET-371" dev="sebb" type="add"> + Create TrustManagerFactory to provide custom TrustManagers. + </action> + <action issue="NET-354" dev="sebb" type="fix" due-to="Leif John Korshavn"> + FTPSClient not properly supporting CCC and PROT P. + </action> + <action issue="NET-368" dev="sebb" type="update"> + Threader.thread should accept an Iterable rather than a List. + </action> + <action issue="NET-327" dev="sebb" type="add" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> + "Unconnected sockets not implemented" when using FTPSClient + Added disconnect() override which resets the socket factories to their defaults + </action> + <action issue="NET-350" dev="sebb" type="add" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> + "java.net.SocketException: Broken pipe" when calling "TelnetClient.sendAYT()" + Added SocketClient#isAvailable() method to perform additional checks on a socket. + </action> + <action issue="NET-237" dev="sebb" type="add"> + Add streaming methods (corresponding to array methods) to NNTPClient. + </action> + <action issue="NET-365" dev="sebb" type="fix"> + FTPClient.listFiles() does not work properly, if remote server speaks German. + Match non-space{3} instead of A-Za-z{3} + </action> + <action issue="NET-366" dev="sebb" type="fix"> + FTPClientConfig: setServerLanguageCode and setShortMonthNames do not work. + Ensure that config is passed to all parsers that can use it. + </action> + <action issue="NET-276" dev="sebb" type="fix"> + NNTPClient has problems with group listings for large groups. + </action> + <action issue="NET-185" dev="sebb" type="fix"> + Possible NPE in Threader.java + </action> + <action issue="NET-364" dev="sebb" type="fix"> + nntp.Article is very inefficient and incorrect. + </action> + <action issue="NET-314" dev="sebb" type="add" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> + The FTP client should autodetect the control encoding. + </action> + <action issue="NET-363" dev="sebb" type="fix" due-to="daniel damon"> + Can't connect to a server behind firewall in passive mode. + </action> + <action issue="NET-348" dev="sebb" type="fix"> + Queue is full TelnetInputStream. + </action> + <action issue="NET-361" dev="sebb" type="add"> + Implement Telnet Command sender. + </action> + <action issue="NET-345" dev="sebb" type="fix" due-to="Archie Cobbs"> + Telnet client: not properly handling IAC bytes within subnegotiation messages: + - failing to double IACs on output + - failing to de-double IACs in input + </action> + <action issue="NET-343" dev="sebb" type="add" due-to="Archie Cobbs"> + Telnet client: Support Client-initiated Subnegotiation Messages. + </action> + <action issue="NET-344" dev="sebb" type="add" due-to="Archie Cobbs"> + Telnet client: Support Listener Notification of Incoming Data. + </action> + <action issue="NET-270" dev="sebb" type="fix"> + Incorrect error handling in method initiateListParsing of FTPClient. + </action> + <action issue="NET-352" dev="sebb" type="add" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> + SASL PLAIN and CRAM-MD5 authentication. + </action> + <action issue="NET-357" dev="sebb" type="add" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> + The POP3 client does not support SSL/TLS connections. + </action> + <action dev="sebb" type="remove"> + Removed deprecated unused fields from FTPSClient: + - KEYSTORE_ALGORITHM, PROVIDER, STORE_TYPE, TRUSTSTORE_ALGORITHM + </action> + <action issue="NET-258" dev="sebb" type="fix"> + Implement A Keepalive Mechanism. Control channel keepalive implemented for the following methods: + appendFile, storeFile, storeUniqueFile, retrieveFile. + </action> + <action issue="NET-289" dev="sebb" type="fix" due-to="Luc Claes"> + StackOverflowError in Threader. + </action> + <action issue="NET-317" dev="sebb" type="fix"> + POP3MessageInfo fields should be final. + </action> + <action issue="NET-252" dev="sebb" type="fix"> + Get rid of using deprecated API in VMSFTPEntryParser. + </action> + <action issue="NET-330" dev="sebb" type="remove"> + The method VMSFTPEntryParser.parseFileList(InputStream listStream) should not be present. + </action> + <action issue="NET-303" dev="sebb" type="fix"> + FTPFileEntryParser API samples are wrong. + </action> + <action issue="NET-229" dev="sebb" type="add"> + Use properties file (/systemType.properties) to handle new OS-type auto-detection. + </action> + <action issue="NET-332" dev="sebb" type="add"> + Commons net ftp cannot handle unknown type parser and should allow override of parser through vm argument. + The system property "org.apache.commons.net.ftp.systemType" can be used to provide the system type. + </action> + <action issue="NET-286" dev="sebb" type="fix"> + Unhandled SecurityException in DefaultFTPFileEntryParserFactory.createFileEntryParser when using applets. + </action> + <action issue="NET-360" dev="sebb" type="fix"> + DefaultFTPFileEntryParserFactory.createFileEntryParser(String key) always tries to load a class. + </action> + <action issue="NET-156" dev="sebb" type="add"> + New FTPClient method to retrieve all directory names in the current working directory. + Added methods listDirectories(), listDirectories(String path). + </action> + <action issue="NET-353" dev="sebb" type="add" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> + The SMTPClient does not support authentication. + </action> + <action issue="NET-356" dev="sebb" type="add" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> + The SMTP client does not support SSL/TLS connections. + </action> + <action issue="NET-358" dev="sebb" type="add"> + Implement copy Listener in FTPClient file operations. + </action> + <action issue="NET-359" dev="sebb" type="fix"> + CopyStreamAdapter unconditionally resets the CopyStreamEvent source and is inefficient. + </action> + <action issue="NET-355" dev="sebb" type="fix"> + examples.nntp.NNTPUtils does not compile + </action> + <action issue="NET-351" dev="sebb" type="add" due-to="Bogdan Drozdowski" due-to-email="bogdandr # op . pl"> + APOP authentication fails most of the time. + Fix by adding leading 0 if necessary. + </action> + </release> + <release version="2.2" date="Nov 22, 2010" + description=" This is primarily a maintenance release, but it also includes new features and enhancements. Users of version 2.0 are encouraged to upgrade to 2.2, as this release includes some important bug fixes. @@ -802,668 +1083,606 @@ This is primarily a maintenance release, but it also includes new features and e See the detailed list of changes below for full description of all bug fixes and enhancements. "> - <action issue="NET-334" dev="sebb" type="fix"> - FromNetASCIIInputStream can throw a NullPointerException - </action> - <action issue="NET-341" dev="sebb" type="fix"> - FTPClient.remoteAppend(String filename) uses STOR instead of APPE - </action> - <action issue="NET-339" dev="sebb" type="fix"> - Incorrect parsing of timestamp on Windows CE - Fix parsing to allow for new-style DOS listing using 24hr clock rather than AM/PM - </action> - <action issue="NET-338" dev="sebb" type="add"> - ftp.FTPClient.initiateListParsing(String parserKey, String pathname) - can call createFileEntryParser with null systemName. - Fix this by adding getSystemType() which does not return null, and deprecating getSystemName(). - </action> - <action issue="NET-244" dev="sebb" type="add"> - Add support for FTPFileFilter filters. New classes FTPFileFilter, FTPFileFilters, new methods: - FTPListParseEngine#getFiles(FTPFileFilter filter) - FTPClient.listFiles(String pathname, FTPFileFilter filter) - </action> - <action issue="NET-313" dev="sebb" type="fix"> - Optionally enable EPSV with IPv4; Only send EPRT with IPv6. - Fix incorrect port used with EPRT. Allow activeMaxPort == activeMinPort in getActivePort() method. - </action> - <action issue="NET-328" dev="sebb" type="fix"> - FromNetASCIIInputStream.read(byte[], int, int) may change length passed to superclass if not doing conversion - </action> - <action issue="NET-74" dev="sebb" type="add"> - Testcase to show WindowSizeOptionHandler is working OK - </action> - <action issue="NET-330" dev="sebb" type="fix"> - The method VMSFTPEntryParser.parseFileList(InputStream listStream) should not be present. - Partial fix - marked method as deprecated and to be removed - </action> - <action issue="NET-180" dev="sebb" type="fix"> - Telnet EOR is "consumed" by TelnetInputStream when in BINARY transmission. - Send notification to TelnetNotificationHandler. - </action> - <action issue="NET-329" dev="sebb" type="fix"> - TelnetInoutStream#__read() bug in the __receiveState handling for the _STATE_IAC state. - </action> - <action issue="NET-283" dev="sebb" type="fix"> - SocketClient should ensure input and output streams are closed - </action> - <action issue="NET-302" dev="sebb" type="fix"> - FTP: initiateListParsing should not cache entryParser - </action> - <action issue="NET-282" dev="rwinston" type="fix"> - Improvement to isInRange method in SubnetUtil.SubnetInfo class - </action> - <action issue="NET-266" dev="rwinston" type="fix"> - FTPClient.listFiles() corrupts file name in certain circumstances - </action> - <action issue="NET-264" dev="sebb" type="fix"> - Telnet spyStream NullPointerException - </action> - <action dev="sebb" type="update"> - Deprecated the following unused fields from org.apache.commons.net.ftp.FTPSClient: - KEYSTORE_ALGORITHM, PROVIDER, STORE_TYPE, TRUSTSTORE_ALGORITHM - </action> - <action dev="niallp" type="fix"> - Fix site reports - </action> - <action issue="NET-285" dev="rwinston" type="fix"> - Add support for setting external host ip/port range - </action> - <action issue="NET-290" dev="rwinston" type="fix"> - Add fix and testcase for DotTerminatedMessageReader - </action> - <action issue="NET-288" dev="rwinston" type="fix"> - Add support for IPv6 EPRT/EPSV - </action> - <action issue="NET-305" dev="rwinston" type="fix"> - Fix SubnetUtils for /32 subnets and add inclusive host count flag - </action> - <action issue="NET-300" dev="rwinston" type="fix"> - Fix NPE when listHiddenFiles was on - </action> - <action issue="NET-215" dev="rwinston" type="fix"> - UNIXFTPEntryParser didn't preserve trailing whitespace in files - </action> - <action issue="NET-236" dev="rwinston" type="fix"> - method SubnetUtils.SubnetInfo.isInRange(addr) returns incorrect result - </action> - <action issue="NET-242" dev="rwinston" type="fix"> - Method createServerSocket of FTPSSocketFactory never called and thus UseClientMode is incorrect in a secured ftp transfer using active mode. - </action> - <action issue="NET-248" dev="rwinston" type="fix"> - Fix inconsistent command list in FTPCommand - </action> - <action issue="NET-250" dev="rwinston" type="fix"> - DefaultFTPFileEntryParserFactory did not work with Netware FTP server returning "NETWARE TYPE: L8" - </action> - <action issue="NET-257" dev="rwinston" type="fix"> - FTP.getReplyStrings() returned array of null Strings - </action> - <action issue="NET-259" dev="rwinston" type="fix"> - UnixFTPEntryParser regex did not match some directory entries - </action> - <action issue="NET-260" dev="rwinston" type="fix"> - SubnetUtils.SubnetInfo.isInRange(...) returned incorrect values - </action> - <action issue="NET-261" dev="rwinston" type="update"> - SubnetUtils.SubnetInfo.isInRange(...) behaviour not documented - </action> - <action issue="NET-262" dev="rwinston" type="fix"> - SubnetUtils did not handle /31 and /32 CIDRs well - </action> - <action issue="NET-265" dev="rwinston" type="fix"> - UnixFTPEntryParser failed to parse entry in certain conditions - </action> - <action issue="NET-266" dev="rwinston" type="fix"> - FTPClient.listFiles() corrupted file name in certain circumstances - </action> - <action issue="NET-251" dev="rwinston" type="update"> - Moved class "ThreadContainer" from Threader.java into its own source file - </action> - <action issue="NET-256" dev="rwinston" type="fix"> - FTPSClient should accept a pre-configured SSLContext - </action> - <action issue="NET-263" dev="rwinston" type="add"> - SubnetUtils / SubNetInfo toString() implementations - </action> - <action dev="rwinston" type="fix"> - Improve NNTPClient handling of invalid articles - </action> - <action dev="rwinston" type="update"> - Refactor examples package. - </action> - <action dev="sebb" type="add"> - Javadoc fixes, improvements, and refactoring. - </action> - <action issue="NET-245" dev="rwinston" type="fix"> - Apply MFMT patch - </action> - <action issue="NET-279" dev="rwinston" type="fix"> - Fix copying of reply lines collection - </action> - <action issue="NET-277" dev="rwinston" type="fix"> - Fix incorrect NNTP constant - </action> - <action issue="NET-274" dev="rwinston" type="fix"> - Restore socket state after CCC command - </action> - <action issue="NET-275" dev="rwinston" type="fix"> - Example code in FTPClient doesn't compile - </action> - <action dev="rwinston" type="fix"> - Fix inconsistent handling of socket read/write buffer size - </action> - <action issue="NET-294" dev="sebb" type="fix"> - UnixFTPEntryParser fails to parse some entries - </action> - </release> + <action issue="NET-334" dev="sebb" type="fix"> + FromNetASCIIInputStream can throw a NullPointerException + </action> + <action issue="NET-341" dev="sebb" type="fix"> + FTPClient.remoteAppend(String filename) uses STOR instead of APPE + </action> + <action issue="NET-339" dev="sebb" type="fix"> + Incorrect parsing of timestamp on Windows CE + Fix parsing to allow for new-style DOS listing using 24hr clock rather than AM/PM + </action> + <action issue="NET-338" dev="sebb" type="add"> + ftp.FTPClient.initiateListParsing(String parserKey, String pathname) + can call createFileEntryParser with null systemName. + Fix this by adding getSystemType() which does not return null, and deprecating getSystemName(). + </action> + <action issue="NET-244" dev="sebb" type="add"> + Add support for FTPFileFilter filters. New classes FTPFileFilter, FTPFileFilters, new methods: + FTPListParseEngine#getFiles(FTPFileFilter filter) + FTPClient.listFiles(String pathname, FTPFileFilter filter) + </action> + <action issue="NET-313" dev="sebb" type="fix"> + Optionally enable EPSV with IPv4; Only send EPRT with IPv6. + Fix incorrect port used with EPRT. Allow activeMaxPort == activeMinPort in getActivePort() method. + </action> + <action issue="NET-328" dev="sebb" type="fix"> + FromNetASCIIInputStream.read(byte[], int, int) may change length passed to superclass if not doing conversion + </action> + <action issue="NET-74" dev="sebb" type="add"> + Testcase to show WindowSizeOptionHandler is working OK + </action> + <action issue="NET-330" dev="sebb" type="fix"> + The method VMSFTPEntryParser.parseFileList(InputStream listStream) should not be present. + Partial fix - marked method as deprecated and to be removed + </action> + <action issue="NET-180" dev="sebb" type="fix"> + Telnet EOR is "consumed" by TelnetInputStream when in BINARY transmission. + Send notification to TelnetNotificationHandler. + </action> + <action issue="NET-329" dev="sebb" type="fix"> + TelnetInoutStream#__read() bug in the __receiveState handling for the _STATE_IAC state. + </action> + <action issue="NET-283" dev="sebb" type="fix"> + SocketClient should ensure input and output streams are closed + </action> + <action issue="NET-302" dev="sebb" type="fix"> + FTP: initiateListParsing should not cache entryParser + </action> + <action issue="NET-282" dev="rwinston" type="fix"> + Improvement to isInRange method in SubnetUtil.SubnetInfo class + </action> + <action issue="NET-266" dev="rwinston" type="fix"> + FTPClient.listFiles() corrupts file name in certain circumstances + </action> + <action issue="NET-264" dev="sebb" type="fix"> + Telnet spyStream NullPointerException + </action> + <action dev="sebb" type="update"> + Deprecated the following unused fields from org.apache.commons.net.ftp.FTPSClient: + KEYSTORE_ALGORITHM, PROVIDER, STORE_TYPE, TRUSTSTORE_ALGORITHM + </action> + <action dev="niallp" type="fix"> + Fix site reports + </action> + <action issue="NET-285" dev="rwinston" type="fix"> + Add support for setting external host ip/port range + </action> + <action issue="NET-290" dev="rwinston" type="fix"> + Add fix and testcase for DotTerminatedMessageReader + </action> + <action issue="NET-288" dev="rwinston" type="fix"> + Add support for IPv6 EPRT/EPSV + </action> + <action issue="NET-305" dev="rwinston" type="fix"> + Fix SubnetUtils for /32 subnets and add inclusive host count flag + </action> + <action issue="NET-300" dev="rwinston" type="fix"> + Fix NPE when listHiddenFiles was on + </action> + <action issue="NET-215" dev="rwinston" type="fix"> + UNIXFTPEntryParser didn't preserve trailing whitespace in files + </action> + <action issue="NET-236" dev="rwinston" type="fix"> + method SubnetUtils.SubnetInfo.isInRange(addr) returns incorrect result + </action> + <action issue="NET-242" dev="rwinston" type="fix"> + Method createServerSocket of FTPSSocketFactory never called and thus UseClientMode is incorrect in a secured ftp transfer + using active mode. + </action> + <action issue="NET-248" dev="rwinston" type="fix"> + Fix inconsistent command list in FTPCommand + </action> + <action issue="NET-250" dev="rwinston" type="fix"> + DefaultFTPFileEntryParserFactory did not work with Netware FTP server returning "NETWARE TYPE: L8" + </action> + <action issue="NET-257" dev="rwinston" type="fix"> + FTP.getReplyStrings() returned array of null Strings + </action> + <action issue="NET-259" dev="rwinston" type="fix"> + UnixFTPEntryParser regex did not match some directory entries + </action> + <action issue="NET-260" dev="rwinston" type="fix"> + SubnetUtils.SubnetInfo.isInRange(...) returned incorrect values + </action> + <action issue="NET-261" dev="rwinston" type="update"> + SubnetUtils.SubnetInfo.isInRange(...) behavior not documented + </action> + <action issue="NET-262" dev="rwinston" type="fix"> + SubnetUtils did not handle /31 and /32 CIDRs well + </action> + <action issue="NET-265" dev="rwinston" type="fix"> + UnixFTPEntryParser failed to parse entry in certain conditions + </action> + <action issue="NET-266" dev="rwinston" type="fix"> + FTPClient.listFiles() corrupted file name in certain circumstances + </action> + <action issue="NET-251" dev="rwinston" type="update"> + Moved class "ThreadContainer" from Threader.java into its own source file + </action> + <action issue="NET-256" dev="rwinston" type="fix"> + FTPSClient should accept a pre-configured SSLContext + </action> + <action issue="NET-263" dev="rwinston" type="add"> + SubnetUtils / SubNetInfo toString() implementations + </action> + <action dev="rwinston" type="fix"> + Improve NNTPClient handling of invalid articles + </action> + <action dev="rwinston" type="update"> + Refactor examples package. + </action> + <action dev="sebb" type="add"> + Javadoc fixes, improvements, and refactoring. + </action> + <action issue="NET-245" dev="rwinston" type="fix"> + Apply MFMT patch + </action> + <action issue="NET-279" dev="rwinston" type="fix"> + Fix copying of reply lines collection + </action> + <action issue="NET-277" dev="rwinston" type="fix"> + Fix incorrect NNTP constant + </action> + <action issue="NET-274" dev="rwinston" type="fix"> + Restore socket state after CCC command + </action> + <action issue="NET-275" dev="rwinston" type="fix"> + Example code in FTPClient doesn't compile + </action> + <action dev="rwinston" type="fix"> + Fix inconsistent handling of socket read/write buffer size + </action> + <action issue="NET-294" dev="sebb" type="fix"> + UnixFTPEntryParser fails to parse some entries + </action> + </release> - <release version="2.1" description="Not released"/> + <release version="2.1" description="Not released" /> - <release version="2.0" date="October 20, 2008" description="Java 5.0 release"> - <action dev="rwinston" type="fix" issue="NET-307"> - One of the "connect" method in class org.apache.commons.net.SocketClient doesn't handle connection timeout properly - </action> - <action dev="rwinston" type="update"> - Add null check in TelnetClient::disconnect(). - </action> - <action dev="rwinston" type="remove"> - Remove deprecated FTPFileIterator and FTPFileList classes. - </action> - <action dev="rwinston" type="add"> - Add connection timeout functionality to SocketClient. - </action> - <action dev="rwinston" type="update"> - Make the KeyManager and TrustManager settable (niklas@protocol7.com). - </action> - <action dev="rwinston" type="update"> - Patch FTPSClient to set default SSLServerSocketFactory. Thanks niklas@protocol7.com - </action> - <action dev="rwinston" type="fix" issue="NET-68"> - Patch to prevent TFTPClient dropping last packet. Thanks palm@poplarware.com - </action> - <action dev="rwinston" type="update"> - Change isConnected() method to delegate to underlying socket connection. - </action> - <action dev="rwinston" type="add"> - FTPS (TLS and SSL) is now supported. Thanks to Jose Juan Montiel, Paul Ferraro, and Satoshi Ishigami. - </action> - <action dev="rwinston" type="update"> - Commons::Net now uses Maven 2. The project.xml has been replaced with a pom.xml, and the source tree layout - has been changed accordingly. - </action> - <action dev="rwinston" type="remove"> - Removed old ftp2 proposal directories. - </action> - <action dev="rwinston" type="update"> - Commons::Net now uses JDK regex functionality, saving on an extra [oro] dependency. There are now - no external dependencies required. - </action> - <action dev="rwinston" type="fix"> - Various syntactic issues (FindBugs issues, JDK 5.0 generics support) - </action> - <action dev="dfs" type="fix"> - Applied Rob Hasselbaum's - rhasselbaum -> alumni.ithaca.edu - patch for PR 38688 fixing a - TelnetInputStream hang. - </action> - <action dev="dfs" type="update"> - Exposed control connection of FTP - class via _controlInput_ and _controlOutput_ - protected member variables in response - to PR 38309 reported by - josejuan.montiel@gmail.com. - </action> - <action dev="dfs" type="fix"> - Reverted PR 32859 patch to TFTPClient - because it caused final packets to not - be sent. - </action> - <action dev="rwinston" type="update" issue="NET-36"> - Make FTPClient extend SocketClient instead of TelnetClient. From jhindsley@providerlink.com - </action> - <action dev="rwinston" type="fix" issue="NET-39"> - Adds an "e" symbolic link flag to the Unix FTP parser. From denisgaebler@netscape.net - </action> - <action dev="rwinston" type="fix" issue="NET-119"> - Allow hidden files to be listed. Thanks to mario@ops.co.at - </action> - <action dev="rwinston" type="update"> - Remove reflective check for Socket::isConnected() (no longer needed) - </action> - <action dev="rwinston" type="add" issue="NET-136"> - Added WindowSizeOptionHandler for TelnetClient. Thanks to yuvalkashtan@gmail.com - </action> - <action dev="rwinston" type="update"> - Refactored *Client classes under net/ package into separate subpackages, and move PrintCommandListener - out of the examples/ package. - </action> - <action dev="rwinston" type="add"> - Added an ant target to the Maven build to generate an FTP-only jar file, for clients who - wish to use only FTP-based functionality. - </action> - <action dev="rwinston" type="update"> - Custom SocketFactory interface has been replaced with the JDK SocketFactory implementation. Added - ServerSocketFactory instance to SocketClient. - </action> - <action dev="rwinston" type="update"> - Removed redundant FTP.IMAGE_FILE_TYPE flag. - </action> - <action dev="rwinston" type="update"> - Added heavily updated MVSFTPEntryParser from henrik.sorensen@balcab.ch - </action> - <action dev="rwinston" type="remove"> - Removed deprecated classes FTPFileListParser, FTPFileListParserImpl, and DefaultFTPFileListParser. Also - removed associated deprecated methods from FTPClient. - </action> - <action dev="rwinston" type="fix" issue="NET-164"> - Added encoding to FingerClient. From Ulrich Mayring. - </action> - <action dev="rwinston" type="fix" issue="NET-24"> - Catch BindException in RCommandClient::connect(). - </action> - <action dev="rwinston" type="fix" issue="NET-178"> - Add encoding specifier to SMTPClient. - </action> - <action dev="rwinston" type="add"> - Add setters for socket send/receive buffer size to SocketClient. - </action> - <action dev="rwinston" type="fix" issue="NET-177"> - Fix PASV specifiers that broke previously. From Chris Eagle. - </action> - <action dev="rwinston" type="fix" issue="NET-182"> - Catch NPE in FTP parser factory method. - </action> - <action dev="rwinston" type="fix" issue="NET-172"> - Don't bind a UDP socket to NTP protocol port. - </action> - <action dev="rwinston" type="fix"> - Better handling of user and group names with embedded spaces in FTP listings. - </action> - <action dev="rwinston" type="fix" issue="NET-173"> - Add configurable multiline parsing. - </action> - <action dev="rwinston" type="fix" issue="NET-188"> - Add fix for broken leap year date parsing. - </action> - <action dev="rwinston" type="add"> - Add SubnetUtils class (suggested by Kenny McLeod) - </action> - <action dev="rwinston" type="fix" issue="NET-169"> - Add Unix-type handling for UNKNOWN Type: L8 syst() message systems. - </action> - <action dev="rwinston" type="fix" issue="NET-198"> - Allow FTPTimestampParserImpl to take a predefined Calendar instance representing current time. - </action> - <action dev="sebb" type="fix" issue="NET-194"> - Replace Exception with IOException - </action> - <action dev="sebb" type="update" issue="NET-214"> - VMS file permission parsing - </action> - <action dev="sebb" type="fix" issue="NET-208"> - TelnetInputStream swallows interruptedexception as IOException - </action> - <action dev="sebb" type="fix" issue="NET-223"> - the data connection socket is not closed when an IOException occurred - </action> - <action dev="sebb" type="fix" issue="NET-230"> - ParserInitializationException when connecting to a Unix FTP server: comparison string must be upper case - </action> - <action dev="sebb" type="fix" issue="NET-225"> - FTPFileEntryParserImpl.preParse() doesn't remove unparsable entries at the end of the file list - </action> - </release> + <release version="2.0" date="October 20, 2008" description="Java 5.0 release"> + <action dev="rwinston" type="fix" issue="NET-307"> + One of the "connect" method in class org.apache.commons.net.SocketClient doesn't handle connection timeout properly + </action> + <action dev="rwinston" type="update"> + Add null check in TelnetClient::disconnect(). + </action> + <action dev="rwinston" type="remove"> + Remove deprecated FTPFileIterator and FTPFileList classes. + </action> + <action dev="rwinston" type="add"> + Add connection timeout functionality to SocketClient. + </action> + <action dev="rwinston" type="update"> + Make the KeyManager and TrustManager settable (niklas@protocol7.com). + </action> + <action dev="rwinston" type="update"> + Patch FTPSClient to set default SSLServerSocketFactory. Thanks niklas@protocol7.com + </action> + <action dev="rwinston" type="fix" issue="NET-68"> + Patch to prevent TFTPClient dropping last packet. Thanks palm@poplarware.com + </action> + <action dev="rwinston" type="update"> + Change isConnected() method to delegate to underlying socket connection. + </action> + <action dev="rwinston" type="add"> + FTPS (TLS and SSL) is now supported. Thanks to Jose Juan Montiel, Paul Ferraro, and Satoshi Ishigami. + </action> + <action dev="rwinston" type="update"> + Commons::Net now uses Maven 2. The project.xml has been replaced with a pom.xml, and the source tree layout + has been changed accordingly. + </action> + <action dev="rwinston" type="remove"> + Removed old ftp2 proposal directories. + </action> + <action dev="rwinston" type="update"> + Commons::Net now uses JDK regex functionality, saving on an extra [oro] dependency. There are now + no external dependencies required. + </action> + <action dev="rwinston" type="fix"> + Various syntactic issues (FindBugs issues, JDK 5.0 generics support) + </action> + <action dev="dfs" type="fix"> + Applied Rob Hasselbaum's + rhasselbaum -> alumni.ithaca.edu + patch for PR 38688 fixing a + TelnetInputStream hang. + </action> + <action dev="dfs" type="update"> + Exposed control connection of FTP + class via _controlInput_ and _controlOutput_ + protected member variables in response + to PR 38309 reported by + josejuan.montiel@gmail.com. + </action> + <action dev="dfs" type="fix"> + Reverted PR 32859 patch to TFTPClient + because it caused final packets to not + be sent. + </action> + <action dev="rwinston" type="update" issue="NET-36"> + Make FTPClient extend SocketClient instead of TelnetClient. From jhindsley@providerlink.com + </action> + <action dev="rwinston" type="fix" issue="NET-39"> + Adds an "e" symbolic link flag to the Unix FTP parser. From denisgaebler@netscape.net + </action> + <action dev="rwinston" type="fix" issue="NET-119"> + Allow hidden files to be listed. Thanks to mario@ops.co.at + </action> + <action dev="rwinston" type="update"> + Remove reflective check for Socket::isConnected() (no longer needed) + </action> + <action dev="rwinston" type="add" issue="NET-136"> + Added WindowSizeOptionHandler for TelnetClient. Thanks to yuvalkashtan@gmail.com + </action> + <action dev="rwinston" type="update"> + Refactored *Client classes under net/ package into separate subpackages, and move PrintCommandListener + out of the examples/ package. + </action> + <action dev="rwinston" type="add"> + Added an ant target to the Maven build to generate an FTP-only jar file, for clients who + wish to use only FTP-based functionality. + </action> + <action dev="rwinston" type="update"> + Custom SocketFactory interface has been replaced with the JDK SocketFactory implementation. Added + ServerSocketFactory instance to SocketClient. + </action> + <action dev="rwinston" type="update"> + Removed redundant FTP.IMAGE_FILE_TYPE flag. + </action> + <action dev="rwinston" type="update"> + Added heavily updated MVSFTPEntryParser from henrik.sorensen@balcab.ch + </action> + <action dev="rwinston" type="remove"> + Removed deprecated classes FTPFileListParser, FTPFileListParserImpl, and DefaultFTPFileListParser. Also + removed associated deprecated methods from FTPClient. + </action> + <action dev="rwinston" type="fix" issue="NET-164"> + Added encoding to FingerClient. From Ulrich Mayring. + </action> + <action dev="rwinston" type="fix" issue="NET-24"> + Catch BindException in RCommandClient::connect(). + </action> + <action dev="rwinston" type="fix" issue="NET-178"> + Add encoding specifier to SMTPClient. + </action> + <action dev="rwinston" type="add"> + Add setters for socket send/receive buffer size to SocketClient. + </action> + <action dev="rwinston" type="fix" issue="NET-177"> + Fix PASV specifiers that broke previously. From Chris Eagle. + </action> + <action dev="rwinston" type="fix" issue="NET-182"> + Catch NPE in FTP parser factory method. + </action> + <action dev="rwinston" type="fix" issue="NET-172"> + Don't bind a UDP socket to NTP protocol port. + </action> + <action dev="rwinston" type="fix"> + Better handling of user and group names with embedded spaces in FTP listings. + </action> + <action dev="rwinston" type="fix" issue="NET-173"> + Add configurable multiline parsing. + </action> + <action dev="rwinston" type="fix" issue="NET-188"> + Add fix for broken leap year date parsing. + </action> + <action dev="rwinston" type="add"> + Add SubnetUtils class (suggested by Kenny McLeod) + </action> + <action dev="rwinston" type="fix" issue="NET-169"> + Add Unix-type handling for UNKNOWN Type: L8 syst() message systems. + </action> + <action dev="rwinston" type="fix" issue="NET-198"> + Allow FTPTimestampParserImpl to take a predefined Calendar instance representing current time. + </action> + <action dev="sebb" type="fix" issue="NET-194"> + Replace Exception with IOException + </action> + <action dev="sebb" type="update" issue="NET-214"> + VMS file permission parsing + </action> + <action dev="sebb" type="fix" issue="NET-208"> + TelnetInputStream swallows interruptedexception as IOException + </action> + <action dev="sebb" type="fix" issue="NET-223"> + the data connection socket is not closed when an IOException occurred + </action> + <action dev="sebb" type="fix" issue="NET-230"> + ParserInitializationException when connecting to a Unix FTP server: comparison string must be upper case + </action> + <action dev="sebb" type="fix" issue="NET-225"> + FTPFileEntryParserImpl.preParse() doesn't remove unparsable entries at the end of the file list + </action> + </release> -<!-- 1.5.0 has not yet been released, so comment out the section until it is ready for release --> -<!-- - <release version="1.5.0" date="" description=""> - <action dev="dfs" type="fix" issue="NET-3"> - TelnetInputStream.java: Applied Rob - Hasselbaum's - rhasselbaum@alumni.ithaca.edu - patch for PR 38688 fixing a - TelnetInputStream hang. - </action> - <action dev="rwinston" type="fix" - issue="NET-73"> - TelnetInputStream.java: Fixing another - potential deadlock for - telnet and FTP (patch courtesy Rob Hasselbaum). - </action> - <action dev="dfs" type="update" issue="NET-57"> - FTP.java: Exposed control connection of - FTP - class via _controlInput_ and - _controlOutput_ - protected member variables in response - to PR 38309 reported by - josejuan.montiel@gmail.com. - </action> - <action dev="rwinston" type="fix" - issue="NET-68"> - TFTPClient.java: Fix bug causing final - packets - to not be sent. - </action> - <action dev="rwinston" type="fix" - issue="NET-161"> - TFTPClient.java: Fix sendFile() (related - to NET-68). - </action> - <action dev="rwinston" type="fix" - issue="NET-181"> - TFTPClient.java: block number - wraparound. - </action> - <action dev="scohen" type="fix" issue="NET-16"> - UNIXFTPEntryParser.java: support for - group names with - spaces (patch courtesy D. Kilzer). - </action> - <action dev="scohen" type="fix" issue="NET-62"> - DefaultFTPFileEntryParserFactory.java: - Wrap - NoClassDefFoundError in FTP parser exception - when ORO is not available. - </action> - <action dev="rwinston" type="add" - issue="NET-33"> - FTPClient.java: Fix closing FTP - ServerSocket after timeout - </action> - <action dev="rwinston" type="add"> - FTPClientConfig.java: Added an FTP - parser for Netware FTP servers. - Tested on Novell Netware 6.5. - </action> - <action dev="rwinston" type="fix" - issue="NET-188"> - FTPTimestampParserImpl.java: Fix leap - year date parsing bug. - </action> - <action dev="rwinston" type="fix"> - Article.java: Fix minor issues with NNTP - parsing. - </action> - </release> ---> + <!-- 1.5.0 has not yet been released, so comment out the section until it is ready for release --> + <!-- <release version="1.5.0" date="" description=""> <action dev="dfs" type="fix" issue="NET-3"> TelnetInputStream.java: + Applied Rob Hasselbaum's rhasselbaum@alumni.ithaca.edu patch for PR 38688 fixing a TelnetInputStream hang. </action> <action + dev="rwinston" type="fix" issue="NET-73"> TelnetInputStream.java: Fixing another potential deadlock for telnet and FTP (patch + courtesy Rob Hasselbaum). </action> <action dev="dfs" type="update" issue="NET-57"> FTP.java: Exposed control connection + of FTP class via _controlInput_ and _controlOutput_ protected member variables in response to PR 38309 reported by josejuan.montiel@gmail.com. + </action> <action dev="rwinston" type="fix" issue="NET-68"> TFTPClient.java: Fix bug causing final packets to not be sent. + </action> <action dev="rwinston" type="fix" issue="NET-161"> TFTPClient.java: Fix sendFile() (related to NET-68). </action> + <action dev="rwinston" type="fix" issue="NET-181"> TFTPClient.java: block number wraparound. </action> <action dev="scohen" + type="fix" issue="NET-16"> UNIXFTPEntryParser.java: support for group names with spaces (patch courtesy D. Kilzer). </action> + <action dev="scohen" type="fix" issue="NET-62"> DefaultFTPFileEntryParserFactory.java: Wrap NoClassDefFoundError in FTP parser + exception when ORO is not available. </action> <action dev="rwinston" type="add" issue="NET-33"> FTPClient.java: Fix closing + FTP ServerSocket after timeout </action> <action dev="rwinston" type="add"> FTPClientConfig.java: Added an FTP parser for + Netware FTP servers. Tested on Novell Netware 6.5. </action> <action dev="rwinston" type="fix" issue="NET-188"> FTPTimestampParserImpl.java: + Fix leap year date parsing bug. </action> <action dev="rwinston" type="fix"> Article.java: Fix minor issues with NNTP parsing. + </action> </release> --> - <release version="1.4.1" date="December 3, 2005" description="fix release to restore jdk 1.3 compatability"> - <action dev="scohen" type="fix"> - Applied patches for defect 37113. Code incompatible with jdk 1.3. Original patch submitted by Andrea Rombald - </action> - <action dev="scohen" type="fix"> - Applied patches for defect 37522. updated project.xml to correct compatibility level. - </action> - </release> + <release version="1.4.1" date="December 3, 2005" description="fix release to restore jdk 1.3 compatability"> + <action dev="scohen" type="fix"> + Applied patches for defect 37113. Code incompatible with jdk 1.3. Original patch submitted by Andrea Rombald + </action> + <action dev="scohen" type="fix"> + Applied patches for defect 37522. updated project.xml to correct compatibility level. + </action> + </release> - <release version="1.4.0" date="May 7, 2005" description="Some additions and enhancements"> - <action dev="dfs" type="fix"> - Fixed typo in method name. - FTP.removeCommandListener() was missing - the L. Problem reported by - Per.Lindberger@linkon.se. - </action> - <action dev="rwinston" type="fix"> - Applied fix for PR 33942 and PR 31793. Original patch submitted by mario@ops.co.at - </action> - <action dev="rwinston" type="fix"> - TFTPClient was ignoring final ACK (PR 32859). Thanks to perttu.auramo@ekahau.com - </action> - <action dev="rwinston" type="fix"> - Applied fix for ACL parsing in the FTP client (PR 33972). Submitted by robertalasch@yahoo.com - </action> - <action dev="rwinston" type="fix"> - Added missing NTP/SNTP unit tests to the codebase. - </action> - <action dev="dfs" type="fix"> - Applied fix for POP3Client returning empty reply strings (PR 34133). Thanks to sammy_c@lineone.net - </action> - <action dev="rwinston" type="fix"> - NTP port parameter was being ignored (PR 34219). Fixed by felix.eichhorn@3soft.de - </action> - <action dev="scohen" type="add"> - An FTP parser for MVS was added. Submitted by wnoto@openfinance.com - </action> - <action dev="scohen" type="add"> - Added functionality for extensible parsing of FTP responses, using a configurable format string. This should enable the FTP client to operate across many different locales and date formats. - </action> - </release> + <release version="1.4.0" date="May 7, 2005" description="Some additions and enhancements"> + <action dev="dfs" type="fix"> + Fixed typo in method name. + FTP.removeCommandListener() was missing + the L. Problem reported by + Per.Lindberger@linkon.se. + </action> + <action dev="rwinston" type="fix"> + Applied fix for PR 33942 and PR 31793. Original patch submitted by mario@ops.co.at + </action> + <action dev="rwinston" type="fix"> + TFTPClient was ignoring final ACK (PR 32859). Thanks to perttu.auramo@ekahau.com + </action> + <action dev="rwinston" type="fix"> + Applied fix for ACL parsing in the FTP client (PR 33972). Submitted by robertalasch@yahoo.com + </action> + <action dev="rwinston" type="fix"> + Added missing NTP/SNTP unit tests to the codebase. + </action> + <action dev="dfs" type="fix"> + Applied fix for POP3Client returning empty reply strings (PR 34133). Thanks to sammy_c@lineone.net + </action> + <action dev="rwinston" type="fix"> + NTP port parameter was being ignored (PR 34219). Fixed by felix.eichhorn@3soft.de + </action> + <action dev="scohen" type="add"> + An FTP parser for MVS was added. Submitted by wnoto@openfinance.com + </action> + <action dev="scohen" type="add"> + Added functionality for extensible parsing of FTP responses, using a configurable format string. This should enable the + FTP client to operate across many different locales and date formats. + </action> + </release> - <release version="1.3.0" date="December 15, 2004" description="many fixes and enhancements"> - <action dev="rwinston" type="fix"> - Applied patch for PR 31793. Thanks to mario@ops.co.at - </action> - <action dev="rwinston" type="add"> - Added message threading functionality to the NNTP client. - </action> - <action dev="rwinston" type="update"> - Added return code 521 to FTPReply.java - this should obviate the need for the Ant FTP task to manually declare it. - </action> - <action dev="rwinston" type="fix"> - Add explicit notify() in TelnetInputStream::read(), so available() returns an accurate value. Thanks to tpalkot@gmail.com. - </action> - <action dev="rwinston" type="add"> - Added SNTP/NTP components into the Commons-Net codebase, courtesy of - Jason Matthews. - </action> - <action dev="rwinston" type="add"> - Added POP3 test suite, courtesy of Mike George mike.george@comcast.net. - </action> - <action dev="scohen" type="fix"> - Applied fix for FTPClient returning null for certain timestamp formats (BUG #30737) - </action> - <action dev="rwinston" type="fix"> - Build.xml fixes - dont include example classes in redistributable - .jar, remove test dependency from javadoc target, and exclude private members from generated javadoc. - </action> - <action dev="rwinston" type="fix"> - Fixed bug in TFTPClient::setMaxTimeout(), spotted by steve@widge.net - </action> - <action dev="dfs" type="fix"> - Some changes to facilitate compilation under JDK 5.0 - </action> - <action dev="rwinston" type="fix"> - Return correct NNTP article count when high and low watermarks are 0. - Spotted by jmordax@terra.es - </action> - <action dev="rwinston" type="fix"> - Remove trailing null byte in TFTP packets. Thanks to gerard.dens@alcatel.be - </action> - <action dev="dfs" type="fix"> - Many javadoc fixes. - </action> - <action dev="rwinston" type="update"> - Allow FTPClient to set transfer buffer size. - </action> - <action dev="rwinston" type="update"> - Ensure consistent handling of encoding throughout FTPClient - operations. Patch submitted by leif@tanukisoftware.com. - </action> - <action dev="dfs" type="fix"> - Fix TelnetClient zombie thread issue - </action> + <release version="1.3.0" date="December 15, 2004" description="many fixes and enhancements"> + <action dev="rwinston" type="fix"> + Applied patch for PR 31793. Thanks to mario@ops.co.at + </action> + <action dev="rwinston" type="add"> + Added message threading functionality to the NNTP client. + </action> + <action dev="rwinston" type="update"> + Added return code 521 to FTPReply.java - this should obviate the need for the Ant FTP task to manually declare it. + </action> + <action dev="rwinston" type="fix"> + Add explicit notify() in TelnetInputStream::read(), so available() returns an accurate value. Thanks to + tpalkot@gmail.com. + </action> + <action dev="rwinston" type="add"> + Added SNTP/NTP components into the Commons-Net codebase, courtesy of + Jason Matthews. + </action> + <action dev="rwinston" type="add"> + Added POP3 test suite, courtesy of Mike George mike.george@comcast.net. + </action> + <action dev="scohen" type="fix"> + Applied fix for FTPClient returning null for certain timestamp formats (BUG #30737) + </action> + <action dev="rwinston" type="fix"> + Build.xml fixes - dont include example classes in redistributable + .jar, remove test dependency from javadoc target, and exclude private members from generated javadoc. + </action> + <action dev="rwinston" type="fix"> + Fixed bug in TFTPClient::setMaxTimeout(), spotted by steve@widge.net + </action> + <action dev="dfs" type="fix"> + Some changes to facilitate compilation under JDK 5.0 + </action> + <action dev="rwinston" type="fix"> + Return correct NNTP article count when high and low watermarks are 0. + Spotted by jmordax@terra.es + </action> + <action dev="rwinston" type="fix"> + Remove trailing null byte in TFTP packets. Thanks to gerard.dens@alcatel.be + </action> + <action dev="dfs" type="fix"> + Many javadoc fixes. + </action> + <action dev="rwinston" type="update"> + Allow FTPClient to set transfer buffer size. + </action> + <action dev="rwinston" type="update"> + Ensure consistent handling of encoding throughout FTPClient + operations. Patch submitted by leif@tanukisoftware.com. + </action> + <action dev="dfs" type="fix"> + Fix TelnetClient zombie thread issue + </action> - </release> - <release version="1.3.0-dev" date="July 28, 2004" - description="regression fix"> - <action dev="dfs" type="fix"> - Fixed regression from migration to new parsers. Most of the - new parsers parsed the file size as an integer instead of a - long. Changed all of them to set the size to long. This - problem was detected by the reporter of: - http://issues.apache.org/bugzilla/show_bug.cgi?id=30345 - </action> - </release> - <release version="1.2.2" date="June 25, 2004" description="fix release"> - <action dev="scohen" type="fix"> - fixed bug in the way FTPClient.listFiles worked when a directory was not - specified. Current directory was not being 'remembered'. This was most - problematic in the dependent ftp task of Ant. - </action> - <action dev="scohen" type="fix"> - fixed handling of certain unusual "special" file types in the Unix parser. - </action> - </release> + </release> + <release version="1.3.0-dev" date="July 28, 2004" description="regression fix"> + <action dev="dfs" type="fix"> + Fixed regression from migration to new parsers. Most of the + new parsers parsed the file size as an integer instead of a + long. Changed all of them to set the size to long. This + problem was detected by the reporter of: + https://issues.apache.org/bugzilla/show_bug.cgi?id=30345 + </action> + </release> + <release version="1.2.2" date="June 25, 2004" description="fix release"> + <action dev="scohen" type="fix"> + fixed bug in the way FTPClient.listFiles worked when a directory was not + specified. Current directory was not being 'remembered'. This was most + problematic in the dependent ftp task of Ant. + </action> + <action dev="scohen" type="fix"> + fixed handling of certain unusual "special" file types in the Unix parser. + </action> + </release> - <release version="1.2.1" date="May 6, 2004" description="fix release"> - <action dev="scohen" type="fix"> - changed code that rendered package uncompilable under JDK 1.2 - </action> - </release> + <release version="1.2.1" date="May 6, 2004" description="fix release"> + <action dev="scohen" type="fix"> + changed code that rendered package uncompilable under JDK 1.2 + </action> + </release> - <release version="1.2.0" date="April 30, 2004" description="autodetection of system for listings"> - <action dev="scohen" type="fix"> - Mario Ivankovits mario@ops.co.at added - functionality supporting correct handling of the "dirstyle" - attribute of NT and OS400 servers that allows them to mimic Unix ftp servers. - and a bug fix affecting handling of sticky and suid bits on Unix FTP servers. - </action> - <action dev="scohen" type="add"> - Mario Ivankovits mario@ops.co.at added parser for OS400. - </action> - <action dev="jbrekke,scohen" type="fix"> - Added a functional junit test testing list parsing against real servers - and fix several bugs found through this test. - </action> - <action dev="dfs" type="add"> - Ted Wise ctwise@bellsouth.net provided a - patch to add the XHDR extended NNTP command. - </action> - <action dev="scohen,dfs" type="update"> - Deprecated FTPFileListParser interface, DefaultFTPFileListParser - class, and the FTPClient.listFiles methods that accepted an - FTPFileListParser parameter. These deprecated classes and methods - will be removed in version 2.0. - </action> - <action dev="scohen" type="add"> - Added org.apache.commons.net.parser.FTPFileEntryParserFactory - interface and a default implementation: - DefaultFTPFileEntryParserFactory. This addition facilitates the - autodetection of which FTPFileEntryParser to use to generate - listings. FTPClient.listFiles methods were added that implement - autodetection. - </action> - </release> + <release version="1.2.0" date="April 30, 2004" description="autodetection of system for listings"> + <action dev="scohen" type="fix"> + Mario Ivankovits mario@ops.co.at added + functionality supporting correct handling of the "dirstyle" + attribute of NT and OS400 servers that allows them to mimic Unix ftp servers. + and a bug fix affecting handling of sticky and suid bits on Unix FTP servers. + </action> + <action dev="scohen" type="add"> + Mario Ivankovits mario@ops.co.at added parser for OS400. + </action> + <action dev="jbrekke,scohen" type="fix"> + Added a functional junit test testing list parsing against real servers + and fix several bugs found through this test. + </action> + <action dev="dfs" type="add"> + Ted Wise ctwise@bellsouth.net provided a + patch to add the XHDR extended NNTP command. + </action> + <action dev="scohen,dfs" type="update"> + Deprecated FTPFileListParser interface, DefaultFTPFileListParser + class, and the FTPClient.listFiles methods that accepted an + FTPFileListParser parameter. These deprecated classes and methods + will be removed in version 2.0. + </action> + <action dev="scohen" type="add"> + Added org.apache.commons.net.parser.FTPFileEntryParserFactory + interface and a default implementation: + DefaultFTPFileEntryParserFactory. This addition facilitates the + autodetection of which FTPFileEntryParser to use to generate + listings. FTPClient.listFiles methods were added that implement + autodetection. + </action> + </release> -<!-- Not yet released; probably never will be - <release version="1.1.1" date="TBD" description="last jdk1.1 compatible release"> - <action dev="scohen" type="fix"> - Removed all JDK 1.1 incompatibilities that had been introduced - unintentionally in previous versions. Release 1.1.1 is the last - JDK 1.1 compatible release. Any future 1.1.x maintenance releases - will remain JDK !.1 compatible, but version 1.2 may break - compatibility and will be guaranteed to work with only J2SE 1.2 - and later. - </action> - </release> - --> - <release version="1.1.0" date="October 23, 2003" description="many enhancements and bug fixes"> - <action dev="dfs" type="add"> - Rory Winston Rory.Winston@telewest.co.uk provided - patches to add the following extended NNTP commands to - NNTPClient: XOVER, AUTHINFO USER, AUTHINFO PASS, and - LIST ACTIVE. - </action> - <action dev="dfs" type="fix"> - Changed connection hooks for FTP, SMTP, POP3, and NNTP classes - to force use of an 8-bit US-ASCII superset (ISO-8859-1) for - protocol communication. This was necessary because - InputStreamReader and OutputStreamWriter use the default - client-side character set encoding. fasselin@ca.ibm.com - reported failure of SMTP on OS/390 which has EBCDIC as the - native character set. - </action> + <!-- Not yet released; probably never will be <release version="1.1.1" date="TBD" description="last jdk1.1 compatible + release"> <action dev="scohen" type="fix"> Removed all JDK 1.1 incompatibilities that had been introduced unintentionally + in previous versions. Release 1.1.1 is the last JDK 1.1 compatible release. Any future 1.1.x maintenance releases will remain + JDK !.1 compatible, but version 1.2 may break compatibility and will be guaranteed to work with only J2SE 1.2 and later. + </action> </release> --> + <release version="1.1.0" date="October 23, 2003" description="many enhancements and bug fixes"> + <action dev="dfs" type="add"> + Rory Winston Rory.Winston@telewest.co.uk provided + patches to add the following extended NNTP commands to + NNTPClient: XOVER, AUTHINFO USER, AUTHINFO PASS, and + LIST ACTIVE. + </action> + <action dev="dfs" type="fix"> + Changed connection hooks for FTP, SMTP, POP3, and NNTP classes + to force use of an 8-bit US-ASCII superset (ISO-8859-1) for + protocol communication. This was necessary because + InputStreamReader and OutputStreamWriter use the default + client-side character set encoding. fasselin@ca.ibm.com + reported failure of SMTP on OS/390 which has EBCDIC as the + native character set. + </action> - <action dev="dfs" type="fix"> - Applied variation of fix suggested by Matthieu Recouly - matthieu.recouly@laposte.net so that - UnixFTPEntryParser may handle listings of the form: - "drwxr-xr-x 1 usernameftp 512 Jan 29 23:32 prog" - where the space between user name and group is omitted. - </action> - <action dev="dfs" type="fix"> - Applied patch from Stephane Este-Gracias - sestegra@free.fr that fixes the parsing of - VMS listings by VMSFTPEntryParser.. - </action> - <action dev="brekke" type="fix"> - If the buffer queue run full, the run() method sometimes hangs forever. - Changed wait() to wait(100) as with other changes in TelnetInputStream. - Fix submitted From: J. Matysiak ( j.matysiak@cenit.de ). - </action> - <action dev="brekke" type="fix"> - FTP.smnt(String dir) was not passing on the dir to the SMNT command as an argument. - </action> - <action dev="brekke" type="add"> - Added a link to the FAQ currently hosted on the Apache Wiki. - </action> - <action dev="dfs" type="update"> - Changed package private NNTP._reader and NNTP._writer member - variables to protected NNTP._reader_ and NNTP._writer_ - variables as suggested by issue report 16995 to facilitate - extending NNTPClient functionality in subclasses. - </action> - <action dev="dfs" type="update"> - Changed name of FTPClient.__openDataConnection() to - FTPClient._openDataConnection_() to remain consistent - with the convention in the code that protected members - are of the form _foo_. At some point __openDataConnection() - had been changed from private to protected. - </action> - <action dev="brekke" type="add"> - Added terminal option support to the telnet client with tests. - From Bruno D'Avanzo ( b.davanzo@inwind.it ). - </action> - <action dev="scohen" type="add"> - New parsers merged with mainline with support for old list parsers. - </action> - </release> + <action dev="dfs" type="fix"> + Applied variation of fix suggested by Matthieu Recouly + matthieu.recouly@laposte.net so that + UnixFTPEntryParser may handle listings of the form: + "drwxr-xr-x 1 usernameftp 512 Jan 29 23:32 prog" + where the space between user name and group is omitted. + </action> + <action dev="dfs" type="fix"> + Applied patch from Stephane Este-Gracias + sestegra@free.fr that fixes the parsing of + VMS listings by VMSFTPEntryParser.. + </action> + <action dev="brekke" type="fix"> + If the buffer queue run full, the run() method sometimes hangs forever. + Changed wait() to wait(100) as with other changes in TelnetInputStream. + Fix submitted From: J. Matysiak ( j.matysiak@cenit.de ). + </action> + <action dev="brekke" type="fix"> + FTP.smnt(String dir) was not passing on the dir to the SMNT command as an argument. + </action> + <action dev="brekke" type="add"> + Added a link to the FAQ currently hosted on the Apache Wiki. + </action> + <action dev="dfs" type="update"> + Changed package private NNTP._reader and NNTP._writer member + variables to protected NNTP._reader_ and NNTP._writer_ + variables as suggested by issue report 16995 to facilitate + extending NNTPClient functionality in subclasses. + </action> + <action dev="dfs" type="update"> + Changed name of FTPClient.__openDataConnection() to + FTPClient._openDataConnection_() to remain consistent + with the convention in the code that protected members + are of the form _foo_. At some point __openDataConnection() + had been changed from private to protected. + </action> + <action dev="brekke" type="add"> + Added terminal option support to the telnet client with tests. + From Bruno D'Avanzo ( b.davanzo@inwind.it ). + </action> + <action dev="scohen" type="add"> + New parsers merged with mainline with support for old list parsers. + </action> + </release> - <release version="1.0.0" date="February 23, 2003" description="first jakarta-commons release"> - <action dev="brekke" type="add"> - Added a migration document for moving from NetComponents to Commons/Net. - </action> - <action dev="brekke" type="fix"> - Moved the ftp2 tree with tests to a proposal directory and setup - a build for that code. This can grow in this area so users don't - think it is production ready. - </action> - <action dev="dfs" type="fix"> - Cleaned up license header on some source. - </action> - <action dev="dfs" type="fix"> - Moved .io and .util to .net.io and .net.util in preparation for - 1.0 release. - </action> - <action dev="dfs" type="fix"> - Fixed typo in NNTP.removeProtocolCommandListener() method name. It - was missing an L. From: joev@atg.com. - </action> - <action dev="brekke" type="add"> - Various site updates including this changes doc and publish - date information. - </action> - <action dev="dfs" type="fix"> - Patch for restarting FTP file transfers. The offset was not - being sent immediately before the data transfer command on - account. The bug was apparently introduced in NetComponents - when it was decided to always send a PORT command before each data - transfer to avoid socket reuse problems on Windows. - From: Tapan Karecha ( tapan@india.hp.com ). - </action> - <action dev="dfs" type="fix"> - Applied a fix for potential deadlock in TelnetInputStream by - changing a wait() to a wait(100). - From: Tapan Karecha ( tapan@india.hp.com ). - </action> - <action dev="dfs" type="update"> - FTP examples now use passive ftp connections. - </action> - </release> - </body> + <release version="1.0.0" date="February 23, 2003" description="first jakarta-commons release"> + <action dev="brekke" type="add"> + Added a migration document for moving from NetComponents to Commons/Net. + </action> + <action dev="brekke" type="fix"> + Moved the ftp2 tree with tests to a proposal directory and setup + a build for that code. This can grow in this area so users don't + think it is production ready. + </action> + <action dev="dfs" type="fix"> + Cleaned up license header on some source. + </action> + <action dev="dfs" type="fix"> + Moved .io and .util to .net.io and .net.util in preparation for + 1.0 release. + </action> + <action dev="dfs" type="fix"> + Fixed typo in NNTP.removeProtocolCommandListener() method name. It + was missing an L. From: joev@atg.com. + </action> + <action dev="brekke" type="add"> + Various site updates including this changes doc and publish + date information. + </action> + <action dev="dfs" type="fix"> + Patch for restarting FTP file transfers. The offset was not + being sent immediately before the data transfer command on + account. The bug was apparently introduced in NetComponents + when it was decided to always send a PORT command before each data + transfer to avoid socket reuse problems on Windows. + From: Tapan Karecha ( tapan@india.hp.com ). + </action> + <action dev="dfs" type="fix"> + Applied a fix for potential deadlock in TelnetInputStream by + changing a wait() to a wait(100). + From: Tapan Karecha ( tapan@india.hp.com ). + </action> + <action dev="dfs" type="update"> + FTP examples now use passive ftp connections. + </action> + </release> + </body> </document> diff --git a/src/changes/release-notes.vm b/src/changes/release-notes.vm index e0589ca..f9b86b8 100644 --- a/src/changes/release-notes.vm +++ b/src/changes/release-notes.vm @@ -18,11 +18,12 @@ ${project.name} ${version} RELEASE NOTES -The ${developmentTeam} is pleased to announce the release of ${project.name} ${version} +The ${developmentTeam} is pleased to announce the release of ${project.name} ${version}. $introduction.replaceAll("(?<!\015)\012", " ").replaceAll("(?m)^ +","") + ## N.B. the available variables are described here: ## http://maven.apache.org/plugins/maven-changes-plugin/examples/using-a-custom-announcement-template.html ## @@ -30,6 +31,13 @@ $introduction.replaceAll("(?<!\015)\012", " $release.description.replaceAll(" ", " ") +For complete information on ${project.name}, including instructions on how to submit bug reports, +patches, or suggestions for improvement, see the Apache ${project.name} website: + +${project.url} + +Download page: ${project.url}download_net.cgi + ## set up indent sizes. Only change indent1 #set($props=${project.properties}) #set($jiralen=$props.get("commons.jira.id").length()) @@ -80,6 +88,7 @@ o $issue ${action}$dueto No changes defined in this version. #else Changes in this version include: +=============================== #if ($release.getActions('add').size() !=0) New features: @@ -116,7 +125,5 @@ Removed: Historical list of changes: ${project.url}changes-report.html -For complete information on ${project.name}, including instructions on how to submit bug reports, -patches, or suggestions for improvement, see the Apache ${project.name} website: - -${project.url} +Enjoy! +-Apache Commons Net team diff --git a/src/main/java/examples/cidr/SubnetUtilsExample.java b/src/main/java/examples/cidr/SubnetUtilsExample.java deleted file mode 100644 index 35a4432..0000000 --- a/src/main/java/examples/cidr/SubnetUtilsExample.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package examples.cidr; - -import java.util.Arrays; -import java.util.Scanner; - -import org.apache.commons.net.util.SubnetUtils; -import org.apache.commons.net.util.SubnetUtils.SubnetInfo; - -/** - * Example class that shows how to use the {@link SubnetUtils} class. - * - */ -public class SubnetUtilsExample { - - public static void main(String[] args) { - String subnet = "192.168.0.3/31"; - SubnetUtils utils = new SubnetUtils(subnet); - SubnetInfo info = utils.getInfo(); - - System.out.printf("Subnet Information for %s:\n", subnet); - System.out.println("--------------------------------------"); - System.out.printf("IP Address:\t\t\t%s\t[%s]\n", info.getAddress(), - Integer.toBinaryString(info.asInteger(info.getAddress()))); - System.out.printf("Netmask:\t\t\t%s\t[%s]\n", info.getNetmask(), - Integer.toBinaryString(info.asInteger(info.getNetmask()))); - System.out.printf("CIDR Representation:\t\t%s\n\n", info.getCidrSignature()); - - System.out.printf("Supplied IP Address:\t\t%s\n\n", info.getAddress()); - - System.out.printf("Network Address:\t\t%s\t[%s]\n", info.getNetworkAddress(), - Integer.toBinaryString(info.asInteger(info.getNetworkAddress()))); - System.out.printf("Broadcast Address:\t\t%s\t[%s]\n", info.getBroadcastAddress(), - Integer.toBinaryString(info.asInteger(info.getBroadcastAddress()))); - System.out.printf("Low Address:\t\t\t%s\t[%s]\n", info.getLowAddress(), - Integer.toBinaryString(info.asInteger(info.getLowAddress()))); - System.out.printf("High Address:\t\t\t%s\t[%s]\n", info.getHighAddress(), - Integer.toBinaryString(info.asInteger(info.getHighAddress()))); - - System.out.printf("Total usable addresses: \t%d\n", Long.valueOf(info.getAddressCountLong())); - System.out.printf("Address List: %s\n\n", Arrays.toString(info.getAllAddresses())); - - final String prompt ="Enter an IP address (e.g. 192.168.0.10):"; - System.out.println(prompt); - Scanner scanner = new Scanner(System.in); - while (scanner.hasNextLine()) { - String address = scanner.nextLine(); - System.out.println("The IP address [" + address + "] is " - + (info.isInRange(address) ? "" : "not ") - + "within the subnet [" + subnet + "]"); - System.out.println(prompt); - } - scanner.close(); - } - -} diff --git a/src/main/java/examples/telnet/TelnetClientExample.java b/src/main/java/examples/telnet/TelnetClientExample.java deleted file mode 100644 index 9d9449e..0000000 --- a/src/main/java/examples/telnet/TelnetClientExample.java +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package examples.telnet; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.FileOutputStream; -import java.util.StringTokenizer; - -import org.apache.commons.net.telnet.TelnetClient; -import org.apache.commons.net.telnet.TelnetNotificationHandler; -import org.apache.commons.net.telnet.SimpleOptionHandler; -import org.apache.commons.net.telnet.EchoOptionHandler; -import org.apache.commons.net.telnet.TerminalTypeOptionHandler; -import org.apache.commons.net.telnet.SuppressGAOptionHandler; -import org.apache.commons.net.telnet.InvalidTelnetOptionException; - - -/*** - * This is a simple example of use of TelnetClient. - * An external option handler (SimpleTelnetOptionHandler) is used. - * Initial configuration requested by TelnetClient will be: - * WILL ECHO, WILL SUPPRESS-GA, DO SUPPRESS-GA. - * VT100 terminal type will be subnegotiated. - * <p> - * Also, use of the sendAYT(), getLocalOptionState(), getRemoteOptionState() - * is demonstrated. - * When connected, type AYT to send an AYT command to the server and see - * the result. - * Type OPT to see a report of the state of the first 25 options. - ***/ -public class TelnetClientExample implements Runnable, TelnetNotificationHandler -{ - static TelnetClient tc = null; - - /*** - * Main for the TelnetClientExample. - * @param args input params - * @throws Exception on error - ***/ - public static void main(String[] args) throws Exception - { - FileOutputStream fout = null; - - if(args.length < 1) - { - System.err.println("Usage: TelnetClientExample <remote-ip> [<remote-port>]"); - System.exit(1); - } - - String remoteip = args[0]; - - int remoteport; - - if (args.length > 1) - { - remoteport = (new Integer(args[1])).intValue(); - } - else - { - remoteport = 23; - } - - try - { - fout = new FileOutputStream ("spy.log", true); - } - catch (IOException e) - { - System.err.println( - "Exception while opening the spy file: " - + e.getMessage()); - } - - tc = new TelnetClient(); - - TerminalTypeOptionHandler ttopt = new TerminalTypeOptionHandler("VT100", false, false, true, false); - EchoOptionHandler echoopt = new EchoOptionHandler(true, false, true, false); - SuppressGAOptionHandler gaopt = new SuppressGAOptionHandler(true, true, true, true); - - try - { - tc.addOptionHandler(ttopt); - tc.addOptionHandler(echoopt); - tc.addOptionHandler(gaopt); - } - catch (InvalidTelnetOptionException e) - { - System.err.println("Error registering option handlers: " + e.getMessage()); - } - - while (true) - { - boolean end_loop = false; - try - { - tc.connect(remoteip, remoteport); - - - Thread reader = new Thread (new TelnetClientExample()); - tc.registerNotifHandler(new TelnetClientExample()); - System.out.println("TelnetClientExample"); - System.out.println("Type AYT to send an AYT telnet command"); - System.out.println("Type OPT to print a report of status of options (0-24)"); - System.out.println("Type REGISTER to register a new SimpleOptionHandler"); - System.out.println("Type UNREGISTER to unregister an OptionHandler"); - System.out.println("Type SPY to register the spy (connect to port 3333 to spy)"); - System.out.println("Type UNSPY to stop spying the connection"); - System.out.println("Type ^[A-Z] to send the control character; use ^^ to send ^"); - - reader.start(); - OutputStream outstr = tc.getOutputStream(); - - byte[] buff = new byte[1024]; - int ret_read = 0; - - do - { - try - { - ret_read = System.in.read(buff); - if(ret_read > 0) - { - final String line = new String(buff, 0, ret_read); // deliberate use of default charset - if(line.startsWith("AYT")) - { - try - { - System.out.println("Sending AYT"); - - System.out.println("AYT response:" + tc.sendAYT(5000)); - } - catch (IOException e) - { - System.err.println("Exception waiting AYT response: " + e.getMessage()); - } - } - else if(line.startsWith("OPT")) - { - System.out.println("Status of options:"); - for(int ii=0; ii<25; ii++) { - System.out.println("Local Option " + ii + ":" + tc.getLocalOptionState(ii) + - " Remote Option " + ii + ":" + tc.getRemoteOptionState(ii)); - } - } - else if(line.startsWith("REGISTER")) - { - StringTokenizer st = new StringTokenizer(new String(buff)); - try - { - st.nextToken(); - int opcode = Integer.parseInt(st.nextToken()); - boolean initlocal = Boolean.parseBoolean(st.nextToken()); - boolean initremote = Boolean.parseBoolean(st.nextToken()); - boolean acceptlocal = Boolean.parseBoolean(st.nextToken()); - boolean acceptremote = Boolean.parseBoolean(st.nextToken()); - SimpleOptionHandler opthand = new SimpleOptionHandler(opcode, initlocal, initremote, - acceptlocal, acceptremote); - tc.addOptionHandler(opthand); - } - catch (Exception e) - { - if(e instanceof InvalidTelnetOptionException) - { - System.err.println("Error registering option: " + e.getMessage()); - } - else - { - System.err.println("Invalid REGISTER command."); - System.err.println("Use REGISTER optcode initlocal initremote acceptlocal acceptremote"); - System.err.println("(optcode is an integer.)"); - System.err.println("(initlocal, initremote, acceptlocal, acceptremote are boolean)"); - } - } - } - else if(line.startsWith("UNREGISTER")) - { - StringTokenizer st = new StringTokenizer(new String(buff)); - try - { - st.nextToken(); - int opcode = (new Integer(st.nextToken())).intValue(); - tc.deleteOptionHandler(opcode); - } - catch (Exception e) - { - if(e instanceof InvalidTelnetOptionException) - { - System.err.println("Error unregistering option: " + e.getMessage()); - } - else - { - System.err.println("Invalid UNREGISTER command."); - System.err.println("Use UNREGISTER optcode"); - System.err.println("(optcode is an integer)"); - } - } - } - else if(line.startsWith("SPY")) - { - tc.registerSpyStream(fout); - } - else if(line.startsWith("UNSPY")) - { - tc.stopSpyStream(); - } - else if(line.matches("^\\^[A-Z^]\\r?\\n?$")) - { - byte toSend = buff[1]; - if (toSend == '^') { - outstr.write(toSend); - } else { - outstr.write(toSend - 'A' + 1); - } - outstr.flush(); - } - else - { - try - { - outstr.write(buff, 0 , ret_read); - outstr.flush(); - } - catch (IOException e) - { - end_loop = true; - } - } - } - } - catch (IOException e) - { - System.err.println("Exception while reading keyboard:" + e.getMessage()); - end_loop = true; - } - } - while((ret_read > 0) && (end_loop == false)); - - try - { - tc.disconnect(); - } - catch (IOException e) - { - System.err.println("Exception while connecting:" + e.getMessage()); - } - } - catch (IOException e) - { - System.err.println("Exception while connecting:" + e.getMessage()); - System.exit(1); - } - } - } - - - /*** - * Callback method called when TelnetClient receives an option - * negotiation command. - * - * @param negotiation_code - type of negotiation command received - * (RECEIVED_DO, RECEIVED_DONT, RECEIVED_WILL, RECEIVED_WONT, RECEIVED_COMMAND) - * @param option_code - code of the option negotiated - ***/ - @Override - public void receivedNegotiation(int negotiation_code, int option_code) - { - String command = null; - switch (negotiation_code) { - case TelnetNotificationHandler.RECEIVED_DO: - command = "DO"; - break; - case TelnetNotificationHandler.RECEIVED_DONT: - command = "DONT"; - break; - case TelnetNotificationHandler.RECEIVED_WILL: - command = "WILL"; - break; - case TelnetNotificationHandler.RECEIVED_WONT: - command = "WONT"; - break; - case TelnetNotificationHandler.RECEIVED_COMMAND: - command = "COMMAND"; - break; - default: - command = Integer.toString(negotiation_code); // Should not happen - break; - } - System.out.println("Received " + command + " for option code " + option_code); - } - - /*** - * Reader thread. - * Reads lines from the TelnetClient and echoes them - * on the screen. - ***/ - @Override - public void run() - { - InputStream instr = tc.getInputStream(); - - try - { - byte[] buff = new byte[1024]; - int ret_read = 0; - - do - { - ret_read = instr.read(buff); - if(ret_read > 0) - { - System.out.print(new String(buff, 0, ret_read)); - } - } - while (ret_read >= 0); - } - catch (IOException e) - { - System.err.println("Exception while reading socket:" + e.getMessage()); - } - - try - { - tc.disconnect(); - } - catch (IOException e) - { - System.err.println("Exception while closing telnet:" + e.getMessage()); - } - } -} - diff --git a/src/main/java/examples/util/IOUtil.java b/src/main/java/examples/util/IOUtil.java deleted file mode 100644 index cc3257c..0000000 --- a/src/main/java/examples/util/IOUtil.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package examples.util; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import org.apache.commons.net.io.Util; - -/*** - * This is a utility class providing a reader/writer capability required - * by the weatherTelnet, rexec, rshell, and rlogin example programs. - * The only point of the class is to hold the static method readWrite - * which spawns a reader thread and a writer thread. The reader thread - * reads from a local input source (presumably stdin) and writes the - * data to a remote output destination. The writer thread reads from - * a remote input source and writes to a local output destination. - * The threads terminate when the remote input source closes. - ***/ - -public final class IOUtil -{ - - public static final void readWrite(final InputStream remoteInput, - final OutputStream remoteOutput, - final InputStream localInput, - final OutputStream localOutput) - { - Thread reader, writer; - - reader = new Thread() - { - @Override - public void run() - { - int ch; - - try - { - while (!interrupted() && (ch = localInput.read()) != -1) - { - remoteOutput.write(ch); - remoteOutput.flush(); - } - } - catch (IOException e) - { - //e.printStackTrace(); - } - } - } - ; - - - writer = new Thread() - { - @Override - public void run() - { - try - { - Util.copyStream(remoteInput, localOutput); - } - catch (IOException e) - { - e.printStackTrace(); - System.exit(1); - } - } - }; - - - writer.setPriority(Thread.currentThread().getPriority() + 1); - - writer.start(); - reader.setDaemon(true); - reader.start(); - - try - { - writer.join(); - reader.interrupt(); - } - catch (InterruptedException e) - { - // Ignored - } - } - -} - diff --git a/src/main/java/org/apache/commons/net/DatagramSocketClient.java b/src/main/java/org/apache/commons/net/DatagramSocketClient.java index a2980f0..9f906f9 100644 --- a/src/main/java/org/apache/commons/net/DatagramSocketClient.java +++ b/src/main/java/org/apache/commons/net/DatagramSocketClient.java @@ -22,142 +22,60 @@ import java.net.InetAddress; import java.net.SocketException; import java.nio.charset.Charset; -/*** - * The DatagramSocketClient provides the basic operations that are required - * of client objects accessing datagram sockets. It is meant to be - * subclassed to avoid having to rewrite the same code over and over again - * to open a socket, close a socket, set timeouts, etc. Of special note - * is the {@link #setDatagramSocketFactory setDatagramSocketFactory } - * method, which allows you to control the type of DatagramSocket the - * DatagramSocketClient creates for network communications. This is - * especially useful for adding things like proxy support as well as better - * support for applets. For - * example, you could create a - * {@link org.apache.commons.net.DatagramSocketFactory} - * that - * requests browser security capabilities before creating a socket. - * All classes derived from DatagramSocketClient should use the - * {@link #_socketFactory_ _socketFactory_ } member variable to - * create DatagramSocket instances rather than instantiating - * them by directly invoking a constructor. By honoring this contract - * you guarantee that a user will always be able to provide his own - * Socket implementations by substituting his own SocketFactory. +/** + * The DatagramSocketClient provides the basic operations that are required of client objects accessing datagram sockets. It is meant to be subclassed to avoid + * having to rewrite the same code over and over again to open a socket, close a socket, set timeouts, etc. Of special note is the + * {@link #setDatagramSocketFactory setDatagramSocketFactory } method, which allows you to control the type of DatagramSocket the DatagramSocketClient creates + * for network communications. This is especially useful for adding things like proxy support as well as better support for applets. For example, you could + * create a {@link org.apache.commons.net.DatagramSocketFactory} that requests browser security capabilities before creating a socket. All classes derived from + * DatagramSocketClient should use the {@link #_socketFactory_ _socketFactory_ } member variable to create DatagramSocket instances rather than instantiating + * them by directly invoking a constructor. By honoring this contract you guarantee that a user will always be able to provide his own Socket implementations by + * substituting his own SocketFactory. * * * @see DatagramSocketFactory - ***/ + */ -public abstract class DatagramSocketClient -{ - /*** - * The default DatagramSocketFactory shared by all DatagramSocketClient - * instances. - ***/ - private static final DatagramSocketFactory __DEFAULT_SOCKET_FACTORY = - new DefaultDatagramSocketFactory(); +public abstract class DatagramSocketClient { + /** + * The default DatagramSocketFactory shared by all DatagramSocketClient instances. + */ + private static final DatagramSocketFactory DEFAULT_SOCKET_FACTORY = new DefaultDatagramSocketFactory(); /** * Charset to use for byte IO. */ private Charset charset = Charset.defaultCharset(); - /*** The timeout to use after opening a socket. ***/ + /** The timeout to use after opening a socket. */ protected int _timeout_; - /*** The datagram socket used for the connection. ***/ + /** The datagram socket used for the connection. */ protected DatagramSocket _socket_; - /*** + /** * A status variable indicating if the client's socket is currently open. - ***/ + */ protected boolean _isOpen_; - /*** The datagram socket's DatagramSocketFactory. ***/ + /** The datagram socket's DatagramSocketFactory. */ protected DatagramSocketFactory _socketFactory_; - /*** - * Default constructor for DatagramSocketClient. Initializes - * _socket_ to null, _timeout_ to 0, and _isOpen_ to false. - ***/ - public DatagramSocketClient() - { + /** + * Default constructor for DatagramSocketClient. Initializes _socket_ to null, _timeout_ to 0, and _isOpen_ to false. + */ + public DatagramSocketClient() { _socket_ = null; _timeout_ = 0; _isOpen_ = false; - _socketFactory_ = __DEFAULT_SOCKET_FACTORY; - } - - - /*** - * Opens a DatagramSocket on the local host at the first available port. - * Also sets the timeout on the socket to the default timeout set - * by {@link #setDefaultTimeout setDefaultTimeout() }. - * <p> - * _isOpen_ is set to true after calling this method and _socket_ - * is set to the newly opened socket. - * - * @throws SocketException If the socket could not be opened or the - * timeout could not be set. - ***/ - public void open() throws SocketException - { - _socket_ = _socketFactory_.createDatagramSocket(); - _socket_.setSoTimeout(_timeout_); - _isOpen_ = true; + _socketFactory_ = DEFAULT_SOCKET_FACTORY; } - - /*** - * Opens a DatagramSocket on the local host at a specified port. - * Also sets the timeout on the socket to the default timeout set - * by {@link #setDefaultTimeout setDefaultTimeout() }. - * <p> - * _isOpen_ is set to true after calling this method and _socket_ - * is set to the newly opened socket. - * - * @param port The port to use for the socket. - * @throws SocketException If the socket could not be opened or the - * timeout could not be set. - ***/ - public void open(int port) throws SocketException - { - _socket_ = _socketFactory_.createDatagramSocket(port); - _socket_.setSoTimeout(_timeout_); - _isOpen_ = true; - } - - - /*** - * Opens a DatagramSocket at the specified address on the local host - * at a specified port. - * Also sets the timeout on the socket to the default timeout set - * by {@link #setDefaultTimeout setDefaultTimeout() }. - * <p> - * _isOpen_ is set to true after calling this method and _socket_ - * is set to the newly opened socket. - * - * @param port The port to use for the socket. - * @param laddr The local address to use. - * @throws SocketException If the socket could not be opened or the - * timeout could not be set. - ***/ - public void open(int port, InetAddress laddr) throws SocketException - { - _socket_ = _socketFactory_.createDatagramSocket(port, laddr); - _socket_.setSoTimeout(_timeout_); - _isOpen_ = true; - } - - - - /*** - * Closes the DatagramSocket used for the connection. - * You should call this method after you've finished using the class - * instance and also before you call {@link #open open() } - * again. _isOpen_ is set to false and _socket_ is set to null. - ***/ - public void close() - { + /** + * Closes the DatagramSocket used for the connection. You should call this method after you've finished using the class instance and also before you call + * {@link #open open() } again. _isOpen_ is set to false and _socket_ is set to null. + */ + public void close() { if (_socket_ != null) { _socket_.close(); } @@ -165,150 +83,164 @@ public abstract class DatagramSocketClient _isOpen_ = false; } - - /*** - * Returns true if the client has a currently open socket. + /** + * Gets the charset. * - * @return True if the client has a currently open socket, false otherwise. - ***/ - public boolean isOpen() - { - return _isOpen_; + * @return the charset. + * @since 3.3 + */ + public Charset getCharset() { + return charset; } - - /*** - * Set the default timeout in milliseconds to use when opening a socket. - * After a call to open, the timeout for the socket is set using this value. - * This method should be used prior to a call to {@link #open open()} - * and should not be confused with {@link #setSoTimeout setSoTimeout()} - * which operates on the currently open socket. _timeout_ contains - * the new timeout value. + /** + * Gets the charset name. * - * @param timeout The timeout in milliseconds to use for the datagram socket - * connection. - ***/ - public void setDefaultTimeout(int timeout) - { - _timeout_ = timeout; + * @return the charset name. + * @since 3.3 + * @deprecated Use {@link #getCharset()} instead + */ + @Deprecated + public String getCharsetName() { + return charset.name(); } - - /*** - * Returns the default timeout in milliseconds that is used when - * opening a socket. + /** + * Returns the default timeout in milliseconds that is used when opening a socket. * - * @return The default timeout in milliseconds that is used when - * opening a socket. - ***/ - public int getDefaultTimeout() - { + * @return The default timeout in milliseconds that is used when opening a socket. + */ + public int getDefaultTimeout() { return _timeout_; } - - /*** - * Set the timeout in milliseconds of a currently open connection. - * Only call this method after a connection has been opened - * by {@link #open open()}. + /** + * Returns the local address to which the client's socket is bound. If you call this method when the client socket is not open, a NullPointerException is + * thrown. * - * @param timeout The timeout in milliseconds to use for the currently - * open datagram socket connection. - * @throws SocketException if an error setting the timeout - ***/ - public void setSoTimeout(int timeout) throws SocketException - { - _socket_.setSoTimeout(timeout); + * @return The local address to which the client's socket is bound. + */ + public InetAddress getLocalAddress() { + return _socket_.getLocalAddress(); } + /** + * Returns the port number of the open socket on the local host used for the connection. If you call this method when the client socket is not open, a + * NullPointerException is thrown. + * + * @return The port number of the open socket on the local host used for the connection. + */ + public int getLocalPort() { + return _socket_.getLocalPort(); + } - /*** - * Returns the timeout in milliseconds of the currently opened socket. - * If you call this method when the client socket is not open, - * a NullPointerException is thrown. + /** + * Returns the timeout in milliseconds of the currently opened socket. If you call this method when the client socket is not open, a NullPointerException is + * thrown. * * @return The timeout in milliseconds of the currently opened socket. * @throws SocketException if an error getting the timeout - ***/ - public int getSoTimeout() throws SocketException - { + */ + public int getSoTimeout() throws SocketException { return _socket_.getSoTimeout(); } - - /*** - * Returns the port number of the open socket on the local host used - * for the connection. If you call this method when the client socket - * is not open, a NullPointerException is thrown. + /** + * Returns true if the client has a currently open socket. * - * @return The port number of the open socket on the local host used - * for the connection. - ***/ - public int getLocalPort() - { - return _socket_.getLocalPort(); + * @return True if the client has a currently open socket, false otherwise. + */ + public boolean isOpen() { + return _isOpen_; } - - /*** - * Returns the local address to which the client's socket is bound. - * If you call this method when the client socket is not open, a - * NullPointerException is thrown. + /** + * Opens a DatagramSocket on the local host at the first available port. Also sets the timeout on the socket to the default timeout set by + * {@link #setDefaultTimeout setDefaultTimeout() }. + * <p> + * _isOpen_ is set to true after calling this method and _socket_ is set to the newly opened socket. * - * @return The local address to which the client's socket is bound. - ***/ - public InetAddress getLocalAddress() - { - return _socket_.getLocalAddress(); + * @throws SocketException If the socket could not be opened or the timeout could not be set. + */ + public void open() throws SocketException { + _socket_ = _socketFactory_.createDatagramSocket(); + _socket_.setSoTimeout(_timeout_); + _isOpen_ = true; } + /** + * Opens a DatagramSocket on the local host at a specified port. Also sets the timeout on the socket to the default timeout set by {@link #setDefaultTimeout + * setDefaultTimeout() }. + * <p> + * _isOpen_ is set to true after calling this method and _socket_ is set to the newly opened socket. + * + * @param port The port to use for the socket. + * @throws SocketException If the socket could not be opened or the timeout could not be set. + */ + public void open(final int port) throws SocketException { + _socket_ = _socketFactory_.createDatagramSocket(port); + _socket_.setSoTimeout(_timeout_); + _isOpen_ = true; + } - /*** - * Sets the DatagramSocketFactory used by the DatagramSocketClient - * to open DatagramSockets. If the factory value is null, then a default - * factory is used (only do this to reset the factory after having - * previously altered it). + /** + * Opens a DatagramSocket at the specified address on the local host at a specified port. Also sets the timeout on the socket to the default timeout set by + * {@link #setDefaultTimeout setDefaultTimeout() }. + * <p> + * _isOpen_ is set to true after calling this method and _socket_ is set to the newly opened socket. * - * @param factory The new DatagramSocketFactory the DatagramSocketClient - * should use. - ***/ - public void setDatagramSocketFactory(DatagramSocketFactory factory) - { - if (factory == null) { - _socketFactory_ = __DEFAULT_SOCKET_FACTORY; - } else { - _socketFactory_ = factory; - } + * @param port The port to use for the socket. + * @param laddr The local address to use. + * @throws SocketException If the socket could not be opened or the timeout could not be set. + */ + public void open(final int port, final InetAddress laddr) throws SocketException { + _socket_ = _socketFactory_.createDatagramSocket(port, laddr); + _socket_.setSoTimeout(_timeout_); + _isOpen_ = true; } /** - * Gets the charset name. + * Sets the charset. * - * @return the charset name. + * @param charset the charset. * @since 3.3 - * TODO Will be deprecated once the code requires Java 1.6 as a mininmum */ - public String getCharsetName() { - return charset.name(); + public void setCharset(final Charset charset) { + this.charset = charset; } /** - * Gets the charset. + * Sets the DatagramSocketFactory used by the DatagramSocketClient to open DatagramSockets. If the factory value is null, then a default factory is used + * (only do this to reset the factory after having previously altered it). * - * @return the charset. - * @since 3.3 + * @param factory The new DatagramSocketFactory the DatagramSocketClient should use. */ - public Charset getCharset() { - return charset; + public void setDatagramSocketFactory(final DatagramSocketFactory factory) { + if (factory == null) { + _socketFactory_ = DEFAULT_SOCKET_FACTORY; + } else { + _socketFactory_ = factory; + } } /** - * Sets the charset. + * Set the default timeout in milliseconds to use when opening a socket. After a call to open, the timeout for the socket is set using this value. This + * method should be used prior to a call to {@link #open open()} and should not be confused with {@link #setSoTimeout setSoTimeout()} which operates on the + * currently open socket. _timeout_ contains the new timeout value. * - * @param charset the charset. - * @since 3.3 + * @param timeout The timeout in milliseconds to use for the datagram socket connection. */ - public void setCharset(Charset charset) { - this.charset = charset; + public void setDefaultTimeout(final int timeout) { + _timeout_ = timeout; + } + + /** + * Set the timeout in milliseconds of a currently open connection. Only call this method after a connection has been opened by {@link #open open()}. + * + * @param timeout The timeout in milliseconds to use for the currently open datagram socket connection. + * @throws SocketException if an error setting the timeout + */ + public void setSoTimeout(final int timeout) throws SocketException { + _socket_.setSoTimeout(timeout); } } diff --git a/src/main/java/org/apache/commons/net/DatagramSocketFactory.java b/src/main/java/org/apache/commons/net/DatagramSocketFactory.java index 3fe8ded..ba48bf6 100644 --- a/src/main/java/org/apache/commons/net/DatagramSocketFactory.java +++ b/src/main/java/org/apache/commons/net/DatagramSocketFactory.java @@ -21,49 +21,41 @@ import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; -/*** - * The DatagramSocketFactory interface provides a means for the - * programmer to control the creation of datagram sockets and - * provide his own DatagramSocket implementations for use by all - * classes derived from - * {@link org.apache.commons.net.DatagramSocketClient} - * . - * This allows you to provide your own DatagramSocket implementations and - * to perform security checks or browser capability requests before - * creating a DatagramSocket. +/** + * The DatagramSocketFactory interface provides a means for the programmer to control the creation of datagram sockets and provide his own DatagramSocket + * implementations for use by all classes derived from {@link org.apache.commons.net.DatagramSocketClient} . This allows you to provide your own DatagramSocket + * implementations and to perform security checks or browser capability requests before creating a DatagramSocket. * * - ***/ + */ -public interface DatagramSocketFactory -{ +public interface DatagramSocketFactory { - /*** + /** * Creates a DatagramSocket on the local host at the first available port. + * * @return the socket * * @throws SocketException If the socket could not be created. - ***/ - public DatagramSocket createDatagramSocket() throws SocketException; + */ + DatagramSocket createDatagramSocket() throws SocketException; - /*** + /** * Creates a DatagramSocket on the local host at a specified port. * * @param port The port to use for the socket. * @return the socket * @throws SocketException If the socket could not be created. - ***/ - public DatagramSocket createDatagramSocket(int port) throws SocketException; + */ + DatagramSocket createDatagramSocket(int port) throws SocketException; - /*** - * Creates a DatagramSocket at the specified address on the local host - * at a specified port. + /** + * Creates a DatagramSocket at the specified address on the local host at a specified port. * - * @param port The port to use for the socket. - * @param laddr The local address to use. + * @param port The port to use for the socket. + * @param laddr The local address to use. * @return the socket * @throws SocketException If the socket could not be created. - ***/ - public DatagramSocket createDatagramSocket(int port, InetAddress laddr) - throws SocketException; + */ + DatagramSocket createDatagramSocket(int port, InetAddress laddr) throws SocketException; } diff --git a/src/main/java/org/apache/commons/net/DefaultDatagramSocketFactory.java b/src/main/java/org/apache/commons/net/DefaultDatagramSocketFactory.java index 7ad1997..62154ba 100644 --- a/src/main/java/org/apache/commons/net/DefaultDatagramSocketFactory.java +++ b/src/main/java/org/apache/commons/net/DefaultDatagramSocketFactory.java @@ -21,59 +21,51 @@ import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; -/*** - * DefaultDatagramSocketFactory implements the DatagramSocketFactory - * interface by simply wrapping the java.net.DatagramSocket - * constructors. It is the default DatagramSocketFactory used by - * {@link org.apache.commons.net.DatagramSocketClient} - * implementations. +/** + * DefaultDatagramSocketFactory implements the DatagramSocketFactory interface by simply wrapping the java.net.DatagramSocket constructors. It is the default + * DatagramSocketFactory used by {@link org.apache.commons.net.DatagramSocketClient} implementations. * * * @see DatagramSocketFactory * @see DatagramSocketClient * @see DatagramSocketClient#setDatagramSocketFactory - ***/ + */ -public class DefaultDatagramSocketFactory implements DatagramSocketFactory -{ +public class DefaultDatagramSocketFactory implements DatagramSocketFactory { - /*** + /** * Creates a DatagramSocket on the local host at the first available port. + * * @return a new DatagramSocket * @throws SocketException If the socket could not be created. - ***/ + */ @Override - public DatagramSocket createDatagramSocket() throws SocketException - { + public DatagramSocket createDatagramSocket() throws SocketException { return new DatagramSocket(); } - /*** + /** * Creates a DatagramSocket on the local host at a specified port. * * @param port The port to use for the socket. * @return a new DatagramSocket * @throws SocketException If the socket could not be created. - ***/ + */ @Override - public DatagramSocket createDatagramSocket(int port) throws SocketException - { + public DatagramSocket createDatagramSocket(final int port) throws SocketException { return new DatagramSocket(port); } - /*** - * Creates a DatagramSocket at the specified address on the local host - * at a specified port. + /** + * Creates a DatagramSocket at the specified address on the local host at a specified port. * - * @param port The port to use for the socket. - * @param laddr The local address to use. + * @param port The port to use for the socket. + * @param laddr The local address to use. * @return a new DatagramSocket * @throws SocketException If the socket could not be created. - ***/ + */ @Override - public DatagramSocket createDatagramSocket(int port, InetAddress laddr) - throws SocketException - { + public DatagramSocket createDatagramSocket(final int port, final InetAddress laddr) throws SocketException { return new DatagramSocket(port, laddr); } } diff --git a/src/main/java/org/apache/commons/net/DefaultSocketFactory.java b/src/main/java/org/apache/commons/net/DefaultSocketFactory.java index 3e68b75..c466209 100644 --- a/src/main/java/org/apache/commons/net/DefaultSocketFactory.java +++ b/src/main/java/org/apache/commons/net/DefaultSocketFactory.java @@ -27,29 +27,24 @@ import java.net.UnknownHostException; import javax.net.SocketFactory; -/*** - * DefaultSocketFactory implements the SocketFactory interface by - * simply wrapping the java.net.Socket and java.net.ServerSocket - * constructors. It is the default SocketFactory used by - * {@link org.apache.commons.net.SocketClient} - * implementations. +/** + * DefaultSocketFactory implements the SocketFactory interface by simply wrapping the java.net.Socket and java.net.ServerSocket constructors. It is the default + * SocketFactory used by {@link org.apache.commons.net.SocketClient} implementations. * * * @see SocketFactory * @see SocketClient * @see SocketClient#setSocketFactory - ***/ + */ -public class DefaultSocketFactory extends SocketFactory -{ +public class DefaultSocketFactory extends SocketFactory { /** The proxy to use when creating new sockets. */ private final Proxy connProxy; /** * The default constructor. */ - public DefaultSocketFactory() - { + public DefaultSocketFactory() { this(null); } @@ -59,11 +54,48 @@ public class DefaultSocketFactory extends SocketFactory * @param proxy The Proxy to use when creating new Sockets. * @since 3.2 */ - public DefaultSocketFactory(Proxy proxy) - { + public DefaultSocketFactory(final Proxy proxy) { connProxy = proxy; } + /** + * Creates a ServerSocket bound to a specified port. A port of 0 will create the ServerSocket on a system-determined free port. + * + * @param port The port on which to listen, or 0 to use any free port. + * @return A ServerSocket that will listen on a specified port. + * @throws IOException If an I/O error occurs while creating the ServerSocket. + */ + public ServerSocket createServerSocket(final int port) throws IOException { + return new ServerSocket(port); + } + + /** + * Creates a ServerSocket bound to a specified port with a given maximum queue length for incoming connections. A port of 0 will create the ServerSocket on + * a system-determined free port. + * + * @param port The port on which to listen, or 0 to use any free port. + * @param backlog The maximum length of the queue for incoming connections. + * @return A ServerSocket that will listen on a specified port. + * @throws IOException If an I/O error occurs while creating the ServerSocket. + */ + public ServerSocket createServerSocket(final int port, final int backlog) throws IOException { + return new ServerSocket(port, backlog); + } + + /** + * Creates a ServerSocket bound to a specified port on a given local address with a given maximum queue length for incoming connections. A port of 0 will + * create the ServerSocket on a system-determined free port. + * + * @param port The port on which to listen, or 0 to use any free port. + * @param backlog The maximum length of the queue for incoming connections. + * @param bindAddr The local address to which the ServerSocket should bind. + * @return A ServerSocket that will listen on a specified port. + * @throws IOException If an I/O error occurs while creating the ServerSocket. + */ + public ServerSocket createServerSocket(final int port, final int backlog, final InetAddress bindAddr) throws IOException { + return new ServerSocket(port, backlog, bindAddr); + } + /** * Creates an unconnected Socket. * @@ -72,159 +104,90 @@ public class DefaultSocketFactory extends SocketFactory * @since 3.2 */ @Override - public Socket createSocket() throws IOException - { - if (connProxy != null) - { + public Socket createSocket() throws IOException { + if (connProxy != null) { return new Socket(connProxy); } return new Socket(); } - /*** + /** * Creates a Socket connected to the given host and port. * - * @param host The hostname to connect to. - * @param port The port to connect to. + * @param address The address of the host to connect to. + * @param port The port to connect to. * @return A Socket connected to the given host and port. - * @throws UnknownHostException If the hostname cannot be resolved. * @throws IOException If an I/O error occurs while creating the Socket. - ***/ + */ @Override - public Socket createSocket(String host, int port) - throws UnknownHostException, IOException - { - if (connProxy != null) - { - Socket s = new Socket(connProxy); - s.connect(new InetSocketAddress(host, port)); + public Socket createSocket(final InetAddress address, final int port) throws IOException { + if (connProxy != null) { + final Socket s = new Socket(connProxy); + s.connect(new InetSocketAddress(address, port)); return s; } - return new Socket(host, port); + return new Socket(address, port); } - /*** - * Creates a Socket connected to the given host and port. + /** + * Creates a Socket connected to the given host and port and originating from the specified local address and port. * - * @param address The address of the host to connect to. - * @param port The port to connect to. + * @param address The address of the host to connect to. + * @param port The port to connect to. + * @param localAddr The local address to use. + * @param localPort The local port to use. * @return A Socket connected to the given host and port. * @throws IOException If an I/O error occurs while creating the Socket. - ***/ + */ @Override - public Socket createSocket(InetAddress address, int port) - throws IOException - { - if (connProxy != null) - { - Socket s = new Socket(connProxy); + public Socket createSocket(final InetAddress address, final int port, final InetAddress localAddr, final int localPort) throws IOException { + if (connProxy != null) { + final Socket s = new Socket(connProxy); + s.bind(new InetSocketAddress(localAddr, localPort)); s.connect(new InetSocketAddress(address, port)); return s; } - return new Socket(address, port); + return new Socket(address, port, localAddr, localPort); } - /*** - * Creates a Socket connected to the given host and port and - * originating from the specified local address and port. + /** + * Creates a Socket connected to the given host and port. * * @param host The hostname to connect to. * @param port The port to connect to. - * @param localAddr The local address to use. - * @param localPort The local port to use. * @return A Socket connected to the given host and port. - * @throws UnknownHostException If the hostname cannot be resolved. - * @throws IOException If an I/O error occurs while creating the Socket. - ***/ + * @throws UnknownHostException If the hostname cannot be resolved. + * @throws IOException If an I/O error occurs while creating the Socket. + */ @Override - public Socket createSocket(String host, int port, - InetAddress localAddr, int localPort) - throws UnknownHostException, IOException - { - if (connProxy != null) - { - Socket s = new Socket(connProxy); - s.bind(new InetSocketAddress(localAddr, localPort)); + public Socket createSocket(final String host, final int port) throws UnknownHostException, IOException { + if (connProxy != null) { + final Socket s = new Socket(connProxy); s.connect(new InetSocketAddress(host, port)); return s; } - return new Socket(host, port, localAddr, localPort); + return new Socket(host, port); } - /*** - * Creates a Socket connected to the given host and port and - * originating from the specified local address and port. + /** + * Creates a Socket connected to the given host and port and originating from the specified local address and port. * - * @param address The address of the host to connect to. - * @param port The port to connect to. - * @param localAddr The local address to use. - * @param localPort The local port to use. + * @param host The hostname to connect to. + * @param port The port to connect to. + * @param localAddr The local address to use. + * @param localPort The local port to use. * @return A Socket connected to the given host and port. - * @throws IOException If an I/O error occurs while creating the Socket. - ***/ + * @throws UnknownHostException If the hostname cannot be resolved. + * @throws IOException If an I/O error occurs while creating the Socket. + */ @Override - public Socket createSocket(InetAddress address, int port, - InetAddress localAddr, int localPort) - throws IOException - { - if (connProxy != null) - { - Socket s = new Socket(connProxy); + public Socket createSocket(final String host, final int port, final InetAddress localAddr, final int localPort) throws UnknownHostException, IOException { + if (connProxy != null) { + final Socket s = new Socket(connProxy); s.bind(new InetSocketAddress(localAddr, localPort)); - s.connect(new InetSocketAddress(address, port)); + s.connect(new InetSocketAddress(host, port)); return s; } - return new Socket(address, port, localAddr, localPort); - } - - /*** - * Creates a ServerSocket bound to a specified port. A port - * of 0 will create the ServerSocket on a system-determined free port. - * - * @param port The port on which to listen, or 0 to use any free port. - * @return A ServerSocket that will listen on a specified port. - * @throws IOException If an I/O error occurs while creating - * the ServerSocket. - ***/ - public ServerSocket createServerSocket(int port) throws IOException - { - return new ServerSocket(port); - } - - /*** - * Creates a ServerSocket bound to a specified port with a given - * maximum queue length for incoming connections. A port of 0 will - * create the ServerSocket on a system-determined free port. - * - * @param port The port on which to listen, or 0 to use any free port. - * @param backlog The maximum length of the queue for incoming connections. - * @return A ServerSocket that will listen on a specified port. - * @throws IOException If an I/O error occurs while creating - * the ServerSocket. - ***/ - public ServerSocket createServerSocket(int port, int backlog) - throws IOException - { - return new ServerSocket(port, backlog); - } - - /*** - * Creates a ServerSocket bound to a specified port on a given local - * address with a given maximum queue length for incoming connections. - * A port of 0 will - * create the ServerSocket on a system-determined free port. - * - * @param port The port on which to listen, or 0 to use any free port. - * @param backlog The maximum length of the queue for incoming connections. - * @param bindAddr The local address to which the ServerSocket should bind. - * @return A ServerSocket that will listen on a specified port. - * @throws IOException If an I/O error occurs while creating - * the ServerSocket. - ***/ - public ServerSocket createServerSocket(int port, int backlog, - InetAddress bindAddr) - throws IOException - { - return new ServerSocket(port, backlog, bindAddr); + return new Socket(host, port, localAddr, localPort); } } diff --git a/src/main/java/org/apache/commons/net/MalformedServerReplyException.java b/src/main/java/org/apache/commons/net/MalformedServerReplyException.java index e5ade4e..abe8574 100644 --- a/src/main/java/org/apache/commons/net/MalformedServerReplyException.java +++ b/src/main/java/org/apache/commons/net/MalformedServerReplyException.java @@ -19,37 +19,29 @@ package org.apache.commons.net; import java.io.IOException; -/*** - * This exception is used to indicate that the reply from a server - * could not be interpreted. Most of the NetComponents classes attempt - * to be as lenient as possible when receiving server replies. Many - * server implementations deviate from IETF protocol specifications, making - * it necessary to be as flexible as possible. However, there will be - * certain situations where it is not possible to continue an operation - * because the server reply could not be interpreted in a meaningful manner. - * In these cases, a MalformedServerReplyException should be thrown. +/** + * This exception is used to indicate that the reply from a server could not be interpreted. Most of the NetComponents classes attempt to be as lenient as + * possible when receiving server replies. Many server implementations deviate from IETF protocol specifications, making it necessary to be as flexible as + * possible. However, there will be certain situations where it is not possible to continue an operation because the server reply could not be interpreted in a + * meaningful manner. In these cases, a MalformedServerReplyException should be thrown. * * - ***/ + */ -public class MalformedServerReplyException extends IOException -{ +public class MalformedServerReplyException extends IOException { private static final long serialVersionUID = 6006765264250543945L; - /*** Constructs a MalformedServerReplyException with no message ***/ - public MalformedServerReplyException() - { - super(); + /** Constructs a MalformedServerReplyException with no message */ + public MalformedServerReplyException() { } - /*** + /** * Constructs a MalformedServerReplyException with a specified message. * - * @param message The message explaining the reason for the exception. - ***/ - public MalformedServerReplyException(String message) - { + * @param message The message explaining the reason for the exception. + */ + public MalformedServerReplyException(final String message) { super(message); } diff --git a/src/main/java/org/apache/commons/net/PrintCommandListener.java b/src/main/java/org/apache/commons/net/PrintCommandListener.java index 8fff4ed..364eeae 100644 --- a/src/main/java/org/apache/commons/net/PrintCommandListener.java +++ b/src/main/java/org/apache/commons/net/PrintCommandListener.java @@ -20,72 +20,65 @@ package org.apache.commons.net; import java.io.PrintStream; import java.io.PrintWriter; -/*** - * This is a support class for some of the example programs. It is - * a sample implementation of the ProtocolCommandListener interface - * which just prints out to a specified stream all command/reply traffic. +/** + * This is a support class for some of the example programs. It is a sample implementation of the ProtocolCommandListener interface which just prints out to a + * specified stream all command/reply traffic. * * @since 2.0 - ***/ + */ -public class PrintCommandListener implements ProtocolCommandListener -{ - private final PrintWriter __writer; - private final boolean __nologin; - private final char __eolMarker; - private final boolean __directionMarker; +public class PrintCommandListener implements ProtocolCommandListener { + private final PrintWriter writer; + private final boolean nologin; + private final char eolMarker; + private final boolean directionMarker; /** * Create the default instance which prints everything. * - * @param stream where to write the commands and responses - * e.g. System.out + * @param stream where to write the commands and responses e.g. System.out * @since 3.0 */ - public PrintCommandListener(PrintStream stream) - { + public PrintCommandListener(final PrintStream stream) { this(new PrintWriter(stream)); } /** - * Create an instance which optionally suppresses login command text - * and indicates where the EOL starts with the specified character. + * Create an instance which optionally suppresses login command text and indicates where the EOL starts with the specified character. * - * @param stream where to write the commands and responses + * @param stream where to write the commands and responses * @param suppressLogin if {@code true}, only print command name for login * * @since 3.0 */ - public PrintCommandListener(PrintStream stream, boolean suppressLogin) { + public PrintCommandListener(final PrintStream stream, final boolean suppressLogin) { this(new PrintWriter(stream), suppressLogin); } /** - * Create an instance which optionally suppresses login command text - * and indicates where the EOL starts with the specified character. + * Create an instance which optionally suppresses login command text and indicates where the EOL starts with the specified character. * - * @param stream where to write the commands and responses + * @param stream where to write the commands and responses * @param suppressLogin if {@code true}, only print command name for login - * @param eolMarker if non-zero, add a marker just before the EOL. + * @param eolMarker if non-zero, add a marker just before the EOL. * * @since 3.0 */ - public PrintCommandListener(PrintStream stream, boolean suppressLogin, char eolMarker) { + public PrintCommandListener(final PrintStream stream, final boolean suppressLogin, final char eolMarker) { this(new PrintWriter(stream), suppressLogin, eolMarker); } /** - * Create an instance which optionally suppresses login command text - * and indicates where the EOL starts with the specified character. + * Create an instance which optionally suppresses login command text and indicates where the EOL starts with the specified character. * - * @param stream where to write the commands and responses + * @param stream where to write the commands and responses * @param suppressLogin if {@code true}, only print command name for login - * @param eolMarker if non-zero, add a marker just before the EOL. + * @param eolMarker if non-zero, add a marker just before the EOL. * @param showDirection if {@code true}, add {@code "> "} or {@code "< "} as appropriate to the output * * @since 3.0 */ - public PrintCommandListener(PrintStream stream, boolean suppressLogin, char eolMarker, boolean showDirection) { + public PrintCommandListener(final PrintStream stream, final boolean suppressLogin, final char eolMarker, final boolean showDirection) { this(new PrintWriter(stream), suppressLogin, eolMarker, showDirection); } @@ -94,109 +87,100 @@ public class PrintCommandListener implements ProtocolCommandListener * * @param writer where to write the commands and responses */ - public PrintCommandListener(PrintWriter writer) - { + public PrintCommandListener(final PrintWriter writer) { this(writer, false); } /** * Create an instance which optionally suppresses login command text. * - * @param writer where to write the commands and responses + * @param writer where to write the commands and responses * @param suppressLogin if {@code true}, only print command name for login * * @since 3.0 */ - public PrintCommandListener(PrintWriter writer, boolean suppressLogin) - { + public PrintCommandListener(final PrintWriter writer, final boolean suppressLogin) { this(writer, suppressLogin, (char) 0); } /** - * Create an instance which optionally suppresses login command text - * and indicates where the EOL starts with the specified character. + * Create an instance which optionally suppresses login command text and indicates where the EOL starts with the specified character. * - * @param writer where to write the commands and responses + * @param writer where to write the commands and responses * @param suppressLogin if {@code true}, only print command name for login - * @param eolMarker if non-zero, add a marker just before the EOL. + * @param eolMarker if non-zero, add a marker just before the EOL. * * @since 3.0 */ - public PrintCommandListener(PrintWriter writer, boolean suppressLogin, char eolMarker) - { + public PrintCommandListener(final PrintWriter writer, final boolean suppressLogin, final char eolMarker) { this(writer, suppressLogin, eolMarker, false); } /** - * Create an instance which optionally suppresses login command text - * and indicates where the EOL starts with the specified character. + * Create an instance which optionally suppresses login command text and indicates where the EOL starts with the specified character. * - * @param writer where to write the commands and responses + * @param writer where to write the commands and responses * @param suppressLogin if {@code true}, only print command name for login - * @param eolMarker if non-zero, add a marker just before the EOL. + * @param eolMarker if non-zero, add a marker just before the EOL. * @param showDirection if {@code true}, add {@code ">} " or {@code "< "} as appropriate to the output * * @since 3.0 */ - public PrintCommandListener(PrintWriter writer, boolean suppressLogin, char eolMarker, boolean showDirection) - { - __writer = writer; - __nologin = suppressLogin; - __eolMarker = eolMarker; - __directionMarker = showDirection; + public PrintCommandListener(final PrintWriter writer, final boolean suppressLogin, final char eolMarker, final boolean showDirection) { + this.writer = writer; + this.nologin = suppressLogin; + this.eolMarker = eolMarker; + this.directionMarker = showDirection; + } + + private String getPrintableString(final String msg) { + if (eolMarker == 0) { + return msg; + } + final int pos = msg.indexOf(SocketClient.NETASCII_EOL); + if (pos > 0) { + final StringBuilder sb = new StringBuilder(); + sb.append(msg.substring(0, pos)); + sb.append(eolMarker); + sb.append(msg.substring(pos)); + return sb.toString(); + } + return msg; } @Override - public void protocolCommandSent(ProtocolCommandEvent event) - { - if (__directionMarker) { - __writer.print("> "); + public void protocolCommandSent(final ProtocolCommandEvent event) { + if (directionMarker) { + writer.print("> "); } - if (__nologin) { - String cmd = event.getCommand(); + if (nologin) { + final String cmd = event.getCommand(); if ("PASS".equalsIgnoreCase(cmd) || "USER".equalsIgnoreCase(cmd)) { - __writer.print(cmd); - __writer.println(" *******"); // Don't bother with EOL marker for this! + writer.print(cmd); + writer.println(" *******"); // Don't bother with EOL marker for this! } else { final String IMAP_LOGIN = "LOGIN"; if (IMAP_LOGIN.equalsIgnoreCase(cmd)) { // IMAP String msg = event.getMessage(); - msg=msg.substring(0, msg.indexOf(IMAP_LOGIN)+IMAP_LOGIN.length()); - __writer.print(msg); - __writer.println(" *******"); // Don't bother with EOL marker for this! + msg = msg.substring(0, msg.indexOf(IMAP_LOGIN) + IMAP_LOGIN.length()); + writer.print(msg); + writer.println(" *******"); // Don't bother with EOL marker for this! } else { - __writer.print(getPrintableString(event.getMessage())); + writer.print(getPrintableString(event.getMessage())); } } } else { - __writer.print(getPrintableString(event.getMessage())); + writer.print(getPrintableString(event.getMessage())); } - __writer.flush(); - } - - private String getPrintableString(String msg){ - if (__eolMarker == 0) { - return msg; - } - int pos = msg.indexOf(SocketClient.NETASCII_EOL); - if (pos > 0) { - StringBuilder sb = new StringBuilder(); - sb.append(msg.substring(0,pos)); - sb.append(__eolMarker); - sb.append(msg.substring(pos)); - return sb.toString(); - } - return msg; + writer.flush(); } @Override - public void protocolReplyReceived(ProtocolCommandEvent event) - { - if (__directionMarker) { - __writer.print("< "); + public void protocolReplyReceived(final ProtocolCommandEvent event) { + if (directionMarker) { + writer.print("< "); } - __writer.print(event.getMessage()); - __writer.flush(); + writer.print(event.getMessage()); + writer.flush(); } } - diff --git a/src/main/java/org/apache/commons/net/ProtocolCommandEvent.java b/src/main/java/org/apache/commons/net/ProtocolCommandEvent.java index d921c61..7330cf8 100644 --- a/src/main/java/org/apache/commons/net/ProtocolCommandEvent.java +++ b/src/main/java/org/apache/commons/net/ProtocolCommandEvent.java @@ -16,133 +16,102 @@ */ package org.apache.commons.net; + import java.util.EventObject; -/*** - * There exists a large class of IETF protocols that work by sending an - * ASCII text command and arguments to a server, and then receiving an - * ASCII text reply. For debugging and other purposes, it is extremely - * useful to log or keep track of the contents of the protocol messages. - * The ProtocolCommandEvent class coupled with the - * {@link org.apache.commons.net.ProtocolCommandListener} - * interface facilitate this process. +/** + * There exists a large class of IETF protocols that work by sending an ASCII text command and arguments to a server, and then receiving an ASCII text reply. + * For debugging and other purposes, it is extremely useful to log or keep track of the contents of the protocol messages. The ProtocolCommandEvent class + * coupled with the {@link org.apache.commons.net.ProtocolCommandListener} interface facilitate this process. * * * @see ProtocolCommandListener * @see ProtocolCommandSupport - ***/ + */ -public class ProtocolCommandEvent extends EventObject -{ +public class ProtocolCommandEvent extends EventObject { private static final long serialVersionUID = 403743538418947240L; - private final int __replyCode; - private final boolean __isCommand; - private final String __message, __command; + private final int replyCode; + private final boolean isCommand; + private final String message, command; - /*** - * Creates a ProtocolCommandEvent signalling a command was sent to - * the server. ProtocolCommandEvents created with this constructor - * should only be sent after a command has been sent, but before the - * reply has been received. + /** + * Creates a ProtocolCommandEvent signalling a reply to a command was received. ProtocolCommandEvents created with this constructor should only be sent + * after a complete command reply has been received fromt a server. * - * @param source The source of the event. - * @param command The string representation of the command type sent, not - * including the arguments (e.g., "STAT" or "GET"). - * @param message The entire command string verbatim as sent to the server, - * including all arguments. - ***/ - public ProtocolCommandEvent(Object source, String command, String message) - { + * @param source The source of the event. + * @param replyCode The integer code indicating the natureof the reply. This will be the protocol integer value for protocols that use integer reply codes, + * or the reply class constant corresponding to the reply for protocols like POP3 that use strings like OK rather than integer codes (i.e., + * POP3Repy.OK). + * @param message The entire reply as received from the server. + */ + public ProtocolCommandEvent(final Object source, final int replyCode, final String message) { super(source); - __replyCode = 0; - __message = message; - __isCommand = true; - __command = command; + this.replyCode = replyCode; + this.message = message; + this.isCommand = false; + this.command = null; } - - /*** - * Creates a ProtocolCommandEvent signalling a reply to a command was - * received. ProtocolCommandEvents created with this constructor - * should only be sent after a complete command reply has been received - * fromt a server. + /** + * Creates a ProtocolCommandEvent signalling a command was sent to the server. ProtocolCommandEvents created with this constructor should only be sent after + * a command has been sent, but before the reply has been received. * * @param source The source of the event. - * @param replyCode The integer code indicating the natureof the reply. - * This will be the protocol integer value for protocols - * that use integer reply codes, or the reply class constant - * corresponding to the reply for protocols like POP3 that use - * strings like OK rather than integer codes (i.e., POP3Repy.OK). - * @param message The entire reply as received from the server. - ***/ - public ProtocolCommandEvent(Object source, int replyCode, String message) - { + * @param command The string representation of the command type sent, not including the arguments (e.g., "STAT" or "GET"). + * @param message The entire command string verbatim as sent to the server, including all arguments. + */ + public ProtocolCommandEvent(final Object source, final String command, final String message) { super(source); - __replyCode = replyCode; - __message = message; - __isCommand = false; - __command = null; + this.replyCode = 0; + this.message = message; + this.isCommand = true; + this.command = command; } - /*** - * Returns the string representation of the command type sent (e.g., "STAT" - * or "GET"). If the ProtocolCommandEvent is a reply event, then null - * is returned. + /** + * Returns the string representation of the command type sent (e.g., "STAT" or "GET"). If the ProtocolCommandEvent is a reply event, then null is returned. * - * @return The string representation of the command type sent, or null - * if this is a reply event. - ***/ - public String getCommand() - { - return __command; + * @return The string representation of the command type sent, or null if this is a reply event. + */ + public String getCommand() { + return command; } - - /*** - * Returns the reply code of the received server reply. Undefined if - * this is not a reply event. + /** + * Returns the entire message sent to or received from the server. Includes the line terminator. * - * @return The reply code of the received server reply. Undefined if - * not a reply event. - ***/ - public int getReplyCode() - { - return __replyCode; + * @return The entire message sent to or received from the server. + */ + public String getMessage() { + return message; } - /*** - * Returns true if the ProtocolCommandEvent was generated as a result - * of sending a command. + /** + * Returns the reply code of the received server reply. Undefined if this is not a reply event. * - * @return true If the ProtocolCommandEvent was generated as a result - * of sending a command. False otherwise. - ***/ - public boolean isCommand() - { - return __isCommand; + * @return The reply code of the received server reply. Undefined if not a reply event. + */ + public int getReplyCode() { + return replyCode; } - /*** - * Returns true if the ProtocolCommandEvent was generated as a result - * of receiving a reply. + /** + * Returns true if the ProtocolCommandEvent was generated as a result of sending a command. * - * @return true If the ProtocolCommandEvent was generated as a result - * of receiving a reply. False otherwise. - ***/ - public boolean isReply() - { - return !isCommand(); + * @return true If the ProtocolCommandEvent was generated as a result of sending a command. False otherwise. + */ + public boolean isCommand() { + return isCommand; } - /*** - * Returns the entire message sent to or received from the server. - * Includes the line terminator. + /** + * Returns true if the ProtocolCommandEvent was generated as a result of receiving a reply. * - * @return The entire message sent to or received from the server. - ***/ - public String getMessage() - { - return __message; + * @return true If the ProtocolCommandEvent was generated as a result of receiving a reply. False otherwise. + */ + public boolean isReply() { + return !isCommand(); } } diff --git a/src/main/java/org/apache/commons/net/ProtocolCommandListener.java b/src/main/java/org/apache/commons/net/ProtocolCommandListener.java index f6d4231..36f06a2 100644 --- a/src/main/java/org/apache/commons/net/ProtocolCommandListener.java +++ b/src/main/java/org/apache/commons/net/ProtocolCommandListener.java @@ -16,43 +16,36 @@ */ package org.apache.commons.net; + import java.util.EventListener; -/*** - * There exists a large class of IETF protocols that work by sending an - * ASCII text command and arguments to a server, and then receiving an - * ASCII text reply. For debugging and other purposes, it is extremely - * useful to log or keep track of the contents of the protocol messages. - * The ProtocolCommandListener interface coupled with the - * {@link ProtocolCommandEvent} class facilitate this process. +/** + * There exists a large class of IETF protocols that work by sending an ASCII text command and arguments to a server, and then receiving an ASCII text reply. + * For debugging and other purposes, it is extremely useful to log or keep track of the contents of the protocol messages. The ProtocolCommandListener interface + * coupled with the {@link ProtocolCommandEvent} class facilitate this process. * <p> - * To receive ProtocolCommandEvents, you merely implement the - * ProtocolCommandListener interface and register the class as a listener - * with a ProtocolCommandEvent source such as - * {@link org.apache.commons.net.ftp.FTPClient}. + * To receive ProtocolCommandEvents, you merely implement the ProtocolCommandListener interface and register the class as a listener with a ProtocolCommandEvent + * source such as {@link org.apache.commons.net.ftp.FTPClient}. * * * @see ProtocolCommandEvent * @see ProtocolCommandSupport - ***/ + */ -public interface ProtocolCommandListener extends EventListener -{ +public interface ProtocolCommandListener extends EventListener { - /*** - * This method is invoked by a ProtocolCommandEvent source after - * sending a protocol command to a server. + /** + * This method is invoked by a ProtocolCommandEvent source after sending a protocol command to a server. * * @param event The ProtocolCommandEvent fired. - ***/ - public void protocolCommandSent(ProtocolCommandEvent event); + */ + void protocolCommandSent(ProtocolCommandEvent event); - /*** - * This method is invoked by a ProtocolCommandEvent source after - * receiving a reply from a server. + /** + * This method is invoked by a ProtocolCommandEvent source after receiving a reply from a server. * * @param event The ProtocolCommandEvent fired. - ***/ - public void protocolReplyReceived(ProtocolCommandEvent event); + */ + void protocolReplyReceived(ProtocolCommandEvent event); } diff --git a/src/main/java/org/apache/commons/net/ProtocolCommandSupport.java b/src/main/java/org/apache/commons/net/ProtocolCommandSupport.java index 95611f0..78f888e 100644 --- a/src/main/java/org/apache/commons/net/ProtocolCommandSupport.java +++ b/src/main/java/org/apache/commons/net/ProtocolCommandSupport.java @@ -22,114 +22,103 @@ import java.util.EventListener; import org.apache.commons.net.util.ListenerList; -/*** - * ProtocolCommandSupport is a convenience class for managing a list of - * ProtocolCommandListeners and firing ProtocolCommandEvents. You can - * simply delegate ProtocolCommandEvent firing and listener - * registering/unregistering tasks to this class. +/** + * ProtocolCommandSupport is a convenience class for managing a list of ProtocolCommandListeners and firing ProtocolCommandEvents. You can simply delegate + * ProtocolCommandEvent firing and listener registering/unregistering tasks to this class. * * * @see ProtocolCommandEvent * @see ProtocolCommandListener - ***/ + */ -public class ProtocolCommandSupport implements Serializable -{ +public class ProtocolCommandSupport implements Serializable { private static final long serialVersionUID = -8017692739988399978L; - private final Object __source; - private final ListenerList __listeners; + private final Object source; + private final ListenerList listeners; - /*** - * Creates a ProtocolCommandSupport instance using the indicated source - * as the source of ProtocolCommandEvents. + /** + * Creates a ProtocolCommandSupport instance using the indicated source as the source of ProtocolCommandEvents. * - * @param source The source to use for all generated ProtocolCommandEvents. - ***/ - public ProtocolCommandSupport(Object source) - { - __listeners = new ListenerList(); - __source = source; + * @param source The source to use for all generated ProtocolCommandEvents. + */ + public ProtocolCommandSupport(final Object source) { + this.listeners = new ListenerList(); + this.source = source; } + /** + * Adds a ProtocolCommandListener. + * + * @param listener The ProtocolCommandListener to add. + */ + public void addProtocolCommandListener(final ProtocolCommandListener listener) { + listeners.addListener(listener); + } - /*** - * Fires a ProtocolCommandEvent signalling the sending of a command to all - * registered listeners, invoking their - * {@link org.apache.commons.net.ProtocolCommandListener#protocolCommandSent protocolCommandSent() } - * methods. + /** + * Fires a ProtocolCommandEvent signalling the sending of a command to all registered listeners, invoking their + * {@link org.apache.commons.net.ProtocolCommandListener#protocolCommandSent protocolCommandSent() } methods. * - * @param command The string representation of the command type sent, not - * including the arguments (e.g., "STAT" or "GET"). - * @param message The entire command string verbatim as sent to the server, - * including all arguments. - ***/ - public void fireCommandSent(String command, String message) - { - ProtocolCommandEvent event; - - event = new ProtocolCommandEvent(__source, command, message); - - for (EventListener listener : __listeners) - { - ((ProtocolCommandListener)listener).protocolCommandSent(event); + * @param command The string representation of the command type sent, not including the arguments (e.g., "STAT" or "GET"). + * @param message The entire command string verbatim as sent to the server, including all arguments. + */ + public void fireCommandSent(final String command, final String message) { + final ProtocolCommandEvent event; + + event = new ProtocolCommandEvent(source, command, message); + + for (final EventListener listener : listeners) { + ((ProtocolCommandListener) listener).protocolCommandSent(event); } } - /*** - * Fires a ProtocolCommandEvent signalling the reception of a command reply - * to all registered listeners, invoking their - * {@link org.apache.commons.net.ProtocolCommandListener#protocolReplyReceived protocolReplyReceived() } - * methods. + /** + * Fires a ProtocolCommandEvent signalling the reception of a command reply to all registered listeners, invoking their + * {@link org.apache.commons.net.ProtocolCommandListener#protocolReplyReceived protocolReplyReceived() } methods. * - * @param replyCode The integer code indicating the natureof the reply. - * This will be the protocol integer value for protocols - * that use integer reply codes, or the reply class constant - * corresponding to the reply for protocols like POP3 that use - * strings like OK rather than integer codes (i.e., POP3Repy.OK). - * @param message The entire reply as received from the server. - ***/ - public void fireReplyReceived(int replyCode, String message) - { - ProtocolCommandEvent event; - event = new ProtocolCommandEvent(__source, replyCode, message); - - for (EventListener listener : __listeners) - { - ((ProtocolCommandListener)listener).protocolReplyReceived(event); + * @param replyCode The integer code indicating the natureof the reply. This will be the protocol integer value for protocols that use integer reply codes, + * or the reply class constant corresponding to the reply for protocols like POP3 that use strings like OK rather than integer codes (i.e., + * POP3Repy.OK). + * @param message The entire reply as received from the server. + */ + public void fireReplyReceived(final int replyCode, final String message) { + final ProtocolCommandEvent event; + event = new ProtocolCommandEvent(source, replyCode, message); + + for (final EventListener listener : listeners) { + ((ProtocolCommandListener) listener).protocolReplyReceived(event); } } - /*** - * Adds a ProtocolCommandListener. + /** + * Returns the number of ProtocolCommandListeners currently registered. * - * @param listener The ProtocolCommandListener to add. - ***/ - public void addProtocolCommandListener(ProtocolCommandListener listener) - { - __listeners.addListener(listener); + * @return The number of ProtocolCommandListeners currently registered. + */ + public int getListenerCount() { + return listeners.getListenerCount(); } - /*** - * Removes a ProtocolCommandListener. - * - * @param listener The ProtocolCommandListener to remove. - ***/ - public void removeProtocolCommandListener(ProtocolCommandListener listener) - { - __listeners.removeListener(listener); + private void readObject(final java.io.ObjectInputStream in) { + throw new UnsupportedOperationException("Serialization is not supported"); } + /* + * Serialization is unnecessary for this class. Reject attempts to do so until such time as the Serializable attribute can be dropped. + */ - /*** - * Returns the number of ProtocolCommandListeners currently registered. + /** + * Removes a ProtocolCommandListener. * - * @return The number of ProtocolCommandListeners currently registered. - ***/ - public int getListenerCount() - { - return __listeners.getListenerCount(); + * @param listener The ProtocolCommandListener to remove. + */ + public void removeProtocolCommandListener(final ProtocolCommandListener listener) { + listeners.removeListener(listener); } -} + private void writeObject(final java.io.ObjectOutputStream out) { + throw new UnsupportedOperationException("Serialization is not supported"); + } +} diff --git a/src/main/java/org/apache/commons/net/SocketClient.java b/src/main/java/org/apache/commons/net/SocketClient.java index 6c938b5..a1291ba 100644 --- a/src/main/java/org/apache/commons/net/SocketClient.java +++ b/src/main/java/org/apache/commons/net/SocketClient.java @@ -31,48 +31,36 @@ import java.nio.charset.Charset; import javax.net.ServerSocketFactory; import javax.net.SocketFactory; - /** - * The SocketClient provides the basic operations that are required of - * client objects accessing sockets. It is meant to be - * subclassed to avoid having to rewrite the same code over and over again - * to open a socket, close a socket, set timeouts, etc. Of special note - * is the {@link #setSocketFactory setSocketFactory } - * method, which allows you to control the type of Socket the SocketClient - * creates for initiating network connections. This is especially useful - * for adding SSL or proxy support as well as better support for applets. For - * example, you could create a - * {@link javax.net.SocketFactory} that - * requests browser security capabilities before creating a socket. - * All classes derived from SocketClient should use the - * {@link #_socketFactory_ _socketFactory_ } member variable to - * create Socket and ServerSocket instances rather than instantiating - * them by directly invoking a constructor. By honoring this contract - * you guarantee that a user will always be able to provide his own - * Socket implementations by substituting his own SocketFactory. + * The SocketClient provides the basic operations that are required of client objects accessing sockets. It is meant to be subclassed to avoid having to rewrite + * the same code over and over again to open a socket, close a socket, set timeouts, etc. Of special note is the {@link #setSocketFactory setSocketFactory } + * method, which allows you to control the type of Socket the SocketClient creates for initiating network connections. This is especially useful for adding SSL + * or proxy support as well as better support for applets. For example, you could create a {@link javax.net.SocketFactory} that requests browser security + * capabilities before creating a socket. All classes derived from SocketClient should use the {@link #_socketFactory_ _socketFactory_ } member variable to + * create Socket and ServerSocket instances rather than instantiating them by directly invoking a constructor. By honoring this contract you guarantee that a + * user will always be able to provide his own Socket implementations by substituting his own SocketFactory. + * * @see SocketFactory */ -public abstract class SocketClient -{ +public abstract class SocketClient { /** - * The end of line character sequence used by most IETF protocols. That - * is a carriage return followed by a newline: "\r\n" + * The end of line character sequence used by most IETF protocols. That is a carriage return followed by a newline: "\r\n" */ public static final String NETASCII_EOL = "\r\n"; /** The default SocketFactory shared by all SocketClient instances. */ - private static final SocketFactory __DEFAULT_SOCKET_FACTORY = - SocketFactory.getDefault(); + private static final SocketFactory DEFAULT_SOCKET_FACTORY = SocketFactory.getDefault(); /** The default {@link ServerSocketFactory} */ - private static final ServerSocketFactory __DEFAULT_SERVER_SOCKET_FACTORY = - ServerSocketFactory.getDefault(); + private static final ServerSocketFactory DEFAULT_SERVER_SOCKET_FACTORY = ServerSocketFactory.getDefault(); + + /** The socket's connect timeout (0 = infinite timeout) */ + private static final int DEFAULT_CONNECT_TIMEOUT = 60000; /** - * A ProtocolCommandSupport object used to manage the registering of - * ProtocolCommandListeners and the firing of ProtocolCommandEvents. + * A ProtocolCommandSupport object used to manage the registering of ProtocolCommandListeners and the firing of ProtocolCommandEvents. */ - private ProtocolCommandSupport __commandSupport; + private ProtocolCommandSupport commandSupport; /** The timeout to use after opening a socket. */ protected int _timeout_; @@ -98,8 +86,6 @@ public abstract class SocketClient /** The socket's ServerSocket Factory. */ protected ServerSocketFactory _serverSocketFactory_; - /** The socket's connect timeout (0 = infinite timeout) */ - private static final int DEFAULT_CONNECT_TIMEOUT = 0; protected int connectTimeout = DEFAULT_CONNECT_TIMEOUT; /** Hint for SO_RCVBUF size */ @@ -117,208 +103,203 @@ public abstract class SocketClient private Charset charset = Charset.defaultCharset(); /** - * Default constructor for SocketClient. Initializes - * _socket_ to null, _timeout_ to 0, _defaultPort to 0, - * _isConnected_ to false, charset to {@code Charset.defaultCharset()} - * and _socketFactory_ to a shared instance of - * {@link org.apache.commons.net.DefaultSocketFactory}. + * Default constructor for SocketClient. Initializes _socket_ to null, _timeout_ to 0, _defaultPort to 0, _isConnected_ to false, charset to + * {@code Charset.defaultCharset()} and _socketFactory_ to a shared instance of {@link org.apache.commons.net.DefaultSocketFactory}. */ - public SocketClient() - { + public SocketClient() { _socket_ = null; _hostname_ = null; _input_ = null; _output_ = null; _timeout_ = 0; _defaultPort_ = 0; - _socketFactory_ = __DEFAULT_SOCKET_FACTORY; - _serverSocketFactory_ = __DEFAULT_SERVER_SOCKET_FACTORY; + _socketFactory_ = DEFAULT_SOCKET_FACTORY; + _serverSocketFactory_ = DEFAULT_SERVER_SOCKET_FACTORY; } + // helper method to allow code to be shared with connect(String,...) methods + private void _connect(final InetAddress host, final int port, final InetAddress localAddr, final int localPort) throws SocketException, IOException { + _socket_ = _socketFactory_.createSocket(); + if (receiveBufferSize != -1) { + _socket_.setReceiveBufferSize(receiveBufferSize); + } + if (sendBufferSize != -1) { + _socket_.setSendBufferSize(sendBufferSize); + } + if (localAddr != null) { + _socket_.bind(new InetSocketAddress(localAddr, localPort)); + } + _socket_.connect(new InetSocketAddress(host, port), connectTimeout); + _connectAction_(); + } /** - * Because there are so many connect() methods, the _connectAction_() - * method is provided as a means of performing some action immediately - * after establishing a connection, rather than reimplementing all - * of the connect() methods. The last action performed by every - * connect() method after opening a socket is to call this method. + * Because there are so many connect() methods, the _connectAction_() method is provided as a means of performing some action immediately after establishing + * a connection, rather than reimplementing all of the connect() methods. The last action performed by every connect() method after opening a socket is to + * call this method. * <p> - * This method sets the timeout on the just opened socket to the default - * timeout set by {@link #setDefaultTimeout setDefaultTimeout() }, - * sets _input_ and _output_ to the socket's InputStream and OutputStream - * respectively, and sets _isConnected_ to true. + * This method sets the timeout on the just opened socket to the default timeout set by {@link #setDefaultTimeout setDefaultTimeout() }, sets _input_ and + * _output_ to the socket's InputStream and OutputStream respectively, and sets _isConnected_ to true. * <p> - * Subclasses overriding this method should start by calling - * <code> super._connectAction_() </code> first to ensure the - * initialization of the aforementioned protected variables. + * Subclasses overriding this method should start by calling <code> super._connectAction_() </code> first to ensure the initialization of the aforementioned + * protected variables. + * * @throws IOException (SocketException) if a problem occurs with the socket */ - protected void _connectAction_() throws IOException - { - _socket_.setSoTimeout(_timeout_); + protected void _connectAction_() throws IOException { + applySocketAttributes(); _input_ = _socket_.getInputStream(); _output_ = _socket_.getOutputStream(); } + /** + * Adds a ProtocolCommandListener. + * + * @param listener The ProtocolCommandListener to add. + * @since 3.0 + */ + public void addProtocolCommandListener(final ProtocolCommandListener listener) { + getCommandSupport().addProtocolCommandListener(listener); + } /** - * Opens a Socket connected to a remote host at the specified port and - * originating from the current host at a system assigned port. - * Before returning, {@link #_connectAction_ _connectAction_() } - * is called to perform connection initialization actions. - * <p> - * @param host The remote host. - * @param port The port to connect to on the remote host. + * Applies socket attributes. + * + * @throws SocketException if there is an error in the underlying protocol, such as a TCP error. + * @since 3.8.0 + */ + protected void applySocketAttributes() throws SocketException { + _socket_.setSoTimeout(_timeout_); + } + + private void closeQuietly(final Closeable close) { + if (close != null) { + try { + close.close(); + } catch (final IOException e) { + // Ignored + } + } + } + + private void closeQuietly(final Socket socket) { + if (socket != null) { + try { + socket.close(); + } catch (final IOException e) { + // Ignored + } + } + } + + /** + * Opens a Socket connected to a remote host at the current default port and originating from the current host at a system assigned port. Before returning, + * {@link #_connectAction_ _connectAction_() } is called to perform connection initialization actions. + * + * @param host The remote host. * @throws SocketException If the socket timeout could not be set. - * @throws IOException If the socket could not be opened. In most - * cases you will only want to catch IOException since SocketException is - * derived from it. + * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is derived from + * it. */ - public void connect(InetAddress host, int port) - throws SocketException, IOException - { + public void connect(final InetAddress host) throws SocketException, IOException { _hostname_ = null; - _connect(host, port, null, -1); + connect(host, _defaultPort_); } /** - * Opens a Socket connected to a remote host at the specified port and - * originating from the current host at a system assigned port. - * Before returning, {@link #_connectAction_ _connectAction_() } - * is called to perform connection initialization actions. - * <p> - * @param hostname The name of the remote host. - * @param port The port to connect to on the remote host. + * Opens a Socket connected to a remote host at the specified port and originating from the current host at a system assigned port. Before returning, + * {@link #_connectAction_ _connectAction_() } is called to perform connection initialization actions. + * + * @param host The remote host. + * @param port The port to connect to on the remote host. * @throws SocketException If the socket timeout could not be set. - * @throws IOException If the socket could not be opened. In most - * cases you will only want to catch IOException since SocketException is - * derived from it. - * @throws java.net.UnknownHostException If the hostname cannot be resolved. + * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is derived from + * it. */ - public void connect(String hostname, int port) - throws SocketException, IOException - { - _hostname_ = hostname; - _connect(InetAddress.getByName(hostname), port, null, -1); + public void connect(final InetAddress host, final int port) throws SocketException, IOException { + _hostname_ = null; + _connect(host, port, null, -1); } - /** - * Opens a Socket connected to a remote host at the specified port and - * originating from the specified local address and port. - * Before returning, {@link #_connectAction_ _connectAction_() } - * is called to perform connection initialization actions. - * <p> - * @param host The remote host. - * @param port The port to connect to on the remote host. - * @param localAddr The local address to use. - * @param localPort The local port to use. + * Opens a Socket connected to a remote host at the specified port and originating from the specified local address and port. Before returning, + * {@link #_connectAction_ _connectAction_() } is called to perform connection initialization actions. + * + * @param host The remote host. + * @param port The port to connect to on the remote host. + * @param localAddr The local address to use. + * @param localPort The local port to use. * @throws SocketException If the socket timeout could not be set. - * @throws IOException If the socket could not be opened. In most - * cases you will only want to catch IOException since SocketException is - * derived from it. - */ - public void connect(InetAddress host, int port, - InetAddress localAddr, int localPort) - throws SocketException, IOException - { + * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is derived from + * it. + */ + public void connect(final InetAddress host, final int port, final InetAddress localAddr, final int localPort) throws SocketException, IOException { _hostname_ = null; _connect(host, port, localAddr, localPort); } - // helper method to allow code to be shared with connect(String,...) methods - private void _connect(InetAddress host, int port, InetAddress localAddr, int localPort) - throws SocketException, IOException - { - _socket_ = _socketFactory_.createSocket(); - if (receiveBufferSize != -1) { - _socket_.setReceiveBufferSize(receiveBufferSize); - } - if (sendBufferSize != -1) { - _socket_.setSendBufferSize(sendBufferSize); - } - if (localAddr != null) { - _socket_.bind(new InetSocketAddress(localAddr, localPort)); - } - _socket_.connect(new InetSocketAddress(host, port), connectTimeout); - _connectAction_(); - } - /** - * Opens a Socket connected to a remote host at the specified port and - * originating from the specified local address and port. - * Before returning, {@link #_connectAction_ _connectAction_() } - * is called to perform connection initialization actions. - * <p> - * @param hostname The name of the remote host. - * @param port The port to connect to on the remote host. - * @param localAddr The local address to use. - * @param localPort The local port to use. - * @throws SocketException If the socket timeout could not be set. - * @throws IOException If the socket could not be opened. In most - * cases you will only want to catch IOException since SocketException is - * derived from it. + * Opens a Socket connected to a remote host at the current default port and originating from the current host at a system assigned port. Before returning, + * {@link #_connectAction_ _connectAction_() } is called to perform connection initialization actions. + * + * @param hostname The name of the remote host. + * @throws SocketException If the socket timeout could not be set. + * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is + * derived from it. * @throws java.net.UnknownHostException If the hostname cannot be resolved. */ - public void connect(String hostname, int port, - InetAddress localAddr, int localPort) - throws SocketException, IOException - { - _hostname_ = hostname; - _connect(InetAddress.getByName(hostname), port, localAddr, localPort); + public void connect(final String hostname) throws SocketException, IOException { + connect(hostname, _defaultPort_); } - /** - * Opens a Socket connected to a remote host at the current default port - * and originating from the current host at a system assigned port. - * Before returning, {@link #_connectAction_ _connectAction_() } - * is called to perform connection initialization actions. - * <p> - * @param host The remote host. - * @throws SocketException If the socket timeout could not be set. - * @throws IOException If the socket could not be opened. In most - * cases you will only want to catch IOException since SocketException is - * derived from it. + * Opens a Socket connected to a remote host at the specified port and originating from the current host at a system assigned port. Before returning, + * {@link #_connectAction_ _connectAction_() } is called to perform connection initialization actions. + * + * @param hostname The name of the remote host. + * @param port The port to connect to on the remote host. + * @throws SocketException If the socket timeout could not be set. + * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is + * derived from it. + * @throws java.net.UnknownHostException If the hostname cannot be resolved. */ - public void connect(InetAddress host) throws SocketException, IOException - { - _hostname_ = null; - connect(host, _defaultPort_); + public void connect(final String hostname, final int port) throws SocketException, IOException { + _hostname_ = hostname; + _connect(InetAddress.getByName(hostname), port, null, -1); } - /** - * Opens a Socket connected to a remote host at the current default - * port and originating from the current host at a system assigned port. - * Before returning, {@link #_connectAction_ _connectAction_() } - * is called to perform connection initialization actions. - * <p> + * Opens a Socket connected to a remote host at the specified port and originating from the specified local address and port. Before returning, + * {@link #_connectAction_ _connectAction_() } is called to perform connection initialization actions. + * * @param hostname The name of the remote host. - * @throws SocketException If the socket timeout could not be set. - * @throws IOException If the socket could not be opened. In most - * cases you will only want to catch IOException since SocketException is - * derived from it. + * @param port The port to connect to on the remote host. + * @param localAddr The local address to use. + * @param localPort The local port to use. + * @throws SocketException If the socket timeout could not be set. + * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is + * derived from it. * @throws java.net.UnknownHostException If the hostname cannot be resolved. */ - public void connect(String hostname) throws SocketException, IOException - { - connect(hostname, _defaultPort_); + public void connect(final String hostname, final int port, final InetAddress localAddr, final int localPort) throws SocketException, IOException { + _hostname_ = hostname; + _connect(InetAddress.getByName(hostname), port, localAddr, localPort); } + /** + * Create the CommandSupport instance if required + */ + protected void createCommandSupport() { + commandSupport = new ProtocolCommandSupport(this); + } /** - * Disconnects the socket connection. - * You should call this method after you've finished using the class - * instance and also before you call - * {@link #connect connect() } - * again. _isConnected_ is set to false, _socket_ is set to null, - * _input_ is set to null, and _output_ is set to null. - * <p> - * @throws IOException If there is an error closing the socket. + * Disconnects the socket connection. You should call this method after you've finished using the class instance and also before you call {@link #connect + * connect() } again. _isConnected_ is set to false, _socket_ is set to null, _input_ is set to null, and _output_ is set to null. + * + * @throws IOException If there is an error closing the socket. */ - public void disconnect() throws IOException - { + public void disconnect() throws IOException { closeQuietly(_socket_); closeQuietly(_input_); closeQuietly(_output_); @@ -328,560 +309,457 @@ public abstract class SocketClient _output_ = null; } - private void closeQuietly(Socket socket) { - if (socket != null){ - try { - socket.close(); - } catch (IOException e) { - // Ignored - } - } - } - - private void closeQuietly(Closeable close){ - if (close != null){ - try { - close.close(); - } catch (IOException e) { - // Ignored - } - } - } /** - * Returns true if the client is currently connected to a server. - * <p> - * Delegates to {@link Socket#isConnected()} - * @return True if the client is currently connected to a server, - * false otherwise. + * If there are any listeners, send them the command details. + * + * @param command the command name + * @param message the complete message, including command name + * @since 3.0 */ - public boolean isConnected() - { - if (_socket_ == null) { - return false; + protected void fireCommandSent(final String command, final String message) { + if (getCommandSupport().getListenerCount() > 0) { + getCommandSupport().fireCommandSent(command, message); } - - return _socket_.isConnected(); } /** - * Make various checks on the socket to test if it is available for use. - * Note that the only sure test is to use it, but these checks may help - * in some cases. - * @see <a href="https://issues.apache.org/jira/browse/NET-350">NET-350</a> - * @return {@code true} if the socket appears to be available for use + * If there are any listeners, send them the reply details. + * + * @param replyCode the code extracted from the reply + * @param reply the full reply text * @since 3.0 */ - public boolean isAvailable(){ - if (isConnected()) { - try - { - if (_socket_.getInetAddress() == null) { - return false; - } - if (_socket_.getPort() == 0) { - return false; - } - if (_socket_.getRemoteSocketAddress() == null) { - return false; - } - if (_socket_.isClosed()) { - return false; - } - /* these aren't exact checks (a Socket can be half-open), - but since we usually require two-way data transfer, - we check these here too: */ - if (_socket_.isInputShutdown()) { - return false; - } - if (_socket_.isOutputShutdown()) { - return false; - } - /* ignore the result, catch exceptions: */ - _socket_.getInputStream(); - _socket_.getOutputStream(); - } - catch (IOException ioex) - { - return false; - } - return true; - } else { - return false; + protected void fireReplyReceived(final int replyCode, final String reply) { + if (getCommandSupport().getListenerCount() > 0) { + getCommandSupport().fireReplyReceived(replyCode, reply); } } /** - * Sets the default port the SocketClient should connect to when a port - * is not specified. The {@link #_defaultPort_ _defaultPort_ } - * variable stores this value. If never set, the default port is equal - * to zero. - * <p> - * @param port The default port to set. + * Gets the charset. + * + * @return the charset. + * @since 3.3 */ - public void setDefaultPort(int port) - { - _defaultPort_ = port; + public Charset getCharset() { + return charset; } /** - * Returns the current value of the default port (stored in - * {@link #_defaultPort_ _defaultPort_ }). - * <p> - * @return The current value of the default port. + * Gets the charset name. + * + * @return the charset. + * @since 3.3 + * @deprecated Since the code now requires Java 1.6 as a mininmum */ - public int getDefaultPort() - { - return _defaultPort_; + @Deprecated + public String getCharsetName() { + return charset.name(); } - /** - * Set the default timeout in milliseconds to use when opening a socket. - * This value is only used previous to a call to - * {@link #connect connect()} - * and should not be confused with {@link #setSoTimeout setSoTimeout()} - * which operates on an the currently opened socket. _timeout_ contains - * the new timeout value. - * <p> - * @param timeout The timeout in milliseconds to use for the socket - * connection. + * Subclasses can override this if they need to provide their own instance field for backwards compatibilty. + * + * @return the CommandSupport instance, may be {@code null} + * @since 3.0 */ - public void setDefaultTimeout(int timeout) - { - _timeout_ = timeout; + protected ProtocolCommandSupport getCommandSupport() { + return commandSupport; } - /** - * Returns the default timeout in milliseconds that is used when - * opening a socket. - * <p> - * @return The default timeout in milliseconds that is used when - * opening a socket. + * Get the underlying socket connection timeout. + * + * @return timeout (in ms) + * @since 2.0 */ - public int getDefaultTimeout() - { - return _timeout_; + public int getConnectTimeout() { + return connectTimeout; } - /** - * Set the timeout in milliseconds of a currently open connection. - * Only call this method after a connection has been opened - * by {@link #connect connect()}. - * <p> - * To set the initial timeout, use {@link #setDefaultTimeout(int)} instead. + * Returns the current value of the default port (stored in {@link #_defaultPort_ _defaultPort_ }). * - * @param timeout The timeout in milliseconds to use for the currently - * open socket connection. - * @throws SocketException If the operation fails. - * @throws NullPointerException if the socket is not currently open + * @return The current value of the default port. */ - public void setSoTimeout(int timeout) throws SocketException - { - _socket_.setSoTimeout(timeout); + public int getDefaultPort() { + return _defaultPort_; } - /** - * Set the underlying socket send buffer size. - * <p> - * @param size The size of the buffer in bytes. - * @throws SocketException never thrown, but subclasses might want to do so - * @since 2.0 + * Returns the default timeout in milliseconds that is used when opening a socket. + * + * @return The default timeout in milliseconds that is used when opening a socket. */ - public void setSendBufferSize(int size) throws SocketException { - sendBufferSize = size; + public int getDefaultTimeout() { + return _timeout_; } /** - * Get the current sendBuffer size - * @return the size, or -1 if not initialised - * @since 3.0 + * Returns the current value of the SO_KEEPALIVE flag on the currently opened socket. Delegates to {@link Socket#getKeepAlive()} + * + * @return True if SO_KEEPALIVE is enabled. + * @throws SocketException if there is a problem with the socket + * @throws NullPointerException if the socket is not currently open + * @since 2.2 */ - protected int getSendBufferSize(){ - return sendBufferSize; + public boolean getKeepAlive() throws SocketException { + return _socket_.getKeepAlive(); } /** - * Sets the underlying socket receive buffer size. - * <p> - * @param size The size of the buffer in bytes. - * @throws SocketException never (but subclasses may wish to do so) - * @since 2.0 + * Returns the local address to which the client's socket is bound. Delegates to {@link Socket#getLocalAddress()} + * + * @return The local address to which the client's socket is bound. + * @throws NullPointerException if the socket is not currently open */ - public void setReceiveBufferSize(int size) throws SocketException { - receiveBufferSize = size; + public InetAddress getLocalAddress() { + return _socket_.getLocalAddress(); } /** - * Get the current receivedBuffer size - * @return the size, or -1 if not initialised - * @since 3.0 + * Returns the port number of the open socket on the local host used for the connection. Delegates to {@link Socket#getLocalPort()} + * + * @return The port number of the open socket on the local host used for the connection. + * @throws NullPointerException if the socket is not currently open */ - protected int getReceiveBufferSize(){ - return receiveBufferSize; + public int getLocalPort() { + return _socket_.getLocalPort(); } /** - * Returns the timeout in milliseconds of the currently opened socket. - * <p> - * @return The timeout in milliseconds of the currently opened socket. - * @throws SocketException If the operation fails. - * @throws NullPointerException if the socket is not currently open + * Gets the proxy for use with all the connections. + * + * @return the current proxy for connections. */ - public int getSoTimeout() throws SocketException - { - return _socket_.getSoTimeout(); + public Proxy getProxy() { + return connProxy; } /** - * Enables or disables the Nagle's algorithm (TCP_NODELAY) on the - * currently opened socket. - * <p> - * @param on True if Nagle's algorithm is to be enabled, false if not. - * @throws SocketException If the operation fails. - * @throws NullPointerException if the socket is not currently open + * Get the current receivedBuffer size + * + * @return the size, or -1 if not initialized + * @since 3.0 */ - public void setTcpNoDelay(boolean on) throws SocketException - { - _socket_.setTcpNoDelay(on); + protected int getReceiveBufferSize() { + return receiveBufferSize; } - /** - * Returns true if Nagle's algorithm is enabled on the currently opened - * socket. - * <p> - * @return True if Nagle's algorithm is enabled on the currently opened - * socket, false otherwise. - * @throws SocketException If the operation fails. + * @return The remote address to which the client is connected. Delegates to {@link Socket#getInetAddress()} * @throws NullPointerException if the socket is not currently open */ - public boolean getTcpNoDelay() throws SocketException - { - return _socket_.getTcpNoDelay(); + public InetAddress getRemoteAddress() { + return _socket_.getInetAddress(); } /** - * Sets the SO_KEEPALIVE flag on the currently opened socket. + * Returns the port number of the remote host to which the client is connected. Delegates to {@link Socket#getPort()} * - * From the Javadocs, the default keepalive time is 2 hours (although this is - * implementation dependent). It looks as though the Windows WSA sockets implementation - * allows a specific keepalive value to be set, although this seems not to be the case on - * other systems. - * @param keepAlive If true, keepAlive is turned on - * @throws SocketException if there is a problem with the socket + * @return The port number of the remote host to which the client is connected. * @throws NullPointerException if the socket is not currently open - * @since 2.2 */ - public void setKeepAlive(boolean keepAlive) throws SocketException { - _socket_.setKeepAlive(keepAlive); + public int getRemotePort() { + return _socket_.getPort(); } /** - * Returns the current value of the SO_KEEPALIVE flag on the currently opened socket. - * Delegates to {@link Socket#getKeepAlive()} - * @return True if SO_KEEPALIVE is enabled. - * @throws SocketException if there is a problem with the socket - * @throws NullPointerException if the socket is not currently open - * @since 2.2 + * Get the current sendBuffer size + * + * @return the size, or -1 if not initialized + * @since 3.0 */ - public boolean getKeepAlive() throws SocketException { - return _socket_.getKeepAlive(); + protected int getSendBufferSize() { + return sendBufferSize; } /** - * Sets the SO_LINGER timeout on the currently opened socket. - * <p> - * @param on True if linger is to be enabled, false if not. - * @param val The linger timeout (in hundredths of a second?) - * @throws SocketException If the operation fails. - * @throws NullPointerException if the socket is not currently open + * Get the underlying {@link ServerSocketFactory} + * + * @return The server socket factory + * @since 2.2 */ - public void setSoLinger(boolean on, int val) throws SocketException - { - _socket_.setSoLinger(on, val); + public ServerSocketFactory getServerSocketFactory() { + return _serverSocketFactory_; } - /** * Returns the current SO_LINGER timeout of the currently opened socket. - * <p> - * @return The current SO_LINGER timeout. If SO_LINGER is disabled returns - * -1. - * @throws SocketException If the operation fails. + * + * @return The current SO_LINGER timeout. If SO_LINGER is disabled returns -1. + * @throws SocketException If the operation fails. * @throws NullPointerException if the socket is not currently open */ - public int getSoLinger() throws SocketException - { + public int getSoLinger() throws SocketException { return _socket_.getSoLinger(); } - /** - * Returns the port number of the open socket on the local host used - * for the connection. - * Delegates to {@link Socket#getLocalPort()} - * <p> - * @return The port number of the open socket on the local host used - * for the connection. + * Returns the timeout in milliseconds of the currently opened socket. + * + * @return The timeout in milliseconds of the currently opened socket. + * @throws SocketException If the operation fails. * @throws NullPointerException if the socket is not currently open */ - public int getLocalPort() - { - return _socket_.getLocalPort(); + public int getSoTimeout() throws SocketException { + return _socket_.getSoTimeout(); } - /** - * Returns the local address to which the client's socket is bound. - * Delegates to {@link Socket#getLocalAddress()} - * <p> - * @return The local address to which the client's socket is bound. + * Returns true if Nagle's algorithm is enabled on the currently opened socket. + * + * @return True if Nagle's algorithm is enabled on the currently opened socket, false otherwise. + * @throws SocketException If the operation fails. * @throws NullPointerException if the socket is not currently open */ - public InetAddress getLocalAddress() - { - return _socket_.getLocalAddress(); + public boolean getTcpNoDelay() throws SocketException { + return _socket_.getTcpNoDelay(); } /** - * Returns the port number of the remote host to which the client is - * connected. - * Delegates to {@link Socket#getPort()} - * <p> - * @return The port number of the remote host to which the client is - * connected. - * @throws NullPointerException if the socket is not currently open - */ - public int getRemotePort() - { - return _socket_.getPort(); - } - - - /** - * @return The remote address to which the client is connected. - * Delegates to {@link Socket#getInetAddress()} - * @throws NullPointerException if the socket is not currently open + * Make various checks on the socket to test if it is available for use. Note that the only sure test is to use it, but these checks may help in some cases. + * + * @see <a href="https://issues.apache.org/jira/browse/NET-350">NET-350</a> + * @return {@code true} if the socket appears to be available for use + * @since 3.0 */ - public InetAddress getRemoteAddress() - { - return _socket_.getInetAddress(); + @SuppressWarnings("resource") + public boolean isAvailable() { + if (isConnected()) { + try { + if (_socket_.getInetAddress() == null) { + return false; + } + if (_socket_.getPort() == 0) { + return false; + } + if (_socket_.getRemoteSocketAddress() == null) { + return false; + } + if (_socket_.isClosed()) { + return false; + } + /* + * these aren't exact checks (a Socket can be half-open), but since we usually require two-way data transfer, we check these here too: + */ + if (_socket_.isInputShutdown()) { + return false; + } + if (_socket_.isOutputShutdown()) { + return false; + } + /* ignore the result, catch exceptions: */ + // No need to close + _socket_.getInputStream(); + // No need to close + _socket_.getOutputStream(); + } catch (final IOException ioex) { + return false; + } + return true; + } + return false; } - /** - * Verifies that the remote end of the given socket is connected to the - * the same host that the SocketClient is currently connected to. This - * is useful for doing a quick security check when a client needs to - * accept a connection from a server, such as an FTP data connection or - * a BSD R command standard error stream. - * <p> - * @param socket the item to check against - * @return True if the remote hosts are the same, false if not. + * Returns true if the client is currently connected to a server. + * + * Delegates to {@link Socket#isConnected()} + * + * @return True if the client is currently connected to a server, false otherwise. */ - public boolean verifyRemote(Socket socket) - { - InetAddress host1, host2; - - host1 = socket.getInetAddress(); - host2 = getRemoteAddress(); + public boolean isConnected() { + if (_socket_ == null) { + return false; + } - return host1.equals(host2); + return _socket_.isConnected(); } - /** - * Sets the SocketFactory used by the SocketClient to open socket - * connections. If the factory value is null, then a default - * factory is used (only do this to reset the factory after having - * previously altered it). - * Any proxy setting is discarded. - * <p> - * @param factory The new SocketFactory the SocketClient should use. + * Removes a ProtocolCommandListener. + * + * @param listener The ProtocolCommandListener to remove. + * @since 3.0 */ - public void setSocketFactory(SocketFactory factory) - { - if (factory == null) { - _socketFactory_ = __DEFAULT_SOCKET_FACTORY; - } else { - _socketFactory_ = factory; - } - // re-setting the socket factory makes the proxy setting useless, - // so set the field to null so that getProxy() doesn't return a - // Proxy that we're actually not using. - connProxy = null; + public void removeProtocolCommandListener(final ProtocolCommandListener listener) { + getCommandSupport().removeProtocolCommandListener(listener); } /** - * Sets the ServerSocketFactory used by the SocketClient to open ServerSocket - * connections. If the factory value is null, then a default - * factory is used (only do this to reset the factory after having - * previously altered it). - * <p> - * @param factory The new ServerSocketFactory the SocketClient should use. - * @since 2.0 + * Sets the charset. + * + * @param charset the charset. + * @since 3.3 */ - public void setServerSocketFactory(ServerSocketFactory factory) { - if (factory == null) { - _serverSocketFactory_ = __DEFAULT_SERVER_SOCKET_FACTORY; - } else { - _serverSocketFactory_ = factory; - } + public void setCharset(final Charset charset) { + this.charset = charset; } /** - * Sets the connection timeout in milliseconds, which will be passed to the {@link Socket} object's - * connect() method. + * Sets the connection timeout in milliseconds, which will be passed to the {@link Socket} object's connect() method. + * * @param connectTimeout The connection timeout to use (in ms) * @since 2.0 */ - public void setConnectTimeout(int connectTimeout) { + public void setConnectTimeout(final int connectTimeout) { this.connectTimeout = connectTimeout; } /** - * Get the underlying socket connection timeout. - * @return timeout (in ms) - * @since 2.0 - */ - public int getConnectTimeout() { - return connectTimeout; - } - - /** - * Get the underlying {@link ServerSocketFactory} - * @return The server socket factory - * @since 2.2 + * Sets the default port the SocketClient should connect to when a port is not specified. The {@link #_defaultPort_ _defaultPort_ } variable stores this + * value. If never set, the default port is equal to zero. + * + * @param port The default port to set. */ - public ServerSocketFactory getServerSocketFactory() { - return _serverSocketFactory_; + public void setDefaultPort(final int port) { + _defaultPort_ = port; } - /** - * Adds a ProtocolCommandListener. + * Set the default timeout in milliseconds to use when opening a socket. This value is only used previous to a call to {@link #connect connect()} and should + * not be confused with {@link #setSoTimeout setSoTimeout()} which operates on an the currently opened socket. _timeout_ contains the new timeout value. * - * @param listener The ProtocolCommandListener to add. - * @since 3.0 + * @param timeout The timeout in milliseconds to use for the socket connection. */ - public void addProtocolCommandListener(ProtocolCommandListener listener) { - getCommandSupport().addProtocolCommandListener(listener); + public void setDefaultTimeout(final int timeout) { + _timeout_ = timeout; } /** - * Removes a ProtocolCommandListener. + * Sets the SO_KEEPALIVE flag on the currently opened socket. * - * @param listener The ProtocolCommandListener to remove. - * @since 3.0 + * From the Javadocs, the default keepalive time is 2 hours (although this is implementation dependent). It looks as though the Windows WSA sockets + * implementation allows a specific keepalive value to be set, although this seems not to be the case on other systems. + * + * @param keepAlive If true, keepAlive is turned on + * @throws SocketException if there is a problem with the socket + * @throws NullPointerException if the socket is not currently open + * @since 2.2 */ - public void removeProtocolCommandListener(ProtocolCommandListener listener) { - getCommandSupport().removeProtocolCommandListener(listener); + public void setKeepAlive(final boolean keepAlive) throws SocketException { + _socket_.setKeepAlive(keepAlive); } /** - * If there are any listeners, send them the reply details. + * Sets the proxy for use with all the connections. The proxy is used for connections established after the call to this method. * - * @param replyCode the code extracted from the reply - * @param reply the full reply text - * @since 3.0 + * @param proxy the new proxy for connections. + * @since 3.2 */ - protected void fireReplyReceived(int replyCode, String reply) { - if (getCommandSupport().getListenerCount() > 0) { - getCommandSupport().fireReplyReceived(replyCode, reply); - } + public void setProxy(final Proxy proxy) { + setSocketFactory(new DefaultSocketFactory(proxy)); + connProxy = proxy; } /** - * If there are any listeners, send them the command details. + * Sets the underlying socket receive buffer size. * - * @param command the command name - * @param message the complete message, including command name - * @since 3.0 + * @param size The size of the buffer in bytes. + * @throws SocketException never (but subclasses may wish to do so) + * @since 2.0 */ - protected void fireCommandSent(String command, String message) { - if (getCommandSupport().getListenerCount() > 0) { - getCommandSupport().fireCommandSent(command, message); - } + public void setReceiveBufferSize(final int size) throws SocketException { + receiveBufferSize = size; } /** - * Create the CommandSupport instance if required + * Set the underlying socket send buffer size. + * + * @param size The size of the buffer in bytes. + * @throws SocketException never thrown, but subclasses might want to do so + * @since 2.0 */ - protected void createCommandSupport(){ - __commandSupport = new ProtocolCommandSupport(this); + public void setSendBufferSize(final int size) throws SocketException { + sendBufferSize = size; } /** - * Subclasses can override this if they need to provide their own - * instance field for backwards compatibilty. + * Sets the ServerSocketFactory used by the SocketClient to open ServerSocket connections. If the factory value is null, then a default factory is used + * (only do this to reset the factory after having previously altered it). * - * @return the CommandSupport instance, may be {@code null} - * @since 3.0 + * @param factory The new ServerSocketFactory the SocketClient should use. + * @since 2.0 */ - protected ProtocolCommandSupport getCommandSupport() { - return __commandSupport; + public void setServerSocketFactory(final ServerSocketFactory factory) { + if (factory == null) { + _serverSocketFactory_ = DEFAULT_SERVER_SOCKET_FACTORY; + } else { + _serverSocketFactory_ = factory; + } } /** - * Sets the proxy for use with all the connections. - * The proxy is used for connections established after the - * call to this method. + * Sets the SocketFactory used by the SocketClient to open socket connections. If the factory value is null, then a default factory is used (only do this to + * reset the factory after having previously altered it). Any proxy setting is discarded. * - * @param proxy the new proxy for connections. - * @since 3.2 + * @param factory The new SocketFactory the SocketClient should use. */ - public void setProxy(Proxy proxy) { - setSocketFactory(new DefaultSocketFactory(proxy)); - connProxy = proxy; + public void setSocketFactory(final SocketFactory factory) { + if (factory == null) { + _socketFactory_ = DEFAULT_SOCKET_FACTORY; + } else { + _socketFactory_ = factory; + } } /** - * Gets the proxy for use with all the connections. - * @return the current proxy for connections. + * Sets the SO_LINGER timeout on the currently opened socket. + * + * @param on True if linger is to be enabled, false if not. + * @param val The linger timeout (in hundredths of a second?) + * @throws SocketException If the operation fails. + * @throws NullPointerException if the socket is not currently open */ - public Proxy getProxy() { - return connProxy; + public void setSoLinger(final boolean on, final int val) throws SocketException { + _socket_.setSoLinger(on, val); } /** - * Gets the charset name. + * Set the timeout in milliseconds of a currently open connection. Only call this method after a connection has been opened by {@link #connect connect()}. + * <p> + * To set the initial timeout, use {@link #setDefaultTimeout(int)} instead. * - * @return the charset. - * @since 3.3 - * @deprecated Since the code now requires Java 1.6 as a mininmum + * @param timeout The timeout in milliseconds to use for the currently open socket connection. + * @throws SocketException If the operation fails. + * @throws NullPointerException if the socket is not currently open */ - @Deprecated - public String getCharsetName() { - return charset.name(); + public void setSoTimeout(final int timeout) throws SocketException { + _socket_.setSoTimeout(timeout); } /** - * Gets the charset. + * Enables or disables the Nagle's algorithm (TCP_NODELAY) on the currently opened socket. * - * @return the charset. - * @since 3.3 + * @param on True if Nagle's algorithm is to be enabled, false if not. + * @throws SocketException If the operation fails. + * @throws NullPointerException if the socket is not currently open */ - public Charset getCharset() { - return charset; + public void setTcpNoDelay(final boolean on) throws SocketException { + _socket_.setTcpNoDelay(on); } /** - * Sets the charset. + * Verifies that the remote end of the given socket is connected to the the same host that the SocketClient is currently connected to. This is useful for + * doing a quick security check when a client needs to accept a connection from a server, such as an FTP data connection or a BSD R command standard error + * stream. * - * @param charset the charset. - * @since 3.3 + * @param socket the item to check against + * @return True if the remote hosts are the same, false if not. */ - public void setCharset(Charset charset) { - this.charset = charset; + public boolean verifyRemote(final Socket socket) { + final InetAddress host1; + final InetAddress host2; + + host1 = socket.getInetAddress(); + host2 = getRemoteAddress(); + + return host1.equals(host2); } /* - * N.B. Fields cannot be pulled up into a super-class without breaking binary compatibility, - * so the abstract method is needed to pass the instance to the methods which were moved here. + * N.B. Fields cannot be pulled up into a super-class without breaking binary compatibility, so the abstract method is needed to pass the instance to the + * methods which were moved here. */ } - - diff --git a/src/main/java/org/apache/commons/net/bsd/RCommandClient.java b/src/main/java/org/apache/commons/net/bsd/RCommandClient.java index 6819cae..dcb5a37 100644 --- a/src/main/java/org/apache/commons/net/bsd/RCommandClient.java +++ b/src/main/java/org/apache/commons/net/bsd/RCommandClient.java @@ -25,177 +25,101 @@ import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; import org.apache.commons.net.io.SocketInputStream; -/*** - * RCommandClient is very similar to - * {@link org.apache.commons.net.bsd.RExecClient}, - * from which it is derived, and implements the rcmd() facility that - * first appeared in 4.2BSD Unix. rcmd() is the facility used by the rsh - * (rshell) and other commands to execute a command on another machine - * from a trusted host without issuing a password. The trust relationship - * between two machines is established by the contents of a machine's - * /etc/hosts.equiv file and a user's .rhosts file. These files specify - * from which hosts and accounts on those hosts rcmd() requests will be - * accepted. The only additional measure for establishing trust is that - * all client connections must originate from a port between 512 and 1023. - * Consequently, there is an upper limit to the number of rcmd connections - * that can be running simultaneously. The required ports are reserved - * ports on Unix systems, and can only be bound by a - * process running with root permissions (to accomplish this rsh, rlogin, - * and related commands usualy have the suid bit set). Therefore, on a - * Unix system, you will only be able to successfully use the RCommandClient - * class if the process runs as root. However, there is no such restriction - * on Windows95 and some other systems. The security risks are obvious. - * However, when carefully used, rcmd() can be very useful when used behind - * a firewall. +/** + * RCommandClient is very similar to {@link org.apache.commons.net.bsd.RExecClient}, from which it is derived, and implements the rcmd() facility that first + * appeared in 4.2BSD Unix. rcmd() is the facility used by the rsh (rshell) and other commands to execute a command on another machine from a trusted host + * without issuing a password. The trust relationship between two machines is established by the contents of a machine's /etc/hosts.equiv file and a user's + * .rhosts file. These files specify from which hosts and accounts on those hosts rcmd() requests will be accepted. The only additional measure for establishing + * trust is that all client connections must originate from a port between 512 and 1023. Consequently, there is an upper limit to the number of rcmd connections + * that can be running simultaneously. The required ports are reserved ports on Unix systems, and can only be bound by a process running with root permissions + * (to accomplish this rsh, rlogin, and related commands usualy have the suid bit set). Therefore, on a Unix system, you will only be able to successfully use + * the RCommandClient class if the process runs as root. However, there is no such restriction on Windows95 and some other systems. The security risks are + * obvious. However, when carefully used, rcmd() can be very useful when used behind a firewall. * <p> - * As with virtually all of the client classes in org.apache.commons.net, this - * class derives from SocketClient. But it overrides most of its connection - * methods so that the local Socket will originate from an acceptable - * rshell port. The way to use RCommandClient is to first connect - * to the server, call the {@link #rcommand rcommand() } method, - * and then - * fetch the connection's input, output, and optionally error streams. - * Interaction with the remote command is controlled entirely through the - * I/O streams. Once you have finished processing the streams, you should - * invoke {@link org.apache.commons.net.bsd.RExecClient#disconnect disconnect() } - * to clean up properly. + * As with virtually all of the client classes in org.apache.commons.net, this class derives from SocketClient. But it overrides most of its connection methods + * so that the local Socket will originate from an acceptable rshell port. The way to use RCommandClient is to first connect to the server, call the + * {@link #rcommand rcommand() } method, and then fetch the connection's input, output, and optionally error streams. Interaction with the remote command is + * controlled entirely through the I/O streams. Once you have finished processing the streams, you should invoke + * {@link org.apache.commons.net.bsd.RExecClient#disconnect disconnect() } to clean up properly. * <p> - * By default the standard output and standard error streams of the - * remote process are transmitted over the same connection, readable - * from the input stream returned by - * {@link org.apache.commons.net.bsd.RExecClient#getInputStream getInputStream() } - * . However, it is - * possible to tell the rshd daemon to return the standard error - * stream over a separate connection, readable from the input stream - * returned by {@link org.apache.commons.net.bsd.RExecClient#getErrorStream getErrorStream() } - * . You - * can specify that a separate connection should be created for standard - * error by setting the boolean <code> separateErrorStream </code> - * parameter of {@link #rcommand rcommand() } to <code> true </code>. - * The standard input of the remote process can be written to through - * the output stream returned by - * {@link org.apache.commons.net.bsd.RExecClient#getOutputStream getOutputStream() } - * . + * By default the standard output and standard error streams of the remote process are transmitted over the same connection, readable from the input stream + * returned by {@link org.apache.commons.net.bsd.RExecClient#getInputStream getInputStream() } . However, it is possible to tell the rshd daemon to return the + * standard error stream over a separate connection, readable from the input stream returned by {@link org.apache.commons.net.bsd.RExecClient#getErrorStream + * getErrorStream() } . You can specify that a separate connection should be created for standard error by setting the boolean + * <code> separateErrorStream </code> parameter of {@link #rcommand rcommand() } to <code> true </code>. The standard input of the remote process can be written + * to through the output stream returned by {@link org.apache.commons.net.bsd.RExecClient#getOutputStream getOutputStream() } . + * * @see org.apache.commons.net.SocketClient * @see RExecClient * @see RLoginClient - ***/ + */ -public class RCommandClient extends RExecClient -{ - /*** - * The default rshell port. Set to 514 in BSD Unix. - ***/ +public class RCommandClient extends RExecClient { + /** + * The default rshell port. Set to 514 in BSD Unix. + */ public static final int DEFAULT_PORT = 514; - /*** - * The smallest port number an rcmd client may use. By BSD convention - * this number is 512. - ***/ + /** + * The smallest port number an rcmd client may use. By BSD convention this number is 512. + */ public static final int MIN_CLIENT_PORT = 512; - /*** - * The largest port number an rcmd client may use. By BSD convention - * this number is 1023. - ***/ + /** + * The largest port number an rcmd client may use. By BSD convention this number is 1023. + */ public static final int MAX_CLIENT_PORT = 1023; - // Overrides method in RExecClient in order to implement proper - // port number limitations. - @Override - InputStream _createErrorStream() throws IOException - { - int localPort; - ServerSocket server; - Socket socket; - - localPort = MAX_CLIENT_PORT; - server = null; // Keep compiler from barfing - - for (localPort = MAX_CLIENT_PORT; localPort >= MIN_CLIENT_PORT; --localPort) - { - try - { - server = _serverSocketFactory_.createServerSocket(localPort, 1, - getLocalAddress()); - break; // got a socket - } - catch (SocketException e) - { - continue; - } - } - - if (server == null) { - throw new BindException("All ports in use."); - } - - _output_.write(Integer.toString(server.getLocalPort()).getBytes("UTF-8")); // $NON-NLS - _output_.write(NULL_CHAR); - _output_.flush(); - - socket = server.accept(); - server.close(); - - if (isRemoteVerificationEnabled() && !verifyRemote(socket)) - { - socket.close(); - throw new IOException( - "Security violation: unexpected connection attempt by " + - socket.getInetAddress().getHostAddress()); - } - - return (new SocketInputStream(socket, socket.getInputStream())); - } - - /*** - * The default RCommandClient constructor. Initializes the - * default port to <code> DEFAULT_PORT </code>. - ***/ - public RCommandClient() - { + /** + * The default RCommandClient constructor. Initializes the default port to <code> DEFAULT_PORT </code>. + */ + public RCommandClient() { setDefaultPort(DEFAULT_PORT); } + /** + * Opens a Socket connected to a remote host at the specified port and originating from the current host at a port in a range acceptable to the BSD rshell + * daemon. Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } is called to perform connection initialization + * actions. + * + * @param host The remote host. + * @param port The port to connect to on the remote host. + * @throws SocketException If the socket timeout could not be set. + * @throws BindException If all acceptable rshell ports are in use. + * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is derived from + * it. + */ + @Override + public void connect(final InetAddress host, final int port) throws SocketException, IOException { + connect(host, port, InetAddress.getLocalHost()); + } - /*** - * Opens a Socket connected to a remote host at the specified port and - * originating from the specified local address using a port in a range - * acceptable to the BSD rshell daemon. - * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } - * is called to perform connection initialization actions. + /** + * Opens a Socket connected to a remote host at the specified port and originating from the specified local address using a port in a range acceptable to + * the BSD rshell daemon. Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } is called to perform connection + * initialization actions. * - * @param host The remote host. - * @param port The port to connect to on the remote host. - * @param localAddr The local address to use. + * @param host The remote host. + * @param port The port to connect to on the remote host. + * @param localAddr The local address to use. * @throws SocketException If the socket timeout could not be set. - * @throws BindException If all acceptable rshell ports are in use. - * @throws IOException If the socket could not be opened. In most - * cases you will only want to catch IOException since SocketException is - * derived from it. - ***/ - public void connect(InetAddress host, int port, InetAddress localAddr) - throws SocketException, BindException, IOException - { + * @throws BindException If all acceptable rshell ports are in use. + * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is derived from + * it. + */ + public void connect(final InetAddress host, final int port, final InetAddress localAddr) throws SocketException, BindException, IOException { int localPort; localPort = MAX_CLIENT_PORT; - for (localPort = MAX_CLIENT_PORT; localPort >= MIN_CLIENT_PORT; --localPort) - { - try - { - _socket_ = - _socketFactory_.createSocket(host, port, localAddr, localPort); - } - catch (BindException be) { - continue; - } - catch (SocketException e) - { + for (localPort = MAX_CLIENT_PORT; localPort >= MIN_CLIENT_PORT; --localPort) { + try { + _socket_ = _socketFactory_.createSocket(host, port, localAddr, localPort); + } catch (final SocketException e) { continue; } break; @@ -208,201 +132,160 @@ public class RCommandClient extends RExecClient _connectAction_(); } - - - /*** - * Opens a Socket connected to a remote host at the specified port and - * originating from the current host at a port in a range acceptable - * to the BSD rshell daemon. - * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } - * is called to perform connection initialization actions. + /** + * Opens a Socket connected to a remote host at the specified port and originating from the specified local address and port. The local port must lie + * between <code> MIN_CLIENT_PORT </code> and <code> MAX_CLIENT_PORT </code> or an IllegalArgumentException will be thrown. Before returning, + * {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } is called to perform connection initialization actions. * - * @param host The remote host. - * @param port The port to connect to on the remote host. - * @throws SocketException If the socket timeout could not be set. - * @throws BindException If all acceptable rshell ports are in use. - * @throws IOException If the socket could not be opened. In most - * cases you will only want to catch IOException since SocketException is - * derived from it. - ***/ + * @param host The remote host. + * @param port The port to connect to on the remote host. + * @param localAddr The local address to use. + * @param localPort The local port to use. + * @throws SocketException If the socket timeout could not be set. + * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is + * derived from it. + * @throws IllegalArgumentException If an invalid local port number is specified. + */ @Override - public void connect(InetAddress host, int port) - throws SocketException, IOException - { - connect(host, port, InetAddress.getLocalHost()); + public void connect(final InetAddress host, final int port, final InetAddress localAddr, final int localPort) + throws SocketException, IOException, IllegalArgumentException { + if (localPort < MIN_CLIENT_PORT || localPort > MAX_CLIENT_PORT) { + throw new IllegalArgumentException("Invalid port number " + localPort); + } + super.connect(host, port, localAddr, localPort); } - - /*** - * Opens a Socket connected to a remote host at the specified port and - * originating from the current host at a port in a range acceptable - * to the BSD rshell daemon. - * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } - * is called to perform connection initialization actions. + /** + * Opens a Socket connected to a remote host at the specified port and originating from the current host at a port in a range acceptable to the BSD rshell + * daemon. Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } is called to perform connection initialization + * actions. * - * @param hostname The name of the remote host. - * @param port The port to connect to on the remote host. - * @throws SocketException If the socket timeout could not be set. - * @throws BindException If all acceptable rshell ports are in use. - * @throws IOException If the socket could not be opened. In most - * cases you will only want to catch IOException since SocketException is - * derived from it. + * @param hostname The name of the remote host. + * @param port The port to connect to on the remote host. + * @throws SocketException If the socket timeout could not be set. + * @throws BindException If all acceptable rshell ports are in use. + * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is derived + * from it. * @throws UnknownHostException If the hostname cannot be resolved. - ***/ + */ @Override - public void connect(String hostname, int port) - throws SocketException, IOException, UnknownHostException - { + public void connect(final String hostname, final int port) throws SocketException, IOException, UnknownHostException { connect(InetAddress.getByName(hostname), port, InetAddress.getLocalHost()); } - - /*** - * Opens a Socket connected to a remote host at the specified port and - * originating from the specified local address using a port in a range - * acceptable to the BSD rshell daemon. - * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } - * is called to perform connection initialization actions. + /** + * Opens a Socket connected to a remote host at the specified port and originating from the specified local address using a port in a range acceptable to + * the BSD rshell daemon. Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } is called to perform connection + * initialization actions. * * @param hostname The remote host. - * @param port The port to connect to on the remote host. - * @param localAddr The local address to use. + * @param port The port to connect to on the remote host. + * @param localAddr The local address to use. * @throws SocketException If the socket timeout could not be set. - * @throws BindException If all acceptable rshell ports are in use. - * @throws IOException If the socket could not be opened. In most - * cases you will only want to catch IOException since SocketException is - * derived from it. - ***/ - public void connect(String hostname, int port, InetAddress localAddr) - throws SocketException, IOException - { + * @throws BindException If all acceptable rshell ports are in use. + * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is derived from + * it. + */ + public void connect(final String hostname, final int port, final InetAddress localAddr) throws SocketException, IOException { connect(InetAddress.getByName(hostname), port, localAddr); } - - /*** - * Opens a Socket connected to a remote host at the specified port and - * originating from the specified local address and port. The - * local port must lie between <code> MIN_CLIENT_PORT </code> and - * <code> MAX_CLIENT_PORT </code> or an IllegalArgumentException will - * be thrown. - * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } - * is called to perform connection initialization actions. + /** + * Opens a Socket connected to a remote host at the specified port and originating from the specified local address and port. The local port must lie + * between <code> MIN_CLIENT_PORT </code> and <code> MAX_CLIENT_PORT </code> or an IllegalArgumentException will be thrown. Before returning, + * {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } is called to perform connection initialization actions. * - * @param host The remote host. - * @param port The port to connect to on the remote host. - * @param localAddr The local address to use. - * @param localPort The local port to use. - * @throws SocketException If the socket timeout could not be set. - * @throws IOException If the socket could not be opened. In most - * cases you will only want to catch IOException since SocketException is - * derived from it. - * @throws IllegalArgumentException If an invalid local port number - * is specified. - ***/ + * @param hostname The name of the remote host. + * @param port The port to connect to on the remote host. + * @param localAddr The local address to use. + * @param localPort The local port to use. + * @throws SocketException If the socket timeout could not be set. + * @throws IOException If the socket could not be opened. In most cases you will only want to catch IOException since SocketException is + * derived from it. + * @throws UnknownHostException If the hostname cannot be resolved. + * @throws IllegalArgumentException If an invalid local port number is specified. + */ @Override - public void connect(InetAddress host, int port, - InetAddress localAddr, int localPort) - throws SocketException, IOException, IllegalArgumentException - { + public void connect(final String hostname, final int port, final InetAddress localAddr, final int localPort) + throws SocketException, IOException, IllegalArgumentException, UnknownHostException { if (localPort < MIN_CLIENT_PORT || localPort > MAX_CLIENT_PORT) { throw new IllegalArgumentException("Invalid port number " + localPort); } - super.connect(host, port, localAddr, localPort); + super.connect(hostname, port, localAddr, localPort); } - - /*** - * Opens a Socket connected to a remote host at the specified port and - * originating from the specified local address and port. The - * local port must lie between <code> MIN_CLIENT_PORT </code> and - * <code> MAX_CLIENT_PORT </code> or an IllegalArgumentException will - * be thrown. - * Before returning, {@link org.apache.commons.net.SocketClient#_connectAction_ _connectAction_() } - * is called to perform connection initialization actions. - * - * @param hostname The name of the remote host. - * @param port The port to connect to on the remote host. - * @param localAddr The local address to use. - * @param localPort The local port to use. - * @throws SocketException If the socket timeout could not be set. - * @throws IOException If the socket could not be opened. In most - * cases you will only want to catch IOException since SocketException is - * derived from it. - * @throws UnknownHostException If the hostname cannot be resolved. - * @throws IllegalArgumentException If an invalid local port number - * is specified. - ***/ + // Overrides method in RExecClient in order to implement proper + // port number limitations. @Override - public void connect(String hostname, int port, - InetAddress localAddr, int localPort) - throws SocketException, IOException, IllegalArgumentException, UnknownHostException - { - if (localPort < MIN_CLIENT_PORT || localPort > MAX_CLIENT_PORT) { - throw new IllegalArgumentException("Invalid port number " + localPort); + InputStream createErrorStream() throws IOException { + int localPort; + final Socket socket; + + localPort = MAX_CLIENT_PORT; + ServerSocket server = null; + + for (localPort = MAX_CLIENT_PORT; localPort >= MIN_CLIENT_PORT; --localPort) { + try { + server = _serverSocketFactory_.createServerSocket(localPort, 1, getLocalAddress()); + break; // got a socket + } catch (final SocketException e) { + continue; + } } - super.connect(hostname, port, localAddr, localPort); - } + if (server == null) { + throw new BindException("All ports in use."); + } - /*** - * Remotely executes a command through the rshd daemon on the server - * to which the RCommandClient is connected. After calling this method, - * you may interact with the remote process through its standard input, - * output, and error streams. You will typically be able to detect - * the termination of the remote process after reaching end of file - * on its standard output (accessible through - * {@link #getInputStream getInputStream() }. Disconnecting - * from the server or closing the process streams before reaching - * end of file will not necessarily terminate the remote process. - * <p> - * If a separate error stream is requested, the remote server will - * connect to a local socket opened by RCommandClient, providing an - * independent stream through which standard error will be transmitted. - * The local socket must originate from a secure port (512 - 1023), - * and rcommand() ensures that this will be so. - * RCommandClient will also do a simple security check when it accepts a - * connection for this error stream. If the connection does not originate - * from the remote server, an IOException will be thrown. This serves as - * a simple protection against possible hijacking of the error stream by - * an attacker monitoring the rexec() negotiation. You may disable this - * behavior with - * {@link org.apache.commons.net.bsd.RExecClient#setRemoteVerificationEnabled setRemoteVerificationEnabled()} - * . - * <p> - * @param localUsername The user account on the local machine that is - * requesting the command execution. - * @param remoteUsername The account name on the server through which to - * execute the command. - * @param command The command, including any arguments, to execute. - * @param separateErrorStream True if you would like the standard error - * to be transmitted through a different stream than standard output. - * False if not. - * @throws IOException If the rcommand() attempt fails. The exception - * will contain a message indicating the nature of the failure. - ***/ - public void rcommand(String localUsername, String remoteUsername, - String command, boolean separateErrorStream) - throws IOException - { - rexec(localUsername, remoteUsername, command, separateErrorStream); - } + _output_.write(Integer.toString(server.getLocalPort()).getBytes(StandardCharsets.UTF_8)); // $NON-NLS + _output_.write(NULL_CHAR); + _output_.flush(); + + socket = server.accept(); + server.close(); + if (isRemoteVerificationEnabled() && !verifyRemote(socket)) { + socket.close(); + throw new IOException("Security violation: unexpected connection attempt by " + socket.getInetAddress().getHostAddress()); + } + + return new SocketInputStream(socket, socket.getInputStream()); + } - /*** - * Same as - * <code> rcommand(localUsername, remoteUsername, command, false); </code> - * @param localUsername the local user + /** + * Same as <code> rcommand(localUsername, remoteUsername, command, false); </code> + * + * @param localUsername the local user * @param remoteUsername the remote user - * @param command the command + * @param command the command * @throws IOException on error - ***/ - public void rcommand(String localUsername, String remoteUsername, - String command) - throws IOException - { + */ + public void rcommand(final String localUsername, final String remoteUsername, final String command) throws IOException { rcommand(localUsername, remoteUsername, command, false); } -} + /** + * Remotely executes a command through the rshd daemon on the server to which the RCommandClient is connected. After calling this method, you may interact + * with the remote process through its standard input, output, and error streams. You will typically be able to detect the termination of the remote process + * after reaching end of file on its standard output (accessible through {@link #getInputStream getInputStream() }. Disconnecting from the server or closing + * the process streams before reaching end of file will not necessarily terminate the remote process. + * <p> + * If a separate error stream is requested, the remote server will connect to a local socket opened by RCommandClient, providing an independent stream + * through which standard error will be transmitted. The local socket must originate from a secure port (512 - 1023), and rcommand() ensures that this will + * be so. RCommandClient will also do a simple security check when it accepts a connection for this error stream. If the connection does not originate from + * the remote server, an IOException will be thrown. This serves as a simple protection against possible hijacking of the error stream by an attacker + * monitoring the rexec() negotiation. You may disable this behavior with {@link org.apache.commons.net.bsd.RExecClient#setRemoteVerificationEnabled + * setRemoteVerificationEnabled()} . + * <p> + * + * @param localUsername The user account on the local machine that is requesting the command execution. + * @param remoteUsername The account name on the server through which to execute the command. + * @param command The command, including any arguments, to execute. + * @param separateErrorStream True if you would like the standard error to be transmitted through a different stream than standard output. False if not. + * @throws IOException If the rcommand() attempt fails. The exception will contain a message indicating the nature of the failure. + */ + public void rcommand(final String localUsername, final String remoteUsername, final String command, final boolean separateErrorStream) throws IOException { + rexec(localUsername, remoteUsername, command, separateErrorStream); + } +} diff --git a/src/main/java/org/apache/commons/net/bsd/RExecClient.java b/src/main/java/org/apache/commons/net/bsd/RExecClient.java index 37f9fbd..bce823c 100644 --- a/src/main/java/org/apache/commons/net/bsd/RExecClient.java +++ b/src/main/java/org/apache/commons/net/bsd/RExecClient.java @@ -22,195 +22,171 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; +import java.nio.charset.StandardCharsets; import org.apache.commons.net.SocketClient; import org.apache.commons.net.io.SocketInputStream; +import org.apache.commons.net.util.NetConstants; -/*** - * RExecClient implements the rexec() facility that first appeared in - * 4.2BSD Unix. This class will probably only be of use for connecting - * to Unix systems and only when the rexecd daemon is configured to run, - * which is a rarity these days because of the security risks involved. - * However, rexec() can be very useful for performing administrative tasks - * on a network behind a firewall. +/** + * RExecClient implements the rexec() facility that first appeared in 4.2BSD Unix. This class will probably only be of use for connecting to Unix systems and + * only when the rexecd daemon is configured to run, which is a rarity these days because of the security risks involved. However, rexec() can be very useful + * for performing administrative tasks on a network behind a firewall. * <p> - * As with virtually all of the client classes in org.apache.commons.net, this - * class derives from SocketClient, inheriting its connection methods. - * The way to use RExecClient is to first connect - * to the server, call the {@link #rexec rexec()} method, and then - * fetch the connection's input, output, and optionally error streams. - * Interaction with the remote command is controlled entirely through the - * I/O streams. Once you have finished processing the streams, you should - * invoke {@link #disconnect disconnect()} to clean up properly. + * As with virtually all of the client classes in org.apache.commons.net, this class derives from SocketClient, inheriting its connection methods. The way to + * use RExecClient is to first connect to the server, call the {@link #rexec rexec()} method, and then fetch the connection's input, output, and optionally + * error streams. Interaction with the remote command is controlled entirely through the I/O streams. Once you have finished processing the streams, you should + * invoke {@link #disconnect disconnect()} to clean up properly. * <p> - * By default the standard output and standard error streams of the - * remote process are transmitted over the same connection, readable - * from the input stream returned by - * {@link #getInputStream getInputStream()}. However, it is - * possible to tell the rexecd daemon to return the standard error - * stream over a separate connection, readable from the input stream - * returned by {@link #getErrorStream getErrorStream()}. You - * can specify that a separate connection should be created for standard - * error by setting the boolean <code> separateErrorStream </code> - * parameter of {@link #rexec rexec()} to <code> true </code>. - * The standard input of the remote process can be written to through - * the output stream returned by - * {@link #getOutputStream getOutputSream()}. + * By default the standard output and standard error streams of the remote process are transmitted over the same connection, readable from the input stream + * returned by {@link #getInputStream getInputStream()}. However, it is possible to tell the rexecd daemon to return the standard error stream over a separate + * connection, readable from the input stream returned by {@link #getErrorStream getErrorStream()}. You can specify that a separate connection should be created + * for standard error by setting the boolean <code> separateErrorStream </code> parameter of {@link #rexec rexec()} to <code> true </code>. The standard input + * of the remote process can be written to through the output stream returned by {@link #getOutputStream getOutputSream()}. * * @see SocketClient * @see RCommandClient * @see RLoginClient - ***/ + */ -public class RExecClient extends SocketClient -{ +public class RExecClient extends SocketClient { /** * @since 3.3 */ protected static final char NULL_CHAR = '\0'; - /*** - * The default rexec port. Set to 512 in BSD Unix. - ***/ + /** + * The default rexec port. Set to 512 in BSD Unix. + */ public static final int DEFAULT_PORT = 512; - private boolean __remoteVerificationEnabled; + private boolean remoteVerificationEnabled; - /*** - * If a separate error stream is requested, <code>_errorStream_</code> - * will point to an InputStream from which the standard error of the - * remote process can be read (after a call to rexec()). Otherwise, - * <code> _errorStream_ </code> will be null. - ***/ + /** + * If a separate error stream is requested, <code>_errorStream_</code> will point to an InputStream from which the standard error of the remote process can + * be read (after a call to rexec()). Otherwise, <code> _errorStream_ </code> will be null. + */ protected InputStream _errorStream_; + /** + * The default RExecClient constructor. Initializes the default port to <code> DEFAULT_PORT </code>. + */ + public RExecClient() { + _errorStream_ = null; + setDefaultPort(DEFAULT_PORT); + } + // This can be overridden in local package to implement port range // limitations of rcmd and rlogin - InputStream _createErrorStream() throws IOException - { - ServerSocket server; - Socket socket; + InputStream createErrorStream() throws IOException { + final Socket socket; - server = _serverSocketFactory_.createServerSocket(0, 1, getLocalAddress()); - - _output_.write(Integer.toString(server.getLocalPort()).getBytes("UTF-8")); // $NON-NLS-1$ - _output_.write(NULL_CHAR); - _output_.flush(); - - socket = server.accept(); - server.close(); + try (final ServerSocket server = _serverSocketFactory_.createServerSocket(0, 1, getLocalAddress())) { + _output_.write(Integer.toString(server.getLocalPort()).getBytes(StandardCharsets.UTF_8)); // $NON-NLS-1$ + _output_.write(NULL_CHAR); + _output_.flush(); + socket = server.accept(); + } - if (__remoteVerificationEnabled && !verifyRemote(socket)) - { + if (remoteVerificationEnabled && !verifyRemote(socket)) { socket.close(); - throw new IOException( - "Security violation: unexpected connection attempt by " + - socket.getInetAddress().getHostAddress()); + throw new IOException("Security violation: unexpected connection attempt by " + socket.getInetAddress().getHostAddress()); } - return (new SocketInputStream(socket, socket.getInputStream())); + return new SocketInputStream(socket, socket.getInputStream()); } - - /*** - * The default RExecClient constructor. Initializes the - * default port to <code> DEFAULT_PORT </code>. - ***/ - public RExecClient() - { + /** + * Disconnects from the server, closing all associated open sockets and streams. + * + * @throws IOException If there an error occurs while disconnecting. + */ + @Override + public void disconnect() throws IOException { + if (_errorStream_ != null) { + _errorStream_.close(); + } _errorStream_ = null; - setDefaultPort(DEFAULT_PORT); + super.disconnect(); } + /** + * Returns the InputStream from which the standard error of the remote process can be read if a separate error stream is requested from the server. + * Otherwise, null will be returned. The error stream will only be set after a successful rexec() invocation. + * + * @return The InputStream from which the standard error of the remote process can be read if a separate error stream is requested from the server. + * Otherwise, null will be returned. + */ + public InputStream getErrorStream() { + return _errorStream_; + } - /*** - * Returns the InputStream from which the standard output of the remote - * process can be read. The input stream will only be set after a - * successful rexec() invocation. + /** + * Returns the InputStream from which the standard output of the remote process can be read. The input stream will only be set after a successful rexec() + * invocation. * - * @return The InputStream from which the standard output of the remote - * process can be read. - ***/ - public InputStream getInputStream() - { + * @return The InputStream from which the standard output of the remote process can be read. + */ + public InputStream getInputStream() { return _input_; } - - /*** - * Returns the OutputStream through which the standard input of the remote - * process can be written. The output stream will only be set after a - * successful rexec() invocation. + /** + * Returns the OutputStream through which the standard input of the remote process can be written. The output stream will only be set after a successful + * rexec() invocation. * - * @return The OutputStream through which the standard input of the remote - * process can be written. - ***/ - public OutputStream getOutputStream() - { + * @return The OutputStream through which the standard input of the remote process can be written. + */ + public OutputStream getOutputStream() { return _output_; } - - /*** - * Returns the InputStream from which the standard error of the remote - * process can be read if a separate error stream is requested from - * the server. Otherwise, null will be returned. The error stream - * will only be set after a successful rexec() invocation. + /** + * Return whether or not verification of the remote host providing a separate error stream is enabled. The default behavior is for verification to be + * enabled. * - * @return The InputStream from which the standard error of the remote - * process can be read if a separate error stream is requested from - * the server. Otherwise, null will be returned. - ***/ - public InputStream getErrorStream() - { - return _errorStream_; + * @return True if verification is enabled, false if not. + */ + public final boolean isRemoteVerificationEnabled() { + return remoteVerificationEnabled; } + /** + * Same as <code> rexec(username, password, command, false); </code> + * + * @param username the user name + * @param password the password + * @param command the command to run + * @throws IOException if an error occurs + */ + public void rexec(final String username, final String password, final String command) throws IOException { + rexec(username, password, command, false); + } - /*** - * Remotely executes a command through the rexecd daemon on the server - * to which the RExecClient is connected. After calling this method, - * you may interact with the remote process through its standard input, - * output, and error streams. You will typically be able to detect - * the termination of the remote process after reaching end of file - * on its standard output (accessible through - * {@link #getInputStream getInputStream() }. Disconnecting - * from the server or closing the process streams before reaching - * end of file will not necessarily terminate the remote process. + /** + * Remotely executes a command through the rexecd daemon on the server to which the RExecClient is connected. After calling this method, you may interact + * with the remote process through its standard input, output, and error streams. You will typically be able to detect the termination of the remote process + * after reaching end of file on its standard output (accessible through {@link #getInputStream getInputStream() }. Disconnecting from the server or closing + * the process streams before reaching end of file will not necessarily terminate the remote process. * <p> - * If a separate error stream is requested, the remote server will - * connect to a local socket opened by RExecClient, providing an - * independent stream through which standard error will be transmitted. - * RExecClient will do a simple security check when it accepts a - * connection for this error stream. If the connection does not originate - * from the remote server, an IOException will be thrown. This serves as - * a simple protection against possible hijacking of the error stream by - * an attacker monitoring the rexec() negotiation. You may disable this - * behavior with {@link #setRemoteVerificationEnabled setRemoteVerificationEnabled()} - * . + * If a separate error stream is requested, the remote server will connect to a local socket opened by RExecClient, providing an independent stream through + * which standard error will be transmitted. RExecClient will do a simple security check when it accepts a connection for this error stream. If the + * connection does not originate from the remote server, an IOException will be thrown. This serves as a simple protection against possible hijacking of the + * error stream by an attacker monitoring the rexec() negotiation. You may disable this behavior with {@link #setRemoteVerificationEnabled + * setRemoteVerificationEnabled()} . * - * @param username The account name on the server through which to execute - * the command. - * @param password The plain text password of the user account. - * @param command The command, including any arguments, to execute. - * @param separateErrorStream True if you would like the standard error - * to be transmitted through a different stream than standard output. - * False if not. - * @throws IOException If the rexec() attempt fails. The exception - * will contain a message indicating the nature of the failure. - ***/ - public void rexec(String username, String password, - String command, boolean separateErrorStream) - throws IOException - { + * @param username The account name on the server through which to execute the command. + * @param password The plain text password of the user account. + * @param command The command, including any arguments, to execute. + * @param separateErrorStream True if you would like the standard error to be transmitted through a different stream than standard output. False if not. + * @throws IOException If the rexec() attempt fails. The exception will contain a message indicating the nature of the failure. + */ + public void rexec(final String username, final String password, final String command, final boolean separateErrorStream) throws IOException { int ch; - if (separateErrorStream) - { - _errorStream_ = _createErrorStream(); - } - else - { + if (separateErrorStream) { + _errorStream_ = createErrorStream(); + } else { _output_.write(NULL_CHAR); } @@ -224,75 +200,27 @@ public class RExecClient extends SocketClient ch = _input_.read(); if (ch > 0) { - StringBuilder buffer = new StringBuilder(); + final StringBuilder buffer = new StringBuilder(); - while ((ch = _input_.read()) != -1 && ch != '\n') { - buffer.append((char)ch); + while ((ch = _input_.read()) != NetConstants.EOS && ch != '\n') { + buffer.append((char) ch); } throw new IOException(buffer.toString()); - } else if (ch < 0) { - throw new IOException("Server closed connection."); } - } - - - /*** - * Same as <code> rexec(username, password, command, false); </code> - * @param username the user name - * @param password the password - * @param command the command to run - * @throws IOException if an error occurs - ***/ - public void rexec(String username, String password, - String command) - throws IOException - { - rexec(username, password, command, false); - } - - /*** - * Disconnects from the server, closing all associated open sockets and - * streams. - * - * @throws IOException If there an error occurs while disconnecting. - ***/ - @Override - public void disconnect() throws IOException - { - if (_errorStream_ != null) { - _errorStream_.close(); + if (ch < 0) { + throw new IOException("Server closed connection."); } - _errorStream_ = null; - super.disconnect(); } - - /*** - * Enable or disable verification that the remote host connecting to - * create a separate error stream is the same as the host to which - * the standard out stream is connected. The default is for verification - * to be enabled. You may set this value at any time, whether the - * client is currently connected or not. + /** + * Enable or disable verification that the remote host connecting to create a separate error stream is the same as the host to which the standard out stream + * is connected. The default is for verification to be enabled. You may set this value at any time, whether the client is currently connected or not. * * @param enable True to enable verification, false to disable verification. - ***/ - public final void setRemoteVerificationEnabled(boolean enable) - { - __remoteVerificationEnabled = enable; - } - - /*** - * Return whether or not verification of the remote host providing a - * separate error stream is enabled. The default behavior is for - * verification to be enabled. - * - * @return True if verification is enabled, false if not. - ***/ - public final boolean isRemoteVerificationEnabled() - { - return __remoteVerificationEnabled; + */ + public final void setRemoteVerificationEnabled(final boolean enable) { + remoteVerificationEnabled = enable; } } - diff --git a/src/main/java/org/apache/commons/net/bsd/RLoginClient.java b/src/main/java/org/apache/commons/net/bsd/RLoginClient.java index 9e60903..8bcb57c 100644 --- a/src/main/java/org/apache/commons/net/bsd/RLoginClient.java +++ b/src/main/java/org/apache/commons/net/bsd/RLoginClient.java @@ -19,115 +19,73 @@ package org.apache.commons.net.bsd; import java.io.IOException; -/*** - * RLoginClient is very similar to - * {@link org.apache.commons.net.bsd.RCommandClient}, - * from which it is derived, and uses the rcmd() facility implemented - * in RCommandClient to implement the functionality of the rlogin command that - * first appeared in 4.2BSD Unix. rlogin is a command used to login to - * a remote machine from a trusted host, sometimes without issuing a - * password. The trust relationship is the same as described in - * the documentation for +/** + * RLoginClient is very similar to {@link org.apache.commons.net.bsd.RCommandClient}, from which it is derived, and uses the rcmd() facility implemented in + * RCommandClient to implement the functionality of the rlogin command that first appeared in 4.2BSD Unix. rlogin is a command used to login to a remote machine + * from a trusted host, sometimes without issuing a password. The trust relationship is the same as described in the documentation for * {@link org.apache.commons.net.bsd.RCommandClient}. * <p> - * As with virtually all of the client classes in org.apache.commons.net, this - * class derives from SocketClient. But it relies on the connection - * methods defined in RcommandClient which ensure that the local Socket - * will originate from an acceptable rshell port. The way to use - * RLoginClient is to first connect - * to the server, call the {@link #rlogin rlogin() } method, - * and then - * fetch the connection's input and output streams. - * Interaction with the remote command is controlled entirely through the - * I/O streams. Once you have finished processing the streams, you should - * invoke {@link org.apache.commons.net.bsd.RExecClient#disconnect disconnect() } - * to clean up properly. + * As with virtually all of the client classes in org.apache.commons.net, this class derives from SocketClient. But it relies on the connection methods defined + * in RcommandClient which ensure that the local Socket will originate from an acceptable rshell port. The way to use RLoginClient is to first connect to the + * server, call the {@link #rlogin rlogin() } method, and then fetch the connection's input and output streams. Interaction with the remote command is + * controlled entirely through the I/O streams. Once you have finished processing the streams, you should invoke + * {@link org.apache.commons.net.bsd.RExecClient#disconnect disconnect() } to clean up properly. * <p> - * The standard output and standard error streams of the - * remote process are transmitted over the same connection, readable - * from the input stream returned by + * The standard output and standard error streams of the remote process are transmitted over the same connection, readable from the input stream returned by * {@link org.apache.commons.net.bsd.RExecClient#getInputStream getInputStream() } * <p> - * Unlike RExecClient and RCommandClient, it is - * not possible to tell the rlogind daemon to return the standard error - * stream over a separate connection. - * {@link org.apache.commons.net.bsd.RExecClient#getErrorStream getErrorStream() } - * will always return null. - * The standard input of the remote process can be written to through - * the output stream returned by - * {@link org.apache.commons.net.bsd.RExecClient#getOutputStream getOutputSream() } + * Unlike RExecClient and RCommandClient, it is not possible to tell the rlogind daemon to return the standard error stream over a separate connection. + * {@link org.apache.commons.net.bsd.RExecClient#getErrorStream getErrorStream() } will always return null. The standard input of the remote process can be + * written to through the output stream returned by {@link org.apache.commons.net.bsd.RExecClient#getOutputStream getOutputSream() } * * @see org.apache.commons.net.SocketClient * @see RExecClient * @see RCommandClient - ***/ + */ -public class RLoginClient extends RCommandClient -{ - /*** - * The default rlogin port. Set to 513 in BSD Unix and according - * to RFC 1282. - ***/ +public class RLoginClient extends RCommandClient { + /** + * The default rlogin port. Set to 513 in BSD Unix and according to RFC 1282. + */ public static final int DEFAULT_PORT = 513; - /*** - * The default RLoginClient constructor. Initializes the - * default port to <code> DEFAULT_PORT </code>. - ***/ - public RLoginClient() - { + /** + * The default RLoginClient constructor. Initializes the default port to <code> DEFAULT_PORT </code>. + */ + public RLoginClient() { setDefaultPort(DEFAULT_PORT); } - - /*** - * Logins into a remote machine through the rlogind daemon on the server - * to which the RLoginClient is connected. After calling this method, - * you may interact with the remote login shell through its standard input - * and output streams. Standard error is sent over the same stream as - * standard output. You will typically be able to detect - * the termination of the remote login shell after reaching end of file - * on its standard output (accessible through - * {@link #getInputStream getInputStream() }. Disconnecting - * from the server or closing the process streams before reaching - * end of file will terminate the remote login shell in most cases. - * <p> - * If user authentication fails, the rlogind daemon will request that - * a password be entered interactively. You will be able to read the - * prompt from the output stream of the RLoginClient and write the - * password to the input stream of the RLoginClient. - * - * @param localUsername The user account on the local machine that is - * trying to login to the remote host. - * @param remoteUsername The account name on the server that is - * being logged in to. - * @param terminalType The name of the user's terminal (e.g., "vt100", - * "network", etc.) - * @param terminalSpeed The speed of the user's terminal, expressed - * as a baud rate or bps (e.g., 9600 or 38400) - * @throws IOException If the rlogin() attempt fails. The exception - * will contain a message indicating the nature of the failure. - ***/ - public void rlogin(String localUsername, String remoteUsername, - String terminalType, int terminalSpeed) - throws IOException - { - rexec(localUsername, remoteUsername, terminalType + "/" + terminalSpeed, - false); - } - - /*** + /** * Same as the other rlogin method, but no terminal speed is defined. - * @param localUsername the local user + * + * @param localUsername the local user * @param remoteUsername the remote user - * @param terminalType the terminal type + * @param terminalType the terminal type * @throws IOException on error - ***/ - public void rlogin(String localUsername, String remoteUsername, - String terminalType) - throws IOException - { + */ + public void rlogin(final String localUsername, final String remoteUsername, final String terminalType) throws IOException { rexec(localUsername, remoteUsername, terminalType, false); } + /** + * Logins into a remote machine through the rlogind daemon on the server to which the RLoginClient is connected. After calling this method, you may interact + * with the remote login shell through its standard input and output streams. Standard error is sent over the same stream as standard output. You will + * typically be able to detect the termination of the remote login shell after reaching end of file on its standard output (accessible through + * {@link #getInputStream getInputStream() }. Disconnecting from the server or closing the process streams before reaching end of file will terminate the + * remote login shell in most cases. + * <p> + * If user authentication fails, the rlogind daemon will request that a password be entered interactively. You will be able to read the prompt from the + * output stream of the RLoginClient and write the password to the input stream of the RLoginClient. + * + * @param localUsername The user account on the local machine that is trying to login to the remote host. + * @param remoteUsername The account name on the server that is being logged in to. + * @param terminalType The name of the user's terminal (e.g., "vt100", "network", etc.) + * @param terminalSpeed The speed of the user's terminal, expressed as a baud rate or bps (e.g., 9600 or 38400) + * @throws IOException If the rlogin() attempt fails. The exception will contain a message indicating the nature of the failure. + */ + public void rlogin(final String localUsername, final String remoteUsername, final String terminalType, final int terminalSpeed) throws IOException { + rexec(localUsername, remoteUsername, terminalType + "/" + terminalSpeed, false); + } + } diff --git a/src/main/java/org/apache/commons/net/chargen/CharGenTCPClient.java b/src/main/java/org/apache/commons/net/chargen/CharGenTCPClient.java index 50e7174..3a710e5 100644 --- a/src/main/java/org/apache/commons/net/chargen/CharGenTCPClient.java +++ b/src/main/java/org/apache/commons/net/chargen/CharGenTCPClient.java @@ -21,64 +21,45 @@ import java.io.InputStream; import org.apache.commons.net.SocketClient; -/*** - * The CharGenTCPClient class is a TCP implementation of a client for the - * character generator protocol described in RFC 864. It can also be - * used for Systat (RFC 866), Quote of the Day (RFC 865), and netstat - * (port 15). All of these protocols involve connecting to the appropriate - * port, and reading data from an input stream. The chargen protocol - * actually sends data until the receiving end closes the connection. All - * of the others send only a fixed amount of data and then close the - * connection. +/** + * The CharGenTCPClient class is a TCP implementation of a client for the character generator protocol described in RFC 864. It can also be used for Systat (RFC + * 866), Quote of the Day (RFC 865), and netstat (port 15). All of these protocols involve connecting to the appropriate port, and reading data from an input + * stream. The chargen protocol actually sends data until the receiving end closes the connection. All of the others send only a fixed amount of data and then + * close the connection. * <p> - * To use the CharGenTCPClient class, just establish a - * connection with - * {@link org.apache.commons.net.SocketClient#connect connect } - * and call {@link #getInputStream getInputStream() } to access - * the data. Don't close the input stream when you're done with it. Rather, - * call {@link org.apache.commons.net.SocketClient#disconnect disconnect } - * to clean up properly. + * To use the CharGenTCPClient class, just establish a connection with {@link org.apache.commons.net.SocketClient#connect connect } and call + * {@link #getInputStream getInputStream() } to access the data. Don't close the input stream when you're done with it. Rather, call + * {@link org.apache.commons.net.SocketClient#disconnect disconnect } to clean up properly. * * @see CharGenUDPClient - ***/ + */ -public final class CharGenTCPClient extends SocketClient -{ - /*** The systat port value of 11 according to RFC 866. ***/ +public final class CharGenTCPClient extends SocketClient { + /** The systat port value of 11 according to RFC 866. */ public static final int SYSTAT_PORT = 11; - /*** The netstat port value of 19. ***/ + /** The netstat port value of 19. */ public static final int NETSTAT_PORT = 15; - /*** The quote of the day port value of 17 according to RFC 865. ***/ + /** The quote of the day port value of 17 according to RFC 865. */ public static final int QUOTE_OF_DAY_PORT = 17; - /*** The character generator port value of 19 according to RFC 864. ***/ + /** The character generator port value of 19 according to RFC 864. */ public static final int CHARGEN_PORT = 19; - /*** The default chargen port. It is set to 19 according to RFC 864. ***/ + /** The default chargen port. It is set to 19 according to RFC 864. */ public static final int DEFAULT_PORT = 19; - /*** - * The default constructor for CharGenTCPClient. It merely sets the - * default port to <code> DEFAULT_PORT </code>. - ***/ - public CharGenTCPClient () - { + /** + * The default constructor for CharGenTCPClient. It merely sets the default port to <code> DEFAULT_PORT </code>. + */ + public CharGenTCPClient() { setDefaultPort(DEFAULT_PORT); } - /*** - * Returns an InputStream from which the server generated data can be - * read. You should NOT close the InputStream when you're finished - * reading from it. Rather, you should call - * {@link org.apache.commons.net.SocketClient#disconnect disconnect } - * to clean up properly. + /** + * Returns an InputStream from which the server generated data can be read. You should NOT close the InputStream when you're finished reading from it. + * Rather, you should call {@link org.apache.commons.net.SocketClient#disconnect disconnect } to clean up properly. * * @return An InputStream from which the server generated data can be read. - ***/ - public InputStream getInputStream() - { + */ + public InputStream getInputStream() { return _input_; } } - - - - diff --git a/src/main/java/org/apache/commons/net/chargen/CharGenUDPClient.java b/src/main/java/org/apache/commons/net/chargen/CharGenUDPClient.java index 5b51965..834a836 100644 --- a/src/main/java/org/apache/commons/net/chargen/CharGenUDPClient.java +++ b/src/main/java/org/apache/commons/net/chargen/CharGenUDPClient.java @@ -22,109 +22,89 @@ import java.net.DatagramPacket; import java.net.InetAddress; import org.apache.commons.net.DatagramSocketClient; +import org.apache.commons.net.util.NetConstants; -/*** - * The CharGenUDPClient class is a UDP implementation of a client for the - * character generator protocol described in RFC 864. It can also be - * used for Systat (RFC 866), Quote of the Day (RFC 865), and netstat - * (port 15). All of these protocols involve sending a datagram to the - * appropriate port, and reading data contained in one or more reply - * datagrams. The chargen and quote of the day protocols only send - * one reply datagram containing 512 bytes or less of data. The other - * protocols may reply with more than one datagram, in which case you - * must wait for a timeout to determine that all reply datagrams have - * been sent. +/** + * The CharGenUDPClient class is a UDP implementation of a client for the character generator protocol described in RFC 864. It can also be used for Systat (RFC + * 866), Quote of the Day (RFC 865), and netstat (port 15). All of these protocols involve sending a datagram to the appropriate port, and reading data + * contained in one or more reply datagrams. The chargen and quote of the day protocols only send one reply datagram containing 512 bytes or less of data. The + * other protocols may reply with more than one datagram, in which case you must wait for a timeout to determine that all reply datagrams have been sent. * <p> - * To use the CharGenUDPClient class, just open a local UDP port - * with {@link org.apache.commons.net.DatagramSocketClient#open open } - * and call {@link #send send } to send the datagram that will - * initiate the data reply. For chargen or quote of the day, just - * call {@link #receive receive }, and you're done. For netstat and - * systat, call receive in a while loop, and catch a SocketException and - * InterruptedIOException to detect a timeout (don't forget to set the - * timeout duration beforehand). Don't forget to call - * {@link org.apache.commons.net.DatagramSocketClient#close close() } - * to clean up properly. + * To use the CharGenUDPClient class, just open a local UDP port with {@link org.apache.commons.net.DatagramSocketClient#open open } and call {@link #send send + * } to send the datagram that will initiate the data reply. For chargen or quote of the day, just call {@link #receive receive }, and you're done. For netstat + * and systat, call receive in a while loop, and catch a SocketException and InterruptedIOException to detect a timeout (don't forget to set the timeout + * duration beforehand). Don't forget to call {@link org.apache.commons.net.DatagramSocketClient#close close() } to clean up properly. * * @see CharGenTCPClient - ***/ + */ -public final class CharGenUDPClient extends DatagramSocketClient -{ - /*** The systat port value of 11 according to RFC 866. ***/ +public final class CharGenUDPClient extends DatagramSocketClient { + /** The systat port value of 11 according to RFC 866. */ public static final int SYSTAT_PORT = 11; - /*** The netstat port value of 19. ***/ + /** The netstat port value of 19. */ public static final int NETSTAT_PORT = 15; - /*** The quote of the day port value of 17 according to RFC 865. ***/ + /** The quote of the day port value of 17 according to RFC 865. */ public static final int QUOTE_OF_DAY_PORT = 17; - /*** The character generator port value of 19 according to RFC 864. ***/ + /** The character generator port value of 19 according to RFC 864. */ public static final int CHARGEN_PORT = 19; - /*** The default chargen port. It is set to 19 according to RFC 864. ***/ + /** The default chargen port. It is set to 19 according to RFC 864. */ public static final int DEFAULT_PORT = 19; - private final byte[] __receiveData; - private final DatagramPacket __receivePacket; - private final DatagramPacket __sendPacket; + private final byte[] receiveData; + private final DatagramPacket receivePacket; + private final DatagramPacket sendPacket; - /*** - * The default CharGenUDPClient constructor. It initializes some internal - * data structures for sending and receiving the necessary datagrams for - * the chargen and related protocols. - ***/ - public CharGenUDPClient() - { + /** + * The default CharGenUDPClient constructor. It initializes some internal data structures for sending and receiving the necessary datagrams for the chargen + * and related protocols. + */ + public CharGenUDPClient() { // CharGen return packets have a maximum length of 512 - __receiveData = new byte[512]; - __receivePacket = new DatagramPacket(__receiveData, __receiveData.length); - __sendPacket = new DatagramPacket(new byte[0], 0); + receiveData = new byte[512]; + receivePacket = new DatagramPacket(receiveData, receiveData.length); + sendPacket = new DatagramPacket(NetConstants.EMPTY_BTYE_ARRAY, 0); } - - /*** - * Sends the data initiation datagram. This data in the packet is ignored - * by the server, and merely serves to signal that the server should send - * its reply. + /** + * Receive the reply data from the server. This will always be 512 bytes or less. Chargen and quote of the day only return one packet. Netstat and systat + * require multiple calls to receive() with timeout detection. * - * @param host The address of the server. - * @param port The port of the service. - * @throws IOException If an error occurs while sending the datagram. - ***/ - public void send(InetAddress host, int port) throws IOException - { - __sendPacket.setAddress(host); - __sendPacket.setPort(port); - _socket_.send(__sendPacket); + * @return The reply data from the server. + * @throws IOException If an error occurs while receiving the datagram. + */ + public byte[] receive() throws IOException { + final int length; + final byte[] result; + + _socket_.receive(receivePacket); + + result = new byte[length = receivePacket.getLength()]; + System.arraycopy(receiveData, 0, result, 0, length); + + return result; } - /*** Same as <code>send(host, CharGenUDPClient.DEFAULT_PORT);</code> + /** + * Same as <code>send(host, CharGenUDPClient.DEFAULT_PORT);</code> + * * @param host the destination host * @throws IOException on error - ***/ - public void send(InetAddress host) throws IOException - { + */ + public void send(final InetAddress host) throws IOException { send(host, DEFAULT_PORT); } - /*** - * Receive the reply data from the server. This will always be 512 bytes - * or less. Chargen and quote of the day only return one packet. Netstat - * and systat require multiple calls to receive() with timeout detection. + /** + * Sends the data initiation datagram. This data in the packet is ignored by the server, and merely serves to signal that the server should send its reply. * - * @return The reply data from the server. - * @throws IOException If an error occurs while receiving the datagram. - ***/ - public byte[] receive() throws IOException - { - int length; - byte[] result; - - _socket_.receive(__receivePacket); - - result = new byte[length = __receivePacket.getLength()]; - System.arraycopy(__receiveData, 0, result, 0, length); - - return result; + * @param host The address of the server. + * @param port The port of the service. + * @throws IOException If an error occurs while sending the datagram. + */ + public void send(final InetAddress host, final int port) throws IOException { + sendPacket.setAddress(host); + sendPacket.setPort(port); + _socket_.send(sendPacket); } } - diff --git a/src/main/java/org/apache/commons/net/daytime/DaytimeTCPClient.java b/src/main/java/org/apache/commons/net/daytime/DaytimeTCPClient.java index 033a776..5c51b0d 100644 --- a/src/main/java/org/apache/commons/net/daytime/DaytimeTCPClient.java +++ b/src/main/java/org/apache/commons/net/daytime/DaytimeTCPClient.java @@ -24,66 +24,51 @@ import java.io.InputStreamReader; import org.apache.commons.net.SocketClient; /** - * The DaytimeTCPClient class is a TCP implementation of a client for the - * Daytime protocol described in RFC 867. To use the class, merely - * establish a connection with - * {@link org.apache.commons.net.SocketClient#connect connect } - * and call {@link #getTime getTime() } to retrieve the daytime - * string, then - * call {@link org.apache.commons.net.SocketClient#disconnect disconnect } - * to close the connection properly. + * The DaytimeTCPClient class is a TCP implementation of a client for the Daytime protocol described in RFC 867. To use the class, merely establish a connection + * with {@link org.apache.commons.net.SocketClient#connect connect } and call {@link #getTime getTime() } to retrieve the daytime string, then call + * {@link org.apache.commons.net.SocketClient#disconnect disconnect } to close the connection properly. + * * @see DaytimeUDPClient */ -public final class DaytimeTCPClient extends SocketClient -{ - /** The default daytime port. It is set to 13 according to RFC 867. */ +public final class DaytimeTCPClient extends SocketClient { + /** The default daytime port. It is set to 13 according to RFC 867. */ public static final int DEFAULT_PORT = 13; // Received dates will likely be less than 64 characters. // This is a temporary buffer used while receiving data. - private final char[] __buffer = new char[64]; + private final char[] buffer = new char[64]; /** - * The default DaytimeTCPClient constructor. It merely sets the default - * port to <code> DEFAULT_PORT </code>. + * The default DaytimeTCPClient constructor. It merely sets the default port to <code> DEFAULT_PORT </code>. */ - public DaytimeTCPClient () - { + public DaytimeTCPClient() { setDefaultPort(DEFAULT_PORT); } /** - * Retrieves the time string from the server and returns it. The - * server will have closed the connection at this point, so you should - * call - * {@link org.apache.commons.net.SocketClient#disconnect disconnect } - * after calling this method. To retrieve another time, you must - * initiate another connection with - * {@link org.apache.commons.net.SocketClient#connect connect } - * before calling <code> getTime() </code> again. + * Retrieves the time string from the server and returns it. The server will have closed the connection at this point, so you should call + * {@link org.apache.commons.net.SocketClient#disconnect disconnect } after calling this method. To retrieve another time, you must initiate another + * connection with {@link org.apache.commons.net.SocketClient#connect connect } before calling <code> getTime() </code> again. * * @return The time string retrieved from the server. - * @throws IOException If an error occurs while fetching the time string. + * @throws IOException If an error occurs while fetching the time string. */ - public String getTime() throws IOException - { + public String getTime() throws IOException { int read; - StringBuilder result = new StringBuilder(__buffer.length); - BufferedReader reader; + final StringBuilder result = new StringBuilder(buffer.length); + final BufferedReader reader; reader = new BufferedReader(new InputStreamReader(_input_, getCharset())); - while (true) - { - read = reader.read(__buffer, 0, __buffer.length); + while (true) { + read = reader.read(buffer, 0, buffer.length); if (read <= 0) { break; } - result.append(__buffer, 0, read); + result.append(buffer, 0, read); } return result.toString(); } } - diff --git a/src/main/java/org/apache/commons/net/daytime/DaytimeUDPClient.java b/src/main/java/org/apache/commons/net/daytime/DaytimeUDPClient.java index d221f79..deb439a 100644 --- a/src/main/java/org/apache/commons/net/daytime/DaytimeUDPClient.java +++ b/src/main/java/org/apache/commons/net/daytime/DaytimeUDPClient.java @@ -24,61 +24,52 @@ import java.net.InetAddress; import org.apache.commons.net.DatagramSocketClient; /** - * The DaytimeUDPClient class is a UDP implementation of a client for the - * Daytime protocol described in RFC 867. To use the class, merely - * open a local datagram socket with - * {@link org.apache.commons.net.DatagramSocketClient#open open } - * and call {@link #getTime getTime } to retrieve the daytime - * string, then - * call {@link org.apache.commons.net.DatagramSocketClient#close close } - * to close the connection properly. Unlike - * {@link org.apache.commons.net.daytime.DaytimeTCPClient}, - * successive calls to {@link #getTime getTime } are permitted - * without re-establishing a connection. That is because UDP is a - * connectionless protocol and the Daytime protocol is stateless. + * The DaytimeUDPClient class is a UDP implementation of a client for the Daytime protocol described in RFC 867. To use the class, merely open a local datagram + * socket with {@link org.apache.commons.net.DatagramSocketClient#open open } and call {@link #getTime getTime } to retrieve the daytime string, then call + * {@link org.apache.commons.net.DatagramSocketClient#close close } to close the connection properly. Unlike + * {@link org.apache.commons.net.daytime.DaytimeTCPClient}, successive calls to {@link #getTime getTime } are permitted without re-establishing a connection. + * That is because UDP is a connectionless protocol and the Daytime protocol is stateless. + * * @see DaytimeTCPClient */ -public final class DaytimeUDPClient extends DatagramSocketClient -{ - /** The default daytime port. It is set to 13 according to RFC 867. */ +public final class DaytimeUDPClient extends DatagramSocketClient { + /** The default daytime port. It is set to 13 according to RFC 867. */ public static final int DEFAULT_PORT = 13; - private final byte[] __dummyData = new byte[1]; + private final byte[] dummyData = new byte[1]; // Received dates should be less than 256 bytes - private final byte[] __timeData = new byte[256]; + private final byte[] timeData = new byte[256]; + + /** + * Same as <code>getTime(host, DaytimeUDPClient.DEFAULT_PORT);</code> + * + * @param host the host + * @return the time + * @throws IOException on error + */ + public String getTime(final InetAddress host) throws IOException { + return getTime(host, DEFAULT_PORT); + } /** - * Retrieves the time string from the specified server and port and - * returns it. + * Retrieves the time string from the specified server and port and returns it. * * @param host The address of the server. * @param port The port of the service. * @return The time string. * @throws IOException If an error occurs while retrieving the time. */ - public String getTime(InetAddress host, int port) throws IOException - { - DatagramPacket sendPacket, receivePacket; + public String getTime(final InetAddress host, final int port) throws IOException { + final DatagramPacket sendPacket; + final DatagramPacket receivePacket; - sendPacket = - new DatagramPacket(__dummyData, __dummyData.length, host, port); - receivePacket = new DatagramPacket(__timeData, __timeData.length); + sendPacket = new DatagramPacket(dummyData, dummyData.length, host, port); + receivePacket = new DatagramPacket(timeData, timeData.length); _socket_.send(sendPacket); _socket_.receive(receivePacket); - return new String(receivePacket.getData(), 0, receivePacket.getLength(), getCharsetName()); // Java 1.6 can use getCharset() - } - - /** Same as <code>getTime(host, DaytimeUDPClient.DEFAULT_PORT);</code> - * @param host the host - * @return the time - * @throws IOException on error - */ - public String getTime(InetAddress host) throws IOException - { - return getTime(host, DEFAULT_PORT); + return new String(receivePacket.getData(), 0, receivePacket.getLength(), getCharset()); // Java 1.6 can use getCharset() } } - diff --git a/src/main/java/org/apache/commons/net/discard/DiscardTCPClient.java b/src/main/java/org/apache/commons/net/discard/DiscardTCPClient.java index 2580d74..669d8e4 100644 --- a/src/main/java/org/apache/commons/net/discard/DiscardTCPClient.java +++ b/src/main/java/org/apache/commons/net/discard/DiscardTCPClient.java @@ -21,45 +21,33 @@ import java.io.OutputStream; import org.apache.commons.net.SocketClient; -/*** - * The DiscardTCPClient class is a TCP implementation of a client for the - * Discard protocol described in RFC 863. To use the class, merely - * establish a connection with - * {@link org.apache.commons.net.SocketClient#connect connect } - * and call {@link #getOutputStream getOutputStream() } to - * retrieve the discard output stream. Don't close the output stream - * when you're done writing to it. Rather, call - * {@link org.apache.commons.net.SocketClient#disconnect disconnect } - * to clean up properly. +/** + * The DiscardTCPClient class is a TCP implementation of a client for the Discard protocol described in RFC 863. To use the class, merely establish a connection + * with {@link org.apache.commons.net.SocketClient#connect connect } and call {@link #getOutputStream getOutputStream() } to retrieve the discard output stream. + * Don't close the output stream when you're done writing to it. Rather, call {@link org.apache.commons.net.SocketClient#disconnect disconnect } to clean up + * properly. * * @see DiscardUDPClient - ***/ + */ -public class DiscardTCPClient extends SocketClient -{ - /*** The default discard port. It is set to 9 according to RFC 863. ***/ +public class DiscardTCPClient extends SocketClient { + /** The default discard port. It is set to 9 according to RFC 863. */ public static final int DEFAULT_PORT = 9; - /*** - * The default DiscardTCPClient constructor. It merely sets the default - * port to <code> DEFAULT_PORT </code>. - ***/ - public DiscardTCPClient () - { + /** + * The default DiscardTCPClient constructor. It merely sets the default port to <code> DEFAULT_PORT </code>. + */ + public DiscardTCPClient() { setDefaultPort(DEFAULT_PORT); } - /*** - * Returns an OutputStream through which you may write data to the server. - * You should NOT close the OutputStream when you're finished - * reading from it. Rather, you should call - * {@link org.apache.commons.net.SocketClient#disconnect disconnect } - * to clean up properly. + /** + * Returns an OutputStream through which you may write data to the server. You should NOT close the OutputStream when you're finished reading from it. + * Rather, you should call {@link org.apache.commons.net.SocketClient#disconnect disconnect } to clean up properly. * * @return An OutputStream through which you can write data to the server. - ***/ - public OutputStream getOutputStream() - { + */ + public OutputStream getOutputStream() { return _output_; } } diff --git a/src/main/java/org/apache/commons/net/discard/DiscardUDPClient.java b/src/main/java/org/apache/commons/net/discard/DiscardUDPClient.java index e7c092d..5fb9f40 100644 --- a/src/main/java/org/apache/commons/net/discard/DiscardUDPClient.java +++ b/src/main/java/org/apache/commons/net/discard/DiscardUDPClient.java @@ -22,83 +22,66 @@ import java.net.DatagramPacket; import java.net.InetAddress; import org.apache.commons.net.DatagramSocketClient; +import org.apache.commons.net.util.NetConstants; -/*** - * The DiscardUDPClient class is a UDP implementation of a client for the - * Discard protocol described in RFC 863. To use the class, - * just open a local UDP port - * with {@link org.apache.commons.net.DatagramSocketClient#open open } - * and call {@link #send send } to send datagrams to the server - * After you're done sending discard data, call - * {@link org.apache.commons.net.DatagramSocketClient#close close() } - * to clean up properly. +/** + * The DiscardUDPClient class is a UDP implementation of a client for the Discard protocol described in RFC 863. To use the class, just open a local UDP port + * with {@link org.apache.commons.net.DatagramSocketClient#open open } and call {@link #send send } to send datagrams to the server After you're done sending + * discard data, call {@link org.apache.commons.net.DatagramSocketClient#close close() } to clean up properly. * * @see DiscardTCPClient - ***/ + */ -public class DiscardUDPClient extends DatagramSocketClient -{ - /*** The default discard port. It is set to 9 according to RFC 863. ***/ +public class DiscardUDPClient extends DatagramSocketClient { + /** The default discard port. It is set to 9 according to RFC 863. */ public static final int DEFAULT_PORT = 9; - DatagramPacket _sendPacket; + private final DatagramPacket sendPacket; - public DiscardUDPClient() - { - _sendPacket = new DatagramPacket(new byte[0], 0); + public DiscardUDPClient() { + sendPacket = new DatagramPacket(NetConstants.EMPTY_BTYE_ARRAY, 0); } - - /*** - * Sends the specified data to the specified server at the specified port. + /** + * Same as <code>send(data, data.length, host. DiscardUDPClient.DEFAULT_PORT)</code>. * - * @param data The discard data to send. - * @param length The length of the data to send. Should be less than - * or equal to the length of the data byte array. - * @param host The address of the server. - * @param port The service port. - * @throws IOException If an error occurs during the datagram send - * operation. - ***/ - public void send(byte[] data, int length, InetAddress host, int port) - throws IOException - { - _sendPacket.setData(data); - _sendPacket.setLength(length); - _sendPacket.setAddress(host); - _sendPacket.setPort(port); - _socket_.send(_sendPacket); - } - - - /*** - * Same as - * <code>send(data, length, host. DiscardUDPClient.DEFAULT_PORT)</code>. * @param data the buffer to send - * @param length the length of the data in the buffer * @param host the target host * @see #send(byte[], int, InetAddress, int) * @throws IOException if an error occurs - ***/ - public void send(byte[] data, int length, InetAddress host) - throws IOException - { - send(data, length, host, DEFAULT_PORT); + */ + public void send(final byte[] data, final InetAddress host) throws IOException { + send(data, data.length, host, DEFAULT_PORT); } - - /*** - * Same as - * <code>send(data, data.length, host. DiscardUDPClient.DEFAULT_PORT)</code>. - * @param data the buffer to send - * @param host the target host + /** + * Same as <code>send(data, length, host. DiscardUDPClient.DEFAULT_PORT)</code>. + * + * @param data the buffer to send + * @param length the length of the data in the buffer + * @param host the target host * @see #send(byte[], int, InetAddress, int) * @throws IOException if an error occurs - ***/ - public void send(byte[] data, InetAddress host) throws IOException - { - send(data, data.length, host, DEFAULT_PORT); + */ + public void send(final byte[] data, final int length, final InetAddress host) throws IOException { + send(data, length, host, DEFAULT_PORT); } -} + /** + * Sends the specified data to the specified server at the specified port. + * + * @param data The discard data to send. + * @param length The length of the data to send. Should be less than or equal to the length of the data byte array. + * @param host The address of the server. + * @param port The service port. + * @throws IOException If an error occurs during the datagram send operation. + */ + public void send(final byte[] data, final int length, final InetAddress host, final int port) throws IOException { + sendPacket.setData(data); + sendPacket.setLength(length); + sendPacket.setAddress(host); + sendPacket.setPort(port); + _socket_.send(sendPacket); + } +} diff --git a/src/main/java/org/apache/commons/net/echo/EchoTCPClient.java b/src/main/java/org/apache/commons/net/echo/EchoTCPClient.java index aa701f7..77d22ab 100644 --- a/src/main/java/org/apache/commons/net/echo/EchoTCPClient.java +++ b/src/main/java/org/apache/commons/net/echo/EchoTCPClient.java @@ -21,49 +21,34 @@ import java.io.InputStream; import org.apache.commons.net.discard.DiscardTCPClient; -/*** - * The EchoTCPClient class is a TCP implementation of a client for the - * Echo protocol described in RFC 862. To use the class, merely - * establish a connection with - * {@link org.apache.commons.net.SocketClient#connect connect } - * and call {@link DiscardTCPClient#getOutputStream getOutputStream() } to - * retrieve the echo output stream and - * {@link #getInputStream getInputStream() } - * to get the echo input stream. - * Don't close either stream when you're done using them. Rather, call - * {@link org.apache.commons.net.SocketClient#disconnect disconnect } - * to clean up properly. +/** + * The EchoTCPClient class is a TCP implementation of a client for the Echo protocol described in RFC 862. To use the class, merely establish a connection with + * {@link org.apache.commons.net.SocketClient#connect connect } and call {@link DiscardTCPClient#getOutputStream getOutputStream() } to retrieve the echo output + * stream and {@link #getInputStream getInputStream() } to get the echo input stream. Don't close either stream when you're done using them. Rather, call + * {@link org.apache.commons.net.SocketClient#disconnect disconnect } to clean up properly. * * @see EchoUDPClient * @see DiscardTCPClient - ***/ + */ -public final class EchoTCPClient extends DiscardTCPClient -{ - /*** The default echo port. It is set to 7 according to RFC 862. ***/ +public final class EchoTCPClient extends DiscardTCPClient { + /** The default echo port. It is set to 7 according to RFC 862. */ public static final int DEFAULT_PORT = 7; - /*** - * The default EchoTCPClient constructor. It merely sets the default - * port to <code> DEFAULT_PORT </code>. - ***/ - public EchoTCPClient () - { + /** + * The default EchoTCPClient constructor. It merely sets the default port to <code> DEFAULT_PORT </code>. + */ + public EchoTCPClient() { setDefaultPort(DEFAULT_PORT); } - /*** - * Returns an InputStream from which you may read echoed data from - * the server. You should NOT close the InputStream when you're finished - * reading from it. Rather, you should call - * {@link org.apache.commons.net.SocketClient#disconnect disconnect } - * to clean up properly. + /** + * Returns an InputStream from which you may read echoed data from the server. You should NOT close the InputStream when you're finished reading from it. + * Rather, you should call {@link org.apache.commons.net.SocketClient#disconnect disconnect } to clean up properly. * - * @return An InputStream from which you can read echoed data from the - * server. - ***/ - public InputStream getInputStream() - { + * @return An InputStream from which you can read echoed data from the server. + */ + public InputStream getInputStream() { return _input_; } diff --git a/src/main/java/org/apache/commons/net/echo/EchoUDPClient.java b/src/main/java/org/apache/commons/net/echo/EchoUDPClient.java index 96ab7df..fb4caa5 100644 --- a/src/main/java/org/apache/commons/net/echo/EchoUDPClient.java +++ b/src/main/java/org/apache/commons/net/echo/EchoUDPClient.java @@ -22,84 +22,68 @@ import java.net.DatagramPacket; import java.net.InetAddress; import org.apache.commons.net.discard.DiscardUDPClient; +import org.apache.commons.net.util.NetConstants; -/*** - * The EchoUDPClient class is a UDP implementation of a client for the - * Echo protocol described in RFC 862. To use the class, - * just open a local UDP port - * with {@link org.apache.commons.net.DatagramSocketClient#open open } - * and call {@link #send send } to send datagrams to the server, - * then call {@link #receive receive } to receive echoes. - * After you're done echoing data, call - * {@link org.apache.commons.net.DatagramSocketClient#close close() } - * to clean up properly. +/** + * The EchoUDPClient class is a UDP implementation of a client for the Echo protocol described in RFC 862. To use the class, just open a local UDP port with + * {@link org.apache.commons.net.DatagramSocketClient#open open } and call {@link #send send } to send datagrams to the server, then call {@link #receive + * receive } to receive echoes. After you're done echoing data, call {@link org.apache.commons.net.DatagramSocketClient#close close() } to clean up properly. * * @see EchoTCPClient * @see DiscardUDPClient - ***/ + */ -public final class EchoUDPClient extends DiscardUDPClient -{ - /*** The default echo port. It is set to 7 according to RFC 862. ***/ +public final class EchoUDPClient extends DiscardUDPClient { + /** The default echo port. It is set to 7 according to RFC 862. */ public static final int DEFAULT_PORT = 7; - private final DatagramPacket __receivePacket = new DatagramPacket(new byte[0], 0); + private final DatagramPacket receivePacket = new DatagramPacket(NetConstants.EMPTY_BTYE_ARRAY, 0); - /*** - * Sends the specified data to the specified server at the default echo - * port. + /** + * Same as <code> receive(data, data.length)</code> * - * @param data The echo data to send. - * @param length The length of the data to send. Should be less than - * or equal to the length of the data byte array. - * @param host The address of the server. - * @throws IOException If an error occurs during the datagram send - * operation. - ***/ - @Override - public void send(byte[] data, int length, InetAddress host) - throws IOException - { - send(data, length, host, DEFAULT_PORT); + * @param data the buffer to receive the input + * @return the number of bytes + * @throws IOException on error + */ + public int receive(final byte[] data) throws IOException { + return receive(data, data.length); } + /** + * Receives echoed data and returns its length. The data may be divided up among multiple datagrams, requiring multiple calls to receive. Also, the UDP + * packets will not necessarily arrive in the same order they were sent. + * + * @param data the buffer to receive the input + * @param length of the buffer + * + * @return Length of actual data received. + * @throws IOException If an error occurs while receiving the data. + */ + public int receive(final byte[] data, final int length) throws IOException { + receivePacket.setData(data); + receivePacket.setLength(length); + _socket_.receive(receivePacket); + return receivePacket.getLength(); + } - /*** Same as <code> send(data, data.length, host) </code> ***/ + /** Same as <code> send(data, data.length, host) </code> */ @Override - public void send(byte[] data, InetAddress host) throws IOException - { + public void send(final byte[] data, final InetAddress host) throws IOException { send(data, data.length, host, DEFAULT_PORT); } - - /*** - * Receives echoed data and returns its length. The data may be divided - * up among multiple datagrams, requiring multiple calls to receive. - * Also, the UDP packets will not necessarily arrive in the same order - * they were sent. - * @param data the buffer to receive the input - * @param length of the buffer + /** + * Sends the specified data to the specified server at the default echo port. * - * @return Length of actual data received. - * @throws IOException If an error occurs while receiving the data. - ***/ - public int receive(byte[] data, int length) throws IOException - { - __receivePacket.setData(data); - __receivePacket.setLength(length); - _socket_.receive(__receivePacket); - return __receivePacket.getLength(); - } - - /*** Same as <code> receive(data, data.length)</code> - * @param data the buffer to receive the input - * @return the number of bytes - * @throws IOException on error - ***/ - public int receive(byte[] data) throws IOException - { - return receive(data, data.length); + * @param data The echo data to send. + * @param length The length of the data to send. Should be less than or equal to the length of the data byte array. + * @param host The address of the server. + * @throws IOException If an error occurs during the datagram send operation. + */ + @Override + public void send(final byte[] data, final int length, final InetAddress host) throws IOException { + send(data, length, host, DEFAULT_PORT); } } - diff --git a/src/main/java/examples/Main.java b/src/main/java/org/apache/commons/net/examples/Main.java similarity index 60% rename from src/main/java/examples/Main.java rename to src/main/java/org/apache/commons/net/examples/Main.java index b327b85..c1ee0c2 100644 --- a/src/main/java/examples/Main.java +++ b/src/main/java/org/apache/commons/net/examples/Main.java @@ -16,7 +16,7 @@ * */ -package examples; +package org.apache.commons.net.examples; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; @@ -31,17 +31,22 @@ import java.util.Properties; */ public class Main { + private static boolean fromJar() { + final CodeSource codeSource = Main.class.getProtectionDomain().getCodeSource(); + if (codeSource != null) { + return codeSource.getLocation().getFile().endsWith(".jar"); + } + return false; // No idea if this can happen + } + /** - * Helper application for example classes. - * Lists available classes, and provides shorthand invocation. - * For example:<br> + * Helper application for example classes. Lists available classes, and provides shorthand invocation. For example:<br> * <code>java -jar commons-net-examples-m.n.jar FTPClientExample -l host user password</code> * - * @param args the first argument is used to name the class; remaining arguments - * are passed to the target class. + * @param args the first argument is used to name the class; remaining arguments are passed to the target class. * @throws Throwable if an error occurs */ - public static void main(String[] args) throws Throwable { + public static void main(final String[] args) throws Throwable { final Properties fp = new Properties(); final InputStream ras = Main.class.getResourceAsStream("examples.properties"); if (ras != null) { @@ -51,63 +56,49 @@ public class Main { } if (args.length == 0) { if (Thread.currentThread().getStackTrace().length > 2) { // called by Maven - System.out.println("Usage: mvn -q exec:java -Dexec.arguments=<alias or" + - " exampleClass>,<exampleClass parameters> (comma-separated, no spaces)"); - System.out.println("Or : mvn -q exec:java -Dexec.args=\"<alias" + - " or exampleClass> <exampleClass parameters>\" (space separated)"); + System.out.println( + "Usage: mvn -q exec:java -Dexec.arguments=<alias or" + " exampleClass>,<exampleClass parameters> (comma-separated, no spaces)"); + System.out.println("Or : mvn -q exec:java -Dexec.args=\"<alias" + " or exampleClass> <exampleClass parameters>\" (space separated)"); + } else if (fromJar()) { + System.out.println("Usage: java -jar commons-net-examples-m.n.jar <alias or exampleClass> <exampleClass parameters>"); } else { - if (fromJar()) { - System.out.println( - "Usage: java -jar commons-net-examples-m.n.jar <alias or exampleClass> <exampleClass parameters>"); - } else { - System.out.println( - "Usage: java -cp target/classes examples/Main <alias or exampleClass> <exampleClass parameters>"); - } + System.out + .println("Usage: java -cp target/classes org.apache.commons.net.examples.Main" + " <alias or exampleClass> <exampleClass parameters>"); } @SuppressWarnings("unchecked") // property names are Strings - List<String> l = (List<String>) Collections.list(fp.propertyNames()); + final List<String> l = (List<String>) Collections.list(fp.propertyNames()); if (l.isEmpty()) { return; } - Collections.sort(l); + l.sort(null); System.out.println("\nAliases and their classes:"); - for(String s : l) { - System.out.printf("%-25s %s%n",s,fp.getProperty(s)); + for (final String s : l) { + System.out.printf("%-25s %s%n", s, fp.getProperty(s)); } return; } - String shortName = args[0]; + final String shortName = args[0]; String fullName = fp.getProperty(shortName); if (fullName == null) { fullName = shortName; } - fullName = fullName.replace('/', '.'); try { - Class<?> clazz = Class.forName(fullName); - Method m = clazz.getDeclaredMethod("main", new Class[]{args.getClass()}); - String[] args2 = new String[args.length-1]; + final Class<?> clazz = Class.forName(fullName); + final Method m = clazz.getDeclaredMethod("main", args.getClass()); + final String[] args2 = new String[args.length - 1]; System.arraycopy(args, 1, args2, 0, args2.length); try { - m.invoke(null, (Object)args2); - } catch (InvocationTargetException ite) { - Throwable cause = ite.getCause(); + m.invoke(null, (Object) args2); + } catch (final InvocationTargetException ite) { + final Throwable cause = ite.getCause(); if (cause != null) { throw cause; - } else { - throw ite; } + throw ite; } - } catch (ClassNotFoundException e) { + } catch (final ClassNotFoundException e) { System.out.println(e); } } - - private static boolean fromJar() { - final CodeSource codeSource = Main.class.getProtectionDomain().getCodeSource(); - if ( codeSource != null) { - return codeSource.getLocation().getFile().endsWith(".jar"); - } - return false; // No idea if this can happen - } } diff --git a/src/main/java/org/apache/commons/net/examples/cidr/SubnetUtilsExample.java b/src/main/java/org/apache/commons/net/examples/cidr/SubnetUtilsExample.java new file mode 100644 index 0000000..61399b4 --- /dev/null +++ b/src/main/java/org/apache/commons/net/examples/cidr/SubnetUtilsExample.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.net.examples.cidr; + +import java.util.Arrays; +import java.util.Scanner; + +import org.apache.commons.net.util.SubnetUtils; +import org.apache.commons.net.util.SubnetUtils.SubnetInfo; + +/** + * Example class that shows how to use the {@link SubnetUtils} class. + * + */ +public class SubnetUtilsExample { + + public static void main(final String[] args) { + final String subnet = "192.168.0.3/31"; + final SubnetUtils utils = new SubnetUtils(subnet); + final SubnetInfo info = utils.getInfo(); + + System.out.printf("Subnet Information for %s:%n", subnet); + System.out.println("--------------------------------------"); + System.out.printf("IP Address:\t\t\t%s\t[%s]%n", info.getAddress(), Integer.toBinaryString(info.asInteger(info.getAddress()))); + System.out.printf("Netmask:\t\t\t%s\t[%s]%n", info.getNetmask(), Integer.toBinaryString(info.asInteger(info.getNetmask()))); + System.out.printf("CIDR Representation:\t\t%s%n%n", info.getCidrSignature()); + + System.out.printf("Supplied IP Address:\t\t%s%n%n", info.getAddress()); + + System.out.printf("Network Address:\t\t%s\t[%s]%n", info.getNetworkAddress(), Integer.toBinaryString(info.asInteger(info.getNetworkAddress()))); + System.out.printf("Broadcast Address:\t\t%s\t[%s]%n", info.getBroadcastAddress(), Integer.toBinaryString(info.asInteger(info.getBroadcastAddress()))); + System.out.printf("Low Address:\t\t\t%s\t[%s]%n", info.getLowAddress(), Integer.toBinaryString(info.asInteger(info.getLowAddress()))); + System.out.printf("High Address:\t\t\t%s\t[%s]%n", info.getHighAddress(), Integer.toBinaryString(info.asInteger(info.getHighAddress()))); + + System.out.printf("Total usable addresses: \t%d%n", Long.valueOf(info.getAddressCountLong())); + System.out.printf("Address List: %s%n%n", Arrays.toString(info.getAllAddresses())); + + final String prompt = "Enter an IP address (e.g. 192.168.0.10):"; + System.out.println(prompt); + try (final Scanner scanner = new Scanner(System.in)) { + while (scanner.hasNextLine()) { + final String address = scanner.nextLine(); + System.out.println("The IP address [" + address + "] is " + (info.isInRange(address) ? "" : "not ") + "within the subnet [" + subnet + "]"); + System.out.println(prompt); + } + } + } + +} diff --git a/src/main/java/examples/ftp/FTPClientExample.java b/src/main/java/org/apache/commons/net/examples/ftp/FTPClientExample.java similarity index 55% rename from src/main/java/examples/ftp/FTPClientExample.java rename to src/main/java/org/apache/commons/net/examples/ftp/FTPClientExample.java index d906120..ac0d91e 100644 --- a/src/main/java/examples/ftp/FTPClientExample.java +++ b/src/main/java/org/apache/commons/net/examples/ftp/FTPClientExample.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.ftp; +package org.apache.commons.net.examples.ftp; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -25,15 +25,16 @@ import java.io.OutputStream; import java.io.PrintWriter; import java.net.InetAddress; import java.net.UnknownHostException; +import java.time.Duration; import java.util.Arrays; import org.apache.commons.net.PrintCommandListener; import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; -import org.apache.commons.net.ftp.FTPHTTPClient; import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPConnectionClosedException; import org.apache.commons.net.ftp.FTPFile; +import org.apache.commons.net.ftp.FTPHTTPClient; import org.apache.commons.net.ftp.FTPReply; import org.apache.commons.net.ftp.FTPSClient; import org.apache.commons.net.io.CopyStreamEvent; @@ -41,59 +42,64 @@ import org.apache.commons.net.io.CopyStreamListener; import org.apache.commons.net.util.TrustManagerUtils; /** - * This is an example program demonstrating how to use the FTPClient class. - * This program connects to an FTP server and retrieves the specified - * file. If the -s flag is used, it stores the local file at the FTP server. - * Just so you can see what's happening, all reply strings are printed. - * If the -b flag is used, a binary transfer is assumed (default is ASCII). - * See below for further options. + * This is an example program demonstrating how to use the FTPClient class. This program connects to an FTP server and retrieves the specified file. If the -s + * flag is used, it stores the local file at the FTP server. Just so you can see what's happening, all reply strings are printed. If the -b flag is used, a + * binary transfer is assumed (default is ASCII). See below for further options. */ -public final class FTPClientExample -{ - - public static final String USAGE = - "Expected Parameters: [options] <hostname> <username> <password> [<remote file> [<local file>]]\n" + - "\nDefault behavior is to download a file and use ASCII transfer mode.\n" + - "\t-a - use local active mode (default is local passive)\n" + - "\t-A - anonymous login (omit username and password parameters)\n" + - "\t-b - use binary transfer mode\n" + - "\t-c cmd - issue arbitrary command (remote is used as a parameter if provided) \n" + - "\t-d - list directory details using MLSD (remote is used as the pathname if provided)\n" + - "\t-e - use EPSV with IPv4 (default false)\n" + - "\t-E - encoding to use for control channel\n" + - "\t-f - issue FEAT command (remote and local files are ignored)\n" + - "\t-h - list hidden files (applies to -l and -n only)\n" + - "\t-k secs - use keep-alive timer (setControlKeepAliveTimeout)\n" + - "\t-l - list files using LIST (remote is used as the pathname if provided)\n" + - "\t Files are listed twice: first in raw mode, then as the formatted parsed data.\n" + - "\t N.B. if the wrong server-type is used, output may be lost. Use -U or -S as necessary.\n" + - "\t-L - use lenient future dates (server dates may be up to 1 day into future)\n" + - "\t-m - list file details using MDTM (remote is used as the pathname if provided)\n" + - "\t-n - list file names using NLST (remote is used as the pathname if provided)\n" + - "\t-p true|false|protocol[,true|false] - use FTPSClient with the specified protocol and/or isImplicit setting\n" + - "\t-s - store file on server (upload)\n" + - "\t-S - systemType set server system type (e.g. UNIX VMS WINDOWS)\n" + - "\t-t - list file details using MLST (remote is used as the pathname if provided)\n" + - "\t-U - save unparseable responses\n" + - "\t-w msec - wait time for keep-alive reply (setControlKeepAliveReplyTimeout)\n" + - "\t-T all|valid|none - use one of the built-in TrustManager implementations (none = JVM default)\n" + - "\t-y format - set default date format string\n" + - "\t-Y format - set recent date format string\n" + - "\t-Z timezone - set the server timezone for parsing LIST responses\n" + - "\t-z timezone - set the timezone for displaying MDTM, LIST, MLSD, MLST responses\n" + - "\t-PrH server[:port] - HTTP Proxy host and optional port[80] \n" + - "\t-PrU user - HTTP Proxy server username\n" + - "\t-PrP password - HTTP Proxy server password\n" + - "\t-# - add hash display during transfers\n"; - - public static void main(String[] args) throws UnknownHostException - { +public final class FTPClientExample { + + public static final String USAGE = "Expected Parameters: [options] <hostname> <username> <password> [<remote file> [<local file>]]\n" + + "\nDefault behavior is to download a file and use ASCII transfer mode.\n" + "\t-a - use local active mode (default is local passive)\n" + + "\t-A - anonymous login (omit username and password parameters)\n" + "\t-b - use binary transfer mode\n" + + "\t-c cmd - issue arbitrary command (remote is used as a parameter if provided) \n" + + "\t-d - list directory details using MLSD (remote is used as the pathname if provided)\n" + "\t-e - use EPSV with IPv4 (default false)\n" + + "\t-E - encoding to use for control channel\n" + "\t-f - issue FEAT command (remote and local files are ignored)\n" + + "\t-h - list hidden files (applies to -l and -n only)\n" + "\t-i - issue SIZE command for a file\n" + + "\t-k secs - use keep-alive timer (setControlKeepAliveTimeout)\n" + "\t-l - list files using LIST (remote is used as the pathname if provided)\n" + + "\t Files are listed twice: first in raw mode, then as the formatted parsed data.\n" + + "\t N.B. if the wrong server-type is used, output may be lost. Use -U or -S as necessary.\n" + + "\t-L - use lenient future dates (server dates may be up to 1 day into future)\n" + + "\t-m - list file details using MDTM (remote is used as the pathname if provided)\n" + + "\t-n - list file names using NLST (remote is used as the pathname if provided)\n" + + "\t-p true|false|protocol[,true|false] - use FTPSClient with the specified protocol and/or isImplicit setting\n" + + "\t-s - store file on server (upload)\n" + "\t-S - systemType set server system type (e.g. UNIX VMS WINDOWS)\n" + + "\t-t - list file details using MLST (remote is used as the pathname if provided)\n" + "\t-U - save unparseable responses\n" + + "\t-w msec - wait time for keep-alive reply (setControlKeepAliveReplyTimeout)\n" + + "\t-T all|valid|none - use one of the built-in TrustManager implementations (none = JVM default)\n" + + "\t-y format - set default date format string\n" + "\t-Y format - set recent date format string\n" + + "\t-Z timezone - set the server time zone for parsing LIST responses\n" + + "\t-z timezone - set the time zone for displaying MDTM, LIST, MLSD, MLST responses\n" + + "\t-PrH server[:port] - HTTP Proxy host and optional port[80] \n" + "\t-PrU user - HTTP Proxy server username\n" + + "\t-PrP password - HTTP Proxy server password\n" + "\t-# - add hash display during transfers\n"; + + private static CopyStreamListener createListener() { + return new CopyStreamListener() { + private long megsTotal; + + @Override + public void bytesTransferred(final CopyStreamEvent event) { + bytesTransferred(event.getTotalBytesTransferred(), event.getBytesTransferred(), event.getStreamSize()); + } + + @Override + public void bytesTransferred(final long totalBytesTransferred, final int bytesTransferred, final long streamSize) { + final long megs = totalBytesTransferred / 1000000; + for (long l = megsTotal; l < megs; l++) { + System.err.print("#"); + } + megsTotal = megs; + } + }; + } + + public static void main(final String[] args) throws UnknownHostException { boolean storeFile = false, binaryTransfer = false, error = false, listFiles = false, listNames = false, hidden = false; boolean localActive = false, useEpsvWithIPv4 = false, feat = false, printHash = false; boolean mlst = false, mlsd = false, mdtm = false, saveUnparseable = false; + boolean size = false; boolean lenient = false; - long keepAliveTimeout = -1; - int controlKeepAliveReplyTimeout = -1; + long keepAliveTimeoutSeconds = -1; + int controlKeepAliveReplyTimeoutMillis = -1; int minParams = 5; // listings require 3 params String protocol = null; // SSL protocol String doCommand = null; @@ -111,116 +117,88 @@ public final class FTPClientExample String defaultDateFormat = null; String recentDateFormat = null; - int base = 0; - for (base = 0; base < args.length; base++) - { + for (base = 0; base < args.length; base++) { if (args[base].equals("-s")) { storeFile = true; - } - else if (args[base].equals("-a")) { + } else if (args[base].equals("-a")) { localActive = true; - } - else if (args[base].equals("-A")) { + } else if (args[base].equals("-A")) { username = "anonymous"; - password = System.getProperty("user.name")+"@"+InetAddress.getLocalHost().getHostName(); - } - else if (args[base].equals("-b")) { + password = System.getProperty("user.name") + "@" + InetAddress.getLocalHost().getHostName(); + } else if (args[base].equals("-b")) { binaryTransfer = true; - } - else if (args[base].equals("-c")) { + } else if (args[base].equals("-c")) { doCommand = args[++base]; minParams = 3; - } - else if (args[base].equals("-d")) { + } else if (args[base].equals("-d")) { mlsd = true; minParams = 3; - } - else if (args[base].equals("-e")) { + } else if (args[base].equals("-e")) { useEpsvWithIPv4 = true; - } - else if (args[base].equals("-E")) { + } else if (args[base].equals("-E")) { encoding = args[++base]; - } - else if (args[base].equals("-f")) { + } else if (args[base].equals("-f")) { feat = true; minParams = 3; - } - else if (args[base].equals("-h")) { + } else if (args[base].equals("-h")) { hidden = true; - } - else if (args[base].equals("-k")) { - keepAliveTimeout = Long.parseLong(args[++base]); - } - else if (args[base].equals("-l")) { + } else if (args[base].equals("-i")) { + size = true; + minParams = 3; + } else if (args[base].equals("-k")) { + keepAliveTimeoutSeconds = Long.parseLong(args[++base]); + } else if (args[base].equals("-l")) { listFiles = true; minParams = 3; - } - else if (args[base].equals("-m")) { + } else if (args[base].equals("-m")) { mdtm = true; minParams = 3; - } - else if (args[base].equals("-L")) { + } else if (args[base].equals("-L")) { lenient = true; - } - else if (args[base].equals("-n")) { + } else if (args[base].equals("-n")) { listNames = true; minParams = 3; - } - else if (args[base].equals("-p")) { + } else if (args[base].equals("-p")) { protocol = args[++base]; - } - else if (args[base].equals("-S")) { + } else if (args[base].equals("-S")) { serverType = args[++base]; - } - else if (args[base].equals("-t")) { + } else if (args[base].equals("-t")) { mlst = true; minParams = 3; - } - else if (args[base].equals("-U")) { + } else if (args[base].equals("-U")) { saveUnparseable = true; - } - else if (args[base].equals("-w")) { - controlKeepAliveReplyTimeout = Integer.parseInt(args[++base]); - } - else if (args[base].equals("-T")) { + } else if (args[base].equals("-w")) { + controlKeepAliveReplyTimeoutMillis = Integer.parseInt(args[++base]); + } else if (args[base].equals("-T")) { trustmgr = args[++base]; - } - else if (args[base].equals("-y")) { + } else if (args[base].equals("-y")) { defaultDateFormat = args[++base]; - } - else if (args[base].equals("-Y")) { + } else if (args[base].equals("-Y")) { recentDateFormat = args[++base]; - } - else if (args[base].equals("-Z")) { + } else if (args[base].equals("-Z")) { serverTimeZoneId = args[++base]; - } - else if (args[base].equals("-z")) { + } else if (args[base].equals("-z")) { displayTimeZoneId = args[++base]; - } - else if (args[base].equals("-PrH")) { + } else if (args[base].equals("-PrH")) { proxyHost = args[++base]; - String parts[] = proxyHost.split(":"); - if (parts.length == 2){ - proxyHost=parts[0]; - proxyPort=Integer.parseInt(parts[1]); + final String parts[] = proxyHost.split(":"); + if (parts.length == 2) { + proxyHost = parts[0]; + proxyPort = Integer.parseInt(parts[1]); } - } - else if (args[base].equals("-PrU")) { + } else if (args[base].equals("-PrU")) { proxyUser = args[++base]; - } - else if (args[base].equals("-PrP")) { + } else if (args[base].equals("-PrP")) { proxyPassword = args[++base]; - } - else if (args[base].equals("-#")) { + } else if (args[base].equals("-#")) { printHash = true; - } - else { + } else { break; } } - int remain = args.length - base; + final int remain = args.length - base; if (username != null) { minParams -= 2; } @@ -235,10 +213,10 @@ public final class FTPClientExample String server = args[base++]; int port = 0; - String parts[] = server.split(":"); - if (parts.length == 2){ - server=parts[0]; - port=Integer.parseInt(parts[1]); + final String parts[] = server.split(":"); + if (parts.length == 2) { + server = parts[0]; + port = Integer.parseInt(parts[1]); } if (username == null) { username = args[base++]; @@ -256,22 +234,21 @@ public final class FTPClientExample } final FTPClient ftp; - if (protocol == null ) { - if(proxyHost !=null) { + if (protocol == null) { + if (proxyHost != null) { System.out.println("Using HTTP proxy server: " + proxyHost); ftp = new FTPHTTPClient(proxyHost, proxyPort, proxyUser, proxyPassword); - } - else { + } else { ftp = new FTPClient(); } } else { - FTPSClient ftps; + final FTPSClient ftps; if (protocol.equals("true")) { ftps = new FTPSClient(true); } else if (protocol.equals("false")) { ftps = new FTPSClient(false); } else { - String prot[] = protocol.split(","); + final String prot[] = protocol.split(","); if (prot.length == 1) { // Just protocol ftps = new FTPSClient(protocol); } else { // protocol,true|false @@ -291,11 +268,11 @@ public final class FTPClientExample if (printHash) { ftp.setCopyStreamListener(createListener()); } - if (keepAliveTimeout >= 0) { - ftp.setControlKeepAliveTimeout(keepAliveTimeout); + if (keepAliveTimeoutSeconds >= 0) { + ftp.setControlKeepAliveTimeout(Duration.ofSeconds(keepAliveTimeoutSeconds)); } - if (controlKeepAliveReplyTimeout >= 0) { - ftp.setControlKeepAliveReplyTimeout(controlKeepAliveReplyTimeout); + if (controlKeepAliveReplyTimeoutMillis >= 0) { + ftp.setControlKeepAliveReplyTimeout(Duration.ofMillis(controlKeepAliveReplyTimeoutMillis)); } if (encoding != null) { ftp.setControlEncoding(encoding); @@ -320,37 +297,29 @@ public final class FTPClientExample } ftp.configure(config); - try - { - int reply; + try { + final int reply; if (port > 0) { ftp.connect(server, port); } else { ftp.connect(server); } - System.out.println("Connected to " + server + " on " + (port>0 ? port : ftp.getDefaultPort())); + System.out.println("Connected to " + server + " on " + (port > 0 ? port : ftp.getDefaultPort())); // After connection attempt, you should check the reply code to verify // success. reply = ftp.getReplyCode(); - if (!FTPReply.isPositiveCompletion(reply)) - { + if (!FTPReply.isPositiveCompletion(reply)) { ftp.disconnect(); System.err.println("FTP server refused connection."); System.exit(1); } - } - catch (IOException e) - { - if (ftp.isConnected()) - { - try - { + } catch (final IOException e) { + if (ftp.isConnected()) { + try { ftp.disconnect(); - } - catch (IOException f) - { + } catch (final IOException f) { // do nothing } } @@ -359,11 +328,8 @@ public final class FTPClientExample System.exit(1); } -__main: - try - { - if (!ftp.login(username, password)) - { + __main: try { + if (!ftp.login(username, password)) { ftp.logout(); error = true; break __main; @@ -389,27 +355,25 @@ __main: ftp.setUseEPSVwithIPv4(useEpsvWithIPv4); - if (storeFile) - { - InputStream input; - - input = new FileInputStream(local); - - ftp.storeFile(remote, input); + if (storeFile) { + try (final InputStream input = new FileInputStream(local)) { + ftp.storeFile(remote, input); + } - input.close(); + if (keepAliveTimeoutSeconds > 0) { + showCslStats(ftp); + } } // Allow multiple list types for single invocation - else if (listFiles || mlsd || mdtm || mlst || listNames) - { + else if (listFiles || mlsd || mdtm || mlst || listNames || size) { if (mlsd) { - for (FTPFile f : ftp.mlistDir(remote)) { + for (final FTPFile f : ftp.mlistDir(remote)) { System.out.println(f.getRawListing()); System.out.println(f.toFormattedString(displayTimeZoneId)); } } if (mdtm) { - FTPFile f = ftp.mdtmFile(remote); + final FTPFile f = ftp.mdtmFile(remote); if (f != null) { System.out.println(f.getRawListing()); System.out.println(f.toFormattedString(displayTimeZoneId)); @@ -418,16 +382,19 @@ __main: } } if (mlst) { - FTPFile f = ftp.mlistFile(remote); - if (f != null){ + final FTPFile f = ftp.mlistFile(remote); + if (f != null) { System.out.println(f.toFormattedString(displayTimeZoneId)); } } if (listNames) { - for (String s : ftp.listNames(remote)) { + for (final String s : ftp.listNames(remote)) { System.out.println(s); } } + if (size) { + System.out.println("Size=" + ftp.getSize(remote)); + } // Do this last because it changes the client if (listFiles) { if (lenient || serverTimeZoneId != null) { @@ -435,97 +402,75 @@ __main: if (serverTimeZoneId != null) { config.setServerTimeZoneId(serverTimeZoneId); } - ftp.configure(config ); + ftp.configure(config); } - for (FTPFile f : ftp.listFiles(remote)) { + for (final FTPFile f : ftp.listFiles(remote)) { System.out.println(f.getRawListing()); System.out.println(f.toFormattedString(displayTimeZoneId)); } } - } - else if (feat) - { + } else if (feat) { // boolean feature check if (remote != null) { // See if the command is present if (ftp.hasFeature(remote)) { - System.out.println("Has feature: "+remote); + System.out.println("Has feature: " + remote); + } else if (FTPReply.isPositiveCompletion(ftp.getReplyCode())) { + System.out.println("FEAT " + remote + " was not detected"); } else { - if (FTPReply.isPositiveCompletion(ftp.getReplyCode())) { - System.out.println("FEAT "+remote+" was not detected"); - } else { - System.out.println("Command failed: "+ftp.getReplyString()); - } + System.out.println("Command failed: " + ftp.getReplyString()); } // Strings feature check - String []features = ftp.featureValues(remote); + final String[] features = ftp.featureValues(remote); if (features != null) { - for(String f : features) { - System.out.println("FEAT "+remote+"="+f+"."); + for (final String f : features) { + System.out.println("FEAT " + remote + "=" + f + "."); } + } else if (FTPReply.isPositiveCompletion(ftp.getReplyCode())) { + System.out.println("FEAT " + remote + " is not present"); } else { - if (FTPReply.isPositiveCompletion(ftp.getReplyCode())) { - System.out.println("FEAT "+remote+" is not present"); - } else { - System.out.println("Command failed: "+ftp.getReplyString()); - } + System.out.println("Command failed: " + ftp.getReplyString()); } - } else { - if (ftp.features()) { + } else if (ftp.features()) { // Command listener has already printed the output - } else { - System.out.println("Failed: "+ftp.getReplyString()); - } + } else { + System.out.println("Failed: " + ftp.getReplyString()); } - } - else if (doCommand != null) - { + } else if (doCommand != null) { if (ftp.doCommand(doCommand, remote)) { // Command listener has already printed the output // for(String s : ftp.getReplyStrings()) { // System.out.println(s); // } } else { - System.out.println("Failed: "+ftp.getReplyString()); + System.out.println("Failed: " + ftp.getReplyString()); + } + } else { + try (final OutputStream output = new FileOutputStream(local)) { + ftp.retrieveFile(remote, output); } - } - else - { - OutputStream output; - - output = new FileOutputStream(local); - - ftp.retrieveFile(remote, output); - output.close(); + if (keepAliveTimeoutSeconds > 0) { + showCslStats(ftp); + } } ftp.noop(); // check that control connection is working OK ftp.logout(); - } - catch (FTPConnectionClosedException e) - { + } catch (final FTPConnectionClosedException e) { error = true; System.err.println("Server closed connection."); e.printStackTrace(); - } - catch (IOException e) - { + } catch (final IOException e) { error = true; e.printStackTrace(); - } - finally - { - if (ftp.isConnected()) - { - try - { + } finally { + if (ftp.isConnected()) { + try { ftp.disconnect(); - } - catch (IOException f) - { + } catch (final IOException f) { // do nothing } } @@ -534,25 +479,10 @@ __main: System.exit(error ? 1 : 0); } // end main - private static CopyStreamListener createListener(){ - return new CopyStreamListener(){ - private long megsTotal = 0; + private static void showCslStats(final FTPClient ftp) { + @SuppressWarnings("deprecation") // debug code + final int[] stats = ftp.getCslDebug(); + System.out.println("CslDebug=" + Arrays.toString(stats)); - @Override - public void bytesTransferred(CopyStreamEvent event) { - bytesTransferred(event.getTotalBytesTransferred(), event.getBytesTransferred(), event.getStreamSize()); - } - - @Override - public void bytesTransferred(long totalBytesTransferred, - int bytesTransferred, long streamSize) { - long megs = totalBytesTransferred / 1000000; - for (long l = megsTotal; l < megs; l++) { - System.err.print("#"); - } - megsTotal = megs; - } - }; } } - diff --git a/src/main/java/examples/ftp/ServerToServerFTP.java b/src/main/java/org/apache/commons/net/examples/ftp/ServerToServerFTP.java similarity index 61% rename from src/main/java/examples/ftp/ServerToServerFTP.java rename to src/main/java/org/apache/commons/net/examples/ftp/ServerToServerFTP.java index 1d8ca1a..86631d3 100644 --- a/src/main/java/examples/ftp/ServerToServerFTP.java +++ b/src/main/java/org/apache/commons/net/examples/ftp/ServerToServerFTP.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.ftp; +package org.apache.commons.net.examples.ftp; import java.io.IOException; import java.io.PrintWriter; @@ -26,40 +26,39 @@ import org.apache.commons.net.ProtocolCommandListener; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPReply; -/*** - * This is an example program demonstrating how to use the FTPClient class. - * This program arranges a server to server file transfer that transfers - * a file from host1 to host2. Keep in mind, this program might only work - * if host2 is the same as the host you run it on (for security reasons, - * some ftp servers only allow PORT commands to be issued with a host - * argument equal to the client host). +/** + * This is an example program demonstrating how to use the FTPClient class. This program arranges a server to server file transfer that transfers a file from + * host1 to host2. Keep in mind, this program might only work if host2 is the same as the host you run it on (for security reasons, some ftp servers only allow + * PORT commands to be issued with a host argument equal to the client host). * <p> * Usage: ftp <host1> <user1> <pass1> <file1> <host2> <user2> <pass2> <file2> - ***/ -public final class ServerToServerFTP -{ - - public static void main(String[] args) - { - String server1, username1, password1, file1; - String server2, username2, password2, file2; - String [] parts; - int port1=0, port2=0; - FTPClient ftp1, ftp2; - ProtocolCommandListener listener; - - if (args.length < 8) - { - System.err.println( - "Usage: ftp <host1> <user1> <pass1> <file1> <host2> <user2> <pass2> <file2>" - ); + */ +public final class ServerToServerFTP { + + public static void main(final String[] args) { + String server1; + final String username1; + final String password1; + final String file1; + String server2; + final String username2; + final String password2; + final String file2; + String[] parts; + int port1 = 0, port2 = 0; + final FTPClient ftp1; + final FTPClient ftp2; + final ProtocolCommandListener listener; + + if (args.length < 8) { + System.err.println("Usage: ftp <host1> <user1> <pass1> <file1> <host2> <user2> <pass2> <file2>"); System.exit(1); } server1 = args[0]; parts = server1.split(":"); if (parts.length == 2) { - server1=parts[0]; + server1 = parts[0]; port1 = Integer.parseInt(parts[1]); } username1 = args[1]; @@ -68,7 +67,7 @@ public final class ServerToServerFTP server2 = args[4]; parts = server2.split(":"); if (parts.length == 2) { - server2=parts[0]; + server2 = parts[0]; port2 = Integer.parseInt(parts[1]); } username2 = args[5]; @@ -81,9 +80,8 @@ public final class ServerToServerFTP ftp2 = new FTPClient(); ftp2.addProtocolCommandListener(listener); - try - { - int reply; + try { + final int reply; if (port1 > 0) { ftp1.connect(server1, port1); } else { @@ -93,23 +91,16 @@ public final class ServerToServerFTP reply = ftp1.getReplyCode(); - if (!FTPReply.isPositiveCompletion(reply)) - { + if (!FTPReply.isPositiveCompletion(reply)) { ftp1.disconnect(); System.err.println("FTP server1 refused connection."); System.exit(1); } - } - catch (IOException e) - { - if (ftp1.isConnected()) - { - try - { + } catch (final IOException e) { + if (ftp1.isConnected()) { + try { ftp1.disconnect(); - } - catch (IOException f) - { + } catch (final IOException f) { // do nothing } } @@ -118,9 +109,8 @@ public final class ServerToServerFTP System.exit(1); } - try - { - int reply; + try { + final int reply; if (port2 > 0) { ftp2.connect(server2, port2); } else { @@ -130,23 +120,16 @@ public final class ServerToServerFTP reply = ftp2.getReplyCode(); - if (!FTPReply.isPositiveCompletion(reply)) - { + if (!FTPReply.isPositiveCompletion(reply)) { ftp2.disconnect(); System.err.println("FTP server2 refused connection."); System.exit(1); } - } - catch (IOException e) - { - if (ftp2.isConnected()) - { - try - { + } catch (final IOException e) { + if (ftp2.isConnected()) { + try { ftp2.disconnect(); - } - catch (IOException f) - { + } catch (final IOException f) { // do nothing } } @@ -155,17 +138,13 @@ public final class ServerToServerFTP System.exit(1); } -__main: - try - { - if (!ftp1.login(username1, password1)) - { + __main: try { + if (!ftp1.login(username1, password1)) { System.err.println("Could not login to " + server1); break __main; } - if (!ftp2.login(username2, password2)) - { + if (!ftp2.login(username2, password2)) { System.err.println("Could not login to " + server2); break __main; } @@ -173,61 +152,43 @@ __main: // Let's just assume success for now. ftp2.enterRemotePassiveMode(); - ftp1.enterRemoteActiveMode(InetAddress.getByName(ftp2.getPassiveHost()), - ftp2.getPassivePort()); + ftp1.enterRemoteActiveMode(InetAddress.getByName(ftp2.getPassiveHost()), ftp2.getPassivePort()); // Although you would think the store command should be sent to server2 // first, in reality, ftp servers like wu-ftpd start accepting data - // connections right after entering passive mode. Additionally, they + // connections right after entering passive mode. Additionally, they // don't even send the positive preliminary reply until after the // transfer is completed (in the case of passive mode transfers). // Therefore, calling store first would hang waiting for a preliminary // reply. - if (ftp1.remoteRetrieve(file1) && ftp2.remoteStoreUnique(file2)) - { - // if(ftp1.remoteRetrieve(file1) && ftp2.remoteStore(file2)) { - // We have to fetch the positive completion reply. - ftp1.completePendingCommand(); - ftp2.completePendingCommand(); - } - else - { - System.err.println( - "Couldn't initiate transfer. Check that filenames are valid."); + if (!ftp1.remoteRetrieve(file1) || !ftp2.remoteStoreUnique(file2)) { + System.err.println("Couldn't initiate transfer. Check that file names are valid."); break __main; } + // if(ftp1.remoteRetrieve(file1) && ftp2.remoteStore(file2)) { + // We have to fetch the positive completion reply. + ftp1.completePendingCommand(); + ftp2.completePendingCommand(); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); System.exit(1); - } - finally - { - try - { - if (ftp1.isConnected()) - { + } finally { + try { + if (ftp1.isConnected()) { ftp1.logout(); ftp1.disconnect(); } - } - catch (IOException e) - { + } catch (final IOException e) { // do nothing } - try - { - if (ftp2.isConnected()) - { + try { + if (ftp2.isConnected()) { ftp2.logout(); ftp2.disconnect(); } - } - catch (IOException e) - { + } catch (final IOException e) { // do nothing } } diff --git a/src/main/java/examples/ftp/TFTPExample.java b/src/main/java/org/apache/commons/net/examples/ftp/TFTPExample.java similarity index 52% rename from src/main/java/examples/ftp/TFTPExample.java rename to src/main/java/org/apache/commons/net/examples/ftp/TFTPExample.java index 6775d6a..bb0455b 100644 --- a/src/main/java/examples/ftp/TFTPExample.java +++ b/src/main/java/org/apache/commons/net/examples/ftp/TFTPExample.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.ftp; +package org.apache.commons.net.examples.ftp; import java.io.Closeable; import java.io.File; @@ -24,85 +24,80 @@ import java.io.FileOutputStream; import java.io.IOException; import java.net.SocketException; import java.net.UnknownHostException; + import org.apache.commons.net.tftp.TFTP; import org.apache.commons.net.tftp.TFTPClient; import org.apache.commons.net.tftp.TFTPPacket; -/*** - * This is an example of a simple Java tftp client. - * Notice how all of the code is really just argument processing and - * error handling. +/** + * This is an example of a simple Java tftp client. Notice how all of the code is really just argument processing and error handling. * <p> - * Usage: tftp [options] hostname localfile remotefile - * hostname - The name of the remote host, with optional :port - * localfile - The name of the local file to send or the name to use for - * the received file - * remotefile - The name of the remote file to receive or the name for - * the remote server to use to name the local file being sent. - * options: (The default is to assume -r -b) - * -s Send a local file - * -r Receive a remote file - * -a Use ASCII transfer mode - * -b Use binary transfer mode - ***/ -public final class TFTPExample -{ - static final String USAGE = - "Usage: tftp [options] hostname localfile remotefile\n\n" + - "hostname - The name of the remote host [:port]\n" + - "localfile - The name of the local file to send or the name to use for\n" + - "\tthe received file\n" + - "remotefile - The name of the remote file to receive or the name for\n" + - "\tthe remote server to use to name the local file being sent.\n\n" + - "options: (The default is to assume -r -b)\n" + - "\t-t timeout in seconds (default 60s)\n" + - "\t-s Send a local file\n" + - "\t-r Receive a remote file\n" + - "\t-a Use ASCII transfer mode\n" + - "\t-b Use binary transfer mode\n" + - "\t-v Verbose (trace packets)\n" - ; + * Usage: tftp [options] hostname localfile remotefile hostname - The name of the remote host, with optional :port localfile - The name of the local file to + * send or the name to use for the received file remotefile - The name of the remote file to receive or the name for the remote server to use to name the local + * file being sent. options: (The default is to assume -r -b) -s Send a local file -r Receive a remote file -a Use ASCII transfer mode -b Use binary transfer + * mode + */ +public final class TFTPExample { + static final String USAGE = "Usage: tftp [options] hostname localfile remotefile\n\n" + "hostname - The name of the remote host [:port]\n" + + "localfile - The name of the local file to send or the name to use for\n" + "\tthe received file\n" + + "remotefile - The name of the remote file to receive or the name for\n" + "\tthe remote server to use to name the local file being sent.\n\n" + + "options: (The default is to assume -r -b)\n" + "\t-t timeout in seconds (default 60s)\n" + "\t-s Send a local file\n" + + "\t-r Receive a remote file\n" + "\t-a Use ASCII transfer mode\n" + "\t-b Use binary transfer mode\n" + "\t-v Verbose (trace packets)\n"; + + private static boolean close(final TFTPClient tftp, final Closeable output) { + boolean closed; + tftp.close(); + try { + if (output != null) { + output.close(); + } + closed = true; + } catch (final IOException e) { + closed = false; + System.err.println("Error: error closing file."); + System.err.println(e.getMessage()); + } + return closed; + } - public static void main(String[] args) - { + public static void main(final String[] args) { boolean receiveFile = true, closed; int transferMode = TFTP.BINARY_MODE, argc; - String arg, hostname, localFilename, remoteFilename; + String arg; + final String hostname; + final String localFilename; + final String remoteFilename; final TFTPClient tftp; int timeout = 60000; boolean verbose = false; // Parse options - for (argc = 0; argc < args.length; argc++) - { + for (argc = 0; argc < args.length; argc++) { arg = args[argc]; - if (arg.startsWith("-")) - { - if (arg.equals("-r")) { - receiveFile = true; - } else if (arg.equals("-s")) { - receiveFile = false; - } else if (arg.equals("-a")) { - transferMode = TFTP.ASCII_MODE; - } else if (arg.equals("-b")) { - transferMode = TFTP.BINARY_MODE; - } else if (arg.equals("-t")) { - timeout = 1000*Integer.parseInt(args[++argc]); - } else if (arg.equals("-v")) { - verbose = true; - } else { - System.err.println("Error: unrecognized option."); - System.err.print(USAGE); - System.exit(1); - } - } else { + if (!arg.startsWith("-")) { break; } + if (arg.equals("-r")) { + receiveFile = true; + } else if (arg.equals("-s")) { + receiveFile = false; + } else if (arg.equals("-a")) { + transferMode = TFTP.ASCII_MODE; + } else if (arg.equals("-b")) { + transferMode = TFTP.BINARY_MODE; + } else if (arg.equals("-t")) { + timeout = 1000 * Integer.parseInt(args[++argc]); + } else if (arg.equals("-v")) { + verbose = true; + } else { + System.err.println("Error: unrecognized option."); + System.err.print(USAGE); + System.exit(1); + } } // Make sure there are enough arguments - if (args.length - argc != 3) - { + if (args.length - argc != 3) { System.err.println("Error: invalid number of arguments."); System.err.print(USAGE); System.exit(1); @@ -117,7 +112,7 @@ public final class TFTPExample if (verbose) { tftp = new TFTPClient() { @Override - protected void trace(String direction, TFTPPacket packet) { + protected void trace(final String direction, final TFTPPacket packet) { System.out.println(direction + " " + packet); } }; @@ -132,15 +127,14 @@ public final class TFTPExample closed = false; // If we're receiving a file, receive, otherwise send. - if (receiveFile) - { + if (receiveFile) { closed = receive(transferMode, hostname, localFilename, remoteFilename, tftp); } else { // We're sending a file closed = send(transferMode, hostname, localFilename, remoteFilename, tftp); } - System.out.println("Recd: "+tftp.getTotalBytesReceived()+" Sent: "+tftp.getTotalBytesSent()); + System.out.println("Recd: " + tftp.getTotalBytesReceived() + " Sent: " + tftp.getTotalBytesSent()); if (!closed) { System.out.println("Failed"); @@ -150,112 +144,55 @@ public final class TFTPExample System.out.println("OK"); } - private static boolean send(int transferMode, String hostname, String localFilename, String remoteFilename, - TFTPClient tftp) { - boolean closed; - FileInputStream input = null; - - // Try to open local file for reading - try - { - input = new FileInputStream(localFilename); - } - catch (IOException e) - { - tftp.close(); - System.err.println("Error: could not open local file for reading."); - System.err.println(e.getMessage()); - System.exit(1); - } - - open(tftp); - - // Try to send local file via TFTP - try - { - String [] parts = hostname.split(":"); - if (parts.length == 2) { - tftp.sendFile(remoteFilename, transferMode, input, parts[0], Integer.parseInt(parts[1])); - } else { - tftp.sendFile(remoteFilename, transferMode, input, hostname); - } - } - catch (UnknownHostException e) - { - System.err.println("Error: could not resolve hostname."); - System.err.println(e.getMessage()); - System.exit(1); - } - catch (IOException e) - { - System.err.println("Error: I/O exception occurred while sending file."); - System.err.println(e.getMessage()); - System.exit(1); - } - finally - { - // Close local socket and input file - closed = close(tftp, input); + private static void open(final TFTPClient tftp) { + try { + tftp.open(); + } catch (final SocketException e) { + throw new RuntimeException("Error: could not open local UDP socket.", e); } - - return closed; } - private static boolean receive(int transferMode, String hostname, String localFilename, String remoteFilename, - TFTPClient tftp) { - boolean closed; + private static boolean receive(final int transferMode, final String hostname, final String localFilename, final String remoteFilename, + final TFTPClient tftp) { + final boolean closed; FileOutputStream output = null; - File file; + final File file; file = new File(localFilename); // If file exists, don't overwrite it. - if (file.exists()) - { + if (file.exists()) { System.err.println("Error: " + localFilename + " already exists."); - System.exit(1); + return false; } // Try to open local file for writing - try - { + try { output = new FileOutputStream(file); - } - catch (IOException e) - { + } catch (final IOException e) { tftp.close(); - System.err.println("Error: could not open local file for writing."); - System.err.println(e.getMessage()); - System.exit(1); + throw new RuntimeException("Error: could not open local file for writing.", e); } open(tftp); // Try to receive remote file via TFTP - try - { - String [] parts = hostname.split(":"); + try { + final String[] parts = hostname.split(":"); if (parts.length == 2) { tftp.receiveFile(remoteFilename, transferMode, output, parts[0], Integer.parseInt(parts[1])); } else { tftp.receiveFile(remoteFilename, transferMode, output, hostname); } - } - catch (UnknownHostException e) - { + } catch (final UnknownHostException e) { System.err.println("Error: could not resolve hostname."); System.err.println(e.getMessage()); System.exit(1); - } - catch (IOException e) - { - System.err.println( - "Error: I/O exception occurred while receiving file."); + } catch (final IOException e) { + System.err.println("Error: I/O exception occurred while receiving file."); System.err.println(e.getMessage()); System.exit(1); - } - finally - { + } finally { // Close local socket and output file closed = close(tftp, output); } @@ -263,38 +200,42 @@ public final class TFTPExample return closed; } - private static boolean close(TFTPClient tftp, Closeable output) { - boolean closed; - tftp.close(); - try - { - if (output != null) { - output.close(); - } - closed = true; - } - catch (IOException e) - { - closed = false; - System.err.println("Error: error closing file."); - System.err.println(e.getMessage()); - } - return closed; - } + private static boolean send(final int transferMode, final String hostname, final String localFilename, final String remoteFilename, final TFTPClient tftp) { + final boolean closed; + FileInputStream input = null; - private static void open(TFTPClient tftp) { - try - { - tftp.open(); + // Try to open local file for reading + try { + input = new FileInputStream(localFilename); + } catch (final IOException e) { + tftp.close(); + throw new RuntimeException("Error: could not open local file for reading.", e); } - catch (SocketException e) - { - System.err.println("Error: could not open local UDP socket."); + + open(tftp); + + // Try to send local file via TFTP + try { + final String[] parts = hostname.split(":"); + if (parts.length == 2) { + tftp.sendFile(remoteFilename, transferMode, input, parts[0], Integer.parseInt(parts[1])); + } else { + tftp.sendFile(remoteFilename, transferMode, input, hostname); + } + } catch (final UnknownHostException e) { + System.err.println("Error: could not resolve hostname."); + System.err.println(e.getMessage()); + System.exit(1); + } catch (final IOException e) { + System.err.println("Error: I/O exception occurred while sending file."); System.err.println(e.getMessage()); System.exit(1); + } finally { + // Close local socket and input file + closed = close(tftp, input); } + + return closed; } } - - diff --git a/src/main/java/examples/mail/IMAPExportMbox.java b/src/main/java/org/apache/commons/net/examples/mail/IMAPExportMbox.java similarity index 60% rename from src/main/java/examples/mail/IMAPExportMbox.java rename to src/main/java/org/apache/commons/net/examples/mail/IMAPExportMbox.java index a35a354..d2b171c 100644 --- a/src/main/java/examples/mail/IMAPExportMbox.java +++ b/src/main/java/org/apache/commons/net/examples/mail/IMAPExportMbox.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.mail; +package org.apache.commons.net.examples.mail; import java.io.BufferedWriter; import java.io.File; @@ -30,19 +30,20 @@ import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.TimeZone; +import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.net.PrintCommandListener; import org.apache.commons.net.ProtocolCommandEvent; -import org.apache.commons.net.imap.IMAP.IMAPChunkListener; import org.apache.commons.net.imap.IMAP; +import org.apache.commons.net.imap.IMAP.IMAPChunkListener; import org.apache.commons.net.imap.IMAPClient; import org.apache.commons.net.imap.IMAPReply; /** - * This is an example program demonstrating how to use the IMAP[S]Client class. - * This program connects to a IMAP[S] server and exports selected messages from a folder into an mbox file. + * This is an example program demonstrating how to use the IMAP[S]Client class. This program connects to a IMAP[S] server and exports selected messages from a + * folder into an mbox file. * <p> * Usage: IMAPExportMbox imap[s]://user:password@host[:port]/folder/path <mboxfile> [sequence-set] [item-names] * <p> @@ -71,26 +72,151 @@ import org.apache.commons.net.imap.IMAPReply; * IMAPExportMbox imaps://username:password@imap.googlemail.com/messages_for_export exported.mbox 3 ENVELOPE X-GM-LABELS<br> * <p> * The sequence-set is passed unmodified to the FETCH command.<br> - * The item names are wrapped in parentheses if more than one is provided. - * Otherwise, the parameter is assumed to be wrapped if necessary.<br> + * The item names are wrapped in parentheses if more than one is provided. Otherwise, the parameter is assumed to be wrapped if necessary.<br> * Parameters with spaces must be quoted otherwise the OS shell will normally treat them as separate parameters.<br> - * Also the listener that writes the mailbox only captures the multi-line responses (e.g. ones that include BODY references). - * It does not capture the output from FETCH commands using item names such as ENVELOPE or FLAGS that return a single line response. + * Also the listener that writes the mailbox only captures the multi-line responses (e.g. ones that include BODY references). It does not capture the output + * from FETCH commands using item names such as ENVELOPE or FLAGS that return a single line response. */ -public final class IMAPExportMbox -{ +public final class IMAPExportMbox { + + private static class MboxListener implements IMAPChunkListener { + + private final BufferedWriter bufferedWriter; + volatile AtomicInteger total = new AtomicInteger(); + volatile String lastFetched; + volatile List<String> missingIds = new ArrayList<>(); + volatile long lastSeq = -1; + private final String lineSeparator; + private final SimpleDateFormat DATE_FORMAT // for mbox From_ lines + = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy"); + + // e.g. INTERNALDATE "27-Oct-2013 07:43:24 +0000" + // for parsing INTERNALDATE + private final SimpleDateFormat IDPARSE = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z"); + private final boolean printHash; + private final boolean printMarker; + private final boolean checkSequence; + + MboxListener(final BufferedWriter bufferedWriter, final String lineSeparator, final boolean printHash, final boolean printMarker, + final boolean checkSequence) { + this.lineSeparator = lineSeparator; + this.printHash = printHash; + this.printMarker = printMarker; + DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT")); + this.bufferedWriter = bufferedWriter; + this.checkSequence = checkSequence; + } + + @Override + public boolean chunkReceived(final IMAP imap) { + final String[] replyStrings = imap.getReplyStrings(); + Date received = new Date(); + final String firstLine = replyStrings[0]; + Matcher m = PATID.matcher(firstLine); + if (m.lookingAt()) { // found a match + final String date = m.group(PATID_DATE_GROUP); + try { + received = IDPARSE.parse(date); + } catch (final ParseException e) { + System.err.println(e); + } + } else { + System.err.println("No timestamp found in: " + firstLine + " - using current time"); + } + String replyTo = "MAILER-DAEMON"; // default + for (int i = 1; i < replyStrings.length - 1; i++) { + final String line = replyStrings[i]; + if (line.startsWith("Return-Path: ")) { + final String[] parts = line.split(" ", 2); + if (!parts[1].equals("<>")) {// Don't replace default with blank + replyTo = parts[1]; + if (replyTo.startsWith("<")) { + if (replyTo.endsWith(">")) { + replyTo = replyTo.substring(1, replyTo.length() - 1); // drop <> wrapper + } else { + System.err.println("Unexpected Return-path: '" + line + "' in " + firstLine); + } + } + } + break; + } + } + try { + // Add initial mbox header line + bufferedWriter.append("From "); + bufferedWriter.append(replyTo); + bufferedWriter.append(' '); + bufferedWriter.append(DATE_FORMAT.format(received)); + bufferedWriter.append(lineSeparator); + // Debug + bufferedWriter.append("X-IMAP-Response: ").append(firstLine).append(lineSeparator); + if (printMarker) { + System.err.println("[" + total + "] " + firstLine); + } + // Skip first and last lines + for (int i = 1; i < replyStrings.length - 1; i++) { + final String line = replyStrings[i]; + if (startsWith(line, PATFROM)) { + bufferedWriter.append('>'); // Escape a From_ line + } + bufferedWriter.append(line); + bufferedWriter.append(lineSeparator); + } + // The last line ends with the trailing closing ")" which needs to be stripped + final String lastLine = replyStrings[replyStrings.length - 1]; + final int lastLength = lastLine.length(); + if (lastLength > 1) { // there's some content, we need to save it + bufferedWriter.append(lastLine, 0, lastLength - 1); + bufferedWriter.append(lineSeparator); + } + bufferedWriter.append(lineSeparator); // blank line between entries + } catch (final IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); // chunkReceived cannot throw a checked Exception + } + lastFetched = firstLine; + total.incrementAndGet(); + if (checkSequence) { + m = PATSEQ.matcher(firstLine); + if (m.lookingAt()) { // found a match + final long msgSeq = Long.parseLong(m.group(PATSEQ_SEQUENCE_GROUP)); // Cannot fail to parse + if (lastSeq != -1) { + final long missing = msgSeq - lastSeq - 1; + if (missing != 0) { + for (long j = lastSeq + 1; j < msgSeq; j++) { + missingIds.add(String.valueOf(j)); + } + System.err.println("*** Sequence error: current=" + msgSeq + " previous=" + lastSeq + " Missing=" + missing); + } + } + lastSeq = msgSeq; + } + } + if (printHash) { + System.err.print("."); + } + return true; + } + + public void close() throws IOException { + if (bufferedWriter != null) { + bufferedWriter.close(); + } + } + } private static final String CRLF = "\r\n"; private static final String LF = "\n"; - private static final String EOL_DEFAULT = System.getProperty("line.separator"); + private static final String EOL_DEFAULT = System.lineSeparator(); private static final Pattern PATFROM = Pattern.compile(">*From "); // unescaped From_ - // e.g. * nnn (INTERNALDATE "27-Oct-2013 07:43:24 +0000" BODY[] {nn} ...) + // e.g. * nnn (INTERNALDATE "27-Oct-2013 07:43:24 +0000" BODY[] {nn} ...) private static final Pattern PATID = // INTERNALDATE Pattern.compile(".*INTERNALDATE \"(\\d\\d-\\w{3}-\\d{4} \\d\\d:\\d\\d:\\d\\d [+-]\\d+)\""); - private static final int PATID_DATE_GROUP = 1; + private static final int PATID_DATE_GROUP = 1; private static final Pattern PATSEQ = Pattern.compile("\\* (\\d+) "); // Sequence number + private static final int PATSEQ_SEQUENCE_GROUP = 1; // e.g. * 382 EXISTS @@ -98,12 +224,11 @@ public final class IMAPExportMbox // AAAC NO [TEMPFAIL] FETCH Temporary failure on server [CODE: WBL] private static final Pattern PATTEMPFAIL = Pattern.compile("[A-Z]{4} NO \\[TEMPFAIL\\] FETCH .*"); - private static final int CONNECT_TIMEOUT = 10; // Seconds + private static final int READ_TIMEOUT = 10; - public static void main(String[] args) throws IOException, URISyntaxException - { + public static void main(final String[] args) throws IOException, URISyntaxException { int connect_timeout = CONNECT_TIMEOUT; int read_timeout = READ_TIMEOUT; @@ -113,7 +238,7 @@ public final class IMAPExportMbox boolean printMarker = false; int retryWaitSecs = 0; - for(argIdx = 0; argIdx < args.length; argIdx++) { + for (argIdx = 0; argIdx < args.length; argIdx++) { if (args[argIdx].equals("-c")) { connect_timeout = Integer.parseInt(args[++argIdx]); } else if (args[argIdx].equals("-r")) { @@ -135,10 +260,9 @@ public final class IMAPExportMbox final int argCount = args.length - argIdx; - if (argCount < 2) - { - System.err.println("Usage: IMAPExportMbox [-LF|-CRLF] [-c n] [-r n] [-R n] [-.] [-X]" + - " imap[s]://user:password@host[:port]/folder/path [+|-]<mboxfile> [sequence-set] [itemnames]"); + if (argCount < 2) { + System.err.println("Usage: IMAPExportMbox [-LF|-CRLF] [-c n] [-r n] [-R n] [-.] [-X]" + + " imap[s]://user:password@host[:port]/folder/path [+|-]<mboxfile> [sequence-set] [itemnames]"); System.err.println("\t-LF | -CRLF set end-of-line to LF or CRLF (default is the line.separator system property)"); System.err.println("\t-c connect timeout in seconds (default 10)"); System.err.println("\t-r read timeout in seconds (default 10)"); @@ -146,36 +270,35 @@ public final class IMAPExportMbox System.err.println("\t-. print a . for each complete message received"); System.err.println("\t-X print the X-IMAP line for each complete message received"); System.err.println("\tthe mboxfile is where the messages are stored; use '-' to write to standard output."); - System.err.println("\tPrefix filename with '+' to append to the file. Prefix with '-' to allow overwrite."); + System.err.println("\tPrefix file name with '+' to append to the file. Prefix with '-' to allow overwrite."); System.err.println("\ta sequence-set is a list of numbers/number ranges e.g. 1,2,3-10,20:* - default 1:*"); - System.err.println("\titemnames are the message data item name(s) e.g. BODY.PEEK[HEADER.FIELDS (SUBJECT)]" + - " or a macro e.g. ALL - default (INTERNALDATE BODY.PEEK[])"); + System.err.println("\titemnames are the message data item name(s) e.g. BODY.PEEK[HEADER.FIELDS (SUBJECT)]" + + " or a macro e.g. ALL - default (INTERNALDATE BODY.PEEK[])"); System.exit(1); } final String uriString = args[argIdx++]; URI uri; try { - uri = URI.create(uriString); - } catch(IllegalArgumentException e) { // cannot parse the path as is; let's pull it apart and try again - Matcher m = Pattern.compile("(imaps?://[^/]+)(/.*)").matcher(uriString); - if (m.matches()) { - uri = URI.create(m.group(1)); // Just the scheme and auth parts - uri = new URI(uri.getScheme(), uri.getAuthority(), m.group(2), null, null); - } else { + uri = URI.create(uriString); + } catch (final IllegalArgumentException e) { // cannot parse the path as is; let's pull it apart and try again + final Matcher m = Pattern.compile("(imaps?://[^/]+)(/.*)").matcher(uriString); + if (!m.matches()) { throw e; } + uri = URI.create(m.group(1)); // Just the scheme and auth parts + uri = new URI(uri.getScheme(), uri.getAuthority(), m.group(2), null, null); } - final String file = args[argIdx++]; + final String file = args[argIdx++]; String sequenceSet = argCount > 2 ? args[argIdx++] : "1:*"; final String itemNames; // Handle 0, 1 or multiple item names if (argCount > 3) { if (argCount > 4) { - StringBuilder sb = new StringBuilder(); + final StringBuilder sb = new StringBuilder(); sb.append("("); - for(int i=4; i <= argCount; i++) { - if (i>4) { + for (int i = 4; i <= argCount; i++) { + if (i > 4) { sb.append(" "); } sb.append(args[argIdx++]); @@ -190,39 +313,37 @@ public final class IMAPExportMbox } final boolean checkSequence = sequenceSet.matches("\\d+:(\\d+|\\*)"); // are we expecting a sequence? - final MboxListener chunkListener; + final MboxListener mboxListener; if (file.equals("-")) { - chunkListener = null; + mboxListener = null; } else if (file.startsWith("+")) { final File mbox = new File(file.substring(1)); System.out.println("Appending to file " + mbox); - chunkListener = new MboxListener( - new BufferedWriter(new FileWriter(mbox, true)), eol, printHash, printMarker, checkSequence); + mboxListener = new MboxListener(new BufferedWriter(new FileWriter(mbox, true)), eol, printHash, printMarker, checkSequence); } else if (file.startsWith("-")) { final File mbox = new File(file.substring(1)); System.out.println("Writing to file " + mbox); - chunkListener = new MboxListener( - new BufferedWriter(new FileWriter(mbox, false)), eol, printHash, printMarker, checkSequence); + mboxListener = new MboxListener(new BufferedWriter(new FileWriter(mbox, false)), eol, printHash, printMarker, checkSequence); } else { - final File mbox = new File(file); - if (mbox.exists() && mbox.length() > 0) { - throw new IOException("mailbox file: " + mbox + " already exists and is non-empty!"); + final File mboxFile = new File(file); + if (mboxFile.exists() && mboxFile.length() > 0) { + throw new IOException("mailbox file: " + mboxFile + " already exists and is non-empty!"); } - System.out.println("Creating file " + mbox); - chunkListener = new MboxListener(new BufferedWriter(new FileWriter(mbox)), eol, printHash, printMarker, checkSequence); + System.out.println("Creating file " + mboxFile); + mboxListener = new MboxListener(new BufferedWriter(new FileWriter(mboxFile)), eol, printHash, printMarker, checkSequence); } - String path = uri.getPath(); + final String path = uri.getPath(); if (path == null || path.length() < 1) { throw new IllegalArgumentException("Invalid folderPath: '" + path + "'"); } - String folder = path.substring(1); // skip the leading / + final String folder = path.substring(1); // skip the leading / // suppress login details final PrintCommandListener listener = new PrintCommandListener(System.out, true) { @Override - public void protocolReplyReceived(ProtocolCommandEvent event) { - if (event.getReplyCode() != IMAPReply.PARTIAL){ // This is dealt with by the chunk listener + public void protocolReplyReceived(final ProtocolCommandEvent event) { + if (event.getReplyCode() != IMAPReply.PARTIAL) { // This is dealt with by the chunk listener super.protocolReplyReceived(event); } } @@ -237,49 +358,45 @@ public final class IMAPExportMbox imap.setSoTimeout(read_timeout * 1000); - if (!imap.select(folder)){ + if (!imap.select(folder)) { throw new IOException("Could not select folder: " + folder); } - for(String line : imap.getReplyStrings()) { + for (final String line : imap.getReplyStrings()) { maxIndexInFolder = matches(line, PATEXISTS, 1); if (maxIndexInFolder != null) { break; } } - if (chunkListener != null) { - imap.setChunkListener(chunkListener); + if (mboxListener != null) { + imap.setChunkListener(mboxListener); } // else the command listener displays the full output without processing - - while(true) { - boolean ok = imap.fetch(sequenceSet, itemNames); + while (true) { + final boolean ok = imap.fetch(sequenceSet, itemNames); // If the fetch failed, can we retry? - if (!ok && retryWaitSecs > 0 && chunkListener != null && checkSequence) { - final String replyString = imap.getReplyString(); //includes EOL - if (startsWith(replyString, PATTEMPFAIL)) { - System.err.println("Temporary error detected, will retry in " + retryWaitSecs + "seconds"); - sequenceSet = (chunkListener.lastSeq+1)+":*"; - try { - Thread.sleep(retryWaitSecs * 1000); - } catch (InterruptedException e) { - // ignored - } - } else { - throw new IOException("FETCH " + sequenceSet + " " + itemNames+ " failed with " + replyString); - } - } else { + if (ok || (retryWaitSecs <= 0) || (mboxListener == null) || !checkSequence) { break; } + final String replyString = imap.getReplyString(); // includes EOL + if (!startsWith(replyString, PATTEMPFAIL)) { + throw new IOException("FETCH " + sequenceSet + " " + itemNames + " failed with " + replyString); + } + System.err.println("Temporary error detected, will retry in " + retryWaitSecs + "seconds"); + sequenceSet = mboxListener.lastSeq + 1 + ":*"; + try { + Thread.sleep(retryWaitSecs * 1000); + } catch (final InterruptedException e) { + // ignored + } } - } catch (IOException ioe) { - String count = chunkListener == null ? "?" : Integer.toString(chunkListener.total); - System.err.println( - "FETCH " + sequenceSet + " " + itemNames + " failed after processing " + count + " complete messages "); - if (chunkListener != null) { - System.err.println("Last complete response seen: "+chunkListener.lastFetched); + } catch (final IOException ioe) { + final String count = mboxListener == null ? "?" : mboxListener.total.toString(); + System.err.println("FETCH " + sequenceSet + " " + itemNames + " failed after processing " + count + " complete messages "); + if (mboxListener != null) { + System.err.println("Last complete response seen: " + mboxListener.lastFetched); } throw ioe; } finally { @@ -288,12 +405,12 @@ public final class IMAPExportMbox System.err.println(); } - if (chunkListener != null) { - chunkListener.close(); - final Iterator<String> missingIds = chunkListener.missingIds.iterator(); + if (mboxListener != null) { + mboxListener.close(); + final Iterator<String> missingIds = mboxListener.missingIds.iterator(); if (missingIds.hasNext()) { - StringBuilder sb = new StringBuilder(); - for(;;) { + final StringBuilder sb = new StringBuilder(); + for (;;) { sb.append(missingIds.next()); if (!missingIds.hasNext()) { break; @@ -306,147 +423,24 @@ public final class IMAPExportMbox imap.logout(); imap.disconnect(); } - if (chunkListener != null) { - System.out.println("Processed " + chunkListener.total + " messages."); + if (mboxListener != null) { + System.out.println("Processed " + mboxListener.total + " messages."); } if (maxIndexInFolder != null) { System.out.println("Folder contained " + maxIndexInFolder + " messages."); } } - private static boolean startsWith(String input, Pattern pat) { - Matcher m = pat.matcher(input); - return m.lookingAt(); - } - - private static String matches(String input, Pattern pat, int index) { - Matcher m = pat.matcher(input); + private static String matches(final String input, final Pattern pat, final int index) { + final Matcher m = pat.matcher(input); if (m.lookingAt()) { return m.group(index); } return null; } - private static class MboxListener implements IMAPChunkListener { - - private final BufferedWriter bw; - volatile int total = 0; - volatile String lastFetched; - volatile List<String> missingIds = new ArrayList<String>(); - volatile long lastSeq = -1; - private final String eol; - private final SimpleDateFormat DATE_FORMAT // for mbox From_ lines - = new SimpleDateFormat("EEE MMM dd HH:mm:ss YYYY"); - - // e.g. INTERNALDATE "27-Oct-2013 07:43:24 +0000" - private final SimpleDateFormat IDPARSE // for parsing INTERNALDATE - = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z"); - private final boolean printHash; - private final boolean printMarker; - private final boolean checkSequence; - - MboxListener(BufferedWriter bw, String eol, boolean printHash, boolean printMarker, boolean checkSequence) - throws IOException { - this.eol = eol; - this.printHash = printHash; - this.printMarker = printMarker; - DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT")); - this.bw = bw; - this.checkSequence = checkSequence; - } - - @Override - public boolean chunkReceived(IMAP imap) { - final String[] replyStrings = imap.getReplyStrings(); - Date received = new Date(); - final String firstLine = replyStrings[0]; - Matcher m = PATID.matcher(firstLine); - if (m.lookingAt()) { // found a match - String date = m.group(PATID_DATE_GROUP); - try { - received=IDPARSE.parse(date); - } catch (ParseException e) { - System.err.println(e); - } - } else { - System.err.println("No timestamp found in: " + firstLine + " - using current time"); - } - String replyTo = "MAILER-DAEMON"; // default - for(int i=1; i< replyStrings.length - 1; i++) { - final String line = replyStrings[i]; - if (line.startsWith("Return-Path: ")) { - String[] parts = line.split(" ", 2); - replyTo = parts[1]; - if (replyTo.startsWith("<")) { - replyTo = replyTo.substring(1,replyTo.length()-1); // drop <> wrapper - } else { - System.err.println("Unexpected Return-path:" + line+ " in " + firstLine); - } - break; - } - } - try { - // Add initial mbox header line - bw.append("From "); - bw.append(replyTo); - bw.append(' '); - bw.append(DATE_FORMAT.format(received)); - bw.append(eol); - // Debug - bw.append("X-IMAP-Response: ").append(firstLine).append(eol); - if (printMarker) { - System.err.println("[" + total + "] " + firstLine); - } - // Skip first and last lines - for(int i=1; i< replyStrings.length - 1; i++) { - final String line = replyStrings[i]; - if (startsWith(line, PATFROM)) { - bw.append('>'); // Escape a From_ line - } - bw.append(line); - bw.append(eol); - } - // The last line ends with the trailing closing ")" which needs to be stripped - String lastLine = replyStrings[replyStrings.length-1]; - final int lastLength = lastLine.length(); - if (lastLength > 1) { // there's some content, we need to save it - bw.append(lastLine, 0, lastLength-1); - bw.append(eol); - } - bw.append(eol); // blank line between entries - } catch (IOException e) { - e.printStackTrace(); - throw new RuntimeException(e); // chunkReceived cannot throw a checked Exception - } - lastFetched = firstLine; - total++; - if (checkSequence) { - m = PATSEQ.matcher(firstLine); - if (m.lookingAt()) { // found a match - final long msgSeq = Long.parseLong(m.group(PATSEQ_SEQUENCE_GROUP)); // Cannot fail to parse - if (lastSeq != -1) { - long missing = msgSeq - lastSeq - 1; - if (missing != 0) { - for(long j = lastSeq + 1; j < msgSeq; j++) { - missingIds.add(String.valueOf(j)); - } - System.err.println( - "*** Sequence error: current=" + msgSeq + " previous=" + lastSeq + " Missing=" + missing); - } - } - lastSeq = msgSeq; - } - } - if (printHash) { - System.err.print("."); - } - return true; - } - - public void close() throws IOException { - if (bw != null) { - bw.close(); - } - } + private static boolean startsWith(final String input, final Pattern pat) { + final Matcher m = pat.matcher(input); + return m.lookingAt(); } } diff --git a/src/main/java/examples/mail/IMAPImportMbox.java b/src/main/java/org/apache/commons/net/examples/mail/IMAPImportMbox.java similarity index 62% rename from src/main/java/examples/mail/IMAPImportMbox.java rename to src/main/java/org/apache/commons/net/examples/mail/IMAPImportMbox.java index 3dca79b..3748351 100644 --- a/src/main/java/examples/mail/IMAPImportMbox.java +++ b/src/main/java/org/apache/commons/net/examples/mail/IMAPImportMbox.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.mail; +package org.apache.commons.net.examples.mail; import java.io.BufferedReader; import java.io.File; @@ -31,8 +31,8 @@ import java.util.regex.Pattern; import org.apache.commons.net.imap.IMAPClient; /** - * This is an example program demonstrating how to use the IMAP[S]Client class. - * This program connects to a IMAP[S] server and imports messages into the folder from an mbox file. + * This is an example program demonstrating how to use the IMAP[S]Client class. This program connects to a IMAP[S] server and imports messages into the folder + * from an mbox file. * <p> * Usage: IMAPImportMbox imap[s]://user:password@host[:port]/folder/path <mboxfile> [selectors] * <p> @@ -45,48 +45,74 @@ import org.apache.commons.net.imap.IMAPClient; * For example:<br> * IMAPImportMbox imaps://user:pass@imap.googlemail.com/imported_messages 201401.mbox 1-10,20 -142986- */ -public final class IMAPImportMbox -{ +public final class IMAPImportMbox { private static final String CRLF = "\r\n"; private static final Pattern PATFROM = Pattern.compile(">+From "); // escaped From - public static void main(String[] args) throws IOException - { - if (args.length < 2) - { + private static String getDate(final String msg) { + // From SENDER Fri Sep 13 17:04:01 2019 + final Pattern FROM_RE = Pattern.compile("From \\S+ +\\S+ (\\S+) ?(\\S+) (\\S+) (\\S+)"); + // [Fri] Sep 13 HMS 2019 + // output date: 13-Sep-2019 17:04:01 +0000 + String date = null; + final Matcher m = FROM_RE.matcher(msg); + if (m.lookingAt()) { + date = m.group(2) + "-" + m.group(1) + "-" + m.group(4) + " " + m.group(3) + " +0000"; + } + return date; + } + + /** + * Is at least one entry in the list contained in the string? + * + * @param contains the list of strings to look for + * @param string the String to check against + * @return true if at least one entry in the contains list is contained in the string + */ + private static boolean listContains(final List<String> contains, final String string) { + for (final String entry : contains) { + if (string.contains(entry)) { + return true; + } + } + return false; + } + + public static void main(final String[] args) throws IOException { + if (args.length < 2) { System.err.println("Usage: IMAPImportMbox imap[s]://user:password@host[:port]/folder/path <mboxfile> [selectors]"); - System.err.println("\tWhere: a selector is a list of numbers/number ranges - 1,2,3-10" + - " - or a list of strings to match in the initial From line"); + System.err + .println("\tWhere: a selector is a list of numbers/number ranges - 1,2,3-10" + " - or a list of strings to match in the initial From line"); System.exit(1); } - final URI uri = URI.create(args[0]); - final String file = args[1]; + final URI uri = URI.create(args[0]); + final String file = args[1]; final File mbox = new File(file); if (!mbox.isFile() || !mbox.canRead()) { throw new IOException("Cannot read mailbox file: " + mbox); } - String path = uri.getPath(); + final String path = uri.getPath(); if (path == null || path.length() < 1) { throw new IllegalArgumentException("Invalid folderPath: '" + path + "'"); } - String folder = path.substring(1); // skip the leading / + final String folder = path.substring(1); // skip the leading / - List<String> contains = new ArrayList<String>(); // list of strings to find - BitSet msgNums = new BitSet(); // list of message numbers + final List<String> contains = new ArrayList<>(); // list of strings to find + final BitSet msgNums = new BitSet(); // list of message numbers - for(int i = 2; i < args.length; i++) { - String arg = args[i]; + for (int i = 2; i < args.length; i++) { + final String arg = args[i]; if (arg.matches("\\d+(-\\d+)?(,\\d+(-\\d+)?)*")) { // number,m-n - for(String entry : arg.split(",")) { - String []parts = entry.split("-"); + for (final String entry : arg.split(",")) { + final String[] parts = entry.split("-"); if (parts.length == 2) { // m-n - int low = Integer.parseInt(parts[0]); - int high = Integer.parseInt(parts[1]); - for(int j=low; j <= high; j++) { + final int low = Integer.parseInt(parts[0]); + final int high = Integer.parseInt(parts[1]); + for (int j = low; j <= high; j++) { msgNums.set(j); } } else { @@ -111,15 +137,15 @@ public final class IMAPImportMbox final BufferedReader br = new BufferedReader(new FileReader(file)); // TODO charset? String line; - StringBuilder sb = new StringBuilder(); + final StringBuilder sb = new StringBuilder(); boolean wanted = false; // Skip any leading rubbish - while((line=br.readLine())!=null) { + while ((line = br.readLine()) != null) { if (line.startsWith("From ")) { // start of message; i.e. end of previous (if any) if (process(sb, imap, folder, total)) { // process previous message (if any) loaded++; } sb.setLength(0); - total ++; + total++; wanted = wanted(total, line, msgNums, contains); } else if (startsWith(line, PATFROM)) { // Unescape ">+From " in body text line = line.substring(1); @@ -134,8 +160,8 @@ public final class IMAPImportMbox if (wanted && process(sb, imap, folder, total)) { // last message (if any) loaded++; } - } catch (IOException e) { - System.out.println(imap.getReplyString()); + } catch (final IOException e) { + System.out.println("Error processing msg: " + total + " " + imap.getReplyString()); e.printStackTrace(); System.exit(10); return; @@ -146,54 +172,38 @@ public final class IMAPImportMbox System.out.println("Processed " + total + " messages, loaded " + loaded); } - private static boolean startsWith(String input, Pattern pat) { - Matcher m = pat.matcher(input); - return m.lookingAt(); - } - - private static boolean process(final StringBuilder sb, final IMAPClient imap, final String folder - ,final int msgNum) throws IOException { + private static boolean process(final StringBuilder sb, final IMAPClient imap, final String folder, final int msgNum) throws IOException { final int length = sb.length(); - boolean haveMessage = length > 2; + final boolean haveMessage = length > 2; if (haveMessage) { - System.out.println("MsgNum: " + msgNum +" Length " + length); - sb.setLength(length-2); // drop trailing CRLF - String msg = sb.toString(); - if (!imap.append(folder, null, null, msg)) { + System.out.println("MsgNum: " + msgNum + " Length " + length); + sb.setLength(length - 2); // drop trailing CRLF (mbox format has trailing blank line) + final String msg = sb.toString(); + if (!imap.append(folder, null, getDate(msg), msg)) { throw new IOException("Failed to import message: " + msgNum + " " + imap.getReplyString()); } } return haveMessage; } + private static boolean startsWith(final String input, final Pattern pat) { + final Matcher m = pat.matcher(input); + return m.lookingAt(); + } + /** * Is the message wanted? * - * @param msgNum the message number - * @param line the From line - * @param msgNums the list of wanted message numbers + * @param msgNum the message number + * @param line the From line + * @param msgNums the list of wanted message numbers * @param contains the list of strings to be contained * @return true if the message is wanted */ - private static boolean wanted(int msgNum, String line, BitSet msgNums, List<String> contains) { + private static boolean wanted(final int msgNum, final String line, final BitSet msgNums, final List<String> contains) { return (msgNums.isEmpty() && contains.isEmpty()) // no selectors - || msgNums.get(msgNum) // matches message number - || listContains(contains, line); // contains string - } - - /** - * Is at least one entry in the list contained in the string? - * @param contains the list of strings to look for - * @param string the String to check against - * @return true if at least one entry in the contains list is contained in the string - */ - private static boolean listContains(List<String> contains, String string) { - for(String entry : contains) { - if (string.contains(entry)) { - return true; - } - } - return false; + || msgNums.get(msgNum) // matches message number + || listContains(contains, line); // contains string } } diff --git a/src/main/java/examples/mail/IMAPMail.java b/src/main/java/org/apache/commons/net/examples/mail/IMAPMail.java similarity index 77% rename from src/main/java/examples/mail/IMAPMail.java rename to src/main/java/org/apache/commons/net/examples/mail/IMAPMail.java index 986c612..7ad05b0 100644 --- a/src/main/java/examples/mail/IMAPMail.java +++ b/src/main/java/org/apache/commons/net/examples/mail/IMAPMail.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.mail; +package org.apache.commons.net.examples.mail; import java.io.IOException; import java.net.URI; @@ -24,9 +24,8 @@ import org.apache.commons.net.PrintCommandListener; import org.apache.commons.net.imap.IMAPClient; /** - * This is an example program demonstrating how to use the IMAP[S]Client class. - * This program connects to a IMAP[S] server, lists its capabilities and shows - * the status of the Inbox. + * This is an example program demonstrating how to use the IMAP[S]Client class. This program connects to a IMAP[S] server, lists its capabilities and shows the + * status of the Inbox. * <p> * Usage: IMAPMail imap[s]://username:password@server/ * <p> @@ -35,19 +34,16 @@ import org.apache.commons.net.imap.IMAPClient; * or<br> * IMAPMail imaps://username:password@imap.googlemail.com/ */ -public final class IMAPMail -{ - - public static void main(String[] args) throws IOException { - if (args.length != 1) - { - System.err.println( - "Usage: IMAPMail imap[s]://username:password@server/"); +public final class IMAPMail { + + public static void main(final String[] args) throws IOException { + if (args.length != 1) { + System.err.println("Usage: IMAPMail imap[s]://username:password@server/"); System.err.println("Connects to server; lists capabilities and shows Inbox status"); System.exit(1); } - URI uri = URI.create(args[0]); + final URI uri = URI.create(args[0]); // Connect and login final IMAPClient imap = IMAPUtils.imapLogin(uri, 10000, null); @@ -64,9 +60,11 @@ public final class IMAPMail imap.examine("inbox"); - imap.status("inbox", new String[]{"MESSAGES"}); + imap.status("inbox", new String[] { "MESSAGES" }); + + imap.list("", "*"); // Show the folders - } catch (IOException e) { + } catch (final IOException e) { System.out.println(imap.getReplyString()); e.printStackTrace(); System.exit(10); diff --git a/src/main/java/examples/mail/IMAPUtils.java b/src/main/java/org/apache/commons/net/examples/mail/IMAPUtils.java similarity index 85% rename from src/main/java/examples/mail/IMAPUtils.java rename to src/main/java/org/apache/commons/net/examples/mail/IMAPUtils.java index 49740c8..7e7f980 100644 --- a/src/main/java/examples/mail/IMAPUtils.java +++ b/src/main/java/org/apache/commons/net/examples/mail/IMAPUtils.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.mail; +package org.apache.commons.net.examples.mail; import java.io.IOException; import java.net.URI; @@ -33,25 +33,24 @@ class IMAPUtils { /** * Parse the URI and use the details to connect to the IMAP(S) server and login. * - * @param uri the URI to use, e.g. imaps://user:pass@imap.mail.yahoo.com/folder - * or imaps://user:pass@imap.googlemail.com/folder + * @param uri the URI to use, e.g. imaps://user:pass@imap.mail.yahoo.com/folder or imaps://user:pass@imap.googlemail.com/folder * @param defaultTimeout initial timeout (in milliseconds) - * @param listener for tracing protocol IO (may be null) + * @param listener for tracing protocol IO (may be null) * @return the IMAP client - connected and logged in * @throws IOException if any problems occur */ - static IMAPClient imapLogin(URI uri, int defaultTimeout, ProtocolCommandListener listener) throws IOException { + static IMAPClient imapLogin(final URI uri, final int defaultTimeout, final ProtocolCommandListener listener) throws IOException { final String userInfo = uri.getUserInfo(); if (userInfo == null) { throw new IllegalArgumentException("Missing userInfo details"); } - String []userpass = userInfo.split(":"); + final String[] userpass = userInfo.split(":"); if (userpass.length != 2) { throw new IllegalArgumentException("Invalid userInfo details: '" + userInfo + "'"); } - String username = userpass[0]; + final String username = userpass[0]; String password = userpass[1]; // prompt for the password if necessary password = Utils.getPassword(username, password); @@ -84,7 +83,7 @@ class IMAPUtils { try { imap.connect(server); System.out.println("Successfully connected"); - } catch (IOException e) { + } catch (final IOException e) { throw new RuntimeException("Could not connect to server.", e); } diff --git a/src/main/java/org/apache/commons/net/examples/mail/POP3ExportMbox.java b/src/main/java/org/apache/commons/net/examples/mail/POP3ExportMbox.java new file mode 100644 index 0000000..536e658 --- /dev/null +++ b/src/main/java/org/apache/commons/net/examples/mail/POP3ExportMbox.java @@ -0,0 +1,193 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.net.examples.mail; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.net.pop3.POP3Client; +import org.apache.commons.net.pop3.POP3MessageInfo; +import org.apache.commons.net.pop3.POP3SClient; + +/** + * This is an example program demonstrating how to use the POP3[S]Client class. This program connects to a POP3[S] server and writes the messages to an mbox + * file. + * <p> + * The code currently assumes that POP3Client decodes the POP3 data as iso-8859-1. The POP3 standard only allows for ASCII so in theory iso-8859-1 should be OK. + * However it appears that actual POP3 implementations may return 8bit data that is outside the ASCII range; this may result in loss of data when the mailbox is + * created. + * <p> + * See main() method for usage details + */ +public final class POP3ExportMbox { + + private static final Pattern PATFROM = Pattern.compile(">*From "); // unescaped From_ + + public static void main(final String[] args) { + int argIdx; + String file = null; + for (argIdx = 0; argIdx < args.length; argIdx++) { + if (!args[argIdx].equals("-F")) { + break; + } + file = args[++argIdx]; + } + + final int argCount = args.length - argIdx; + if (argCount < 3) { + System.err.println("Usage: POP3Mail [-F file/directory] <server[:port]> <username> <password|-|*|VARNAME> [TLS [true=implicit]]"); + System.exit(1); + } + + final String arg0[] = args[argIdx++].split(":"); + final String server = arg0[0]; + final String username = args[argIdx++]; + String password = args[argIdx++]; + // prompt for the password if necessary + try { + password = Utils.getPassword(username, password); + } catch (final IOException e1) { + System.err.println("Could not retrieve password: " + e1.getMessage()); + return; + } + + final String proto = argCount > 3 ? args[argIdx++] : null; + final boolean implicit = argCount > 4 && Boolean.parseBoolean(args[argIdx++]); + + final POP3Client pop3; + + if (proto != null) { + System.out.println("Using secure protocol: " + proto); + pop3 = new POP3SClient(proto, implicit); + } else { + pop3 = new POP3Client(); + } + + final int port; + if (arg0.length == 2) { + port = Integer.parseInt(arg0[1]); + } else { + port = pop3.getDefaultPort(); + } + System.out.println("Connecting to server " + server + " on " + port); + + // We want to timeout if a response takes longer than 60 seconds + pop3.setDefaultTimeout(60000); + + try { + pop3.connect(server); + } catch (final IOException e) { + System.err.println("Could not connect to server."); + e.printStackTrace(); + return; + } + + try { + if (!pop3.login(username, password)) { + System.err.println("Could not login to server. Check password."); + pop3.disconnect(); + return; + } + + final POP3MessageInfo status = pop3.status(); + if (status == null) { + System.err.println("Could not retrieve status."); + pop3.logout(); + pop3.disconnect(); + return; + } + + System.out.println("Status: " + status); + final int count = status.number; + if (file != null) { + System.out.println("Getting messages: " + count); + final File mbox = new File(file); + if (mbox.isDirectory()) { + System.out.println("Writing dir: " + mbox); + // Currently POP3Client uses iso-8859-1 + for (int i = 1; i <= count; i++) { + try (final OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream(new File(mbox, i + ".eml")), + StandardCharsets.ISO_8859_1)) { + writeFile(pop3, fw, i); + } + } + } else { + System.out.println("Writing file: " + mbox); + // Currently POP3Client uses iso-8859-1 + try (final OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream(mbox), StandardCharsets.ISO_8859_1)) { + for (int i = 1; i <= count; i++) { + writeMbox(pop3, fw, i); + } + } + } + } + + pop3.logout(); + pop3.disconnect(); + } catch (final IOException e) { + e.printStackTrace(); + return; + } + } + + private static boolean startsWith(final String input, final Pattern pat) { + final Matcher m = pat.matcher(input); + return m.lookingAt(); + } + + private static void writeFile(final POP3Client pop3, final OutputStreamWriter fw, final int i) throws IOException { + try (final BufferedReader r = (BufferedReader) pop3.retrieveMessage(i)) { + String line; + while ((line = r.readLine()) != null) { + fw.write(line); + fw.write("\n"); + } + } + } + + private static void writeMbox(final POP3Client pop3, final OutputStreamWriter fw, final int i) throws IOException { + final SimpleDateFormat DATE_FORMAT // for mbox From_ lines + = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy"); + final String replyTo = "MAILER-DAEMON"; // default + final Date received = new Date(); + try (final BufferedReader r = (BufferedReader) pop3.retrieveMessage(i)) { + fw.append("From "); + fw.append(replyTo); + fw.append(' '); + fw.append(DATE_FORMAT.format(received)); + fw.append("\n"); + String line; + while ((line = r.readLine()) != null) { + if (startsWith(line, PATFROM)) { + fw.write(">"); + } + fw.write(line); + fw.write("\n"); + } + fw.write("\n"); + } + } +} diff --git a/src/main/java/examples/mail/POP3Mail.java b/src/main/java/org/apache/commons/net/examples/mail/POP3Mail.java similarity index 68% rename from src/main/java/examples/mail/POP3Mail.java rename to src/main/java/org/apache/commons/net/examples/mail/POP3Mail.java index 413c598..29b170e 100644 --- a/src/main/java/examples/mail/POP3Mail.java +++ b/src/main/java/org/apache/commons/net/examples/mail/POP3Mail.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.mail; +package org.apache.commons.net.examples.mail; import java.io.BufferedReader; import java.io.IOException; @@ -28,73 +28,50 @@ import org.apache.commons.net.pop3.POP3MessageInfo; import org.apache.commons.net.pop3.POP3SClient; /** - * This is an example program demonstrating how to use the POP3[S]Client class. - * This program connects to a POP3[S] server and retrieves the message - * headers of all the messages, printing the From: and Subject: header - * entries for each message. + * This is an example program demonstrating how to use the POP3[S]Client class. This program connects to a POP3[S] server and retrieves the message headers of + * all the messages, printing the From: and Subject: header entries for each message. * <p> * See main() method for usage details */ -public final class POP3Mail -{ +public final class POP3Mail { - public static final void printMessageInfo(BufferedReader reader, int id) throws IOException { - String from = ""; - String subject = ""; - String line; - while ((line = reader.readLine()) != null) - { - String lower = line.toLowerCase(Locale.ENGLISH); - if (lower.startsWith("from: ")) { - from = line.substring(6).trim(); - } else if (lower.startsWith("subject: ")) { - subject = line.substring(9).trim(); - } - } - - System.out.println(Integer.toString(id) + " From: " + from + " Subject: " + subject); - } - - public static void main(String[] args) - { - if (args.length < 3) - { - System.err.println( - "Usage: POP3Mail <server[:port]> <username> <password|-|*|VARNAME> [TLS [true=implicit]]"); + public static void main(final String[] args) { + if (args.length < 3) { + System.err.println("Usage: POP3Mail <server[:port]> <username> <password|-|*|VARNAME> [TLS [true=implicit]]"); System.exit(1); } - String arg0[] = args[0].split(":"); - String server=arg0[0]; - String username = args[1]; + final String arg0[] = args[0].split(":"); + final String server = arg0[0]; + final String username = args[1]; String password = args[2]; // prompt for the password if necessary try { password = Utils.getPassword(username, password); - } catch (IOException e1) { + } catch (final IOException e1) { System.err.println("Could not retrieve password: " + e1.getMessage()); return; } - String proto = args.length > 3 ? args[3] : null; - boolean implicit = args.length > 4 ? Boolean.parseBoolean(args[4]) : false; + final String proto = args.length > 3 ? args[3] : null; + final boolean implicit = args.length > 4 && Boolean.parseBoolean(args[4]); - POP3Client pop3; + final POP3Client pop3; if (proto != null) { - System.out.println("Using secure protocol: "+proto); + System.out.println("Using secure protocol: " + proto); pop3 = new POP3SClient(proto, implicit); } else { pop3 = new POP3Client(); } - int port; + final int port; if (arg0.length == 2) { port = Integer.parseInt(arg0[1]); } else { port = pop3.getDefaultPort(); } - System.out.println("Connecting to server "+server+" on "+port); + System.out.println("Connecting to server " + server + " on " + port); // We want to timeout if a response takes longer than 60 seconds pop3.setDefaultTimeout(60000); @@ -102,27 +79,22 @@ public final class POP3Mail // suppress login details pop3.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true)); - try - { + try { pop3.connect(server); - } - catch (IOException e) - { + } catch (final IOException e) { System.err.println("Could not connect to server."); e.printStackTrace(); return; } - try - { - if (!pop3.login(username, password)) - { + try { + if (!pop3.login(username, password)) { System.err.println("Could not login to server. Check password."); pop3.disconnect(); return; } - POP3MessageInfo status = pop3.status(); + final POP3MessageInfo status = pop3.status(); if (status == null) { System.err.println("Could not retrieve status."); pop3.logout(); @@ -132,17 +104,15 @@ public final class POP3Mail System.out.println("Status: " + status); - POP3MessageInfo[] messages = pop3.listMessages(); + final POP3MessageInfo[] messages = pop3.listMessages(); - if (messages == null) - { + if (messages == null) { System.err.println("Could not retrieve message list."); pop3.logout(); pop3.disconnect(); return; } - else if (messages.length == 0) - { + if (messages.length == 0) { System.out.println("No messages"); pop3.logout(); pop3.disconnect(); @@ -151,8 +121,8 @@ public final class POP3Mail System.out.println("Message count: " + messages.length); - for (POP3MessageInfo msginfo : messages) { - BufferedReader reader = (BufferedReader) pop3.retrieveMessageTop(msginfo.number, 0); + for (final POP3MessageInfo msginfo : messages) { + final BufferedReader reader = (BufferedReader) pop3.retrieveMessageTop(msginfo.number, 0); if (reader == null) { System.err.println("Could not retrieve message header."); @@ -165,12 +135,25 @@ public final class POP3Mail pop3.logout(); pop3.disconnect(); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); return; } } -} + public static void printMessageInfo(final BufferedReader reader, final int id) throws IOException { + String from = ""; + String subject = ""; + String line; + while ((line = reader.readLine()) != null) { + final String lower = line.toLowerCase(Locale.ENGLISH); + if (lower.startsWith("from: ")) { + from = line.substring(6).trim(); + } else if (lower.startsWith("subject: ")) { + subject = line.substring(9).trim(); + } + } + + System.out.println(Integer.toString(id) + " From: " + from + " Subject: " + subject); + } +} diff --git a/src/main/java/examples/mail/SMTPMail.java b/src/main/java/org/apache/commons/net/examples/mail/SMTPMail.java similarity index 74% rename from src/main/java/examples/mail/SMTPMail.java rename to src/main/java/org/apache/commons/net/examples/mail/SMTPMail.java index 7c01607..366c7b4 100644 --- a/src/main/java/examples/mail/SMTPMail.java +++ b/src/main/java/org/apache/commons/net/examples/mail/SMTPMail.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.mail; +package org.apache.commons.net.examples.mail; import java.io.BufferedReader; import java.io.FileNotFoundException; @@ -33,28 +33,29 @@ import org.apache.commons.net.smtp.SMTPClient; import org.apache.commons.net.smtp.SMTPReply; import org.apache.commons.net.smtp.SimpleSMTPHeader; -/*** - * This is an example program using the SMTP package to send a message - * to the specified recipients. It prompts you for header information and - * a filename containing the message. - ***/ - -public final class SMTPMail -{ +/** + * This is an example program using the SMTP package to send a message to the specified recipients. It prompts you for header information and a file name + * containing the message. + */ - public static void main(String[] args) - { - String sender, recipient, subject, filename, server, cc; - List<String> ccList = new ArrayList<String>(); - BufferedReader stdin; +public final class SMTPMail { + + public static void main(final String[] args) { + final String sender; + final String recipient; + final String subject; + final String fileName; + final String server; + String cc; + final List<String> ccList = new ArrayList<>(); + final BufferedReader stdin; FileReader fileReader = null; - Writer writer; - SimpleSMTPHeader header; - SMTPClient client; + final Writer writer; + final SimpleSMTPHeader header; + final SMTPClient client; - if (args.length < 1) - { - System.err.println("Usage: mail smtpserver"); + if (args.length < 1) { + System.err.println("Usage: SMTPMail <smtpserver>"); System.exit(1); } @@ -62,8 +63,7 @@ public final class SMTPMail stdin = new BufferedReader(new InputStreamReader(System.in)); - try - { + try { System.out.print("From: "); System.out.flush(); @@ -81,15 +81,13 @@ public final class SMTPMail header = new SimpleSMTPHeader(sender, recipient, subject); - - while (true) - { + while (true) { System.out.print("CC <enter one address per line, hit enter to end>: "); System.out.flush(); cc = stdin.readLine(); - if (cc== null || cc.length() == 0) { + if (cc == null || cc.isEmpty()) { break; } @@ -100,25 +98,20 @@ public final class SMTPMail System.out.print("Filename: "); System.out.flush(); - filename = stdin.readLine(); + fileName = stdin.readLine(); - try - { - fileReader = new FileReader(filename); - } - catch (FileNotFoundException e) - { + try { + fileReader = new FileReader(fileName); + } catch (final FileNotFoundException e) { System.err.println("File not found. " + e.getMessage()); } client = new SMTPClient(); - client.addProtocolCommandListener(new PrintCommandListener( - new PrintWriter(System.out), true)); + client.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true)); client.connect(server); - if (!SMTPReply.isPositiveCompletion(client.getReplyCode())) - { + if (!SMTPReply.isPositiveCompletion(client.getReplyCode())) { client.disconnect(); System.err.println("SMTP server refused connection."); System.exit(1); @@ -129,36 +122,29 @@ public final class SMTPMail client.setSender(sender); client.addRecipient(recipient); - - - for (String recpt : ccList) { + for (final String recpt : ccList) { client.addRecipient(recpt); } writer = client.sendMessageData(); - if (writer != null) - { + if (writer != null) { writer.write(header.toString()); Util.copyReader(fileReader, writer); writer.close(); client.completePendingCommand(); } - if (fileReader != null ) { + if (fileReader != null) { fileReader.close(); } client.logout(); client.disconnect(); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); System.exit(1); } } } - - diff --git a/src/main/java/examples/mail/Utils.java b/src/main/java/org/apache/commons/net/examples/mail/Utils.java similarity index 68% rename from src/main/java/examples/mail/Utils.java rename to src/main/java/org/apache/commons/net/examples/mail/Utils.java index 500eb58..8f9c4b4 100644 --- a/src/main/java/examples/mail/Utils.java +++ b/src/main/java/org/apache/commons/net/examples/mail/Utils.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.mail; +package org.apache.commons.net.examples.mail; import java.io.BufferedReader; import java.io.Console; @@ -28,43 +28,38 @@ import java.util.Locale; */ class Utils { - private Utils() { - // not instantiable - } - /** - * If the initial password is: - * '*' - replace it with a line read from the system console - * '-' - replace it with next line from STDIN - * 'ABCD' - if the input is all upper case, use the field as an environment variable name + * If the initial password is: '*' - replace it with a line read from the system console '-' - replace it with next line from STDIN 'ABCD' - if the input is + * all upper case, use the field as an environment variable name * * Note: there are no guarantees that the password cannot be snooped. * - * Even using the console may be subject to memory snooping, - * however it should be safer than the other methods. + * Even using the console may be subject to memory snooping, however it should be safer than the other methods. * - * STDIN may require creating a temporary file which could be read by others - * Environment variables may be visible by using PS + * STDIN may require creating a temporary file which could be read by others Environment variables may be visible by using PS */ - static String getPassword(String username, String password) throws IOException { + static String getPassword(final String username, String password) throws IOException { if ("-".equals(password)) { // stdin - BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); + final BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); password = in.readLine(); } else if ("*".equals(password)) { // console - Console con = System.console(); // Java 1.6 - if (con != null) { - char[] pwd = con.readPassword("Password for " + username + ": "); - password = new String(pwd); - } else { + final Console con = System.console(); // Java 1.6 + if (con == null) { throw new IOException("Cannot access Console"); } + final char[] pwd = con.readPassword("Password for " + username + ": "); + password = new String(pwd); } else if (password.equals(password.toUpperCase(Locale.ROOT))) { // environment variable name final String tmp = System.getenv(password); if (tmp != null) { // don't overwrite if variable does not exist (just in case password is all uppers) - password=tmp; + password = tmp; } } return password; } + private Utils() { + // not instantiable + } + } diff --git a/src/main/java/examples/nntp/ArticleReader.java b/src/main/java/org/apache/commons/net/examples/nntp/ArticleReader.java similarity index 75% rename from src/main/java/examples/nntp/ArticleReader.java rename to src/main/java/org/apache/commons/net/examples/nntp/ArticleReader.java index ba53e2a..7f8a1e5 100644 --- a/src/main/java/examples/nntp/ArticleReader.java +++ b/src/main/java/org/apache/commons/net/examples/nntp/ArticleReader.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.nntp; +package org.apache.commons.net.examples.nntp; import java.io.BufferedReader; import java.io.IOException; @@ -31,57 +31,57 @@ import org.apache.commons.net.nntp.NewsgroupInfo; */ public class ArticleReader { - public static void main(String[] args) throws SocketException, IOException { + public static void main(final String[] args) throws SocketException, IOException { if (args.length != 2 && args.length != 3 && args.length != 5) { System.out.println("Usage: MessageThreading <hostname> <groupname> [<article specifier> [<user> <password>]]"); return; } - String hostname = args[0]; - String newsgroup = args[1]; + final String hostname = args[0]; + final String newsgroup = args[1]; // Article specifier can be numeric or Id in form <m.n.o.x@host> - String articleSpec = args.length >= 3 ? args[2] : null; + final String articleSpec = args.length >= 3 ? args[2] : null; - NNTPClient client = new NNTPClient(); + final NNTPClient client = new NNTPClient(); client.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true)); client.connect(hostname); if (args.length == 5) { // Optional auth - String user = args[3]; - String password = args[4]; - if(!client.authenticate(user, password)) { + final String user = args[3]; + final String password = args[4]; + if (!client.authenticate(user, password)) { System.out.println("Authentication failed for user " + user + "!"); System.exit(1); } } - NewsgroupInfo group = new NewsgroupInfo(); + final NewsgroupInfo group = new NewsgroupInfo(); client.selectNewsgroup(newsgroup, group); - BufferedReader brHdr; + final BufferedReader brHdr; String line; if (articleSpec != null) { brHdr = (BufferedReader) client.retrieveArticleHeader(articleSpec); } else { - long articleNum = group.getLastArticleLong(); + final long articleNum = group.getLastArticleLong(); brHdr = client.retrieveArticleHeader(articleNum); } if (brHdr != null) { - while((line=brHdr.readLine()) != null) { + while ((line = brHdr.readLine()) != null) { System.out.println(line); } brHdr.close(); } - BufferedReader brBody; + final BufferedReader brBody; if (articleSpec != null) { brBody = (BufferedReader) client.retrieveArticleBody(articleSpec); } else { - long articleNum = group.getLastArticleLong(); + final long articleNum = group.getLastArticleLong(); brBody = client.retrieveArticleBody(articleNum); } if (brBody != null) { - while((line=brBody.readLine()) != null) { + while ((line = brBody.readLine()) != null) { System.out.println(line); } brBody.close(); diff --git a/src/main/java/examples/nntp/ExtendedNNTPOps.java b/src/main/java/org/apache/commons/net/examples/nntp/ExtendedNNTPOps.java similarity index 67% rename from src/main/java/examples/nntp/ExtendedNNTPOps.java rename to src/main/java/org/apache/commons/net/examples/nntp/ExtendedNNTPOps.java index 9ba51bf..9018ce6 100644 --- a/src/main/java/examples/nntp/ExtendedNNTPOps.java +++ b/src/main/java/org/apache/commons/net/examples/nntp/ExtendedNNTPOps.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.nntp; +package org.apache.commons.net.examples.nntp; import java.io.IOException; import java.io.PrintWriter; @@ -25,28 +25,38 @@ import org.apache.commons.net.nntp.Article; import org.apache.commons.net.nntp.NNTPClient; import org.apache.commons.net.nntp.NewsgroupInfo; - /** * Simple class showing some of the extended commands (AUTH, XOVER, LIST ACTIVE) */ public class ExtendedNNTPOps { + public static void main(final String[] args) { + final ExtendedNNTPOps ops; - NNTPClient client; + final int argc = args.length; + if (argc < 1) { + System.err.println("usage: ExtendedNNTPOps nntpserver [username password]"); + System.exit(1); + } + + ops = new ExtendedNNTPOps(); + ops.demo(args[0], argc >= 3 ? args[1] : null, argc >= 3 ? args[2] : null); + } + + private final NNTPClient client; public ExtendedNNTPOps() { client = new NNTPClient(); client.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true)); } - - private void demo(String host, String user, String password) { + private void demo(final String host, final String user, final String password) { try { client.connect(host); // AUTHINFO USER/AUTHINFO PASS if (user != null && password != null) { - boolean success = client.authenticate(user, password); + final boolean success = client.authenticate(user, password); if (success) { System.out.println("Authentication succeeded"); } else { @@ -55,51 +65,29 @@ public class ExtendedNNTPOps { } // XOVER - NewsgroupInfo testGroup = new NewsgroupInfo(); + final NewsgroupInfo testGroup = new NewsgroupInfo(); client.selectNewsgroup("alt.test", testGroup); - long lowArticleNumber = testGroup.getFirstArticleLong(); - long highArticleNumber = lowArticleNumber + 100; - Iterable<Article> articles = client.iterateArticleInfo(lowArticleNumber, highArticleNumber); + final long lowArticleNumber = testGroup.getFirstArticleLong(); + final long highArticleNumber = lowArticleNumber + 100; + final Iterable<Article> articles = client.iterateArticleInfo(lowArticleNumber, highArticleNumber); - for (Article article : articles) { + for (final Article article : articles) { if (article.isDummy()) { // Subject will contain raw response - System.out.println("Could not parse: "+article.getSubject()); + System.out.println("Could not parse: " + article.getSubject()); } else { System.out.println(article.getSubject()); } } // LIST ACTIVE - NewsgroupInfo[] fanGroups = client.listNewsgroups("alt.fan.*"); - for (NewsgroupInfo fanGroup : fanGroups) - { + final NewsgroupInfo[] fanGroups = client.listNewsgroups("alt.fan.*"); + for (final NewsgroupInfo fanGroup : fanGroups) { System.out.println(fanGroup.getNewsgroup()); } - } catch (IOException e) { + } catch (final IOException e) { e.printStackTrace(); } } - public static void main(String[] args) { - ExtendedNNTPOps ops; - - int argc = args.length; - if (argc < 1) { - System.err.println("usage: ExtendedNNTPOps nntpserver [username password]"); - System.exit(1); - } - - ops = new ExtendedNNTPOps(); - ops.demo(args[0], argc >=3 ? args[1] : null, argc >=3 ? args[2] : null); - } - } - -/* Emacs configuration - * Local variables: ** - * mode: java ** - * c-basic-offset: 4 ** - * indent-tabs-mode: nil ** - * End: ** - */ diff --git a/src/main/java/examples/nntp/ListNewsgroups.java b/src/main/java/org/apache/commons/net/examples/nntp/ListNewsgroups.java similarity index 66% rename from src/main/java/examples/nntp/ListNewsgroups.java rename to src/main/java/org/apache/commons/net/examples/nntp/ListNewsgroups.java index bc133bb..515fa40 100644 --- a/src/main/java/examples/nntp/ListNewsgroups.java +++ b/src/main/java/org/apache/commons/net/examples/nntp/ListNewsgroups.java @@ -15,70 +15,58 @@ * limitations under the License. */ -package examples.nntp; +package org.apache.commons.net.examples.nntp; import java.io.IOException; + import org.apache.commons.net.nntp.NNTPClient; import org.apache.commons.net.nntp.NewsgroupInfo; -/*** - * This is a trivial example using the NNTP package to approximate the - * Unix newsgroups command. It merely connects to the specified news - * server and issues fetches the list of newsgroups stored by the server. - * On servers that store a lot of newsgroups, this command can take a very - * long time (listing upwards of 30,000 groups). - ***/ +/** + * This is a trivial example using the NNTP package to approximate the Unix newsgroups command. It merely connects to the specified news server and issues + * fetches the list of newsgroups stored by the server. On servers that store a lot of newsgroups, this command can take a very long time (listing upwards of + * 30,000 groups). + */ -public final class ListNewsgroups -{ +public final class ListNewsgroups { - public static void main(String[] args) - { - if (args.length < 1) - { + public static void main(final String[] args) { + if (args.length < 1) { System.err.println("Usage: newsgroups newsserver [pattern]"); return; } - NNTPClient client = new NNTPClient(); - String pattern = args.length >= 2 ? args[1] : ""; + final NNTPClient client = new NNTPClient(); + final String pattern = args.length >= 2 ? args[1] : ""; - try - { + try { client.connect(args[0]); int j = 0; try { - for(String s : client.iterateNewsgroupListing(pattern)) { + for (final String s : client.iterateNewsgroupListing(pattern)) { j++; System.out.println(s); } - } catch (IOException e1) { + } catch (final IOException e1) { e1.printStackTrace(); } System.out.println(j); j = 0; - for(NewsgroupInfo n : client.iterateNewsgroups(pattern)) { + for (final NewsgroupInfo n : client.iterateNewsgroups(pattern)) { j++; System.out.println(n.getNewsgroup()); } System.out.println(j); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); - } - finally - { - try - { + } finally { + try { if (client.isConnected()) { client.disconnect(); } - } - catch (IOException e) - { + } catch (final IOException e) { System.err.println("Error disconnecting from server."); e.printStackTrace(); System.exit(1); @@ -88,5 +76,3 @@ public final class ListNewsgroups } } - - diff --git a/src/main/java/examples/nntp/MessageThreading.java b/src/main/java/org/apache/commons/net/examples/nntp/MessageThreading.java similarity index 71% rename from src/main/java/examples/nntp/MessageThreading.java rename to src/main/java/org/apache/commons/net/examples/nntp/MessageThreading.java index f1414a0..5be1bdb 100644 --- a/src/main/java/examples/nntp/MessageThreading.java +++ b/src/main/java/org/apache/commons/net/examples/nntp/MessageThreading.java @@ -15,12 +15,12 @@ * limitations under the License. */ - -package examples.nntp; +package org.apache.commons.net.examples.nntp; import java.io.IOException; import java.io.PrintWriter; import java.net.SocketException; + import org.apache.commons.net.PrintCommandListener; import org.apache.commons.net.nntp.Article; import org.apache.commons.net.nntp.NNTPClient; @@ -31,55 +31,55 @@ import org.apache.commons.net.nntp.Threader; * Sample program demonstrating the use of article iteration and threading. */ public class MessageThreading { - public MessageThreading() { - } - - public static void main(String[] args) throws SocketException, IOException { + public static void main(final String[] args) throws SocketException, IOException { if (args.length != 2 && args.length != 4) { System.out.println("Usage: MessageThreading <hostname> <groupname> [<user> <password>]"); return; } - String hostname = args[0]; - String newsgroup = args[1]; + final String hostname = args[0]; + final String newsgroup = args[1]; - NNTPClient client = new NNTPClient(); + final NNTPClient client = new NNTPClient(); client.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true)); client.connect(hostname); if (args.length == 4) { // Optional auth - String user = args[2]; - String password = args[3]; - if(!client.authenticate(user, password)) { + final String user = args[2]; + final String password = args[3]; + if (!client.authenticate(user, password)) { System.out.println("Authentication failed for user " + user + "!"); System.exit(1); } } - String fmt[] = client.listOverviewFmt(); + final String fmt[] = client.listOverviewFmt(); if (fmt != null) { System.out.println("LIST OVERVIEW.FMT:"); - for(String s : fmt) { + for (final String s : fmt) { System.out.println(s); } } else { System.out.println("Failed to get OVERVIEW.FMT"); } - NewsgroupInfo group = new NewsgroupInfo(); + final NewsgroupInfo group = new NewsgroupInfo(); client.selectNewsgroup(newsgroup, group); - long lowArticleNumber = group.getFirstArticleLong(); - long highArticleNumber = lowArticleNumber + 5000; + final long lowArticleNumber = group.getFirstArticleLong(); + final long highArticleNumber = lowArticleNumber + 5000; System.out.println("Retrieving articles between [" + lowArticleNumber + "] and [" + highArticleNumber + "]"); - Iterable<Article> articles = client.iterateArticleInfo(lowArticleNumber, highArticleNumber); + final Iterable<Article> articles = client.iterateArticleInfo(lowArticleNumber, highArticleNumber); System.out.println("Building message thread tree..."); - Threader threader = new Threader(); - Article root = (Article)threader.thread(articles); + final Threader threader = new Threader(); + final Article root = (Article) threader.thread(articles); Article.printThread(root, 0); } + public MessageThreading() { + } + } diff --git a/src/main/java/examples/nntp/NNTPUtils.java b/src/main/java/org/apache/commons/net/examples/nntp/NNTPUtils.java similarity index 70% rename from src/main/java/examples/nntp/NNTPUtils.java rename to src/main/java/org/apache/commons/net/examples/nntp/NNTPUtils.java index 07053ff..cc6b91e 100644 --- a/src/main/java/examples/nntp/NNTPUtils.java +++ b/src/main/java/org/apache/commons/net/examples/nntp/NNTPUtils.java @@ -14,11 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package examples.nntp; +package org.apache.commons.net.examples.nntp; import java.io.IOException; import java.util.ArrayList; import java.util.List; + import org.apache.commons.net.nntp.Article; import org.apache.commons.net.nntp.NNTPClient; @@ -28,19 +29,18 @@ import org.apache.commons.net.nntp.NNTPClient; public class NNTPUtils { /** - * Given an {@link NNTPClient} instance, and an integer range of messages, return - * an array of {@link Article} instances. - * @param client the client to use - * @param lowArticleNumber low number + * Given an {@link NNTPClient} instance, and an integer range of messages, return an array of {@link Article} instances. + * + * @param client the client to use + * @param lowArticleNumber low number * @param highArticleNumber high number * @return Article[] An array of Article * @throws IOException on error */ - public static List<Article> getArticleInfo(NNTPClient client, long lowArticleNumber, long highArticleNumber) - throws IOException { - List<Article> articles = new ArrayList<Article>(); - Iterable<Article> arts = client.iterateArticleInfo(lowArticleNumber, highArticleNumber); - for(Article article : arts){ + public static List<Article> getArticleInfo(final NNTPClient client, final long lowArticleNumber, final long highArticleNumber) throws IOException { + final List<Article> articles = new ArrayList<>(); + final Iterable<Article> arts = client.iterateArticleInfo(lowArticleNumber, highArticleNumber); + for (final Article article : arts) { articles.add(article); } return articles; diff --git a/src/main/java/examples/nntp/PostMessage.java b/src/main/java/org/apache/commons/net/examples/nntp/PostMessage.java similarity index 74% rename from src/main/java/examples/nntp/PostMessage.java rename to src/main/java/org/apache/commons/net/examples/nntp/PostMessage.java index c9583c5..9b2f716 100644 --- a/src/main/java/examples/nntp/PostMessage.java +++ b/src/main/java/org/apache/commons/net/examples/nntp/PostMessage.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.nntp; +package org.apache.commons.net.examples.nntp; import java.io.BufferedReader; import java.io.FileNotFoundException; @@ -31,27 +31,27 @@ import org.apache.commons.net.nntp.NNTPClient; import org.apache.commons.net.nntp.NNTPReply; import org.apache.commons.net.nntp.SimpleNNTPHeader; +/** + * This is an example program using the NNTP package to post an article to the specified newsgroup(s). It prompts you for header information and a file name to + * post. + */ -/*** - * This is an example program using the NNTP package to post an article - * to the specified newsgroup(s). It prompts you for header information and - * a filename to post. - ***/ - -public final class PostMessage -{ - - public static void main(String[] args) - { - String from, subject, newsgroup, filename, server, organization; - String references; - BufferedReader stdin; +public final class PostMessage { + + public static void main(final String[] args) { + final String from; + final String subject; + String newsgroup; + final String fileName; + final String server; + final String organization; + final String references; + final BufferedReader stdin; FileReader fileReader = null; - SimpleNNTPHeader header; - NNTPClient client; + final SimpleNNTPHeader header; + final NNTPClient client; - if (args.length < 1) - { + if (args.length < 1) { System.err.println("Usage: post newsserver"); System.exit(1); } @@ -60,8 +60,7 @@ public final class PostMessage stdin = new BufferedReader(new InputStreamReader(System.in)); - try - { + try { System.out.print("From: "); System.out.flush(); @@ -80,8 +79,7 @@ public final class PostMessage newsgroup = stdin.readLine(); header.addNewsgroup(newsgroup); - while (true) - { + while (true) { System.out.print("Additional Newsgroup <Hit enter to end>: "); System.out.flush(); @@ -92,7 +90,7 @@ public final class PostMessage newsgroup = newsgroup.trim(); - if (newsgroup.length() == 0) { + if (newsgroup.isEmpty()) { break; } @@ -109,11 +107,11 @@ public final class PostMessage references = stdin.readLine(); - if (organization != null && organization.length() > 0) { + if (organization != null && !organization.isEmpty()) { header.addHeaderField("Organization", organization); } - if (references != null && references.length() > 0) { + if (references != null && !references.isEmpty()) { header.addHeaderField("References", references); } @@ -122,37 +120,30 @@ public final class PostMessage System.out.print("Filename: "); System.out.flush(); - filename = stdin.readLine(); + fileName = stdin.readLine(); - try - { - fileReader = new FileReader(filename); - } - catch (FileNotFoundException e) - { + try { + fileReader = new FileReader(fileName); + } catch (final FileNotFoundException e) { System.err.println("File not found. " + e.getMessage()); System.exit(1); } client = new NNTPClient(); - client.addProtocolCommandListener(new PrintCommandListener( - new PrintWriter(System.out), true)); + client.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true)); client.connect(server); - if (!NNTPReply.isPositiveCompletion(client.getReplyCode())) - { + if (!NNTPReply.isPositiveCompletion(client.getReplyCode())) { client.disconnect(); System.err.println("NNTP server refused connection."); System.exit(1); } - if (client.isAllowedToPost()) - { - Writer writer = client.postArticle(); + if (client.isAllowedToPost()) { + final Writer writer = client.postArticle(); - if (writer != null) - { + if (writer != null) { writer.write(header.toString()); Util.copyReader(fileReader, writer); writer.close(); @@ -160,20 +151,14 @@ public final class PostMessage } } - if (fileReader != null) { - fileReader.close(); - } + fileReader.close(); client.logout(); client.disconnect(); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); System.exit(1); } } } - - diff --git a/src/main/java/examples/ntp/NTPClient.java b/src/main/java/org/apache/commons/net/examples/ntp/NTPClient.java similarity index 67% rename from src/main/java/examples/ntp/NTPClient.java rename to src/main/java/org/apache/commons/net/examples/ntp/NTPClient.java index f8b699d..562e05d 100644 --- a/src/main/java/examples/ntp/NTPClient.java +++ b/src/main/java/org/apache/commons/net/examples/ntp/NTPClient.java @@ -1,4 +1,3 @@ -package examples.ntp; /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -16,6 +15,7 @@ package examples.ntp; * limitations under the License. */ +package org.apache.commons.net.examples.ntp; import java.io.IOException; import java.net.InetAddress; @@ -29,35 +29,59 @@ import org.apache.commons.net.ntp.NtpV3Packet; import org.apache.commons.net.ntp.TimeInfo; import org.apache.commons.net.ntp.TimeStamp; -/*** - * This is an example program demonstrating how to use the NTPUDPClient - * class. This program sends a Datagram client request packet to a - * Network time Protocol (NTP) service port on a specified server, - * retrieves the time, and prints it to standard output along with - * the fields from the NTP message header (e.g. stratum level, reference id, - * poll interval, root delay, mode, ...) - * See <A HREF="ftp://ftp.rfc-editor.org/in-notes/rfc868.txt"> the spec </A> - * for details. +/** + * This is an example program demonstrating how to use the NTPUDPClient class. This program sends a Datagram client request packet to a Network time Protocol + * (NTP) service port on a specified server, retrieves the time, and prints it to standard output along with the fields from the NTP message header (e.g. + * stratum level, reference id, poll interval, root delay, mode, ...) See <A HREF="ftp://ftp.rfc-editor.org/in-notes/rfc868.txt"> the spec </A> for details. * <p> * Usage: NTPClient <hostname-or-address-list> - * <br> + * </p> + * <p> * Example: NTPClient clock.psu.edu - * - ***/ -public final class NTPClient -{ + * </p> + */ +public final class NTPClient { private static final NumberFormat numberFormat = new java.text.DecimalFormat("0.00"); + public static void main(final String[] args) { + if (args.length == 0) { + System.err.println("Usage: NTPClient <hostname-or-address-list>"); + System.exit(1); + } + + final NTPUDPClient client = new NTPUDPClient(); + // We want to timeout if a response takes longer than 10 seconds + client.setDefaultTimeout(10000); + try { + client.open(); + for (final String arg : args) { + System.out.println(); + try { + final InetAddress hostAddr = InetAddress.getByName(arg); + System.out.println("> " + hostAddr.getHostName() + "/" + hostAddr.getHostAddress()); + final TimeInfo info = client.getTime(hostAddr); + processResponse(info); + } catch (final IOException ioe) { + ioe.printStackTrace(); + } + } + } catch (final SocketException e) { + e.printStackTrace(); + } + + client.close(); + } + /** * Process <code>TimeInfo</code> object and print its details. + * * @param info <code>TimeInfo</code> object. */ - public static void processResponse(TimeInfo info) - { - NtpV3Packet message = info.getMessage(); - int stratum = message.getStratum(); - String refType; + public static void processResponse(final TimeInfo info) { + final NtpV3Packet message = info.getMessage(); + final int stratum = message.getStratum(); + final String refType; if (stratum <= 0) { refType = "(Unspecified or Unavailable)"; } else if (stratum == 1) { @@ -67,21 +91,18 @@ public final class NTPClient } // stratum should be 0..15... System.out.println(" Stratum: " + stratum + " " + refType); - int version = message.getVersion(); - int li = message.getLeapIndicator(); - System.out.println(" leap=" + li + ", version=" - + version + ", precision=" + message.getPrecision()); + final int version = message.getVersion(); + final int li = message.getLeapIndicator(); + System.out.println(" leap=" + li + ", version=" + version + ", precision=" + message.getPrecision()); System.out.println(" mode: " + message.getModeName() + " (" + message.getMode() + ")"); - int poll = message.getPoll(); + final int poll = message.getPoll(); // poll value typically btwn MINPOLL (4) and MAXPOLL (14) - System.out.println(" poll: " + (poll <= 0 ? 1 : (int) Math.pow(2, poll)) - + " seconds" + " (2 ** " + poll + ")"); - double disp = message.getRootDispersionInMillisDouble(); - System.out.println(" rootdelay=" + numberFormat.format(message.getRootDelayInMillisDouble()) - + ", rootdispersion(ms): " + numberFormat.format(disp)); + System.out.println(" poll: " + (poll <= 0 ? 1 : (int) Math.pow(2, poll)) + " seconds" + " (2 ** " + poll + ")"); + final double disp = message.getRootDispersionInMillisDouble(); + System.out.println(" rootdelay=" + numberFormat.format(message.getRootDelayInMillisDouble()) + ", rootdispersion(ms): " + numberFormat.format(disp)); - int refId = message.getReferenceId(); + final int refId = message.getReferenceId(); String refAddr = NtpUtils.getHostAddress(refId); String refName = null; if (refId != 0) { @@ -93,12 +114,12 @@ public final class NTPClient // for GENERIC DCF77 AM; see refclock.htm from the NTP software distribution. if (!refAddr.startsWith("127.127")) { try { - InetAddress addr = InetAddress.getByName(refAddr); - String name = addr.getHostName(); + final InetAddress addr = InetAddress.getByName(refAddr); + final String name = addr.getHostName(); if (name != null && !name.equals(refAddr)) { refName = name; } - } catch (UnknownHostException e) { + } catch (final UnknownHostException e) { // some stratum-2 servers sync to ref clock device but fudge stratum level higher... (e.g. 2) // ref not valid host maybe it's a reference clock name? // otherwise just show the ref IP address. @@ -116,65 +137,33 @@ public final class NTPClient } System.out.println(" Reference Identifier:\t" + refAddr); - TimeStamp refNtpTime = message.getReferenceTimeStamp(); + final TimeStamp refNtpTime = message.getReferenceTimeStamp(); System.out.println(" Reference Timestamp:\t" + refNtpTime + " " + refNtpTime.toDateString()); // Originate Time is time request sent by client (t1) - TimeStamp origNtpTime = message.getOriginateTimeStamp(); + final TimeStamp origNtpTime = message.getOriginateTimeStamp(); System.out.println(" Originate Timestamp:\t" + origNtpTime + " " + origNtpTime.toDateString()); - long destTime = info.getReturnTime(); + final long destTimeMillis = info.getReturnTime(); // Receive Time is time request received by server (t2) - TimeStamp rcvNtpTime = message.getReceiveTimeStamp(); + final TimeStamp rcvNtpTime = message.getReceiveTimeStamp(); System.out.println(" Receive Timestamp:\t" + rcvNtpTime + " " + rcvNtpTime.toDateString()); // Transmit time is time reply sent by server (t3) - TimeStamp xmitNtpTime = message.getTransmitTimeStamp(); + final TimeStamp xmitNtpTime = message.getTransmitTimeStamp(); System.out.println(" Transmit Timestamp:\t" + xmitNtpTime + " " + xmitNtpTime.toDateString()); // Destination time is time reply received by client (t4) - TimeStamp destNtpTime = TimeStamp.getNtpTime(destTime); + final TimeStamp destNtpTime = TimeStamp.getNtpTime(destTimeMillis); System.out.println(" Destination Timestamp:\t" + destNtpTime + " " + destNtpTime.toDateString()); info.computeDetails(); // compute offset/delay if not already done - Long offsetValue = info.getOffset(); - Long delayValue = info.getDelay(); - String delay = (delayValue == null) ? "N/A" : delayValue.toString(); - String offset = (offsetValue == null) ? "N/A" : offsetValue.toString(); - - System.out.println(" Roundtrip delay(ms)=" + delay - + ", clock offset(ms)=" + offset); // offset in ms - } - - public static void main(String[] args) - { - if (args.length == 0) { - System.err.println("Usage: NTPClient <hostname-or-address-list>"); - System.exit(1); - } + final Long offsetMillis = info.getOffset(); + final Long delayMillis = info.getDelay(); + final String delay = delayMillis == null ? "N/A" : delayMillis.toString(); + final String offset = offsetMillis == null ? "N/A" : offsetMillis.toString(); - NTPUDPClient client = new NTPUDPClient(); - // We want to timeout if a response takes longer than 10 seconds - client.setDefaultTimeout(10000); - try { - client.open(); - for (String arg : args) - { - System.out.println(); - try { - InetAddress hostAddr = InetAddress.getByName(arg); - System.out.println("> " + hostAddr.getHostName() + "/" + hostAddr.getHostAddress()); - TimeInfo info = client.getTime(hostAddr); - processResponse(info); - } catch (IOException ioe) { - ioe.printStackTrace(); - } - } - } catch (SocketException e) { - e.printStackTrace(); - } - - client.close(); + System.out.println(" Roundtrip delay(ms)=" + delay + ", clock offset(ms)=" + offset); // offset in ms } } diff --git a/src/main/java/examples/ntp/SimpleNTPServer.java b/src/main/java/org/apache/commons/net/examples/ntp/SimpleNTPServer.java similarity index 66% rename from src/main/java/examples/ntp/SimpleNTPServer.java rename to src/main/java/org/apache/commons/net/examples/ntp/SimpleNTPServer.java index bf04df4..cd10bb5 100644 --- a/src/main/java/examples/ntp/SimpleNTPServer.java +++ b/src/main/java/org/apache/commons/net/examples/ntp/SimpleNTPServer.java @@ -14,94 +14,76 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package examples.ntp; +package org.apache.commons.net.examples.ntp; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; import org.apache.commons.net.ntp.NtpUtils; import org.apache.commons.net.ntp.NtpV3Impl; import org.apache.commons.net.ntp.NtpV3Packet; import org.apache.commons.net.ntp.TimeStamp; -import java.io.IOException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; - /** - * The SimpleNTPServer class is a UDP implementation of a server for the - * Network Time Protocol (NTP) version 3 as described in RFC 1305. - * It is a minimal NTP server that doesn't actually adjust the time but - * only responds to NTP datagram requests with response sent back to - * originating host with info filled out using the current clock time. - * To be used for debugging or testing. + * The SimpleNTPServer class is a UDP implementation of a server for the Network Time Protocol (NTP) version 3 as described in RFC 1305. It is a minimal NTP + * server that doesn't actually adjust the time but only responds to NTP datagram requests with response sent back to originating host with info filled out + * using the current clock time. To be used for debugging or testing. * - * To prevent this from interfering with the actual NTP service it can be - * run from any local port. + * To prevent this from interfering with the actual NTP service it can be run from any local port. */ public class SimpleNTPServer implements Runnable { - private int port; + public static void main(final String[] args) { + int port = NtpV3Packet.NTP_PORT; + if (args.length != 0) { + try { + port = Integer.parseInt(args[0]); + } catch (final NumberFormatException nfe) { + nfe.printStackTrace(); + } + } + final SimpleNTPServer timeServer = new SimpleNTPServer(port); + try { + timeServer.start(); + } catch (final IOException e) { + e.printStackTrace(); + } + } + private int port; private volatile boolean running; private boolean started; private DatagramSocket socket; /** - * Create SimpleNTPServer listening on default NTP port. + * Creates SimpleNTPServer listening on default NTP port. */ - public SimpleNTPServer() - { + public SimpleNTPServer() { this(NtpV3Packet.NTP_PORT); } /** - * Create SimpleNTPServer. + * Creates SimpleNTPServer. * - * @param port the local port the server socket is bound to, or - * <code>zero</code> for a system selected free port. + * @param port the local port the server socket is bound to, or <code>zero</code> for a system selected free port. * @throws IllegalArgumentException if port number less than 0 */ - public SimpleNTPServer(int port) - { + public SimpleNTPServer(final int port) { if (port < 0) { throw new IllegalArgumentException(); } this.port = port; } - public int getPort() - { - return port; - } - /** - * Return state of whether time service is running. - * - * @return true if time service is running - */ - public boolean isRunning() - { - return running; - } - - /** - * Return state of whether time service is running. - * - * @return true if time service is running - */ - public boolean isStarted() - { - return started; - } - - /** - * Connect to server socket and listen for client connections. + * Connects to server socket and listen for client connections. * * @throws IOException if an I/O error occurs when creating the socket. */ - public void connect() throws IOException - { - if (socket == null) - { + public void connect() throws IOException { + if (socket == null) { socket = new DatagramSocket(port); // port = 0 is bound to available free port if (port == 0) { @@ -111,65 +93,24 @@ public class SimpleNTPServer implements Runnable { } } - /** - * Start time service and provide time to client connections. - * - * @throws java.io.IOException if an I/O error occurs when creating the socket. - */ - public void start() throws IOException - { - if (socket == null) - { - connect(); - } - if (!started) - { - started = true; - new Thread(this).start(); - } - } - - /** - * main thread to service client connections. - */ - @Override - public void run() - { - running = true; - byte buffer[] = new byte[48]; - final DatagramPacket request = new DatagramPacket(buffer, buffer.length); - do { - try { - socket.receive(request); - final long rcvTime = System.currentTimeMillis(); - handlePacket(request, rcvTime); - } catch (IOException e) { - if (running) - { - e.printStackTrace(); - } - // otherwise socket thrown exception during shutdown - } - } while (running); + public int getPort() { + return port; } /** - * Handle incoming packet. If NTP packet is client-mode then respond - * to that host with a NTP response packet otherwise ignore. + * Handles incoming packet. If NTP packet is client-mode then respond to that host with a NTP response packet otherwise ignore. * * @param request incoming DatagramPacket * @param rcvTime time packet received * - * @throws IOException if an I/O error occurs. + * @throws IOException if an I/O error occurs. */ - protected void handlePacket(DatagramPacket request, long rcvTime) throws IOException - { - NtpV3Packet message = new NtpV3Impl(); + protected void handlePacket(final DatagramPacket request, final long rcvTime) throws IOException { + final NtpV3Packet message = new NtpV3Impl(); message.setDatagramPacket(request); - System.out.printf("NTP packet from %s mode=%s%n", request.getAddress().getHostAddress(), - NtpUtils.getModeName(message.getMode())); + System.out.printf("NTP packet from %s mode=%s%n", request.getAddress().getHostAddress(), NtpUtils.getModeName(message.getMode())); if (message.getMode() == NtpV3Packet.MODE_CLIENT) { - NtpV3Packet response = new NtpV3Impl(); + final NtpV3Packet response = new NtpV3Impl(); response.setStratum(1); response.setMode(NtpV3Packet.MODE_SERVER); @@ -189,7 +130,7 @@ public class SimpleNTPServer implements Runnable { // Transmit time is time reply sent by server (t3) response.setTransmitTime(TimeStamp.getNtpTime(System.currentTimeMillis())); - DatagramPacket dp = response.getDatagramPacket(); + final DatagramPacket dp = response.getDatagramPacket(); dp.setPort(request.getPort()); dp.setAddress(request.getAddress()); socket.send(dp); @@ -198,36 +139,70 @@ public class SimpleNTPServer implements Runnable { } /** - * Close server socket and stop listening. + * Returns state of whether time service is running. + * + * @return true if time service is running */ - public void stop() - { - running = false; - if (socket != null) - { - socket.close(); // force closing of the socket - socket = null; - } - started = false; + public boolean isRunning() { + return running; } - public static void main(String[] args) - { - int port = NtpV3Packet.NTP_PORT; - if (args.length != 0) - { + /** + * Returns state of whether time service is running. + * + * @return true if time service is running + */ + public boolean isStarted() { + return started; + } + + /** + * Main method to service client connections. + */ + @Override + public void run() { + running = true; + final byte buffer[] = new byte[48]; + final DatagramPacket request = new DatagramPacket(buffer, buffer.length); + do { try { - port = Integer.parseInt(args[0]); - } catch (NumberFormatException nfe) { - nfe.printStackTrace(); + socket.receive(request); + final long rcvTime = System.currentTimeMillis(); + handlePacket(request, rcvTime); + } catch (final IOException e) { + if (running) { + e.printStackTrace(); + } + // otherwise socket thrown exception during shutdown } + } while (running); + } + + /** + * Starts time service and provide time to client connections. + * + * @throws IOException if an I/O error occurs when creating the socket. + */ + public void start() throws IOException { + if (socket == null) { + connect(); } - SimpleNTPServer timeServer = new SimpleNTPServer(port); - try { - timeServer.start(); - } catch (IOException e) { - e.printStackTrace(); + if (!started) { + started = true; + new Thread(this).start(); + } + } + + /** + * Closes server socket and stop listening. + */ + public void stop() { + running = false; + if (socket != null) { + socket.close(); // force closing of the socket + socket = null; } + started = false; } } diff --git a/src/main/java/examples/ntp/TimeClient.java b/src/main/java/org/apache/commons/net/examples/ntp/TimeClient.java similarity index 58% rename from src/main/java/examples/ntp/TimeClient.java rename to src/main/java/org/apache/commons/net/examples/ntp/TimeClient.java index d355ca5..3d5619d 100644 --- a/src/main/java/examples/ntp/TimeClient.java +++ b/src/main/java/org/apache/commons/net/examples/ntp/TimeClient.java @@ -1,5 +1,3 @@ -package examples.ntp; - /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -17,6 +15,7 @@ package examples.ntp; * limitations under the License. */ +package org.apache.commons.net.examples.ntp; import java.io.IOException; import java.net.InetAddress; @@ -24,78 +23,59 @@ import java.net.InetAddress; import org.apache.commons.net.time.TimeTCPClient; import org.apache.commons.net.time.TimeUDPClient; -/*** - * This is an example program demonstrating how to use the TimeTCPClient - * and TimeUDPClient classes. - * This program connects to the default time service port of a - * specified server, retrieves the time, and prints it to standard output. - * See <A HREF="ftp://ftp.rfc-editor.org/in-notes/rfc868.txt"> the spec </A> - * for details. The default is to use the TCP port. Use the -udp flag to - * use the UDP port. +/** + * This is an example program demonstrating how to use the TimeTCPClient and TimeUDPClient classes. This program connects to the default time service port of a + * specified server, retrieves the time, and prints it to standard output. See <A HREF="ftp://ftp.rfc-editor.org/in-notes/rfc868.txt"> the spec </A> for + * details. The default is to use the TCP port. Use the -udp flag to use the UDP port. * <p> * Usage: TimeClient [-udp] <hostname> - ***/ -public final class TimeClient -{ - - public static final void timeTCP(String host) throws IOException - { - TimeTCPClient client = new TimeTCPClient(); - try { - // We want to timeout if a response takes longer than 60 seconds - client.setDefaultTimeout(60000); - client.connect(host); - System.out.println(client.getDate()); - } finally { - client.disconnect(); - } - } - - public static final void timeUDP(String host) throws IOException - { - TimeUDPClient client = new TimeUDPClient(); - - // We want to timeout if a response takes longer than 60 seconds - client.setDefaultTimeout(60000); - client.open(); - System.out.println(client.getDate(InetAddress.getByName(host))); - client.close(); - } + * </p> + */ +public final class TimeClient { - public static void main(String[] args) - { + public static void main(final String[] args) { - if (args.length == 1) - { - try - { + if (args.length == 1) { + try { timeTCP(args[0]); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); System.exit(1); } - } - else if (args.length == 2 && args[0].equals("-udp")) - { - try - { + } else if (args.length == 2 && args[0].equals("-udp")) { + try { timeUDP(args[1]); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); System.exit(1); } - } - else - { + } else { System.err.println("Usage: TimeClient [-udp] <hostname>"); System.exit(1); } } -} + public static void timeTCP(final String host) throws IOException { + final TimeTCPClient client = new TimeTCPClient(); + try { + // We want to timeout if a response takes longer than 60 seconds + client.setDefaultTimeout(60000); + client.connect(host); + System.out.println(client.getDate()); + } finally { + client.disconnect(); + } + } + + public static void timeUDP(final String host) throws IOException { + final TimeUDPClient client = new TimeUDPClient(); + + // We want to timeout if a response takes longer than 60 seconds + client.setDefaultTimeout(60000); + client.open(); + System.out.println(client.getDate(InetAddress.getByName(host))); + client.close(); + } +} diff --git a/src/main/java/org/apache/commons/net/examples/package-info.java b/src/main/java/org/apache/commons/net/examples/package-info.java new file mode 100644 index 0000000..520014c --- /dev/null +++ b/src/main/java/org/apache/commons/net/examples/package-info.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Example classes. + * <p> + * <b>These do not form part of the public API and may change without notice.</b> + */ +package org.apache.commons.net.examples; \ No newline at end of file diff --git a/src/main/java/org/apache/commons/net/examples/telnet/TelnetClientExample.java b/src/main/java/org/apache/commons/net/examples/telnet/TelnetClientExample.java new file mode 100644 index 0000000..13f94c7 --- /dev/null +++ b/src/main/java/org/apache/commons/net/examples/telnet/TelnetClientExample.java @@ -0,0 +1,263 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.net.examples.telnet; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.StringTokenizer; + +import org.apache.commons.net.telnet.EchoOptionHandler; +import org.apache.commons.net.telnet.InvalidTelnetOptionException; +import org.apache.commons.net.telnet.SimpleOptionHandler; +import org.apache.commons.net.telnet.SuppressGAOptionHandler; +import org.apache.commons.net.telnet.TelnetClient; +import org.apache.commons.net.telnet.TelnetNotificationHandler; +import org.apache.commons.net.telnet.TerminalTypeOptionHandler; + +/** + * This is a simple example of use of TelnetClient. An external option handler (SimpleTelnetOptionHandler) is used. Initial configuration requested by + * TelnetClient will be: WILL ECHO, WILL SUPPRESS-GA, DO SUPPRESS-GA. VT100 terminal type will be subnegotiated. + * <p> + * Also, use of the sendAYT(), getLocalOptionState(), getRemoteOptionState() is demonstrated. When connected, type AYT to send an AYT command to the server and + * see the result. Type OPT to see a report of the state of the first 25 options. + */ +public class TelnetClientExample implements Runnable, TelnetNotificationHandler { + private static TelnetClient tc; + + /** + * Main for the TelnetClientExample. + * + * @param args input params + * @throws Exception on error + */ + public static void main(final String[] args) throws Exception { + FileOutputStream fout = null; + + if (args.length < 1) { + System.err.println("Usage: TelnetClientExample <remote-ip> [<remote-port>]"); + System.exit(1); + } + + final String remoteip = args[0]; + + final int remoteport; + + if (args.length > 1) { + remoteport = Integer.parseInt(args[1]); + } else { + remoteport = 23; + } + + try { + fout = new FileOutputStream("spy.log", true); + } catch (final IOException e) { + System.err.println("Exception while opening the spy file: " + e.getMessage()); + } + + tc = new TelnetClient(); + + final TerminalTypeOptionHandler ttopt = new TerminalTypeOptionHandler("VT100", false, false, true, false); + final EchoOptionHandler echoopt = new EchoOptionHandler(true, false, true, false); + final SuppressGAOptionHandler gaopt = new SuppressGAOptionHandler(true, true, true, true); + + try { + tc.addOptionHandler(ttopt); + tc.addOptionHandler(echoopt); + tc.addOptionHandler(gaopt); + } catch (final InvalidTelnetOptionException e) { + System.err.println("Error registering option handlers: " + e.getMessage()); + } + + while (true) { + boolean end_loop = false; + try { + tc.connect(remoteip, remoteport); + + final Thread reader = new Thread(new TelnetClientExample()); + tc.registerNotifHandler(new TelnetClientExample()); + System.out.println("TelnetClientExample"); + System.out.println("Type AYT to send an AYT telnet command"); + System.out.println("Type OPT to print a report of status of options (0-24)"); + System.out.println("Type REGISTER to register a new SimpleOptionHandler"); + System.out.println("Type UNREGISTER to unregister an OptionHandler"); + System.out.println("Type SPY to register the spy (connect to port 3333 to spy)"); + System.out.println("Type UNSPY to stop spying the connection"); + System.out.println("Type ^[A-Z] to send the control character; use ^^ to send ^"); + + reader.start(); + final OutputStream outstr = tc.getOutputStream(); + + final byte[] buff = new byte[1024]; + int ret_read = 0; + + do { + try { + ret_read = System.in.read(buff); + if (ret_read > 0) { + final String line = new String(buff, 0, ret_read); // deliberate use of default charset + if (line.startsWith("AYT")) { + try { + System.out.println("Sending AYT"); + + System.out.println("AYT response:" + tc.sendAYT(5000)); + } catch (final IOException e) { + System.err.println("Exception waiting AYT response: " + e.getMessage()); + } + } else if (line.startsWith("OPT")) { + System.out.println("Status of options:"); + for (int ii = 0; ii < 25; ii++) { + System.out.println("Local Option " + ii + ":" + tc.getLocalOptionState(ii) + " Remote Option " + ii + ":" + + tc.getRemoteOptionState(ii)); + } + } else if (line.startsWith("REGISTER")) { + final StringTokenizer st = new StringTokenizer(new String(buff)); + try { + st.nextToken(); + final int opcode = Integer.parseInt(st.nextToken()); + final boolean initlocal = Boolean.parseBoolean(st.nextToken()); + final boolean initremote = Boolean.parseBoolean(st.nextToken()); + final boolean acceptlocal = Boolean.parseBoolean(st.nextToken()); + final boolean acceptremote = Boolean.parseBoolean(st.nextToken()); + final SimpleOptionHandler opthand = new SimpleOptionHandler(opcode, initlocal, initremote, acceptlocal, acceptremote); + tc.addOptionHandler(opthand); + } catch (final Exception e) { + if (e instanceof InvalidTelnetOptionException) { + System.err.println("Error registering option: " + e.getMessage()); + } else { + System.err.println("Invalid REGISTER command."); + System.err.println("Use REGISTER optcode initlocal initremote acceptlocal acceptremote"); + System.err.println("(optcode is an integer.)"); + System.err.println("(initlocal, initremote, acceptlocal, acceptremote are boolean)"); + } + } + } else if (line.startsWith("UNREGISTER")) { + final StringTokenizer st = new StringTokenizer(new String(buff)); + try { + st.nextToken(); + final int opcode = Integer.parseInt(st.nextToken()); + tc.deleteOptionHandler(opcode); + } catch (final Exception e) { + if (e instanceof InvalidTelnetOptionException) { + System.err.println("Error unregistering option: " + e.getMessage()); + } else { + System.err.println("Invalid UNREGISTER command."); + System.err.println("Use UNREGISTER optcode"); + System.err.println("(optcode is an integer)"); + } + } + } else if (line.startsWith("SPY")) { + tc.registerSpyStream(fout); + } else if (line.startsWith("UNSPY")) { + tc.stopSpyStream(); + } else if (line.matches("^\\^[A-Z^]\\r?\\n?$")) { + final byte toSend = buff[1]; + if (toSend == '^') { + outstr.write(toSend); + } else { + outstr.write(toSend - 'A' + 1); + } + outstr.flush(); + } else { + try { + outstr.write(buff, 0, ret_read); + outstr.flush(); + } catch (final IOException e) { + end_loop = true; + } + } + } + } catch (final IOException e) { + System.err.println("Exception while reading keyboard:" + e.getMessage()); + end_loop = true; + } + } while (ret_read > 0 && !end_loop); + + try { + tc.disconnect(); + } catch (final IOException e) { + System.err.println("Exception while connecting:" + e.getMessage()); + } + } catch (final IOException e) { + System.err.println("Exception while connecting:" + e.getMessage()); + System.exit(1); + } + } + } + + /** + * Callback method called when TelnetClient receives an option negotiation command. + * + * @param negotiation_code - type of negotiation command received (RECEIVED_DO, RECEIVED_DONT, RECEIVED_WILL, RECEIVED_WONT, RECEIVED_COMMAND) + * @param option_code - code of the option negotiated + */ + @Override + public void receivedNegotiation(final int negotiation_code, final int option_code) { + String command = null; + switch (negotiation_code) { + case TelnetNotificationHandler.RECEIVED_DO: + command = "DO"; + break; + case TelnetNotificationHandler.RECEIVED_DONT: + command = "DONT"; + break; + case TelnetNotificationHandler.RECEIVED_WILL: + command = "WILL"; + break; + case TelnetNotificationHandler.RECEIVED_WONT: + command = "WONT"; + break; + case TelnetNotificationHandler.RECEIVED_COMMAND: + command = "COMMAND"; + break; + default: + command = Integer.toString(negotiation_code); // Should not happen + break; + } + System.out.println("Received " + command + " for option code " + option_code); + } + + /** + * Reader thread. Reads lines from the TelnetClient and echoes them on the screen. + */ + @Override + public void run() { + final InputStream instr = tc.getInputStream(); + + try { + final byte[] buff = new byte[1024]; + int ret_read = 0; + + do { + ret_read = instr.read(buff); + if (ret_read > 0) { + System.out.print(new String(buff, 0, ret_read)); + } + } while (ret_read >= 0); + } catch (final IOException e) { + System.err.println("Exception while reading socket:" + e.getMessage()); + } + + try { + tc.disconnect(); + } catch (final IOException e) { + System.err.println("Exception while closing telnet:" + e.getMessage()); + } + } +} diff --git a/src/main/java/examples/telnet/WeatherTelnet.java b/src/main/java/org/apache/commons/net/examples/telnet/WeatherTelnet.java similarity index 59% rename from src/main/java/examples/telnet/WeatherTelnet.java rename to src/main/java/org/apache/commons/net/examples/telnet/WeatherTelnet.java index 3da7194..ee297e5 100644 --- a/src/main/java/examples/telnet/WeatherTelnet.java +++ b/src/main/java/org/apache/commons/net/examples/telnet/WeatherTelnet.java @@ -15,54 +15,40 @@ * limitations under the License. */ -package examples.telnet; +package org.apache.commons.net.examples.telnet; import java.io.IOException; -import org.apache.commons.net.telnet.TelnetClient; -import examples.util.IOUtil; +import org.apache.commons.net.examples.util.IOUtil; +import org.apache.commons.net.telnet.TelnetClient; -/*** - * This is an example of a trivial use of the TelnetClient class. - * It connects to the weather server at the University of Michigan, - * um-weather.sprl.umich.edu port 3000, and allows the user to interact - * with the server via standard input. You could use this example to - * connect to any telnet server, but it is obviously not general purpose - * because it reads from standard input a line at a time, making it - * inconvenient for use with a remote interactive shell. The TelnetClient - * class used by itself is mostly intended for automating access to telnet - * resources rather than interactive use. - ***/ +/** + * This is an example of a trivial use of the TelnetClient class. It connects to the weather server at the University of Michigan, um-weather.sprl.umich.edu + * port 3000, and allows the user to interact with the server via standard input. You could use this example to connect to any telnet server, but it is + * obviously not general purpose because it reads from standard input a line at a time, making it inconvenient for use with a remote interactive shell. The + * TelnetClient class used by itself is mostly intended for automating access to telnet resources rather than interactive use. + */ // This class requires the IOUtil support class! -public final class WeatherTelnet -{ +public final class WeatherTelnet { - public static final void main(String[] args) - { - TelnetClient telnet; + public static void main(final String[] args) { + final TelnetClient telnet; telnet = new TelnetClient(); - try - { + try { telnet.connect("rainmaker.wunderground.com", 3000); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); System.exit(1); } - IOUtil.readWrite(telnet.getInputStream(), telnet.getOutputStream(), - System.in, System.out); + IOUtil.readWrite(telnet.getInputStream(), telnet.getOutputStream(), System.in, System.out); - try - { + try { telnet.disconnect(); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); System.exit(1); } @@ -71,5 +57,3 @@ public final class WeatherTelnet } } - - diff --git a/src/main/java/examples/unix/chargen.java b/src/main/java/org/apache/commons/net/examples/unix/chargen.java similarity index 59% rename from src/main/java/examples/unix/chargen.java rename to src/main/java/org/apache/commons/net/examples/unix/chargen.java index c36b05f..326a7ef 100644 --- a/src/main/java/examples/unix/chargen.java +++ b/src/main/java/org/apache/commons/net/examples/unix/chargen.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.unix; +package org.apache.commons.net.examples.unix; import java.io.BufferedReader; import java.io.IOException; @@ -27,54 +27,43 @@ import java.net.SocketException; import org.apache.commons.net.chargen.CharGenTCPClient; import org.apache.commons.net.chargen.CharGenUDPClient; -/*** - * This is an example program demonstrating how to use the CharGenTCPClient - * and CharGenUDPClient classes. This program connects to the default - * chargen service port of a specified server, then reads 100 lines from - * of generated output, writing each line to standard output, and then - * closes the connection. The UDP invocation of the program sends 50 - * datagrams, printing the reply to each. - * The default is to use the TCP port. Use the -udp flag to use the UDP - * port. +/** + * This is an example program demonstrating how to use the CharGenTCPClient and CharGenUDPClient classes. This program connects to the default chargen service + * port of a specified server, then reads 100 lines from of generated output, writing each line to standard output, and then closes the connection. The UDP + * invocation of the program sends 50 datagrams, printing the reply to each. The default is to use the TCP port. Use the -udp flag to use the UDP port. * <p> * Usage: chargen [-udp] <hostname> - ***/ -public final class chargen -{ + */ +public final class chargen { - public static final void chargenTCP(String host) throws IOException - { + public static void chargenTCP(final String host) throws IOException { int lines = 100; String line; - CharGenTCPClient client = new CharGenTCPClient(); - BufferedReader chargenInput; + final CharGenTCPClient client = new CharGenTCPClient(); // We want to timeout if a response takes longer than 60 seconds client.setDefaultTimeout(60000); client.connect(host); - chargenInput = - new BufferedReader(new InputStreamReader(client.getInputStream())); - - // We assume the chargen service outputs lines, but it really doesn't - // have to, so this code might actually not work if no newlines are - // present. - while (lines-- > 0) - { - if ((line = chargenInput.readLine()) == null) { - break; + try (final BufferedReader chargenInput = new BufferedReader(new InputStreamReader(client.getInputStream()))) { + + // We assume the chargen service outputs lines, but it really doesn't + // have to, so this code might actually not work if no newlines are + // present. + while (lines-- > 0) { + if ((line = chargenInput.readLine()) == null) { + break; + } + System.out.println(line); } - System.out.println(line); } - client.disconnect(); } - public static final void chargenUDP(String host) throws IOException - { + public static void chargenUDP(final String host) throws IOException { int packets = 50; byte[] data; - InetAddress address; - CharGenUDPClient client; + final InetAddress address; + final CharGenUDPClient client; address = InetAddress.getByName(host); client = new CharGenUDPClient(); @@ -84,29 +73,23 @@ public final class chargen // the packet is lost. client.setSoTimeout(5000); - while (packets-- > 0) - { + while (packets-- > 0) { client.send(address); - try - { + try { data = client.receive(); } // Here we catch both SocketException and InterruptedIOException, // because even though the JDK 1.1 docs claim that // InterruptedIOException is thrown on a timeout, it seems // SocketException is also thrown. - catch (SocketException e) - { + catch (final SocketException e) { // We timed out and assume the packet is lost. System.err.println("SocketException: Timed out and dropped packet"); continue; - } - catch (InterruptedIOException e) - { + } catch (final InterruptedIOException e) { // We timed out and assume the packet is lost. - System.err.println( - "InterruptedIOException: Timed out and dropped packet"); + System.err.println("InterruptedIOException: Timed out and dropped packet"); continue; } System.out.write(data); @@ -116,36 +99,23 @@ public final class chargen client.close(); } + public static void main(final String[] args) { - public static void main(String[] args) - { - - if (args.length == 1) - { - try - { + if (args.length == 1) { + try { chargenTCP(args[0]); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); System.exit(1); } - } - else if (args.length == 2 && args[0].equals("-udp")) - { - try - { + } else if (args.length == 2 && args[0].equals("-udp")) { + try { chargenUDP(args[1]); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); System.exit(1); } - } - else - { + } else { System.err.println("Usage: chargen [-udp] <hostname>"); System.exit(1); } @@ -153,4 +123,3 @@ public final class chargen } } - diff --git a/src/main/java/examples/unix/daytime.java b/src/main/java/org/apache/commons/net/examples/unix/daytime.java similarity index 65% rename from src/main/java/examples/unix/daytime.java rename to src/main/java/org/apache/commons/net/examples/unix/daytime.java index 2a72d15..ecb11ea 100644 --- a/src/main/java/examples/unix/daytime.java +++ b/src/main/java/org/apache/commons/net/examples/unix/daytime.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.unix; +package org.apache.commons.net.examples.unix; import java.io.IOException; import java.net.InetAddress; @@ -23,22 +23,16 @@ import java.net.InetAddress; import org.apache.commons.net.daytime.DaytimeTCPClient; import org.apache.commons.net.daytime.DaytimeUDPClient; -/*** - * This is an example program demonstrating how to use the DaytimeTCP - * and DaytimeUDP classes. - * This program connects to the default daytime service port of a - * specified server, retrieves the daytime, and prints it to standard output. - * The default is to use the TCP port. Use the -udp flag to use the UDP - * port. +/** + * This is an example program demonstrating how to use the DaytimeTCP and DaytimeUDP classes. This program connects to the default daytime service port of a + * specified server, retrieves the daytime, and prints it to standard output. The default is to use the TCP port. Use the -udp flag to use the UDP port. * <p> * Usage: daytime [-udp] <hostname> - ***/ -public final class daytime -{ + */ +public final class daytime { - public static final void daytimeTCP(String host) throws IOException - { - DaytimeTCPClient client = new DaytimeTCPClient(); + public static void daytimeTCP(final String host) throws IOException { + final DaytimeTCPClient client = new DaytimeTCPClient(); // We want to timeout if a response takes longer than 60 seconds client.setDefaultTimeout(60000); @@ -47,48 +41,33 @@ public final class daytime client.disconnect(); } - public static final void daytimeUDP(String host) throws IOException - { - DaytimeUDPClient client = new DaytimeUDPClient(); + public static void daytimeUDP(final String host) throws IOException { + final DaytimeUDPClient client = new DaytimeUDPClient(); // We want to timeout if a response takes longer than 60 seconds client.setDefaultTimeout(60000); client.open(); - System.out.println(client.getTime( - InetAddress.getByName(host)).trim()); + System.out.println(client.getTime(InetAddress.getByName(host)).trim()); client.close(); } + public static void main(final String[] args) { - public static void main(String[] args) - { - - if (args.length == 1) - { - try - { + if (args.length == 1) { + try { daytimeTCP(args[0]); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); System.exit(1); } - } - else if (args.length == 2 && args[0].equals("-udp")) - { - try - { + } else if (args.length == 2 && args[0].equals("-udp")) { + try { daytimeUDP(args[1]); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); System.exit(1); } - } - else - { + } else { System.err.println("Usage: daytime [-udp] <hostname>"); System.exit(1); } @@ -96,4 +75,3 @@ public final class daytime } } - diff --git a/src/main/java/examples/unix/echo.java b/src/main/java/org/apache/commons/net/examples/unix/echo.java similarity index 63% rename from src/main/java/examples/unix/echo.java rename to src/main/java/org/apache/commons/net/examples/unix/echo.java index d37e9a9..b753179 100644 --- a/src/main/java/examples/unix/echo.java +++ b/src/main/java/org/apache/commons/net/examples/unix/echo.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.unix; +package org.apache.commons.net.examples.unix; import java.io.BufferedReader; import java.io.IOException; @@ -29,24 +29,20 @@ import java.net.SocketException; import org.apache.commons.net.echo.EchoTCPClient; import org.apache.commons.net.echo.EchoUDPClient; -/*** - * This is an example program demonstrating how to use the EchoTCPClient - * and EchoUDPClient classes. This program connects to the default echo - * service port of a specified server, then reads lines from standard - * input, writing them to the echo server, and then printing the echo. - * The default is to use the TCP port. Use the -udp flag to use the UDP - * port. +/** + * This is an example program demonstrating how to use the EchoTCPClient and EchoUDPClient classes. This program connects to the default echo service port of a + * specified server, then reads lines from standard input, writing them to the echo server, and then printing the echo. The default is to use the TCP port. Use + * the -udp flag to use the UDP port. * <p> * Usage: echo [-udp] <hostname> - ***/ -public final class echo -{ - - public static final void echoTCP(String host) throws IOException - { - EchoTCPClient client = new EchoTCPClient(); - BufferedReader input, echoInput; - PrintWriter echoOutput; + */ +public final class echo { + + public static void echoTCP(final String host) throws IOException { + final EchoTCPClient client = new EchoTCPClient(); + final BufferedReader input; + final BufferedReader echoInput; + final PrintWriter echoOutput; String line; // We want to timeout if a response takes longer than 60 seconds @@ -54,28 +50,26 @@ public final class echo client.connect(host); System.out.println("Connected to " + host + "."); input = new BufferedReader(new InputStreamReader(System.in)); - echoOutput = - new PrintWriter(new OutputStreamWriter(client.getOutputStream()), true); - echoInput = - new BufferedReader(new InputStreamReader(client.getInputStream())); + echoOutput = new PrintWriter(new OutputStreamWriter(client.getOutputStream()), true); + echoInput = new BufferedReader(new InputStreamReader(client.getInputStream())); - while ((line = input.readLine()) != null) - { + while ((line = input.readLine()) != null) { echoOutput.println(line); System.out.println(echoInput.readLine()); } - + echoOutput.close(); + echoInput.close(); + echoInput.close(); client.disconnect(); } - public static final void echoUDP(String host) throws IOException - { + public static void echoUDP(final String host) throws IOException { int length, count; byte[] data; String line; - BufferedReader input; - InetAddress address; - EchoUDPClient client; + final BufferedReader input; + final InetAddress address; + final EchoUDPClient client; input = new BufferedReader(new InputStreamReader(System.in)); address = InetAddress.getByName(host); @@ -88,39 +82,30 @@ public final class echo // Remember, there are no guarantees about the ordering of returned // UDP packets, so there is a chance the output may be jumbled. - while ((line = input.readLine()) != null) - { + while ((line = input.readLine()) != null) { data = line.getBytes(); client.send(data, address); count = 0; - do - { - try - { + do { + try { length = client.receive(data); } // Here we catch both SocketException and InterruptedIOException, // because even though the JDK 1.1 docs claim that // InterruptedIOException is thrown on a timeout, it seems // SocketException is also thrown. - catch (SocketException e) - { + catch (final SocketException e) { // We timed out and assume the packet is lost. - System.err.println( - "SocketException: Timed out and dropped packet"); + System.err.println("SocketException: Timed out and dropped packet"); break; - } - catch (InterruptedIOException e) - { + } catch (final InterruptedIOException e) { // We timed out and assume the packet is lost. - System.err.println( - "InterruptedIOException: Timed out and dropped packet"); + System.err.println("InterruptedIOException: Timed out and dropped packet"); break; } System.out.print(new String(data, 0, length)); count += length; - } - while (count < data.length); + } while (count < data.length); System.out.println(); } @@ -128,36 +113,23 @@ public final class echo client.close(); } + public static void main(final String[] args) { - public static void main(String[] args) - { - - if (args.length == 1) - { - try - { + if (args.length == 1) { + try { echoTCP(args[0]); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); System.exit(1); } - } - else if (args.length == 2 && args[0].equals("-udp")) - { - try - { + } else if (args.length == 2 && args[0].equals("-udp")) { + try { echoUDP(args[1]); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); System.exit(1); } - } - else - { + } else { System.err.println("Usage: echo [-udp] <hostname>"); System.exit(1); } @@ -165,4 +137,3 @@ public final class echo } } - diff --git a/src/main/java/examples/unix/finger.java b/src/main/java/org/apache/commons/net/examples/unix/finger.java similarity index 70% rename from src/main/java/examples/unix/finger.java rename to src/main/java/org/apache/commons/net/examples/unix/finger.java index c2ec965..0ee6e1f 100644 --- a/src/main/java/examples/unix/finger.java +++ b/src/main/java/org/apache/commons/net/examples/unix/finger.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.unix; +package org.apache.commons.net.examples.unix; import java.io.IOException; import java.net.InetAddress; @@ -23,29 +23,23 @@ import java.net.UnknownHostException; import org.apache.commons.net.finger.FingerClient; -/*** - * This is an example of how you would implement the finger command - * in Java using NetComponents. The Java version is much shorter. - * But keep in mind that the Unix finger command reads all sorts of - * local files to output local finger information. This program only - * queries the finger daemon. +/** + * This is an example of how you would implement the finger command in Java using NetComponents. The Java version is much shorter. But keep in mind that the + * Unix finger command reads all sorts of local files to output local finger information. This program only queries the finger daemon. * <p> * The -l flag is used to request long output from the server. - ***/ -public final class finger -{ + */ +public final class finger { - public static void main(String[] args) - { + public static void main(final String[] args) { boolean longOutput = false; int arg = 0, index; String handle, host; - FingerClient finger; + final FingerClient finger; InetAddress address = null; - // Get flags. If an invalid flag is present, exit with usage message. - while (arg < args.length && args[arg].startsWith("-")) - { + // Get flags. If an invalid flag is present, exit with usage message. + while (arg < args.length && args[arg].startsWith("-")) { if (args[arg].equals("-l")) { longOutput = true; } else { @@ -55,84 +49,63 @@ public final class finger ++arg; } - finger = new FingerClient(); // We want to timeout if a response takes longer than 60 seconds finger.setDefaultTimeout(60000); - if (arg >= args.length) - { + if (arg >= args.length) { // Finger local host - try - { + try { address = InetAddress.getLocalHost(); - } - catch (UnknownHostException e) - { + } catch (final UnknownHostException e) { System.err.println("Error unknown host: " + e.getMessage()); System.exit(1); } - try - { + try { finger.connect(address); System.out.print(finger.query(longOutput)); finger.disconnect(); - } - catch (IOException e) - { + } catch (final IOException e) { System.err.println("Error I/O exception: " + e.getMessage()); System.exit(1); } - return ; + return; } // Finger each argument - while (arg < args.length) - { + while (arg < args.length) { - index = args[arg].lastIndexOf("@"); + index = args[arg].lastIndexOf('@'); - if (index == -1) - { + if (index == -1) { handle = args[arg]; - try - { + try { address = InetAddress.getLocalHost(); - } - catch (UnknownHostException e) - { + } catch (final UnknownHostException e) { System.err.println("Error unknown host: " + e.getMessage()); System.exit(1); } - } - else - { + } else { handle = args[arg].substring(0, index); host = args[arg].substring(index + 1); - try - { + try { address = InetAddress.getByName(host); System.out.println("[" + address.getHostName() + "]"); - } - catch (UnknownHostException e) - { + } catch (final UnknownHostException e) { System.err.println("Error unknown host: " + e.getMessage()); System.exit(1); } } - try - { + try { finger.connect(address); System.out.print(finger.query(longOutput, handle)); finger.disconnect(); - } - catch (IOException e) - { + } catch (final IOException e) { System.err.println("Error I/O exception: " + e.getMessage()); System.exit(1); } @@ -144,4 +117,3 @@ public final class finger } } } - diff --git a/src/main/java/examples/unix/fwhois.java b/src/main/java/org/apache/commons/net/examples/unix/fwhois.java similarity index 77% rename from src/main/java/examples/unix/fwhois.java rename to src/main/java/org/apache/commons/net/examples/unix/fwhois.java index 9764adf..262050b 100644 --- a/src/main/java/examples/unix/fwhois.java +++ b/src/main/java/org/apache/commons/net/examples/unix/fwhois.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.unix; +package org.apache.commons.net.examples.unix; import java.io.IOException; import java.net.InetAddress; @@ -23,62 +23,50 @@ import java.net.UnknownHostException; import org.apache.commons.net.whois.WhoisClient; -/*** - * This is an example of how you would implement the Linux fwhois command - * in Java using NetComponents. The Java version is much shorter. - ***/ -public final class fwhois -{ +/** + * This is an example of how you would implement the Linux fwhois command in Java using NetComponents. The Java version is much shorter. + */ +public final class fwhois { - public static void main(String[] args) - { - int index; - String handle, host; + public static void main(final String[] args) { + final int index; + final String handle; + final String host; InetAddress address = null; - WhoisClient whois; + final WhoisClient whois; - if (args.length != 1) - { + if (args.length != 1) { System.err.println("usage: fwhois handle[@<server>]"); System.exit(1); } - index = args[0].lastIndexOf("@"); + index = args[0].lastIndexOf('@'); whois = new WhoisClient(); // We want to timeout if a response takes longer than 60 seconds whois.setDefaultTimeout(60000); - if (index == -1) - { + if (index == -1) { handle = args[0]; host = WhoisClient.DEFAULT_HOST; - } - else - { + } else { handle = args[0].substring(0, index); host = args[0].substring(index + 1); } - try - { + try { address = InetAddress.getByName(host); System.out.println("[" + address.getHostName() + "]"); - } - catch (UnknownHostException e) - { + } catch (final UnknownHostException e) { System.err.println("Error unknown host: " + e.getMessage()); System.exit(1); } - try - { + try { whois.connect(address); System.out.print(whois.query(handle)); whois.disconnect(); - } - catch (IOException e) - { + } catch (final IOException e) { System.err.println("Error I/O exception: " + e.getMessage()); System.exit(1); } diff --git a/src/main/java/examples/unix/rdate.java b/src/main/java/org/apache/commons/net/examples/unix/rdate.java similarity index 62% rename from src/main/java/examples/unix/rdate.java rename to src/main/java/org/apache/commons/net/examples/unix/rdate.java index b8bc257..ed39c54 100644 --- a/src/main/java/examples/unix/rdate.java +++ b/src/main/java/org/apache/commons/net/examples/unix/rdate.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package examples.unix; +package org.apache.commons.net.examples.unix; import java.io.IOException; import java.net.InetAddress; @@ -23,23 +23,40 @@ import java.net.InetAddress; import org.apache.commons.net.time.TimeTCPClient; import org.apache.commons.net.time.TimeUDPClient; -/*** - * This is an example program demonstrating how to use the TimeTCPClient - * and TimeUDPClient classes. It's very similar to the simple Unix rdate - * command. This program connects to the default time service port of a - * specified server, retrieves the time, and prints it to standard output. - * The default is to use the TCP port. Use the -udp flag to use the UDP - * port. You can test this program by using the NIST time server at - * 132.163.135.130 (warning: the IP address may change). +/** + * This is an example program demonstrating how to use the TimeTCPClient and TimeUDPClient classes. It's very similar to the simple Unix rdate command. This + * program connects to the default time service port of a specified server, retrieves the time, and prints it to standard output. The default is to use the TCP + * port. Use the -udp flag to use the UDP port. You can test this program by using the NIST time server at 132.163.135.130 (warning: the IP address may change). * <p> * Usage: rdate [-udp] <hostname> - ***/ -public final class rdate -{ + */ +public final class rdate { - public static final void timeTCP(String host) throws IOException - { - TimeTCPClient client = new TimeTCPClient(); + public static void main(final String[] args) { + + if (args.length == 1) { + try { + timeTCP(args[0]); + } catch (final IOException e) { + e.printStackTrace(); + System.exit(1); + } + } else if (args.length == 2 && args[0].equals("-udp")) { + try { + timeUDP(args[1]); + } catch (final IOException e) { + e.printStackTrace(); + System.exit(1); + } + } else { + System.err.println("Usage: rdate [-udp] <hostname>"); + System.exit(1); + } + + } + + public static void timeTCP(final String host) throws IOException { + final TimeTCPClient client = new TimeTCPClient(); // We want to timeout if a response takes longer than 60 seconds client.setDefaultTimeout(60000); @@ -48,9 +65,8 @@ public final class rdate client.disconnect(); } - public static final void timeUDP(String host) throws IOException - { - TimeUDPClient client = new TimeUDPClient(); + public static void timeUDP(final String host) throws IOException { + final TimeUDPClient client = new TimeUDPClient(); // We want to timeout if a response takes longer than 60 seconds client.setDefaultTimeout(60000); @@ -59,41 +75,4 @@ public final class rdate client.close(); } - - public static void main(String[] args) - { - - if (args.length == 1) - { - try - { - timeTCP(args[0]); - } - catch (IOException e) - { - e.printStackTrace(); - System.exit(1); - } - } - else if (args.length == 2 && args[0].equals("-udp")) - { - try - { - timeUDP(args[1]); - } - catch (IOException e) - { - e.printStackTrace(); - System.exit(1); - } - } - else - { - System.err.println("Usage: rdate [-udp] <hostname>"); - System.exit(1); - } - - } - } - diff --git a/src/main/java/examples/unix/rexec.java b/src/main/java/org/apache/commons/net/examples/unix/rexec.java similarity index 61% rename from src/main/java/examples/unix/rexec.java rename to src/main/java/org/apache/commons/net/examples/unix/rexec.java index 56b583d..2577c2a 100644 --- a/src/main/java/examples/unix/rexec.java +++ b/src/main/java/org/apache/commons/net/examples/unix/rexec.java @@ -15,41 +15,37 @@ * limitations under the License. */ -package examples.unix; +package org.apache.commons.net.examples.unix; import java.io.IOException; -import org.apache.commons.net.bsd.RExecClient; -import examples.util.IOUtil; +import org.apache.commons.net.bsd.RExecClient; +import org.apache.commons.net.examples.util.IOUtil; -/*** - * This is an example program demonstrating how to use the RExecClient class. - * This program connects to an rexec server and requests that the - * given command be executed on the server. It then reads input from stdin - * (this will be line buffered on most systems, so don't expect character - * at a time interactivity), passing it to the remote process and writes - * the process stdout and stderr to local stdout. +/** + * This is an example program demonstrating how to use the RExecClient class. This program connects to an rexec server and requests that the given command be + * executed on the server. It then reads input from stdin (this will be line buffered on most systems, so don't expect character at a time interactivity), + * passing it to the remote process and writes the process stdout and stderr to local stdout. * <p> * Example: java rexec myhost myusername mypassword "ps -aux" * <p> * Usage: rexec <hostname> <username> <password> <command> - ***/ + */ // This class requires the IOUtil support class! -public final class rexec -{ +public final class rexec { - public static void main(String[] args) - { - String server, username, password, command; - RExecClient client; + public static void main(final String[] args) { + final String server; + final String username; + final String password; + final String command; + final RExecClient client; - if (args.length != 4) - { - System.err.println( - "Usage: rexec <hostname> <username> <password> <command>"); + if (args.length != 4) { + System.err.println("Usage: rexec <hostname> <username> <password> <command>"); System.exit(1); - return ; // so compiler can do proper flow control analysis + return; // so compiler can do proper flow control analysis } client = new RExecClient(); @@ -59,44 +55,31 @@ public final class rexec password = args[2]; command = args[3]; - try - { + try { client.connect(server); - } - catch (IOException e) - { + } catch (final IOException e) { System.err.println("Could not connect to server."); e.printStackTrace(); System.exit(1); } - try - { + try { client.rexec(username, password, command); - } - catch (IOException e) - { - try - { + } catch (final IOException e) { + try { client.disconnect(); - } - catch (IOException f) - {/* ignored */} + } catch (final IOException f) { + /* ignored */} e.printStackTrace(); System.err.println("Could not execute command."); System.exit(1); } + IOUtil.readWrite(client.getInputStream(), client.getOutputStream(), System.in, System.out); - IOUtil.readWrite(client.getInputStream(), client.getOutputStream(), - System.in, System.out); - - try - { + try { client.disconnect(); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); System.exit(1); } @@ -105,4 +88,3 @@ public final class rexec } } - diff --git a/src/main/java/examples/unix/rlogin.java b/src/main/java/org/apache/commons/net/examples/unix/rlogin.java similarity index 55% rename from src/main/java/examples/unix/rlogin.java rename to src/main/java/org/apache/commons/net/examples/unix/rlogin.java index b6b8ea2..9ac5e82 100644 --- a/src/main/java/examples/unix/rlogin.java +++ b/src/main/java/org/apache/commons/net/examples/unix/rlogin.java @@ -15,52 +15,44 @@ * limitations under the License. */ -package examples.unix; +package org.apache.commons.net.examples.unix; import java.io.IOException; -import org.apache.commons.net.bsd.RLoginClient; -import examples.util.IOUtil; +import org.apache.commons.net.bsd.RLoginClient; +import org.apache.commons.net.examples.util.IOUtil; -/*** - * This is an example program demonstrating how to use the RLoginClient - * class. This program connects to an rlogin daemon and begins to - * interactively read input from stdin (this will be line buffered on most - * systems, so don't expect character at a time interactivity), passing it - * to the remote login process and writing the remote stdout and stderr - * to local stdout. If you don't have .rhosts or hosts.equiv files set up, - * the rlogin daemon will prompt you for a password. +/** + * This is an example program demonstrating how to use the RLoginClient class. This program connects to an rlogin daemon and begins to interactively read input + * from stdin (this will be line buffered on most systems, so don't expect character at a time interactivity), passing it to the remote login process and + * writing the remote stdout and stderr to local stdout. If you don't have .rhosts or hosts.equiv files set up, the rlogin daemon will prompt you for a + * password. * <p> - * On Unix systems you will not be able to use the rshell capability - * unless the process runs as root since only root can bind port addresses - * lower than 1024. + * On Unix systems you will not be able to use the rshell capability unless the process runs as root since only root can bind port addresses lower than 1024. * <p> - * JVM's using green threads will likely have problems if the rlogin daemon - * requests a password. This program is merely a demonstration and is - * not suitable for use as an application, especially given that it relies - * on line buffered input from System.in. The best way to run this example - * is probably from a Win95 dos box into a Unix host. + * JVM's using green threads will likely have problems if the rlogin daemon requests a password. This program is merely a demonstration and is not suitable for + * use as an application, especially given that it relies on line buffered input from System.in. The best way to run this example is probably from a Win95 dos + * box into a Unix host. * <p> * Example: java rlogin myhost localusername remoteusername vt100 * <p> * Usage: rlogin <hostname> <localuser> <remoteuser> <terminal> - ***/ + */ // This class requires the IOUtil support class! -public final class rlogin -{ +public final class rlogin { - public static void main(String[] args) - { - String server, localuser, remoteuser, terminal; - RLoginClient client; + public static void main(final String[] args) { + final String server; + final String localuser; + final String remoteuser; + final String terminal; + final RLoginClient client; - if (args.length != 4) - { - System.err.println( - "Usage: rlogin <hostname> <localuser> <remoteuser> <terminal>"); + if (args.length != 4) { + System.err.println("Usage: rlogin <hostname> <localuser> <remoteuser> <terminal>"); System.exit(1); - return ; // so compiler can do proper flow control analysis + return; // so compiler can do proper flow control analysis } client = new RLoginClient(); @@ -70,44 +62,31 @@ public final class rlogin remoteuser = args[2]; terminal = args[3]; - try - { + try { client.connect(server); - } - catch (IOException e) - { + } catch (final IOException e) { System.err.println("Could not connect to server."); e.printStackTrace(); System.exit(1); } - try - { + try { client.rlogin(localuser, remoteuser, terminal); - } - catch (IOException e) - { - try - { + } catch (final IOException e) { + try { client.disconnect(); - } - catch (IOException f) - {/* ignored */} + } catch (final IOException f) { + /* ignored */} e.printStackTrace(); System.err.println("rlogin authentication failed."); System.exit(1); } + IOUtil.readWrite(client.getInputStream(), client.getOutputStream(), System.in, System.out); - IOUtil.readWrite(client.getInputStream(), client.getOutputStream(), - System.in, System.out); - - try - { + try { client.disconnect(); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); System.exit(1); } @@ -116,4 +95,3 @@ public final class rlogin } } - diff --git a/src/main/java/examples/unix/rshell.java b/src/main/java/org/apache/commons/net/examples/unix/rshell.java similarity index 60% rename from src/main/java/examples/unix/rshell.java rename to src/main/java/org/apache/commons/net/examples/unix/rshell.java index 5a959b2..6502f3a 100644 --- a/src/main/java/examples/unix/rshell.java +++ b/src/main/java/org/apache/commons/net/examples/unix/rshell.java @@ -15,45 +15,39 @@ * limitations under the License. */ -package examples.unix; +package org.apache.commons.net.examples.unix; import java.io.IOException; -import org.apache.commons.net.bsd.RCommandClient; -import examples.util.IOUtil; +import org.apache.commons.net.bsd.RCommandClient; +import org.apache.commons.net.examples.util.IOUtil; -/*** - * This is an example program demonstrating how to use the RCommandClient - * class. This program connects to an rshell daemon and requests that the - * given command be executed on the server. It then reads input from stdin - * (this will be line buffered on most systems, so don't expect character - * at a time interactivity), passing it to the remote process and writes - * the process stdout and stderr to local stdout. +/** + * This is an example program demonstrating how to use the RCommandClient class. This program connects to an rshell daemon and requests that the given command + * be executed on the server. It then reads input from stdin (this will be line buffered on most systems, so don't expect character at a time interactivity), + * passing it to the remote process and writes the process stdout and stderr to local stdout. * <p> - * On Unix systems you will not be able to use the rshell capability - * unless the process runs as root since only root can bind port addresses - * lower than 1024. + * On Unix systems you will not be able to use the rshell capability unless the process runs as root since only root can bind port addresses lower than 1024. * <p> * Example: java rshell myhost localusername remoteusername "ps -aux" * <p> * Usage: rshell <hostname> <localuser> <remoteuser> <command> - ***/ + */ // This class requires the IOUtil support class! -public final class rshell -{ +public final class rshell { - public static void main(String[] args) - { - String server, localuser, remoteuser, command; - RCommandClient client; + public static void main(final String[] args) { + final String server; + final String localuser; + final String remoteuser; + final String command; + final RCommandClient client; - if (args.length != 4) - { - System.err.println( - "Usage: rshell <hostname> <localuser> <remoteuser> <command>"); + if (args.length != 4) { + System.err.println("Usage: rshell <hostname> <localuser> <remoteuser> <command>"); System.exit(1); - return ; // so compiler can do proper flow control analysis + return; // so compiler can do proper flow control analysis } client = new RCommandClient(); @@ -63,44 +57,31 @@ public final class rshell remoteuser = args[2]; command = args[3]; - try - { + try { client.connect(server); - } - catch (IOException e) - { + } catch (final IOException e) { System.err.println("Could not connect to server."); e.printStackTrace(); System.exit(1); } - try - { + try { client.rcommand(localuser, remoteuser, command); - } - catch (IOException e) - { - try - { + } catch (final IOException e) { + try { client.disconnect(); - } - catch (IOException f) - {/* ignored */} + } catch (final IOException f) { + /* ignored */} e.printStackTrace(); System.err.println("Could not execute command."); System.exit(1); } + IOUtil.readWrite(client.getInputStream(), client.getOutputStream(), System.in, System.out); - IOUtil.readWrite(client.getInputStream(), client.getOutputStream(), - System.in, System.out); - - try - { + try { client.disconnect(); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); System.exit(1); } @@ -109,4 +90,3 @@ public final class rshell } } - diff --git a/src/main/java/org/apache/commons/net/examples/util/IOUtil.java b/src/main/java/org/apache/commons/net/examples/util/IOUtil.java new file mode 100644 index 0000000..14b6f19 --- /dev/null +++ b/src/main/java/org/apache/commons/net/examples/util/IOUtil.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.net.examples.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.commons.net.io.Util; +import org.apache.commons.net.util.NetConstants; + +/** + * This is a utility class providing a reader/writer capability required by the weatherTelnet, rexec, rshell, and rlogin example programs. The only point of the + * class is to hold the static method readWrite which spawns a reader thread and a writer thread. The reader thread reads from a local input source (presumably + * stdin) and writes the data to a remote output destination. The writer thread reads from a remote input source and writes to a local output destination. The + * threads terminate when the remote input source closes. + */ + +public final class IOUtil { + + public static void readWrite(final InputStream remoteInput, final OutputStream remoteOutput, final InputStream localInput, final OutputStream localOutput) { + final Thread reader; + final Thread writer; + + reader = new Thread(() -> { + int ch; + + try { + while (!Thread.interrupted() && (ch = localInput.read()) != NetConstants.EOS) { + remoteOutput.write(ch); + remoteOutput.flush(); + } + } catch (final IOException e) { + // e.printStackTrace(); + } + }); + + writer = new Thread(() -> { + try { + Util.copyStream(remoteInput, localOutput); + } catch (final IOException e) { + e.printStackTrace(); + System.exit(1); + } + }); + + writer.setPriority(Thread.currentThread().getPriority() + 1); + + writer.start(); + reader.setDaemon(true); + reader.start(); + + try { + writer.join(); + reader.interrupt(); + } catch (final InterruptedException e) { + // Ignored + } + } + +} diff --git a/src/main/java/org/apache/commons/net/finger/FingerClient.java b/src/main/java/org/apache/commons/net/finger/FingerClient.java index 7655917..2fdb66e 100644 --- a/src/main/java/org/apache/commons/net/finger/FingerClient.java +++ b/src/main/java/org/apache/commons/net/finger/FingerClient.java @@ -16,159 +16,103 @@ */ package org.apache.commons.net.finger; +import java.io.BufferedOutputStream; import java.io.BufferedReader; +import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.BufferedOutputStream; -import java.io.DataOutputStream; import org.apache.commons.net.SocketClient; import org.apache.commons.net.util.Charsets; -/*** - * The FingerClient class implements the client side of the Internet Finger - * Protocol defined in RFC 1288. To finger a host you create a - * FingerClient instance, connect to the host, query the host, and finally - * disconnect from the host. If the finger service you want to query is on - * a non-standard port, connect to the host at that port. - * Here's a sample use: +/** + * The FingerClient class implements the client side of the Internet Finger Protocol defined in RFC 1288. To finger a host you create a FingerClient instance, + * connect to the host, query the host, and finally disconnect from the host. If the finger service you want to query is on a non-standard port, connect to the + * host at that port. Here's a sample use: + * * <pre> - * FingerClient finger; + * FingerClient finger; * - * finger = new FingerClient(); + * finger = new FingerClient(); * - * try { - * finger.connect("foo.bar.com"); - * System.out.println(finger.query("foobar", false)); - * finger.disconnect(); - * } catch(IOException e) { - * System.err.println("Error I/O exception: " + e.getMessage()); - * return; - * } + * try { + * finger.connect("foo.bar.com"); + * System.out.println(finger.query("foobar", false)); + * finger.disconnect(); + * } catch (IOException e) { + * System.err.println("Error I/O exception: " + e.getMessage()); + * return; + * } * </pre> * - ***/ + */ -public class FingerClient extends SocketClient -{ - /*** - * The default FINGER port. Set to 79 according to RFC 1288. - ***/ +public class FingerClient extends SocketClient { + /** + * The default FINGER port. Set to 79 according to RFC 1288. + */ public static final int DEFAULT_PORT = 79; - private static final String __LONG_FLAG = "/W "; + private static final String LONG_FLAG = "/W "; - private transient char[] __buffer = new char[1024]; + private final transient char[] buffer = new char[1024]; - /*** - * The default FingerClient constructor. Initializes the - * default port to <code> DEFAULT_PORT </code>. - ***/ - public FingerClient() - { + /** + * The default FingerClient constructor. Initializes the default port to <code> DEFAULT_PORT </code>. + */ + public FingerClient() { setDefaultPort(DEFAULT_PORT); } - - /*** - * Fingers a user at the connected host and returns the output - * as a String. You must first connect to a finger server before - * calling this method, and you should disconnect afterward. - * - * @param longOutput Set to true if long output is requested, false if not. - * @param username The name of the user to finger. - * @return The result of the finger query. - * @throws IOException If an I/O error occurs while reading the socket. - ***/ - public String query(boolean longOutput, String username) throws IOException - { - int read; - StringBuilder result = new StringBuilder(__buffer.length); - BufferedReader input; - - input = - new BufferedReader(new InputStreamReader(getInputStream(longOutput, - username), getCharset())); - - try { - while (true) - { - read = input.read(__buffer, 0, __buffer.length); - if (read <= 0) { - break; - } - result.append(__buffer, 0, read); - } - } finally { - input.close(); - } - - return result.toString(); - } - - - /*** - * Fingers the connected host and returns the output - * as a String. You must first connect to a finger server before - * calling this method, and you should disconnect afterward. - * This is equivalent to calling <code> query(longOutput, "") </code>. + /** + * Fingers the connected host and returns the input stream from the network connection of the finger query. This is equivalent to calling + * getInputStream(longOutput, ""). You must first connect to a finger server before calling this method, and you should disconnect after finishing reading + * the stream. * * @param longOutput Set to true if long output is requested, false if not. - * @return The result of the finger query. - * @throws IOException If an I/O error occurs while reading the socket. - ***/ - public String query(boolean longOutput) throws IOException - { - return query(longOutput, ""); + * @return The InputStream of the network connection of the finger query. Can be read to obtain finger results. + * @throws IOException If an I/O error during the operation. + */ + public InputStream getInputStream(final boolean longOutput) throws IOException { + return getInputStream(longOutput, ""); } - - /*** - * Fingers a user and returns the input stream from the network connection - * of the finger query. You must first connect to a finger server before - * calling this method, and you should disconnect after finishing reading - * the stream. + /** + * Fingers a user and returns the input stream from the network connection of the finger query. You must first connect to a finger server before calling + * this method, and you should disconnect after finishing reading the stream. * * @param longOutput Set to true if long output is requested, false if not. - * @param username The name of the user to finger. - * @return The InputStream of the network connection of the finger query. - * Can be read to obtain finger results. + * @param username The name of the user to finger. + * @return The InputStream of the network connection of the finger query. Can be read to obtain finger results. * @throws IOException If an I/O error during the operation. - ***/ - public InputStream getInputStream(boolean longOutput, String username) - throws IOException - { + */ + public InputStream getInputStream(final boolean longOutput, final String username) throws IOException { return getInputStream(longOutput, username, null); } - /*** - * Fingers a user and returns the input stream from the network connection - * of the finger query. You must first connect to a finger server before - * calling this method, and you should disconnect after finishing reading - * the stream. + /** + * Fingers a user and returns the input stream from the network connection of the finger query. You must first connect to a finger server before calling + * this method, and you should disconnect after finishing reading the stream. * * @param longOutput Set to true if long output is requested, false if not. - * @param username The name of the user to finger. - * @param encoding the character encoding that should be used for the query, - * null for the platform's default encoding - * @return The InputStream of the network connection of the finger query. - * Can be read to obtain finger results. + * @param username The name of the user to finger. + * @param encoding the character encoding that should be used for the query, null for the platform's default encoding + * @return The InputStream of the network connection of the finger query. Can be read to obtain finger results. * @throws IOException If an I/O error during the operation. - ***/ - public InputStream getInputStream(boolean longOutput, String username, String encoding) - throws IOException - { - DataOutputStream output; - StringBuilder buffer = new StringBuilder(64); + */ + public InputStream getInputStream(final boolean longOutput, final String username, final String encoding) throws IOException { + final DataOutputStream output; + final StringBuilder buffer = new StringBuilder(64); if (longOutput) { - buffer.append(__LONG_FLAG); + buffer.append(LONG_FLAG); } buffer.append(username); buffer.append(SocketClient.NETASCII_EOL); // Note: Charsets.toCharset() returns the platform default for null input - byte[] encodedQuery = buffer.toString().getBytes(Charsets.toCharset(encoding).name()); // Java 1.6 can use charset directly + final byte[] encodedQuery = buffer.toString().getBytes(Charsets.toCharset(encoding).name()); // Java 1.6 can use + // charset directly output = new DataOutputStream(new BufferedOutputStream(_output_, 1024)); output.write(encodedQuery, 0, encodedQuery.length); @@ -177,22 +121,42 @@ public class FingerClient extends SocketClient return _input_; } + /** + * Fingers the connected host and returns the output as a String. You must first connect to a finger server before calling this method, and you should + * disconnect afterward. This is equivalent to calling <code> query(longOutput, "") </code>. + * + * @param longOutput Set to true if long output is requested, false if not. + * @return The result of the finger query. + * @throws IOException If an I/O error occurs while reading the socket. + */ + public String query(final boolean longOutput) throws IOException { + return query(longOutput, ""); + } - /*** - * Fingers the connected host and returns the input stream from - * the network connection of the finger query. This is equivalent to - * calling getInputStream(longOutput, ""). You must first connect to a - * finger server before calling this method, and you should disconnect - * after finishing reading the stream. + /** + * Fingers a user at the connected host and returns the output as a String. You must first connect to a finger server before calling this method, and you + * should disconnect afterward. * * @param longOutput Set to true if long output is requested, false if not. - * @return The InputStream of the network connection of the finger query. - * Can be read to obtain finger results. - * @throws IOException If an I/O error during the operation. - ***/ - public InputStream getInputStream(boolean longOutput) throws IOException - { - return getInputStream(longOutput, ""); + * @param username The name of the user to finger. + * @return The result of the finger query. + * @throws IOException If an I/O error occurs while reading the socket. + */ + public String query(final boolean longOutput, final String username) throws IOException { + int read; + final StringBuilder result = new StringBuilder(buffer.length); + + try (final BufferedReader input = new BufferedReader(new InputStreamReader(getInputStream(longOutput, username), getCharset()))) { + while (true) { + read = input.read(buffer, 0, buffer.length); + if (read <= 0) { + break; + } + result.append(buffer, 0, read); + } + } + + return result.toString(); } } diff --git a/src/main/java/org/apache/commons/net/ftp/Configurable.java b/src/main/java/org/apache/commons/net/ftp/Configurable.java index b04ded9..2b24bbb 100644 --- a/src/main/java/org/apache/commons/net/ftp/Configurable.java +++ b/src/main/java/org/apache/commons/net/ftp/Configurable.java @@ -17,19 +17,14 @@ package org.apache.commons.net.ftp; - /** - * This interface adds the aspect of configurability by means of - * a supplied FTPClientConfig object to other classes in the - * system, especially listing parsers. + * This interface adds the aspect of configurability by means of a supplied FTPClientConfig object to other classes in the system, especially listing parsers. */ public interface Configurable { /** * @param config the object containing the configuration data - * @throws IllegalArgumentException if the elements of the - * <code>config</code> are somehow inadequate to configure the - * Configurable object. + * @throws IllegalArgumentException if the elements of the <code>config</code> are somehow inadequate to configure the Configurable object. */ - public void configure(FTPClientConfig config); + void configure(FTPClientConfig config); } diff --git a/src/main/java/org/apache/commons/net/ftp/DurationUtils.java b/src/main/java/org/apache/commons/net/ftp/DurationUtils.java new file mode 100644 index 0000000..246b318 --- /dev/null +++ b/src/main/java/org/apache/commons/net/ftp/DurationUtils.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.net.ftp; + +import java.time.Duration; + +/** Temporary until Commons Lang 3.12.0. */ +class DurationUtils { + + /** + * Tests whether the given Duration is positive (>0). + * + * @param duration the value to test + * @return whether the given Duration is positive (>0). + */ + static boolean isPositive(final Duration duration) { + return duration != null && !duration.isNegative() && !duration.isZero(); + } + + static int toMillisInt(final Duration duration) { + final long millis = duration.toMillis(); + return millis > 0 ? (int) Math.min(millis, Integer.MAX_VALUE) : (int) Math.max(millis, Integer.MIN_VALUE); + } + + static Duration zeroIfNull(final Duration controlIdle) { + return controlIdle == null ? Duration.ZERO : controlIdle; + } +} diff --git a/src/main/java/org/apache/commons/net/ftp/FTP.java b/src/main/java/org/apache/commons/net/ftp/FTP.java index 8341f88..af96e1d 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTP.java +++ b/src/main/java/org/apache/commons/net/ftp/FTP.java @@ -16,6 +16,7 @@ */ package org.apache.commons.net.ftp; + import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; @@ -33,189 +34,135 @@ import org.apache.commons.net.MalformedServerReplyException; import org.apache.commons.net.ProtocolCommandSupport; import org.apache.commons.net.SocketClient; import org.apache.commons.net.io.CRLFLineReader; +import org.apache.commons.net.util.NetConstants; -/*** - * FTP provides the basic the functionality necessary to implement your - * own FTP client. It extends org.apache.commons.net.SocketClient since - * extending TelnetClient was causing unwanted behavior (like connections - * that did not time out properly). +/** + * FTP provides the basic the functionality necessary to implement your own FTP client. It extends org.apache.commons.net.SocketClient since extending + * TelnetClient was causing unwanted behavior (like connections that did not time out properly). * <p> - * To derive the full benefits of the FTP class requires some knowledge - * of the FTP protocol defined in RFC 959. However, there is no reason - * why you should have to use the FTP class. The - * {@link org.apache.commons.net.ftp.FTPClient} class, - * derived from FTP, - * implements all the functionality required of an FTP client. The - * FTP class is made public to provide access to various FTP constants - * and to make it easier for adventurous programmers (or those with - * special needs) to interact with the FTP protocol and implement their - * own clients. A set of methods with names corresponding to the FTP - * command names are provided to facilitate this interaction. + * To derive the full benefits of the FTP class requires some knowledge of the FTP protocol defined in RFC 959. However, there is no reason why you should have + * to use the FTP class. The {@link org.apache.commons.net.ftp.FTPClient} class, derived from FTP, implements all the functionality required of an FTP client. + * The FTP class is made public to provide access to various FTP constants and to make it easier for adventurous programmers (or those with special needs) to + * interact with the FTP protocol and implement their own clients. A set of methods with names corresponding to the FTP command names are provided to facilitate + * this interaction. * <p> - * You should keep in mind that the FTP server may choose to prematurely - * close a connection if the client has been idle for longer than a - * given time period (usually 900 seconds). The FTP class will detect a - * premature FTP server connection closing when it receives a - * {@link org.apache.commons.net.ftp.FTPReply#SERVICE_NOT_AVAILABLE FTPReply.SERVICE_NOT_AVAILABLE } - * response to a command. - * When that occurs, the FTP class method encountering that reply will throw - * an {@link org.apache.commons.net.ftp.FTPConnectionClosedException} - * . <code>FTPConectionClosedException</code> - * is a subclass of <code> IOException </code> and therefore need not be - * caught separately, but if you are going to catch it separately, its - * catch block must appear before the more general <code> IOException </code> - * catch block. When you encounter an - * {@link org.apache.commons.net.ftp.FTPConnectionClosedException} - * , you must disconnect the connection with - * {@link #disconnect disconnect() } to properly clean up the - * system resources used by FTP. Before disconnecting, you may check the - * last reply code and text with - * {@link #getReplyCode getReplyCode }, - * {@link #getReplyString getReplyString }, - * and {@link #getReplyStrings getReplyStrings}. - * You may avoid server disconnections while the client is idle by - * periodicaly sending NOOP commands to the server. + * You should keep in mind that the FTP server may choose to prematurely close a connection if the client has been idle for longer than a given time period + * (usually 900 seconds). The FTP class will detect a premature FTP server connection closing when it receives a + * {@link org.apache.commons.net.ftp.FTPReply#SERVICE_NOT_AVAILABLE FTPReply.SERVICE_NOT_AVAILABLE } response to a command. When that occurs, the FTP class + * method encountering that reply will throw an {@link org.apache.commons.net.ftp.FTPConnectionClosedException} . <code>FTPConectionClosedException</code> is a + * subclass of <code> IOException </code> and therefore need not be caught separately, but if you are going to catch it separately, its catch block must appear + * before the more general <code> IOException </code> catch block. When you encounter an {@link org.apache.commons.net.ftp.FTPConnectionClosedException} , you + * must disconnect the connection with {@link #disconnect disconnect() } to properly clean up the system resources used by FTP. Before disconnecting, you may + * check the last reply code and text with {@link #getReplyCode getReplyCode }, {@link #getReplyString getReplyString }, and {@link #getReplyStrings + * getReplyStrings}. You may avoid server disconnections while the client is idle by periodicaly sending NOOP commands to the server. * <p> - * Rather than list it separately for each method, we mention here that - * every method communicating with the server and throwing an IOException - * can also throw a - * {@link org.apache.commons.net.MalformedServerReplyException} - * , which is a subclass - * of IOException. A MalformedServerReplyException will be thrown when - * the reply received from the server deviates enough from the protocol - * specification that it cannot be interpreted in a useful manner despite - * attempts to be as lenient as possible. + * Rather than list it separately for each method, we mention here that every method communicating with the server and throwing an IOException can also throw a + * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the + * reply received from the server deviates enough from the protocol specification that it cannot be interpreted in a useful manner despite attempts to be as + * lenient as possible. * * @see FTPClient * @see FTPConnectionClosedException * @see org.apache.commons.net.MalformedServerReplyException - * @version $Id: FTP.java 1782546 2017-02-11 00:05:41Z sebb $ - ***/ + */ -public class FTP extends SocketClient -{ - /*** The default FTP data port (20). ***/ +public class FTP extends SocketClient { + /** The default FTP data port (20). */ public static final int DEFAULT_DATA_PORT = 20; - /*** The default FTP control port (21). ***/ + /** The default FTP control port (21). */ public static final int DEFAULT_PORT = 21; - /*** - * A constant used to indicate the file(s) being transferred should - * be treated as ASCII. This is the default file type. All constants - * ending in <code>FILE_TYPE</code> are used to indicate file types. - ***/ + /** + * A constant used to indicate the file(s) being transferred should be treated as ASCII. This is the default file type. All constants ending in + * <code>FILE_TYPE</code> are used to indicate file types. + */ public static final int ASCII_FILE_TYPE = 0; - /*** - * A constant used to indicate the file(s) being transferred should - * be treated as EBCDIC. Note however that there are several different - * EBCDIC formats. All constants ending in <code>FILE_TYPE</code> - * are used to indicate file types. - ***/ + /** + * A constant used to indicate the file(s) being transferred should be treated as EBCDIC. Note however that there are several different EBCDIC formats. All + * constants ending in <code>FILE_TYPE</code> are used to indicate file types. + */ public static final int EBCDIC_FILE_TYPE = 1; - - /*** - * A constant used to indicate the file(s) being transferred should - * be treated as a binary image, i.e., no translations should be - * performed. All constants ending in <code>FILE_TYPE</code> are used to - * indicate file types. - ***/ + /** + * A constant used to indicate the file(s) being transferred should be treated as a binary image, i.e., no translations should be performed. All constants + * ending in <code>FILE_TYPE</code> are used to indicate file types. + */ public static final int BINARY_FILE_TYPE = 2; - /*** - * A constant used to indicate the file(s) being transferred should - * be treated as a local type. All constants ending in - * <code>FILE_TYPE</code> are used to indicate file types. - ***/ + /** + * A constant used to indicate the file(s) being transferred should be treated as a local type. All constants ending in <code>FILE_TYPE</code> are used to + * indicate file types. + */ public static final int LOCAL_FILE_TYPE = 3; - /*** - * A constant used for text files to indicate a non-print text format. - * This is the default format. - * All constants ending in <code>TEXT_FORMAT</code> are used to indicate - * text formatting for text transfers (both ASCII and EBCDIC). - ***/ + /** + * A constant used for text files to indicate a non-print text format. This is the default format. All constants ending in <code>TEXT_FORMAT</code> are used + * to indicate text formatting for text transfers (both ASCII and EBCDIC). + */ public static final int NON_PRINT_TEXT_FORMAT = 4; - /*** - * A constant used to indicate a text file contains format vertical format - * control characters. - * All constants ending in <code>TEXT_FORMAT</code> are used to indicate - * text formatting for text transfers (both ASCII and EBCDIC). - ***/ + /** + * A constant used to indicate a text file contains format vertical format control characters. All constants ending in <code>TEXT_FORMAT</code> are used to + * indicate text formatting for text transfers (both ASCII and EBCDIC). + */ public static final int TELNET_TEXT_FORMAT = 5; - /*** - * A constant used to indicate a text file contains ASA vertical format - * control characters. - * All constants ending in <code>TEXT_FORMAT</code> are used to indicate - * text formatting for text transfers (both ASCII and EBCDIC). - ***/ + /** + * A constant used to indicate a text file contains ASA vertical format control characters. All constants ending in <code>TEXT_FORMAT</code> are used to + * indicate text formatting for text transfers (both ASCII and EBCDIC). + */ public static final int CARRIAGE_CONTROL_TEXT_FORMAT = 6; - /*** - * A constant used to indicate a file is to be treated as a continuous - * sequence of bytes. This is the default structure. All constants ending - * in <code>_STRUCTURE</code> are used to indicate file structure for - * file transfers. - ***/ + /** + * A constant used to indicate a file is to be treated as a continuous sequence of bytes. This is the default structure. All constants ending in + * <code>_STRUCTURE</code> are used to indicate file structure for file transfers. + */ public static final int FILE_STRUCTURE = 7; - /*** - * A constant used to indicate a file is to be treated as a sequence - * of records. All constants ending in <code>_STRUCTURE</code> - * are used to indicate file structure for file transfers. - ***/ + /** + * A constant used to indicate a file is to be treated as a sequence of records. All constants ending in <code>_STRUCTURE</code> are used to indicate file + * structure for file transfers. + */ public static final int RECORD_STRUCTURE = 8; - /*** - * A constant used to indicate a file is to be treated as a set of - * independent indexed pages. All constants ending in - * <code>_STRUCTURE</code> are used to indicate file structure for file - * transfers. - ***/ + /** + * A constant used to indicate a file is to be treated as a set of independent indexed pages. All constants ending in <code>_STRUCTURE</code> are used to + * indicate file structure for file transfers. + */ public static final int PAGE_STRUCTURE = 9; - /*** - * A constant used to indicate a file is to be transferred as a stream - * of bytes. This is the default transfer mode. All constants ending - * in <code>TRANSFER_MODE</code> are used to indicate file transfer - * modes. - ***/ + /** + * A constant used to indicate a file is to be transferred as a stream of bytes. This is the default transfer mode. All constants ending in + * <code>TRANSFER_MODE</code> are used to indicate file transfer modes. + */ public static final int STREAM_TRANSFER_MODE = 10; - /*** - * A constant used to indicate a file is to be transferred as a series - * of blocks. All constants ending in <code>TRANSFER_MODE</code> are used - * to indicate file transfer modes. - ***/ + /** + * A constant used to indicate a file is to be transferred as a series of blocks. All constants ending in <code>TRANSFER_MODE</code> are used to indicate + * file transfer modes. + */ public static final int BLOCK_TRANSFER_MODE = 11; - /*** - * A constant used to indicate a file is to be transferred as FTP - * compressed data. All constants ending in <code>TRANSFER_MODE</code> - * are used to indicate file transfer modes. - ***/ + /** + * A constant used to indicate a file is to be transferred as FTP compressed data. All constants ending in <code>TRANSFER_MODE</code> are used to indicate + * file transfer modes. + */ public static final int COMPRESSED_TRANSFER_MODE = 12; // We have to ensure that the protocol communication is in ASCII // but we use ISO-8859-1 just in case 8-bit characters cross // the wire. /** - * The default character encoding used for communicating over an - * FTP control connection. The default encoding is an - * ASCII-compatible encoding. Some FTP servers expect other - * encodings. You can change the encoding used by an FTP instance - * with {@link #setControlEncoding setControlEncoding}. + * The default character encoding used for communicating over an FTP control connection. The default encoding is an ASCII-compatible encoding. Some FTP + * servers expect other encodings. You can change the encoding used by an FTP instance with {@link #setControlEncoding setControlEncoding}. */ public static final String DEFAULT_CONTROL_ENCODING = "ISO-8859-1"; /** Length of the FTP reply code (3 alphanumerics) */ public static final int REPLY_CODE_LEN = 3; - private static final String __modes = "AEILNTCFRPSBC"; - + private static final String modes = "AEILNTCFRPSBC"; protected int _replyCode; protected ArrayList<String> _replyLines; protected boolean _newReplyString; @@ -223,314 +170,224 @@ public class FTP extends SocketClient protected String _controlEncoding; /** - * A ProtocolCommandSupport object used to manage the registering of - * ProtocolCommandListeners and te firing of ProtocolCommandEvents. + * A ProtocolCommandSupport object used to manage the registering of ProtocolCommandListeners and the firing of ProtocolCommandEvents. */ protected ProtocolCommandSupport _commandSupport_; /** - * This is used to signal whether a block of multiline responses beginning - * with xxx must be terminated by the same numeric code xxx - * See section 4.2 of RFC 959 for details. + * This is used to signal whether a block of multiline responses beginning with xxx must be terminated by the same numeric code xxx See section 4.2 of RFC + * 959 for details. */ - protected boolean strictMultilineParsing = false; + protected boolean strictMultilineParsing; /** - * If this is true, then non-multiline replies must have the format: - * 3 digit code <space> <text> - * If false, then the 3 digit code does not have to be followed by space - * See section 4.2 of RFC 959 for details. + * If this is true, then non-multiline replies must have the format: 3 digit code <space> <text> If false, then the 3 digit code does not have to be + * followed by space See section 4.2 of RFC 959 for details. */ private boolean strictReplyParsing = true; /** - * Wraps SocketClient._input_ to facilitate the reading of text - * from the FTP control connection. Do not access the control - * connection via SocketClient._input_. This member starts - * with a null value, is initialized in {@link #_connectAction_}, - * and set to null in {@link #disconnect}. + * Wraps SocketClient._input_ to facilitate the reading of text from the FTP control connection. Do not access the control connection via + * SocketClient._input_. This member starts with a null value, is initialized in {@link #_connectAction_}, and set to null in {@link #disconnect}. */ protected BufferedReader _controlInput_; /** - * Wraps SocketClient._output_ to facilitate the writing of text - * to the FTP control connection. Do not access the control - * connection via SocketClient._output_. This member starts - * with a null value, is initialized in {@link #_connectAction_}, - * and set to null in {@link #disconnect}. + * Wraps SocketClient._output_ to facilitate the writing of text to the FTP control connection. Do not access the control connection via + * SocketClient._output_. This member starts with a null value, is initialized in {@link #_connectAction_}, and set to null in {@link #disconnect}. */ protected BufferedWriter _controlOutput_; - /*** - * The default FTP constructor. Sets the default port to - * <code>DEFAULT_PORT</code> and initializes internal data structures - * for saving FTP reply information. - ***/ - public FTP() - { - super(); + /** + * The default FTP constructor. Sets the default port to <code>DEFAULT_PORT</code> and initializes internal data structures for saving FTP reply + * information. + */ + public FTP() { setDefaultPort(DEFAULT_PORT); - _replyLines = new ArrayList<String>(); + _replyLines = new ArrayList<>(); _newReplyString = false; _replyString = null; _controlEncoding = DEFAULT_CONTROL_ENCODING; _commandSupport_ = new ProtocolCommandSupport(this); } - // The RFC-compliant multiline termination check - private boolean __strictCheck(String line, String code) { - return (!(line.startsWith(code) && line.charAt(REPLY_CODE_LEN) == ' ')); - } - - // The strict check is too strong a condition because of non-conforming ftp - // servers like ftp.funet.fi which sent 226 as the last line of a - // 426 multi-line reply in response to ls /. We relax the condition to - // test that the line starts with a digit rather than starting with - // the code. - private boolean __lenientCheck(String line) { - return (!(line.length() > REPLY_CODE_LEN&& line.charAt(REPLY_CODE_LEN) != '-' && - Character.isDigit(line.charAt(0)))); - } - /** - * Get the reply, and pass it to command listeners + * Get the reply, but don't pass it to command listeners. Used for keep-alive processing only. + * + * @since 3.0 + * @throws IOException on error */ - private void __getReply() throws IOException - { - __getReply(true); + protected void __getReplyNoReport() throws IOException { + getReply(false); } /** - * Get the reply, but don't pass it to command listeners. - * Used for keep-alive processing only. - * @since 3.0 + * Send a noop and get the reply without reporting to the command listener. Intended for use with keep-alive. + * * @throws IOException on error + * @since 3.0 */ - protected void __getReplyNoReport() throws IOException - { - __getReply(false); - } - - private void __getReply(boolean reportReply) throws IOException - { - int length; - - _newReplyString = true; - _replyLines.clear(); - - String line = _controlInput_.readLine(); - - if (line == null) { - throw new FTPConnectionClosedException( - "Connection closed without indication."); - } - - // In case we run into an anomaly we don't want fatal index exceptions - // to be thrown. - length = line.length(); - if (length < REPLY_CODE_LEN) { - throw new MalformedServerReplyException( - "Truncated server reply: " + line); - } - - String code = null; - try - { - code = line.substring(0, REPLY_CODE_LEN); - _replyCode = Integer.parseInt(code); - } - catch (NumberFormatException e) - { - throw new MalformedServerReplyException( - "Could not parse response code.\nServer Reply: " + line); - } - - _replyLines.add(line); - - // Check the server reply type - if (length > REPLY_CODE_LEN) { - char sep = line.charAt(REPLY_CODE_LEN); - // Get extra lines if message continues. - if (sep == '-') { - do - { - line = _controlInput_.readLine(); - - if (line == null) { - throw new FTPConnectionClosedException( - "Connection closed without indication."); - } - - _replyLines.add(line); - - // The length() check handles problems that could arise from readLine() - // returning too soon after encountering a naked CR or some other - // anomaly. - } - while ( isStrictMultilineParsing() ? __strictCheck(line, code) : __lenientCheck(line)); - - } else if (isStrictReplyParsing()) { - if (length == REPLY_CODE_LEN + 1) { // expecting some text - throw new MalformedServerReplyException("Truncated server reply: '" + line +"'"); - } else if (sep != ' ') { - throw new MalformedServerReplyException("Invalid server reply: '" + line +"'"); - } - } - } else if (isStrictReplyParsing()) { - throw new MalformedServerReplyException("Truncated server reply: '" + line +"'"); - } - - if (reportReply) { - fireReplyReceived(_replyCode, getReplyString()); - } - - if (_replyCode == FTPReply.SERVICE_NOT_AVAILABLE) { - throw new FTPConnectionClosedException("FTP response 421 received. Server closed connection."); - } + protected void __noop() throws IOException { + final String msg = buildMessage(FTPCmd.NOOP.getCommand(), null); + send(msg); + __getReplyNoReport(); // This may timeout } /** - * Initiates control connections and gets initial reply. - * Initializes {@link #_controlInput_} and {@link #_controlOutput_}. + * Initiates control connections and gets initial reply. Initializes {@link #_controlInput_} and {@link #_controlOutput_}. */ @Override - protected void _connectAction_() throws IOException - { + protected void _connectAction_() throws IOException { _connectAction_(null); } - /** - * Initiates control connections and gets initial reply. - * Initializes {@link #_controlInput_} and {@link #_controlOutput_}. + * Initiates control connections and gets initial reply. Initializes {@link #_controlInput_} and {@link #_controlOutput_}. * * @param socketIsReader the reader to reuse (if non-null) * @throws IOException on error * @since 3.4 */ - protected void _connectAction_(Reader socketIsReader) throws IOException { + protected void _connectAction_(final Reader socketIsReader) throws IOException { super._connectAction_(); // sets up _input_ and _output_ - if(socketIsReader == null) { - _controlInput_ = - new CRLFLineReader(new InputStreamReader(_input_, getControlEncoding())); + if (socketIsReader == null) { + _controlInput_ = new CRLFLineReader(new InputStreamReader(_input_, getControlEncoding())); } else { _controlInput_ = new CRLFLineReader(socketIsReader); } - _controlOutput_ = - new BufferedWriter(new OutputStreamWriter(_output_, getControlEncoding())); + _controlOutput_ = new BufferedWriter(new OutputStreamWriter(_output_, getControlEncoding())); if (connectTimeout > 0) { // NET-385 - int original = _socket_.getSoTimeout(); + final int original = _socket_.getSoTimeout(); _socket_.setSoTimeout(connectTimeout); try { - __getReply(); + getReply(); // If we received code 120, we have to fetch completion reply. if (FTPReply.isPositivePreliminary(_replyCode)) { - __getReply(); + getReply(); } - } catch (SocketTimeoutException e) { - IOException ioe = new IOException("Timed out waiting for initial connect reply"); + } catch (final SocketTimeoutException e) { + final IOException ioe = new IOException("Timed out waiting for initial connect reply"); ioe.initCause(e); throw ioe; } finally { _socket_.setSoTimeout(original); } } else { - __getReply(); + getReply(); // If we received code 120, we have to fetch completion reply. if (FTPReply.isPositivePreliminary(_replyCode)) { - __getReply(); + getReply(); } } } - /** - * Saves the character encoding to be used by the FTP control connection. - * Some FTP servers require that commands be issued in a non-ASCII - * encoding like UTF-8 so that filenames with multi-byte character - * representations (e.g, Big 8) can be specified. - * <p> - * Please note that this has to be set before the connection is established. + * A convenience method to send the FTP ABOR command to the server, receive the reply, and return the reply code. * - * @param encoding The new character encoding for the control connection. + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - public void setControlEncoding(String encoding) { - _controlEncoding = encoding; + public int abor() throws IOException { + return sendCommand(FTPCmd.ABOR); } - /** - * @return The character encoding used to communicate over the - * control connection. + * A convenience method to send the FTP ACCT command to the server, receive the reply, and return the reply code. + * + * @param account The account name to access. + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - public String getControlEncoding() { - return _controlEncoding; + public int acct(final String account) throws IOException { + return sendCommand(FTPCmd.ACCT, account); } - - /*** - * Closes the control connection to the FTP server and sets to null - * some internal data so that the memory may be reclaimed by the - * garbage collector. The reply text and code information from the - * last command is voided so that the memory it used may be reclaimed. - * Also sets {@link #_controlInput_} and {@link #_controlOutput_} to null. + /** + * A convenience method to send the FTP ALLO command to the server, receive the reply, and return the reply code. * - * @throws IOException If an error occurs while disconnecting. - ***/ - @Override - public void disconnect() throws IOException - { - super.disconnect(); - _controlInput_ = null; - _controlOutput_ = null; - _newReplyString = false; - _replyString = null; + * @param bytes The number of bytes to allocate. + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int allo(final int bytes) throws IOException { + return sendCommand(FTPCmd.ALLO, Integer.toString(bytes)); } + /** + * A convenience method to send the FTP ALLO command to the server, receive the reply, and return the reply code. + * + * @param bytes The number of bytes to allocate. + * @param recordSize The size of a record. + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int allo(final int bytes, final int recordSize) throws IOException { + return sendCommand(FTPCmd.ALLO, Integer.toString(bytes) + " R " + Integer.toString(recordSize)); + } - /*** - * Sends an FTP command to the server, waits for a reply and returns the - * numerical response code. After invocation, for more detailed - * information, the actual reply text can be accessed by calling - * {@link #getReplyString getReplyString } or - * {@link #getReplyStrings getReplyStrings }. - * - * @param command The text representation of the FTP command to send. - * @param args The arguments to the FTP command. If this parameter is - * set to null, then the command is sent with no argument. - * @return The integer value of the FTP reply code returned by the server - * in response to the command. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int sendCommand(String command, String args) throws IOException - { - if (_controlOutput_ == null) { - throw new IOException("Connection is not open"); - } - - final String message = __buildMessage(command, args); - - __send(message); + /** + * A convenience method to send the FTP ALLO command to the server, receive the reply, and return the reply code. + * + * @param bytes The number of bytes to allocate. + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int allo(final long bytes) throws IOException { + return sendCommand(FTPCmd.ALLO, Long.toString(bytes)); + } - fireCommandSent(command, message); + /** + * A convenience method to send the FTP ALLO command to the server, receive the reply, and return the reply code. + * + * @param bytes The number of bytes to allocate. + * @param recordSize The size of a record. + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int allo(final long bytes, final int recordSize) throws IOException { + return sendCommand(FTPCmd.ALLO, Long.toString(bytes) + " R " + Integer.toString(recordSize)); + } - __getReply(); - return _replyCode; + /** + * A convenience method to send the FTP APPE command to the server, receive the reply, and return the reply code. Remember, it is up to you to manage the + * data connection. If you don't need this low level of access, use {@link org.apache.commons.net.ftp.FTPClient} , which will handle all low level details + * for you. + * + * @param pathname The pathname to use for the file when stored at the remote end of the transfer. + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int appe(final String pathname) throws IOException { + return sendCommand(FTPCmd.APPE, pathname); } - private String __buildMessage(String command, String args) { + private String buildMessage(final String command, final String args) { final StringBuilder __commandBuffer = new StringBuilder(); __commandBuffer.append(command); - if (args != null) - { + if (args != null) { __commandBuffer.append(' '); __commandBuffer.append(args); } @@ -538,1333 +395,1069 @@ public class FTP extends SocketClient return __commandBuffer.toString(); } - private void __send(String message) throws IOException, - FTPConnectionClosedException, SocketException { - try{ - _controlOutput_.write(message); - _controlOutput_.flush(); - } - catch (SocketException e) - { - if (!isConnected()) - { - throw new FTPConnectionClosedException("Connection unexpectedly closed."); - } - else - { - throw e; - } - } - } - /** - * Send a noop and get the reply without reporting to the command listener. - * Intended for use with keep-alive. + * A convenience method to send the FTP CDUP command to the server, receive the reply, and return the reply code. * - * @throws IOException on error - * @since 3.0 + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - protected void __noop() throws IOException { - String msg = __buildMessage(FTPCmd.NOOP.getCommand(), null); - __send(msg); - __getReplyNoReport(); // This may timeout - } - - /*** - * Sends an FTP command to the server, waits for a reply and returns the - * numerical response code. After invocation, for more detailed - * information, the actual reply text can be accessed by calling - * {@link #getReplyString getReplyString } or - * {@link #getReplyStrings getReplyStrings }. - * - * @param command The FTPCommand constant corresponding to the FTP command - * to send. - * @param args The arguments to the FTP command. If this parameter is - * set to null, then the command is sent with no argument. - * @return The integer value of the FTP reply code returned by the server - * in response to the command. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - * @deprecated (3.3) Use {@link #sendCommand(FTPCmd, String)} instead - ***/ - @Deprecated - public int sendCommand(int command, String args) throws IOException - { - return sendCommand(FTPCommand.getCommand(command), args); + public int cdup() throws IOException { + return sendCommand(FTPCmd.CDUP); } /** - * Sends an FTP command to the server, waits for a reply and returns the - * numerical response code. After invocation, for more detailed - * information, the actual reply text can be accessed by calling - * {@link #getReplyString getReplyString } or - * {@link #getReplyStrings getReplyStrings }. - * - * @param command The FTPCmd enum corresponding to the FTP command - * to send. - * @return The integer value of the FTP reply code returned by the server - * in response to the command. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - * @since 3.3 + * A convenience method to send the FTP CWD command to the server, receive the reply, and return the reply code. + * + * @param directory The new working directory. + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - public int sendCommand(FTPCmd command) throws IOException{ - return sendCommand(command, null); + public int cwd(final String directory) throws IOException { + return sendCommand(FTPCmd.CWD, directory); } /** - * Sends an FTP command to the server, waits for a reply and returns the - * numerical response code. After invocation, for more detailed - * information, the actual reply text can be accessed by calling - * {@link #getReplyString getReplyString } or - * {@link #getReplyStrings getReplyStrings }. - * - * @param command The FTPCmd enum corresponding to the FTP command - * to send. - * @param args The arguments to the FTP command. If this parameter is - * set to null, then the command is sent with no argument. - * @return The integer value of the FTP reply code returned by the server - * in response to the command. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - * @since 3.3 + * A convenience method to send the FTP DELE command to the server, receive the reply, and return the reply code. + * + * @param pathname The pathname to delete. + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - public int sendCommand(FTPCmd command, String args) throws IOException{ - return sendCommand(command.getCommand(), args); - } - - /*** - * Sends an FTP command with no arguments to the server, waits for a - * reply and returns the numerical response code. After invocation, for - * more detailed information, the actual reply text can be accessed by - * calling {@link #getReplyString getReplyString } or - * {@link #getReplyStrings getReplyStrings }. - * - * @param command The text representation of the FTP command to send. - * @return The integer value of the FTP reply code returned by the server - * in response to the command. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int sendCommand(String command) throws IOException - { - return sendCommand(command, null); - } - - - /*** - * Sends an FTP command with no arguments to the server, waits for a - * reply and returns the numerical response code. After invocation, for - * more detailed information, the actual reply text can be accessed by - * calling {@link #getReplyString getReplyString } or - * {@link #getReplyStrings getReplyStrings }. - * - * @param command The FTPCommand constant corresponding to the FTP command - * to send. - * @return The integer value of the FTP reply code returned by the server - * in response to the command. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int sendCommand(int command) throws IOException - { - return sendCommand(command, null); + public int dele(final String pathname) throws IOException { + return sendCommand(FTPCmd.DELE, pathname); } - - /*** - * Returns the integer value of the reply code of the last FTP reply. - * You will usually only use this method after you connect to the - * FTP server to check that the connection was successful since - * <code> connect </code> is of type void. + /** + * Closes the control connection to the FTP server and sets to null some internal data so that the memory may be reclaimed by the garbage collector. The + * reply text and code information from the last command is voided so that the memory it used may be reclaimed. Also sets {@link #_controlInput_} and + * {@link #_controlOutput_} to null. * - * @return The integer value of the reply code of the last FTP reply. - ***/ - public int getReplyCode() - { - return _replyCode; + * @throws IOException If an error occurs while disconnecting. + */ + @Override + public void disconnect() throws IOException { + super.disconnect(); + _controlInput_ = null; + _controlOutput_ = null; + _newReplyString = false; + _replyString = null; } - /*** - * Fetches a reply from the FTP server and returns the integer reply - * code. After calling this method, the actual reply text can be accessed - * from either calling {@link #getReplyString getReplyString } or - * {@link #getReplyStrings getReplyStrings }. Only use this - * method if you are implementing your own FTP client or if you need to - * fetch a secondary response from the FTP server. + /** + * A convenience method to send the FTP EPRT command to the server, receive the reply, and return the reply code. * - * @return The integer value of the reply code of the fetched FTP reply. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while receiving the - * server reply. - ***/ - public int getReply() throws IOException - { - __getReply(); - return _replyCode; - } - - - /*** - * Returns the lines of text from the last FTP server response as an array - * of strings, one entry per line. The end of line markers of each are - * stripped from each line. + * Examples: + * <ul> + * <li>EPRT |1|132.235.1.2|6275|</li> + * <li>EPRT |2|1080::8:800:200C:417A|5282|</li> + * </ul> * - * @return The lines of text from the last FTP response as an array. - ***/ - public String[] getReplyStrings() - { - return _replyLines.toArray(new String[_replyLines.size()]); - } - - /*** - * Returns the entire text of the last FTP server response exactly - * as it was received, including all end of line markers in NETASCII - * format. + * @see "http://www.faqs.org/rfcs/rfc2428.html" * - * @return The entire text from the last FTP response as a String. - ***/ - public String getReplyString() - { - StringBuilder buffer; + * @param host The host owning the port. + * @param port The new port. + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + * @since 2.2 + */ + public int eprt(final InetAddress host, final int port) throws IOException { + final int num; + final StringBuilder info = new StringBuilder(); + String h; - if (!_newReplyString) { - return _replyString; + // If IPv6, trim the zone index + h = host.getHostAddress(); + num = h.indexOf('%'); + if (num > 0) { + h = h.substring(0, num); } - buffer = new StringBuilder(256); + info.append("|"); - for (String line : _replyLines) { - buffer.append(line); - buffer.append(SocketClient.NETASCII_EOL); + if (host instanceof Inet4Address) { + info.append("1"); + } else if (host instanceof Inet6Address) { + info.append("2"); } + info.append("|"); + info.append(h); + info.append("|"); + info.append(port); + info.append("|"); - _newReplyString = false; - - return (_replyString = buffer.toString()); + return sendCommand(FTPCmd.EPRT, info.toString()); } - - /*** - * A convenience method to send the FTP USER command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the FTP EPSV command to the server, receive the reply, and return the reply code. Remember, it's up to you to interpret the + * reply string containing the host/port information. * - * @param username The username to login under. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int user(String username) throws IOException - { - return sendCommand(FTPCmd.USER, username); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + * @since 2.2 + */ + public int epsv() throws IOException { + return sendCommand(FTPCmd.EPSV); } /** - * A convenience method to send the FTP PASS command to the server, - * receive the reply, and return the reply code. - * @param password The plain text password of the username being logged into. - * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - */ - public int pass(String password) throws IOException - { - return sendCommand(FTPCmd.PASS, password); + * A convenience method to send the FTP FEAT command to the server, receive the reply, and return the reply code. + * + * @return The reply code received by the server + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + * @since 2.2 + */ + public int feat() throws IOException { + return sendCommand(FTPCmd.FEAT); + } + + /** + * Provide command support to super-class + */ + @Override + protected ProtocolCommandSupport getCommandSupport() { + return _commandSupport_; + } + + /** + * @return The character encoding used to communicate over the control connection. + */ + public String getControlEncoding() { + return _controlEncoding; } - /*** - * A convenience method to send the FTP ACCT command to the server, - * receive the reply, and return the reply code. + /** + * Fetches a reply from the FTP server and returns the integer reply code. After calling this method, the actual reply text can be accessed from either + * calling {@link #getReplyString getReplyString } or {@link #getReplyStrings getReplyStrings }. Only use this method if you are implementing your own FTP + * client or if you need to fetch a secondary response from the FTP server. * - * @param account The account name to access. - * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int acct(String account) throws IOException - { - return sendCommand(FTPCmd.ACCT, account); + * @return The integer value of the reply code of the fetched FTP reply. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while receiving the server reply. + */ + public int getReply() throws IOException { + return getReply(true); } + private int getReply(final boolean reportReply) throws IOException { + final int length; + + _newReplyString = true; + _replyLines.clear(); + + String line = _controlInput_.readLine(); + + if (line == null) { + throw new FTPConnectionClosedException("Connection closed without indication."); + } + + // In case we run into an anomaly we don't want fatal index exceptions + // to be thrown. + length = line.length(); + if (length < REPLY_CODE_LEN) { + throw new MalformedServerReplyException("Truncated server reply: " + line); + } + + String code = null; + try { + code = line.substring(0, REPLY_CODE_LEN); + _replyCode = Integer.parseInt(code); + } catch (final NumberFormatException e) { + throw new MalformedServerReplyException("Could not parse response code.\nServer Reply: " + line); + } + + _replyLines.add(line); + + // Check the server reply type + if (length > REPLY_CODE_LEN) { + final char sep = line.charAt(REPLY_CODE_LEN); + // Get extra lines if message continues. + if (sep == '-') { + do { + line = _controlInput_.readLine(); + + if (line == null) { + throw new FTPConnectionClosedException("Connection closed without indication."); + } + + _replyLines.add(line); + + // The length() check handles problems that could arise from readLine() + // returning too soon after encountering a naked CR or some other + // anomaly. + } while (isStrictMultilineParsing() ? strictCheck(line, code) : lenientCheck(line)); + + } else if (isStrictReplyParsing()) { + if (length == REPLY_CODE_LEN + 1) { // expecting some text + throw new MalformedServerReplyException("Truncated server reply: '" + line + "'"); + } + if (sep != ' ') { + throw new MalformedServerReplyException("Invalid server reply: '" + line + "'"); + } + } + } else if (isStrictReplyParsing()) { + throw new MalformedServerReplyException("Truncated server reply: '" + line + "'"); + } + + if (reportReply) { + fireReplyReceived(_replyCode, getReplyString()); + } + + if (_replyCode == FTPReply.SERVICE_NOT_AVAILABLE) { + throw new FTPConnectionClosedException("FTP response 421 received. Server closed connection."); + } + return _replyCode; + } - /*** - * A convenience method to send the FTP ABOR command to the server, - * receive the reply, and return the reply code. + /** + * Returns the integer value of the reply code of the last FTP reply. You will usually only use this method after you connect to the FTP server to check + * that the connection was successful since <code> connect </code> is of type void. * - * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int abor() throws IOException - { - return sendCommand(FTPCmd.ABOR); + * @return The integer value of the reply code of the last FTP reply. + */ + public int getReplyCode() { + return _replyCode; } - /*** - * A convenience method to send the FTP CWD command to the server, - * receive the reply, and return the reply code. + /** + * Returns the entire text of the last FTP server response exactly as it was received, including all end of line markers in NETASCII format. * - * @param directory The new working directory. - * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int cwd(String directory) throws IOException - { - return sendCommand(FTPCmd.CWD, directory); + * @return The entire text from the last FTP response as a String. + */ + public String getReplyString() { + final StringBuilder buffer; + + if (!_newReplyString) { + return _replyString; + } + + buffer = new StringBuilder(256); + + for (final String line : _replyLines) { + buffer.append(line); + buffer.append(SocketClient.NETASCII_EOL); + } + + _newReplyString = false; + + return _replyString = buffer.toString(); } - /*** - * A convenience method to send the FTP CDUP command to the server, - * receive the reply, and return the reply code. + /** + * Returns the nth line of text from the last FTP server response as a string. The end of line markers of each are stripped from the line. * - * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int cdup() throws IOException - { - return sendCommand(FTPCmd.CDUP); + * @param index The index of the line to return, 0-based. + * + * @return The lines of text from the last FTP response as an array. + */ + String getReplyString(final int index) { + return _replyLines.get(index); } - /*** - * A convenience method to send the FTP QUIT command to the server, - * receive the reply, and return the reply code. + /** + * Returns the lines of text from the last FTP server response as an array of strings, one entry per line. The end of line markers of each are stripped from + * each line. * - * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int quit() throws IOException - { - return sendCommand(FTPCmd.QUIT); + * @return The lines of text from the last FTP response as an array. + */ + public String[] getReplyStrings() { + return _replyLines.toArray(NetConstants.EMPTY_STRING_ARRAY); } - /*** - * A convenience method to send the FTP REIN command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the FTP HELP command to the server, receive the reply, and return the reply code. * * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int rein() throws IOException - { - return sendCommand(FTPCmd.REIN); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int help() throws IOException { + return sendCommand(FTPCmd.HELP); } - /*** - * A convenience method to send the FTP SMNT command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the FTP HELP command to the server, receive the reply, and return the reply code. * - * @param dir The directory name. + * @param command The command name on which to request help. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int smnt(String dir) throws IOException - { - return sendCommand(FTPCmd.SMNT, dir); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int help(final String command) throws IOException { + return sendCommand(FTPCmd.HELP, command); } - /*** - * A convenience method to send the FTP PORT command to the server, - * receive the reply, and return the reply code. + /** + * Return whether strict multiline parsing is enabled, as per RFC 959, section 4.2. * - * @param host The host owning the port. - * @param port The new port. - * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int port(InetAddress host, int port) throws IOException - { - int num; - StringBuilder info = new StringBuilder(24); + * @return True if strict, false if lenient + * @since 2.0 + */ + public boolean isStrictMultilineParsing() { + return strictMultilineParsing; + } - info.append(host.getHostAddress().replace('.', ',')); - num = port >>> 8; - info.append(','); - info.append(num); - info.append(','); - num = port & 0xff; - info.append(num); + /** + * Return whether strict non-multiline parsing is enabled, as per RFC 959, section 4.2. + * <p> + * The default is true, which requires the 3 digit code be followed by space and some text. <br> + * If false, only the 3 digit code is required (as was the case for versions up to 3.5) <br> + * + * @return True if strict (default), false if additional checks are not made + * @since 3.6 + */ + public boolean isStrictReplyParsing() { + return strictReplyParsing; + } - return sendCommand(FTPCmd.PORT, info.toString()); + // The strict check is too strong a condition because of non-conforming ftp + // servers like ftp.funet.fi which sent 226 as the last line of a + // 426 multi-line reply in response to ls /. We relax the condition to + // test that the line starts with a digit rather than starting with + // the code. + private boolean lenientCheck(final String line) { + return !(line.length() > REPLY_CODE_LEN && line.charAt(REPLY_CODE_LEN) != '-' && Character.isDigit(line.charAt(0))); } - /*** - * A convenience method to send the FTP EPRT command to the server, - * receive the reply, and return the reply code. - * - * Examples: - * <ul> - * <li>EPRT |1|132.235.1.2|6275|</li> - * <li>EPRT |2|1080::8:800:200C:417A|5282|</li> - * </ul> - * - * @see "http://www.faqs.org/rfcs/rfc2428.html" + /** + * A convenience method to send the FTP LIST command to the server, receive the reply, and return the reply code. Remember, it is up to you to manage the + * data connection. If you don't need this low level of access, use {@link org.apache.commons.net.ftp.FTPClient} , which will handle all low level details + * for you. * - * @param host The host owning the port. - * @param port The new port. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - * @since 2.2 - ***/ - public int eprt(InetAddress host, int port) throws IOException - { - int num; - StringBuilder info = new StringBuilder(); - String h; - - // If IPv6, trim the zone index - h = host.getHostAddress(); - num = h.indexOf("%"); - if (num > 0) { - h = h.substring(0, num); - } - - info.append("|"); - - if (host instanceof Inet4Address) { - info.append("1"); - } else if (host instanceof Inet6Address) { - info.append("2"); - } - info.append("|"); - info.append(h); - info.append("|"); - info.append(port); - info.append("|"); - - return sendCommand(FTPCmd.EPRT, info.toString()); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int list() throws IOException { + return sendCommand(FTPCmd.LIST); } - /*** - * A convenience method to send the FTP PASV command to the server, - * receive the reply, and return the reply code. Remember, it's up - * to you to interpret the reply string containing the host/port - * information. + /** + * A convenience method to send the FTP LIST command to the server, receive the reply, and return the reply code. Remember, it is up to you to manage the + * data connection. If you don't need this low level of access, use {@link org.apache.commons.net.ftp.FTPClient} , which will handle all low level details + * for you. * + * @param pathname The pathname to list, may be {@code null} in which case the command is sent with no parameters * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int pasv() throws IOException - { - return sendCommand(FTPCmd.PASV); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int list(final String pathname) throws IOException { + return sendCommand(FTPCmd.LIST, pathname); } - /*** - * A convenience method to send the FTP EPSV command to the server, - * receive the reply, and return the reply code. Remember, it's up - * to you to interpret the reply string containing the host/port - * information. + /** + * Sends the MDTM command for the given file. * + * @param file name of file + * @return the status + * @throws IOException on error + * @since 2.0 + **/ + public int mdtm(final String file) throws IOException { + return sendCommand(FTPCmd.MDTM, file); + } + + /** + * A convenience method to send the FTP MFMT command to the server, receive the reply, and return the reply code. + * + * @param pathname The pathname for which mtime is to be changed + * @param timeval Timestamp in <code>yyyyMMDDhhmmss</code> format * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. * @since 2.2 - ***/ - public int epsv() throws IOException - { - return sendCommand(FTPCmd.EPSV); + * @see <a href="http://tools.ietf.org/html/draft-somers-ftp-mfxx-04">http://tools.ietf.org/html/draft-somers-ftp-mfxx-04</a> + **/ + public int mfmt(final String pathname, final String timeval) throws IOException { + return sendCommand(FTPCmd.MFMT, timeval + " " + pathname); } /** - * A convenience method to send the FTP TYPE command for text files - * to the server, receive the reply, and return the reply code. - * @param fileType The type of the file (one of the <code>FILE_TYPE</code> - * constants). - * @param formatOrByteSize The format of the file (one of the - * <code>_FORMAT</code> constants. In the case of - * <code>LOCAL_FILE_TYPE</code>, the byte size. + * A convenience method to send the FTP MKD command to the server, receive the reply, and return the reply code. + * + * @param pathname The pathname of the new directory to create. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - */ - public int type(int fileType, int formatOrByteSize) throws IOException - { - StringBuilder arg = new StringBuilder(); - - arg.append(__modes.charAt(fileType)); - arg.append(' '); - if (fileType == LOCAL_FILE_TYPE) { - arg.append(formatOrByteSize); - } else { - arg.append(__modes.charAt(formatOrByteSize)); - } + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int mkd(final String pathname) throws IOException { + return sendCommand(FTPCmd.MKD, pathname); + } - return sendCommand(FTPCmd.TYPE, arg.toString()); + /** + * A convenience method to send the FTP MLSD command to the server, receive the reply, and return the reply code. Remember, it is up to you to manage the + * data connection. If you don't need this low level of access, use {@link org.apache.commons.net.ftp.FTPClient} , which will handle all low level details + * for you. + * + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + * @since 3.0 + */ + public int mlsd() throws IOException { + return sendCommand(FTPCmd.MLSD); } + /** + * A convenience method to send the FTP MLSD command to the server, receive the reply, and return the reply code. Remember, it is up to you to manage the + * data connection. If you don't need this low level of access, use {@link org.apache.commons.net.ftp.FTPClient} , which will handle all low level details + * for you. + * + * @param path the path to report on + * @return The reply code received from the server, may be {@code null} in which case the command is sent with no parameters + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + * @since 3.0 + */ + public int mlsd(final String path) throws IOException { + return sendCommand(FTPCmd.MLSD, path); + } /** - * A convenience method to send the FTP TYPE command to the server, - * receive the reply, and return the reply code. + * A convenience method to send the FTP MLST command to the server, receive the reply, and return the reply code. Remember, it is up to you to manage the + * data connection. If you don't need this low level of access, use {@link org.apache.commons.net.ftp.FTPClient} , which will handle all low level details + * for you. * - * @param fileType The type of the file (one of the <code>FILE_TYPE</code> - * constants). - * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - */ - public int type(int fileType) throws IOException - { - return sendCommand(FTPCmd.TYPE, - __modes.substring(fileType, fileType + 1)); - } - - /*** - * A convenience method to send the FTP STRU command to the server, - * receive the reply, and return the reply code. - * - * @param structure The structure of the file (one of the - * <code>_STRUCTURE</code> constants). - * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int stru(int structure) throws IOException - { - return sendCommand(FTPCmd.STRU, - __modes.substring(structure, structure + 1)); - } - - /*** - * A convenience method to send the FTP MODE command to the server, - * receive the reply, and return the reply code. - * - * @param mode The transfer mode to use (one of the - * <code>TRANSFER_MODE</code> constants). * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int mode(int mode) throws IOException - { - return sendCommand(FTPCmd.MODE, - __modes.substring(mode, mode + 1)); - } - - /*** - * A convenience method to send the FTP RETR command to the server, - * receive the reply, and return the reply code. Remember, it is up - * to you to manage the data connection. If you don't need this low - * level of access, use {@link org.apache.commons.net.ftp.FTPClient} - * , which will handle all low level details for you. - * - * @param pathname The pathname of the file to retrieve. - * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int retr(String pathname) throws IOException - { - return sendCommand(FTPCmd.RETR, pathname); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + * @since 3.0 + */ + public int mlst() throws IOException { + return sendCommand(FTPCmd.MLST); } - /*** - * A convenience method to send the FTP STOR command to the server, - * receive the reply, and return the reply code. Remember, it is up - * to you to manage the data connection. If you don't need this low - * level of access, use {@link org.apache.commons.net.ftp.FTPClient} - * , which will handle all low level details for you. + /** + * A convenience method to send the FTP MLST command to the server, receive the reply, and return the reply code. Remember, it is up to you to manage the + * data connection. If you don't need this low level of access, use {@link org.apache.commons.net.ftp.FTPClient} , which will handle all low level details + * for you. * - * @param pathname The pathname to use for the file when stored at - * the remote end of the transfer. - * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int stor(String pathname) throws IOException - { - return sendCommand(FTPCmd.STOR, pathname); + * @param path the path to report on + * @return The reply code received from the server, may be {@code null} in which case the command is sent with no parameters + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + * @since 3.0 + */ + public int mlst(final String path) throws IOException { + return sendCommand(FTPCmd.MLST, path); } - /*** - * A convenience method to send the FTP STOU command to the server, - * receive the reply, and return the reply code. Remember, it is up - * to you to manage the data connection. If you don't need this low - * level of access, use {@link org.apache.commons.net.ftp.FTPClient} - * , which will handle all low level details for you. + /** + * A convenience method to send the FTP MODE command to the server, receive the reply, and return the reply code. * + * @param mode The transfer mode to use (one of the <code>TRANSFER_MODE</code> constants). * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int stou() throws IOException - { - return sendCommand(FTPCmd.STOU); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int mode(final int mode) throws IOException { + return sendCommand(FTPCmd.MODE, modes.substring(mode, mode + 1)); } - /*** - * A convenience method to send the FTP STOU command to the server, - * receive the reply, and return the reply code. Remember, it is up - * to you to manage the data connection. If you don't need this low - * level of access, use {@link org.apache.commons.net.ftp.FTPClient} - * , which will handle all low level details for you. - * @param pathname The base pathname to use for the file when stored at - * the remote end of the transfer. Some FTP servers - * require this. + /** + * A convenience method to send the FTP NLST command to the server, receive the reply, and return the reply code. Remember, it is up to you to manage the + * data connection. If you don't need this low level of access, use {@link org.apache.commons.net.ftp.FTPClient} , which will handle all low level details + * for you. + * * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - */ - public int stou(String pathname) throws IOException - { - return sendCommand(FTPCmd.STOU, pathname); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int nlst() throws IOException { + return sendCommand(FTPCmd.NLST); } - /*** - * A convenience method to send the FTP APPE command to the server, - * receive the reply, and return the reply code. Remember, it is up - * to you to manage the data connection. If you don't need this low - * level of access, use {@link org.apache.commons.net.ftp.FTPClient} - * , which will handle all low level details for you. + /** + * A convenience method to send the FTP NLST command to the server, receive the reply, and return the reply code. Remember, it is up to you to manage the + * data connection. If you don't need this low level of access, use {@link org.apache.commons.net.ftp.FTPClient} , which will handle all low level details + * for you. * - * @param pathname The pathname to use for the file when stored at - * the remote end of the transfer. + * @param pathname The pathname to list, may be {@code null} in which case the command is sent with no parameters * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int appe(String pathname) throws IOException - { - return sendCommand(FTPCmd.APPE, pathname); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int nlst(final String pathname) throws IOException { + return sendCommand(FTPCmd.NLST, pathname); } - /*** - * A convenience method to send the FTP ALLO command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the FTP NOOP command to the server, receive the reply, and return the reply code. * - * @param bytes The number of bytes to allocate. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int allo(int bytes) throws IOException - { - return sendCommand(FTPCmd.ALLO, Integer.toString(bytes)); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int noop() throws IOException { + return sendCommand(FTPCmd.NOOP); } /** - * A convenience method to send the FTP FEAT command to the server, receive the reply, - * and return the reply code. - * @return The reply code received by the server - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - * @since 2.2 + * A convenience method to send the FTP PASS command to the server, receive the reply, and return the reply code. + * + * @param password The plain text password of the username being logged into. + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - public int feat() throws IOException - { - return sendCommand(FTPCmd.FEAT); + public int pass(final String password) throws IOException { + return sendCommand(FTPCmd.PASS, password); } - /*** - * A convenience method to send the FTP ALLO command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the FTP PASV command to the server, receive the reply, and return the reply code. Remember, it's up to you to interpret the + * reply string containing the host/port information. * - * @param bytes The number of bytes to allocate. - * @param recordSize The size of a record. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int allo(int bytes, int recordSize) throws IOException - { - return sendCommand(FTPCmd.ALLO, Integer.toString(bytes) + " R " + - Integer.toString(recordSize)); - } - - /*** - * A convenience method to send the FTP REST command to the server, - * receive the reply, and return the reply code. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int pasv() throws IOException { + return sendCommand(FTPCmd.PASV); + } + + /** + * A convenience method to send the FTP PORT command to the server, receive the reply, and return the reply code. * - * @param marker The marker at which to restart a transfer. + * @param host The host owning the port. + * @param port The new port. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int rest(String marker) throws IOException - { - return sendCommand(FTPCmd.REST, marker); - } + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int port(final InetAddress host, final int port) throws IOException { + int num; + final StringBuilder info = new StringBuilder(24); + info.append(host.getHostAddress().replace('.', ',')); + num = port >>> 8; + info.append(','); + info.append(num); + info.append(','); + num = port & 0xff; + info.append(num); - /** - * @param file name of file - * @return the status - * @throws IOException on error - * @since 2.0 - **/ - public int mdtm(String file) throws IOException - { - return sendCommand(FTPCmd.MDTM, file); + return sendCommand(FTPCmd.PORT, info.toString()); } - /** - * A convenience method to send the FTP MFMT command to the server, - * receive the reply, and return the reply code. + * A convenience method to send the FTP PWD command to the server, receive the reply, and return the reply code. * - * @param pathname The pathname for which mtime is to be changed - * @param timeval Timestamp in <code>YYYYMMDDhhmmss</code> format * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - * @since 2.2 - * @see <a href="http://tools.ietf.org/html/draft-somers-ftp-mfxx-04">http://tools.ietf.org/html/draft-somers-ftp-mfxx-04</a> - **/ - public int mfmt(String pathname, String timeval) throws IOException - { - return sendCommand(FTPCmd.MFMT, timeval + " " + pathname); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int pwd() throws IOException { + return sendCommand(FTPCmd.PWD); } + /** + * A convenience method to send the FTP QUIT command to the server, receive the reply, and return the reply code. + * + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int quit() throws IOException { + return sendCommand(FTPCmd.QUIT); + } - /*** - * A convenience method to send the FTP RNFR command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the FTP REIN command to the server, receive the reply, and return the reply code. * - * @param pathname The pathname to rename from. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int rnfr(String pathname) throws IOException - { - return sendCommand(FTPCmd.RNFR, pathname); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int rein() throws IOException { + return sendCommand(FTPCmd.REIN); } - /*** - * A convenience method to send the FTP RNTO command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the FTP REST command to the server, receive the reply, and return the reply code. * - * @param pathname The pathname to rename to + * @param marker The marker at which to restart a transfer. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int rnto(String pathname) throws IOException - { - return sendCommand(FTPCmd.RNTO, pathname); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int rest(final String marker) throws IOException { + return sendCommand(FTPCmd.REST, marker); } - /*** - * A convenience method to send the FTP DELE command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the FTP RETR command to the server, receive the reply, and return the reply code. Remember, it is up to you to manage the + * data connection. If you don't need this low level of access, use {@link org.apache.commons.net.ftp.FTPClient} , which will handle all low level details + * for you. * - * @param pathname The pathname to delete. + * @param pathname The pathname of the file to retrieve. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int dele(String pathname) throws IOException - { - return sendCommand(FTPCmd.DELE, pathname); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int retr(final String pathname) throws IOException { + return sendCommand(FTPCmd.RETR, pathname); } - /*** - * A convenience method to send the FTP RMD command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the FTP RMD command to the server, receive the reply, and return the reply code. * * @param pathname The pathname of the directory to remove. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int rmd(String pathname) throws IOException - { + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int rmd(final String pathname) throws IOException { return sendCommand(FTPCmd.RMD, pathname); } - /*** - * A convenience method to send the FTP MKD command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the FTP RNFR command to the server, receive the reply, and return the reply code. * - * @param pathname The pathname of the new directory to create. + * @param pathname The pathname to rename from. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int mkd(String pathname) throws IOException - { - return sendCommand(FTPCmd.MKD, pathname); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int rnfr(final String pathname) throws IOException { + return sendCommand(FTPCmd.RNFR, pathname); } - /*** - * A convenience method to send the FTP PWD command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the FTP RNTO command to the server, receive the reply, and return the reply code. * + * @param pathname The pathname to rename to * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int pwd() throws IOException - { - return sendCommand(FTPCmd.PWD); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int rnto(final String pathname) throws IOException { + return sendCommand(FTPCmd.RNTO, pathname); + } + + private void send(final String message) throws IOException, FTPConnectionClosedException, SocketException { + try { + _controlOutput_.write(message); + _controlOutput_.flush(); + } catch (final SocketException e) { + if (!isConnected()) { + throw new FTPConnectionClosedException("Connection unexpectedly closed."); + } + throw e; + } } - /*** - * A convenience method to send the FTP LIST command to the server, - * receive the reply, and return the reply code. Remember, it is up - * to you to manage the data connection. If you don't need this low - * level of access, use {@link org.apache.commons.net.ftp.FTPClient} - * , which will handle all low level details for you. + /** + * Sends an FTP command to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed information, the + * actual reply text can be accessed by calling {@link #getReplyString getReplyString } or {@link #getReplyStrings getReplyStrings }. * - * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int list() throws IOException - { - return sendCommand(FTPCmd.LIST); + * @param command The FTPCmd enum corresponding to the FTP command to send. + * @return The integer value of the FTP reply code returned by the server in response to the command. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + * @since 3.3 + */ + public int sendCommand(final FTPCmd command) throws IOException { + return sendCommand(command, null); } - /*** - * A convenience method to send the FTP LIST command to the server, - * receive the reply, and return the reply code. Remember, it is up - * to you to manage the data connection. If you don't need this low - * level of access, use {@link org.apache.commons.net.ftp.FTPClient} - * , which will handle all low level details for you. + /** + * Sends an FTP command to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed information, the + * actual reply text can be accessed by calling {@link #getReplyString getReplyString } or {@link #getReplyStrings getReplyStrings }. * - * @param pathname The pathname to list, - * may be {@code null} in which case the command is sent with no parameters - * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int list(String pathname) throws IOException - { - return sendCommand(FTPCmd.LIST, pathname); + * @param command The FTPCmd enum corresponding to the FTP command to send. + * @param args The arguments to the FTP command. If this parameter is set to null, then the command is sent with no argument. + * @return The integer value of the FTP reply code returned by the server in response to the command. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + * @since 3.3 + */ + public int sendCommand(final FTPCmd command, final String args) throws IOException { + return sendCommand(command.getCommand(), args); } /** - * A convenience method to send the FTP MLSD command to the server, - * receive the reply, and return the reply code. Remember, it is up - * to you to manage the data connection. If you don't need this low - * level of access, use {@link org.apache.commons.net.ftp.FTPClient} - * , which will handle all low level details for you. + * Sends an FTP command with no arguments to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed + * information, the actual reply text can be accessed by calling {@link #getReplyString getReplyString } or {@link #getReplyStrings getReplyStrings }. * - * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - * @since 3.0 + * @param command The FTPCommand constant corresponding to the FTP command to send. + * @return The integer value of the FTP reply code returned by the server in response to the command. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - public int mlsd() throws IOException - { - return sendCommand(FTPCmd.MLSD); + public int sendCommand(final int command) throws IOException { + return sendCommand(command, null); } /** - * A convenience method to send the FTP MLSD command to the server, - * receive the reply, and return the reply code. Remember, it is up - * to you to manage the data connection. If you don't need this low - * level of access, use {@link org.apache.commons.net.ftp.FTPClient} - * , which will handle all low level details for you. + * Sends an FTP command to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed information, the + * actual reply text can be accessed by calling {@link #getReplyString getReplyString } or {@link #getReplyStrings getReplyStrings }. * - * @param path the path to report on - * @return The reply code received from the server, - * may be {@code null} in which case the command is sent with no parameters - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - * @since 3.0 + * @param command The FTPCommand constant corresponding to the FTP command to send. + * @param args The arguments to the FTP command. If this parameter is set to null, then the command is sent with no argument. + * @return The integer value of the FTP reply code returned by the server in response to the command. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + * @deprecated (3.3) Use {@link #sendCommand(FTPCmd, String)} instead */ - public int mlsd(String path) throws IOException - { - return sendCommand(FTPCmd.MLSD, path); + @Deprecated + public int sendCommand(final int command, final String args) throws IOException { + return sendCommand(FTPCommand.getCommand(command), args); } /** - * A convenience method to send the FTP MLST command to the server, - * receive the reply, and return the reply code. Remember, it is up - * to you to manage the data connection. If you don't need this low - * level of access, use {@link org.apache.commons.net.ftp.FTPClient} - * , which will handle all low level details for you. + * Sends an FTP command with no arguments to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed + * information, the actual reply text can be accessed by calling {@link #getReplyString getReplyString } or {@link #getReplyStrings getReplyStrings }. * - * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - * @since 3.0 + * @param command The text representation of the FTP command to send. + * @return The integer value of the FTP reply code returned by the server in response to the command. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - public int mlst() throws IOException - { - return sendCommand(FTPCmd.MLST); + public int sendCommand(final String command) throws IOException { + return sendCommand(command, null); } /** - * A convenience method to send the FTP MLST command to the server, - * receive the reply, and return the reply code. Remember, it is up - * to you to manage the data connection. If you don't need this low - * level of access, use {@link org.apache.commons.net.ftp.FTPClient} - * , which will handle all low level details for you. + * Sends an FTP command to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed information, the + * actual reply text can be accessed by calling {@link #getReplyString getReplyString } or {@link #getReplyStrings getReplyStrings }. * - * @param path the path to report on - * @return The reply code received from the server, - * may be {@code null} in which case the command is sent with no parameters - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - * @since 3.0 + * @param command The text representation of the FTP command to send. + * @param args The arguments to the FTP command. If this parameter is set to null, then the command is sent with no argument. + * @return The integer value of the FTP reply code returned by the server in response to the command. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - public int mlst(String path) throws IOException - { - return sendCommand(FTPCmd.MLST, path); + public int sendCommand(final String command, final String args) throws IOException { + if (_controlOutput_ == null) { + throw new IOException("Connection is not open"); + } + + final String message = buildMessage(command, args); + + send(message); + + fireCommandSent(command, message); + + return getReply(); } - /*** - * A convenience method to send the FTP NLST command to the server, - * receive the reply, and return the reply code. Remember, it is up - * to you to manage the data connection. If you don't need this low - * level of access, use {@link org.apache.commons.net.ftp.FTPClient} - * , which will handle all low level details for you. + /** + * Saves the character encoding to be used by the FTP control connection. Some FTP servers require that commands be issued in a non-ASCII encoding like + * UTF-8 so that file names with multi-byte character representations (e.g, Big 8) can be specified. + * <p> + * Please note that this has to be set before the connection is established. * - * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int nlst() throws IOException - { - return sendCommand(FTPCmd.NLST); + * @param encoding The new character encoding for the control connection. + */ + public void setControlEncoding(final String encoding) { + _controlEncoding = encoding; } - /*** - * A convenience method to send the FTP NLST command to the server, - * receive the reply, and return the reply code. Remember, it is up - * to you to manage the data connection. If you don't need this low - * level of access, use {@link org.apache.commons.net.ftp.FTPClient} - * , which will handle all low level details for you. + /** + * Set strict multiline parsing. * - * @param pathname The pathname to list, - * may be {@code null} in which case the command is sent with no parameters - * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int nlst(String pathname) throws IOException - { - return sendCommand(FTPCmd.NLST, pathname); + * @param strictMultilineParsing the setting + * @since 2.0 + */ + public void setStrictMultilineParsing(final boolean strictMultilineParsing) { + this.strictMultilineParsing = strictMultilineParsing; + } + + /** + * Set strict non-multiline parsing. + * <p> + * If true, it requires the 3 digit code be followed by space and some text. <br> + * If false, only the 3 digit code is required (as was the case for versions up to 3.5) + * <p> + * <b>This should not be required by a well-behaved FTP server</b> <br> + * + * @param strictReplyParsing the setting + * @since 3.6 + */ + public void setStrictReplyParsing(final boolean strictReplyParsing) { + this.strictReplyParsing = strictReplyParsing; } - /*** - * A convenience method to send the FTP SITE command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the FTP SITE command to the server, receive the reply, and return the reply code. * - * @param parameters The site parameters to send. + * @param parameters The site parameters to send. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int site(String parameters) throws IOException - { + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int site(final String parameters) throws IOException { return sendCommand(FTPCmd.SITE, parameters); } - /*** - * A convenience method to send the FTP SYST command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the FTP SIZE command to the server, receive the reply, and return the reply code. * + * @param parameters The site parameters to send. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int syst() throws IOException - { - return sendCommand(FTPCmd.SYST); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + * @since 3.7 + */ + public int size(final String parameters) throws IOException { + return sendCommand(FTPCmd.SIZE, parameters); } - /*** - * A convenience method to send the FTP STAT command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the FTP SMNT command to the server, receive the reply, and return the reply code. * + * @param dir The directory name. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int stat() throws IOException - { + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int smnt(final String dir) throws IOException { + return sendCommand(FTPCmd.SMNT, dir); + } + + /** + * A convenience method to send the FTP STAT command to the server, receive the reply, and return the reply code. + * + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int stat() throws IOException { return sendCommand(FTPCmd.STAT); } - /*** - * A convenience method to send the FTP STAT command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the FTP STAT command to the server, receive the reply, and return the reply code. * - * @param pathname A pathname to list. + * @param pathname A pathname to list. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int stat(String pathname) throws IOException - { + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int stat(final String pathname) throws IOException { return sendCommand(FTPCmd.STAT, pathname); } - /*** - * A convenience method to send the FTP HELP command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the FTP STOR command to the server, receive the reply, and return the reply code. Remember, it is up to you to manage the + * data connection. If you don't need this low level of access, use {@link org.apache.commons.net.ftp.FTPClient} , which will handle all low level details + * for you. * + * @param pathname The pathname to use for the file when stored at the remote end of the transfer. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int help() throws IOException - { - return sendCommand(FTPCmd.HELP); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int stor(final String pathname) throws IOException { + return sendCommand(FTPCmd.STOR, pathname); } - /*** - * A convenience method to send the FTP HELP command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the FTP STOU command to the server, receive the reply, and return the reply code. Remember, it is up to you to manage the + * data connection. If you don't need this low level of access, use {@link org.apache.commons.net.ftp.FTPClient} , which will handle all low level details + * for you. * - * @param command The command name on which to request help. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int help(String command) throws IOException - { - return sendCommand(FTPCmd.HELP, command); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int stou() throws IOException { + return sendCommand(FTPCmd.STOU); } - /*** - * A convenience method to send the FTP NOOP command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the FTP STOU command to the server, receive the reply, and return the reply code. Remember, it is up to you to manage the + * data connection. If you don't need this low level of access, use {@link org.apache.commons.net.ftp.FTPClient} , which will handle all low level details + * for you. * + * @param pathname The base pathname to use for the file when stored at the remote end of the transfer. Some FTP servers require this. * @return The reply code received from the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int noop() throws IOException - { - return sendCommand(FTPCmd.NOOP); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int stou(final String pathname) throws IOException { + return sendCommand(FTPCmd.STOU, pathname); + } + + // The RFC-compliant multiline termination check + private boolean strictCheck(final String line, final String code) { + return !(line.startsWith(code) && line.charAt(REPLY_CODE_LEN) == ' '); } /** - * Return whether strict multiline parsing is enabled, as per RFC 959, section 4.2. - * @return True if strict, false if lenient - * @since 2.0 + * A convenience method to send the FTP STRU command to the server, receive the reply, and return the reply code. + * + * @param structure The structure of the file (one of the <code>_STRUCTURE</code> constants). + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - public boolean isStrictMultilineParsing() { - return strictMultilineParsing; + public int stru(final int structure) throws IOException { + return sendCommand(FTPCmd.STRU, modes.substring(structure, structure + 1)); } /** - * Set strict multiline parsing. - * @param strictMultilineParsing the setting - * @since 2.0 + * A convenience method to send the FTP SYST command to the server, receive the reply, and return the reply code. + * + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - public void setStrictMultilineParsing(boolean strictMultilineParsing) { - this.strictMultilineParsing = strictMultilineParsing; + public int syst() throws IOException { + return sendCommand(FTPCmd.SYST); } /** - * Return whether strict non-multiline parsing is enabled, as per RFC 959, section 4.2. - * <p> - * The default is true, which requires the 3 digit code be followed by space and some text. - * <br> - * If false, only the 3 digit code is required (as was the case for versions up to 3.5) - * <br> - * @return True if strict (default), false if additional checks are not made - * @since 3.6 + * A convenience method to send the FTP TYPE command to the server, receive the reply, and return the reply code. + * + * @param fileType The type of the file (one of the <code>FILE_TYPE</code> constants). + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - public boolean isStrictReplyParsing() { - return strictReplyParsing; + public int type(final int fileType) throws IOException { + return sendCommand(FTPCmd.TYPE, modes.substring(fileType, fileType + 1)); } /** - * Set strict non-multiline parsing. - * <p> - * If true, it requires the 3 digit code be followed by space and some text. - * <br> - * If false, only the 3 digit code is required (as was the case for versions up to 3.5) - * <p> - * <b>This should not be required by a well-behaved FTP server</b> - * <br> - * @param strictReplyParsing the setting - * @since 3.6 + * A convenience method to send the FTP TYPE command for text files to the server, receive the reply, and return the reply code. + * + * @param fileType The type of the file (one of the <code>FILE_TYPE</code> constants). + * @param formatOrByteSize The format of the file (one of the <code>_FORMAT</code> constants. In the case of <code>LOCAL_FILE_TYPE</code>, the byte size. + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - public void setStrictReplyParsing(boolean strictReplyParsing) { - this.strictReplyParsing = strictReplyParsing; + public int type(final int fileType, final int formatOrByteSize) throws IOException { + final StringBuilder arg = new StringBuilder(); + + arg.append(modes.charAt(fileType)); + arg.append(' '); + if (fileType == LOCAL_FILE_TYPE) { + arg.append(formatOrByteSize); + } else { + arg.append(modes.charAt(formatOrByteSize)); + } + + return sendCommand(FTPCmd.TYPE, arg.toString()); } /** - * Provide command support to super-class + * A convenience method to send the FTP USER command to the server, receive the reply, and return the reply code. + * + * @param username The username to login under. + * @return The reply code received from the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - @Override - protected ProtocolCommandSupport getCommandSupport() { - return _commandSupport_; + public int user(final String username) throws IOException { + return sendCommand(FTPCmd.USER, username); } } - -/* Emacs configuration - * Local variables: ** - * mode: java ** - * c-basic-offset: 4 ** - * indent-tabs-mode: nil ** - * End: ** - */ diff --git a/src/main/java/org/apache/commons/net/ftp/FTPClient.java b/src/main/java/org/apache/commons/net/ftp/FTPClient.java index bec47d1..f5081fb 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPClient.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPClient.java @@ -15,6 +15,7 @@ * limitations under the License. */ package org.apache.commons.net.ftp; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; @@ -33,13 +34,17 @@ import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; +import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; +import java.util.Calendar; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Properties; import java.util.Random; import java.util.Set; +import java.util.regex.Matcher; import org.apache.commons.net.MalformedServerReplyException; import org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory; @@ -50,22 +55,18 @@ import org.apache.commons.net.io.CopyStreamAdapter; import org.apache.commons.net.io.CopyStreamEvent; import org.apache.commons.net.io.CopyStreamListener; import org.apache.commons.net.io.FromNetASCIIInputStream; +import org.apache.commons.net.io.SocketOutputStream; import org.apache.commons.net.io.ToNetASCIIOutputStream; import org.apache.commons.net.io.Util; +import org.apache.commons.net.util.NetConstants; /** - * FTPClient encapsulates all the functionality necessary to store and - * retrieve files from an FTP server. This class takes care of all - * low level details of interacting with an FTP server and provides - * a convenient higher level interface. As with all classes derived - * from {@link org.apache.commons.net.SocketClient}, - * you must first connect to the server with - * {@link org.apache.commons.net.SocketClient#connect connect } - * before doing anything, and finally - * {@link org.apache.commons.net.SocketClient#disconnect disconnect } - * after you're completely finished interacting with the server. - * Then you need to check the FTP reply code to see if the connection - * was successful. For example: + * FTPClient encapsulates all the functionality necessary to store and retrieve files from an FTP server. This class takes care of all low level details of + * interacting with an FTP server and provides a convenient higher level interface. As with all classes derived from + * {@link org.apache.commons.net.SocketClient}, you must first connect to the server with {@link org.apache.commons.net.SocketClient#connect connect } before + * doing anything, and finally {@link org.apache.commons.net.SocketClient#disconnect disconnect } after you're completely finished interacting with the server. + * Then you need to check the FTP reply code to see if the connection was successful. For example: + * * <pre> * FTPClient ftp = new FTPClient(); * FTPClientConfig config = new FTPClientConfig(); @@ -106,161 +107,113 @@ import org.apache.commons.net.io.Util; * } * </pre> * <p> - * Immediately after connecting is the only real time you need to check the - * reply code (because connect is of type void). The convention for all the - * FTP command methods in FTPClient is such that they either return a - * boolean value or some other value. - * The boolean methods return true on a successful completion reply from - * the FTP server and false on a reply resulting in an error condition or - * failure. The methods returning a value other than boolean return a value - * containing the higher level data produced by the FTP command, or null if a - * reply resulted in an error condition or failure. If you want to access - * the exact FTP reply code causing a success or failure, you must call - * {@link org.apache.commons.net.ftp.FTP#getReplyCode getReplyCode } after - * a success or failure. + * Immediately after connecting is the only real time you need to check the reply code (because connect is of type void). The convention for all the FTP command + * methods in FTPClient is such that they either return a boolean value or some other value. The boolean methods return true on a successful completion reply + * from the FTP server and false on a reply resulting in an error condition or failure. The methods returning a value other than boolean return a value + * containing the higher level data produced by the FTP command, or null if a reply resulted in an error condition or failure. If you want to access the exact + * FTP reply code causing a success or failure, you must call {@link org.apache.commons.net.ftp.FTP#getReplyCode getReplyCode } after a success or failure. * <p> - * The default settings for FTPClient are for it to use - * <code> FTP.ASCII_FILE_TYPE </code>, - * <code> FTP.NON_PRINT_TEXT_FORMAT </code>, - * <code> FTP.STREAM_TRANSFER_MODE </code>, and - * <code> FTP.FILE_STRUCTURE </code>. The only file types directly supported - * are <code> FTP.ASCII_FILE_TYPE </code> and - * <code> FTP.BINARY_FILE_TYPE </code>. Because there are at least 4 - * different EBCDIC encodings, we have opted not to provide direct support - * for EBCDIC. To transfer EBCDIC and other unsupported file types you - * must create your own filter InputStreams and OutputStreams and wrap - * them around the streams returned or required by the FTPClient methods. - * FTPClient uses the {@link ToNetASCIIOutputStream NetASCII} - * filter streams to provide transparent handling of ASCII files. We will - * consider incorporating EBCDIC support if there is enough demand. + * The default settings for FTPClient are for it to use <code> FTP.ASCII_FILE_TYPE </code>, <code> FTP.NON_PRINT_TEXT_FORMAT </code>, + * <code> FTP.STREAM_TRANSFER_MODE </code>, and <code> FTP.FILE_STRUCTURE </code>. The only file types directly supported are <code> FTP.ASCII_FILE_TYPE </code> + * and <code> FTP.BINARY_FILE_TYPE </code>. Because there are at least 4 different EBCDIC encodings, we have opted not to provide direct support for EBCDIC. To + * transfer EBCDIC and other unsupported file types you must create your own filter InputStreams and OutputStreams and wrap them around the streams returned or + * required by the FTPClient methods. FTPClient uses the {@link ToNetASCIIOutputStream NetASCII} filter streams to provide transparent handling of ASCII files. + * We will consider incorporating EBCDIC support if there is enough demand. * <p> - * <code> FTP.NON_PRINT_TEXT_FORMAT </code>, - * <code> FTP.STREAM_TRANSFER_MODE </code>, and - * <code> FTP.FILE_STRUCTURE </code> are the only supported formats, + * <code> FTP.NON_PRINT_TEXT_FORMAT </code>, <code> FTP.STREAM_TRANSFER_MODE </code>, and <code> FTP.FILE_STRUCTURE </code> are the only supported formats, * transfer modes, and file structures. * <p> - * Because the handling of sockets on different platforms can differ - * significantly, the FTPClient automatically issues a new PORT (or EPRT) command - * prior to every transfer requiring that the server connect to the client's - * data port. This ensures identical problem-free behavior on Windows, Unix, - * and Macintosh platforms. Additionally, it relieves programmers from - * having to issue the PORT (or EPRT) command themselves and dealing with platform - * dependent issues. + * Because the handling of sockets on different platforms can differ significantly, the FTPClient automatically issues a new PORT (or EPRT) command prior to + * every transfer requiring that the server connect to the client's data port. This ensures identical problem-free behavior on Windows, Unix, and Macintosh + * platforms. Additionally, it relieves programmers from having to issue the PORT (or EPRT) command themselves and dealing with platform dependent issues. * <p> - * Additionally, for security purposes, all data connections to the - * client are verified to ensure that they originated from the intended - * party (host and port). If a data connection is initiated by an unexpected - * party, the command will close the socket and throw an IOException. You - * may disable this behavior with + * Additionally, for security purposes, all data connections to the client are verified to ensure that they originated from the intended party (host and port). + * If a data connection is initiated by an unexpected party, the command will close the socket and throw an IOException. You may disable this behavior with * {@link #setRemoteVerificationEnabled setRemoteVerificationEnabled()}. * <p> - * You should keep in mind that the FTP server may choose to prematurely - * close a connection if the client has been idle for longer than a - * given time period (usually 900 seconds). The FTPClient class will detect a - * premature FTP server connection closing when it receives a - * {@link org.apache.commons.net.ftp.FTPReply#SERVICE_NOT_AVAILABLE FTPReply.SERVICE_NOT_AVAILABLE } - * response to a command. - * When that occurs, the FTP class method encountering that reply will throw - * an {@link org.apache.commons.net.ftp.FTPConnectionClosedException} - * . - * <code>FTPConnectionClosedException</code> - * is a subclass of <code> IOException </code> and therefore need not be - * caught separately, but if you are going to catch it separately, its - * catch block must appear before the more general <code> IOException </code> - * catch block. When you encounter an - * {@link org.apache.commons.net.ftp.FTPConnectionClosedException} - * , you must disconnect the connection with - * {@link #disconnect disconnect() } to properly clean up the - * system resources used by FTPClient. Before disconnecting, you may check the - * last reply code and text with - * {@link org.apache.commons.net.ftp.FTP#getReplyCode getReplyCode }, - * {@link org.apache.commons.net.ftp.FTP#getReplyString getReplyString }, - * and - * {@link org.apache.commons.net.ftp.FTP#getReplyStrings getReplyStrings}. - * You may avoid server disconnections while the client is idle by - * periodically sending NOOP commands to the server. + * You should keep in mind that the FTP server may choose to prematurely close a connection if the client has been idle for longer than a given time period + * (usually 900 seconds). The FTPClient class will detect a premature FTP server connection closing when it receives a + * {@link org.apache.commons.net.ftp.FTPReply#SERVICE_NOT_AVAILABLE FTPReply.SERVICE_NOT_AVAILABLE } response to a command. When that occurs, the FTP class + * method encountering that reply will throw an {@link org.apache.commons.net.ftp.FTPConnectionClosedException} . <code>FTPConnectionClosedException</code> is a + * subclass of <code> IOException </code> and therefore need not be caught separately, but if you are going to catch it separately, its catch block must appear + * before the more general <code> IOException </code> catch block. When you encounter an {@link org.apache.commons.net.ftp.FTPConnectionClosedException} , you + * must disconnect the connection with {@link #disconnect disconnect() } to properly clean up the system resources used by FTPClient. Before disconnecting, you + * may check the last reply code and text with {@link org.apache.commons.net.ftp.FTP#getReplyCode getReplyCode }, + * {@link org.apache.commons.net.ftp.FTP#getReplyString getReplyString }, and {@link org.apache.commons.net.ftp.FTP#getReplyStrings getReplyStrings}. You may + * avoid server disconnections while the client is idle by periodically sending NOOP commands to the server. * <p> - * Rather than list it separately for each method, we mention here that - * every method communicating with the server and throwing an IOException - * can also throw a - * {@link org.apache.commons.net.MalformedServerReplyException} - * , which is a subclass - * of IOException. A MalformedServerReplyException will be thrown when - * the reply received from the server deviates enough from the protocol - * specification that it cannot be interpreted in a useful manner despite - * attempts to be as lenient as possible. + * Rather than list it separately for each method, we mention here that every method communicating with the server and throwing an IOException can also throw a + * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the + * reply received from the server deviates enough from the protocol specification that it cannot be interpreted in a useful manner despite attempts to be as + * lenient as possible. * <p> - * Listing API Examples - * Both paged and unpaged examples of directory listings are available, - * as follows: + * Listing API Examples Both paged and unpaged examples of directory listings are available, as follows: * <p> * Unpaged (whole list) access, using a parser accessible by auto-detect: + * * <pre> - * FTPClient f = new FTPClient(); - * f.connect(server); - * f.login(username, password); - * FTPFile[] files = f.listFiles(directory); + * FTPClient f = new FTPClient(); + * f.connect(server); + * f.login(username, password); + * FTPFile[] files = f.listFiles(directory); * </pre> * <p> - * Paged access, using a parser not accessible by auto-detect. The class - * defined in the first parameter of initateListParsing should be derived - * from org.apache.commons.net.FTPFileEntryParser: + * Paged access, using a parser not accessible by auto-detect. The class defined in the first parameter of initateListParsing should be derived from + * org.apache.commons.net.FTPFileEntryParser: + * * <pre> - * FTPClient f = new FTPClient(); - * f.connect(server); - * f.login(username, password); - * FTPListParseEngine engine = - * f.initiateListParsing("com.whatever.YourOwnParser", directory); + * FTPClient f = new FTPClient(); + * f.connect(server); + * f.login(username, password); + * FTPListParseEngine engine = f.initiateListParsing("com.whatever.YourOwnParser", directory); * - * while (engine.hasNext()) { - * FTPFile[] files = engine.getNext(25); // "page size" you want - * //do whatever you want with these files, display them, etc. - * //expensive FTPFile objects not created until needed. - * } + * while (engine.hasNext()) { + * FTPFile[] files = engine.getNext(25); // "page size" you want + * // do whatever you want with these files, display them, etc. + * // expensive FTPFile objects not created until needed. + * } * </pre> * <p> * Paged access, using a parser accessible by auto-detect: + * * <pre> - * FTPClient f = new FTPClient(); - * f.connect(server); - * f.login(username, password); - * FTPListParseEngine engine = f.initiateListParsing(directory); + * FTPClient f = new FTPClient(); + * f.connect(server); + * f.login(username, password); + * FTPListParseEngine engine = f.initiateListParsing(directory); * - * while (engine.hasNext()) { - * FTPFile[] files = engine.getNext(25); // "page size" you want - * //do whatever you want with these files, display them, etc. - * //expensive FTPFile objects not created until needed. - * } + * while (engine.hasNext()) { + * FTPFile[] files = engine.getNext(25); // "page size" you want + * // do whatever you want with these files, display them, etc. + * // expensive FTPFile objects not created until needed. + * } * </pre> * <p> * For examples of using FTPClient on servers whose directory listings * <ul> * <li>use languages other than English</li> * <li>use date formats other than the American English "standard" <code>MM d yyyy</code></li> - * <li>are in different timezones and you need accurate timestamps for dependency checking - * as in Ant</li> - * </ul>see {@link FTPClientConfig FTPClientConfig}. + * <li>are in different time zones and you need accurate timestamps for dependency checking as in Ant</li> + * </ul> + * see {@link FTPClientConfig FTPClientConfig}. * <p> * <b>Control channel keep-alive feature</b>: * <p> - * <b>Please note:</b> this does not apply to the methods where the user is responsible for writing or reading - * the data stream, i.e. {@link #retrieveFileStream(String)} , {@link #storeFileStream(String)} - * and the other xxxFileStream methods + * <b>Please note:</b> this does not apply to the methods where the user is responsible for writing or reading the data stream, i.e. + * {@link #retrieveFileStream(String)} , {@link #storeFileStream(String)} and the other xxxFileStream methods * <p> - * During file transfers, the data connection is busy, but the control connection is idle. - * FTP servers know that the control connection is in use, so won't close it through lack of activity, - * but it's a lot harder for network routers to know that the control and data connections are associated - * with each other. - * Some routers may treat the control connection as idle, and disconnect it if the transfer over the data - * connection takes longer than the allowable idle time for the router. - * <br> - * One solution to this is to send a safe command (i.e. NOOP) over the control connection to reset the router's - * idle timer. This is enabled as follows: + * During file transfers, the data connection is busy, but the control connection is idle. FTP servers know that the control connection is in use, so won't + * close it through lack of activity, but it's a lot harder for network routers to know that the control and data connections are associated with each other. + * Some routers may treat the control connection as idle, and disconnect it if the transfer over the data connection takes longer than the allowable idle time + * for the router. <br> + * One solution to this is to send a safe command (i.e. NOOP) over the control connection to reset the router's idle timer. This is enabled as follows: + * * <pre> - * ftpClient.setControlKeepAliveTimeout(300); // set timeout to 5 minutes + * // Set timeout to 5 minutes + * ftpClient.setControlKeepAliveTimeout(Duration.ofMinutes(5)); * </pre> - * This will cause the file upload/download methods to send a NOOP approximately every 5 minutes. - * The following public methods support this: + * + * This will cause the file upload/download methods to send a NOOP approximately every 5 minutes. The following public methods support this: * <ul> * <li>{@link #retrieveFile(String, OutputStream)}</li> * <li>{@link #appendFile(String, InputStream)}</li> @@ -268,14 +221,15 @@ import org.apache.commons.net.io.Util; * <li>{@link #storeUniqueFile(InputStream)}</li> * <li>{@link #storeUniqueFileStream(String)}</li> * </ul> - * This feature does not apply to the methods where the user is responsible for writing or reading - * the data stream, i.e. {@link #retrieveFileStream(String)} , {@link #storeFileStream(String)} - * and the other xxxFileStream methods. - * In such cases, the user is responsible for keeping the control connection alive if necessary. + * This feature does not apply to the methods where the user is responsible for writing or reading the data stream, i.e. {@link #retrieveFileStream(String)} , + * {@link #storeFileStream(String)} and the other xxxFileStream methods. In such cases, the user is responsible for keeping the control connection alive if + * necessary. * <p> * The implementation currently uses a {@link CopyStreamListener} which is passed to the - * {@link Util#copyStream(InputStream, OutputStream, int, long, CopyStreamListener, boolean)} - * method, so the timing is partially dependent on how long each block transfer takes. + * {@link Util#copyStream(InputStream, OutputStream, int, long, CopyStreamListener, boolean)} method, so the timing is partially dependent on how long each + * block transfer takes. + * <p> + * <b>This keep-alive feature is optional; if it does not help or causes problems then don't use it.</b> * * @see #FTP_SYSTEM_TYPE * @see #SYSTEM_TYPE_PROPERTIES @@ -285,12 +239,130 @@ import org.apache.commons.net.io.Util; * @see FTPFileEntryParserFactory * @see DefaultFTPFileEntryParserFactory * @see FTPClientConfig - * * @see org.apache.commons.net.MalformedServerReplyException */ -public class FTPClient extends FTP -implements Configurable -{ +public class FTPClient extends FTP implements Configurable { + + // @since 3.0 + private static class CSL implements CopyStreamListener { + + private final FTPClient parent; + private final long idleMillis; + private final int currentSoTimeoutMillis; + + private long lastIdleTimeMillis = System.currentTimeMillis(); + private int notAcked; + private int acksAcked; + private int ioErrors; + + CSL(final FTPClient parent, final Duration idleDuration, final Duration maxWaitDuration) throws SocketException { + this.idleMillis = idleDuration.toMillis(); + this.parent = parent; + this.currentSoTimeoutMillis = parent.getSoTimeout(); + parent.setSoTimeout(DurationUtils.toMillisInt(maxWaitDuration)); + } + + @Override + public void bytesTransferred(final CopyStreamEvent event) { + bytesTransferred(event.getTotalBytesTransferred(), event.getBytesTransferred(), event.getStreamSize()); + } + + @Override + public void bytesTransferred(final long totalBytesTransferred, final int bytesTransferred, final long streamSize) { + final long nowMillis = System.currentTimeMillis(); + if (nowMillis - lastIdleTimeMillis > idleMillis) { + try { + parent.__noop(); + acksAcked++; + } catch (final SocketTimeoutException e) { + notAcked++; + } catch (final IOException e) { + ioErrors++; + // Ignored + } + lastIdleTimeMillis = nowMillis; + } + } + + int[] cleanUp() throws IOException { + final int remain = notAcked; + try { + while (notAcked > 0) { + parent.getReply(); // we do want to see these + notAcked--; // only decrement if actually received + } + } catch (final SocketTimeoutException e) { // NET-584 + // ignored + } finally { + parent.setSoTimeout(currentSoTimeoutMillis); + } + return new int[] { acksAcked, remain, notAcked, ioErrors }; // debug counts + } + + } + + /** + * Strategy interface for updating host names received from FTP server for passive NAT workaround. + * + * @since 3.6 + */ + public interface HostnameResolver { + String resolve(String hostname) throws UnknownHostException; + } + + /** + * Default strategy for passive NAT workaround (site-local replies are replaced.) + * + * @since 3.6 + */ + public static class NatServerResolverImpl implements HostnameResolver { + private final FTPClient client; + + public NatServerResolverImpl(final FTPClient client) { + this.client = client; + } + + @Override + public String resolve(final String hostname) throws UnknownHostException { + String newHostname = hostname; + final InetAddress host = InetAddress.getByName(newHostname); + // reply is a local address, but target is not - assume NAT box changed the PASV reply + if (host.isSiteLocalAddress()) { + final InetAddress remote = this.client.getRemoteAddress(); + if (!remote.isSiteLocalAddress()) { + newHostname = remote.getHostAddress(); + } + } + return newHostname; + } + } + + private static class PropertiesSingleton { + + static final Properties PROPERTIES; + + static { + final InputStream resourceAsStream = FTPClient.class.getResourceAsStream(SYSTEM_TYPE_PROPERTIES); + Properties p = null; + if (resourceAsStream != null) { + p = new Properties(); + try { + p.load(resourceAsStream); + } catch (final IOException e) { + // Ignored + } finally { + try { + resourceAsStream.close(); + } catch (final IOException e) { + // Ignored + } + } + } + PROPERTIES = p; + } + + } + /** * The system property ({@value}) which can be used to override the system type.<br> * If defined, the value will be used to create any automatically created parsers. @@ -308,12 +380,23 @@ implements Configurable public static final String FTP_SYSTEM_TYPE_DEFAULT = "org.apache.commons.net.ftp.systemType.default"; /** - * The name of an optional systemType properties file ({@value}), which is loaded - * using {@link Class#getResourceAsStream(String)}.<br> - * The entries are the systemType (as determined by {@link FTPClient#getSystemType}) - * and the values are the replacement type or parserClass, which is passed to - * {@link FTPFileEntryParserFactory#createFileEntryParser(String)}.<br> + * The system property that defines the default for {@link #isIpAddressFromPasvResponse()}. This property, if present, configures the default for the + * following: If the client receives the servers response for a PASV request, then that response will contain an IP address. If this property is true, then + * the client will use that IP address, as requested by the server. This is compatible to version {@code 3.8.0}, and before. If this property is false, or + * absent, then the client will ignore that IP address, and instead use the remote address of the control connection. + * + * @see #isIpAddressFromPasvResponse() + * @see #setIpAddressFromPasvResponse(boolean) + * @since 3.9.0 + */ + public static final String FTP_IP_ADDRESS_FROM_PASV_RESPONSE = "org.apache.commons.net.ftp.ipAddressFromPasvResponse"; + + /** + * The name of an optional systemType properties file ({@value}), which is loaded using {@link Class#getResourceAsStream(String)}.<br> + * The entries are the systemType (as determined by {@link FTPClient#getSystemType}) and the values are the replacement type or parserClass, which is passed + * to {@link FTPFileEntryParserFactory#createFileEntryParser(String)}.<br> * For example: + * * <pre> * Plan 9=Unix * OS410=org.apache.commons.net.ftp.parser.OS400FTPEntryParser @@ -324,221 +407,68 @@ implements Configurable public static final String SYSTEM_TYPE_PROPERTIES = "/systemType.properties"; /** - * A constant indicating the FTP session is expecting all transfers - * to occur between the client (local) and server and that the server - * should connect to the client's data port to initiate a data transfer. - * This is the default data connection mode when and FTPClient instance - * is created. + * A constant indicating the FTP session is expecting all transfers to occur between the client (local) and server and that the server should connect to the + * client's data port to initiate a data transfer. This is the default data connection mode when and FTPClient instance is created. */ public static final int ACTIVE_LOCAL_DATA_CONNECTION_MODE = 0; + /** - * A constant indicating the FTP session is expecting all transfers - * to occur between two remote servers and that the server - * the client is connected to should connect to the other server's - * data port to initiate a data transfer. + * A constant indicating the FTP session is expecting all transfers to occur between two remote servers and that the server the client is connected to + * should connect to the other server's data port to initiate a data transfer. */ public static final int ACTIVE_REMOTE_DATA_CONNECTION_MODE = 1; + /** - * A constant indicating the FTP session is expecting all transfers - * to occur between the client (local) and server and that the server - * is in passive mode, requiring the client to connect to the - * server's data port to initiate a transfer. + * A constant indicating the FTP session is expecting all transfers to occur between the client (local) and server and that the server is in passive mode, + * requiring the client to connect to the server's data port to initiate a transfer. */ public static final int PASSIVE_LOCAL_DATA_CONNECTION_MODE = 2; - /** - * A constant indicating the FTP session is expecting all transfers - * to occur between two remote servers and that the server - * the client is connected to is in passive mode, requiring the other - * server to connect to the first server's data port to initiate a data - * transfer. - */ - public static final int PASSIVE_REMOTE_DATA_CONNECTION_MODE = 3; - - private int __dataConnectionMode; - private int __dataTimeout; - private int __passivePort; - private String __passiveHost; - private final Random __random; - private int __activeMinPort; - private int __activeMaxPort; - private InetAddress __activeExternalHost; - private InetAddress __reportActiveExternalHost; // overrides __activeExternalHost in EPRT/PORT commands - /** The address to bind to on passive connections, if necessary. */ - private InetAddress __passiveLocalHost; - - private int __fileType; - @SuppressWarnings("unused") // fields are written, but currently not read - private int __fileFormat; - @SuppressWarnings("unused") // field is written, but currently not read - private int __fileStructure; - @SuppressWarnings("unused") // field is written, but currently not read - private int __fileTransferMode; - private boolean __remoteVerificationEnabled; - private long __restartOffset; - private FTPFileEntryParserFactory __parserFactory; - private int __bufferSize; // buffersize for buffered data streams - private int __sendDataSocketBufferSize; - private int __receiveDataSocketBufferSize; - private boolean __listHiddenFiles; - private boolean __useEPSVwithIPv4; // whether to attempt EPSV with an IPv4 connection - - // __systemName is a cached value that should not be referenced directly - // except when assigned in getSystemName and __initDefaults. - private String __systemName; - - // __entryParser is a cached value that should not be referenced directly - // except when assigned in listFiles(String, String) and __initDefaults. - private FTPFileEntryParser __entryParser; - - // Key used to create the parser; necessary to ensure that the parser type is not ignored - private String __entryParserKey; - - private FTPClientConfig __configuration; - - // Listener used by store/retrieve methods to handle keepalive - private CopyStreamListener __copyStreamListener; - - // How long to wait before sending another control keep-alive message - private long __controlKeepAliveTimeout; - - // How long to wait (ms) for keepalive message replies before continuing - // Most FTP servers don't seem to support concurrent control and data connection usage - private int __controlKeepAliveReplyTimeout=1000; /** - * Enable or disable replacement of internal IP in passive mode. Default enabled - * using {code NatServerResolverImpl}. + * A constant indicating the FTP session is expecting all transfers to occur between two remote servers and that the server the client is connected to is in + * passive mode, requiring the other server to connect to the first server's data port to initiate a data transfer. */ - private HostnameResolver __passiveNatWorkaroundStrategy = new NatServerResolverImpl(this); + public static final int PASSIVE_REMOTE_DATA_CONNECTION_MODE = 3; /** Pattern for PASV mode responses. Groups: (n,n,n,n),(n),(n) */ - private static final java.util.regex.Pattern __PARMS_PAT; + private static final java.util.regex.Pattern PARMS_PAT; static { - __PARMS_PAT = java.util.regex.Pattern.compile( - "(\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}),(\\d{1,3}),(\\d{1,3})"); + PARMS_PAT = java.util.regex.Pattern.compile("(\\d{1,3},\\d{1,3},\\d{1,3},\\d{1,3}),(\\d{1,3}),(\\d{1,3})"); } - /** Controls the automatic server encoding detection (only UTF-8 supported). */ - private boolean __autodetectEncoding = false; - - /** Map of FEAT responses. If null, has not been initialised. */ - private HashMap<String, Set<String>> __featuresMap; - - private static class PropertiesSingleton { - - static final Properties PROPERTIES; - - static { - InputStream resourceAsStream = FTPClient.class.getResourceAsStream(SYSTEM_TYPE_PROPERTIES); - Properties p = null; - if (resourceAsStream != null) { - p = new Properties(); - try { - p.load(resourceAsStream); - } catch (IOException e) { - // Ignored - } finally { - try { - resourceAsStream.close(); - } catch (IOException e) { - // Ignored - } - } - } - PROPERTIES = p; - } - - } - private static Properties getOverrideProperties(){ + private static Properties getOverrideProperties() { return PropertiesSingleton.PROPERTIES; } - /** - * Default FTPClient constructor. Creates a new FTPClient instance - * with the data connection mode set to - * <code> ACTIVE_LOCAL_DATA_CONNECTION_MODE </code>, the file type - * set to <code> FTP.ASCII_FILE_TYPE </code>, the - * file format set to <code> FTP.NON_PRINT_TEXT_FORMAT </code>, - * the file structure set to <code> FTP.FILE_STRUCTURE </code>, and - * the transfer mode set to <code> FTP.STREAM_TRANSFER_MODE </code>. - * <p> - * The list parsing auto-detect feature can be configured to use lenient future - * dates (short dates may be up to one day in the future) as follows: - * <pre> - * FTPClient ftp = new FTPClient(); - * FTPClientConfig config = new FTPClientConfig(); - * config.setLenientFutureDates(true); - * ftp.configure(config ); - * </pre> - */ - public FTPClient() - { - __initDefaults(); - __dataTimeout = -1; - __remoteVerificationEnabled = true; - __parserFactory = new DefaultFTPFileEntryParserFactory(); - __configuration = null; - __listHiddenFiles = false; - __useEPSVwithIPv4 = false; - __random = new Random(); - __passiveLocalHost = null; - } - - - private void __initDefaults() - { - __dataConnectionMode = ACTIVE_LOCAL_DATA_CONNECTION_MODE; - __passiveHost = null; - __passivePort = -1; - __activeExternalHost = null; - __reportActiveExternalHost = null; - __activeMinPort = 0; - __activeMaxPort = 0; - __fileType = FTP.ASCII_FILE_TYPE; - __fileStructure = FTP.FILE_STRUCTURE; - __fileFormat = FTP.NON_PRINT_TEXT_FORMAT; - __fileTransferMode = FTP.STREAM_TRANSFER_MODE; - __restartOffset = 0; - __systemName = null; - __entryParser = null; - __entryParserKey = ""; - __featuresMap = null; - } - /** * Parse the pathname from a CWD reply. * <p> - * According to RFC959 (http://www.ietf.org/rfc/rfc959.txt), - * it should be the same as for MKD i.e. - * {@code 257<space>"<directory-name>"[<space>commentary]} - * where any double-quotes in {@code <directory-name>} are doubled. - * Unlike MKD, the commentary is optional. + * According to RFC959 (http://www.ietf.org/rfc/rfc959.txt), it should be the same as for MKD i.e. {@code 257<space>"<directory-name>"[<space>commentary]} + * where any double-quotes in {@code <directory-name>} are doubled. Unlike MKD, the commentary is optional. * <p> * However, see NET-442 for an exception. * * @param reply - * @return the pathname, without enclosing quotes, - * or the full string after the reply code and space if the syntax is invalid - * (i.e. enclosing quotes are missing or embedded quotes are not doubled) + * @return the pathname, without enclosing quotes, or the full string after the reply code and space if the syntax is invalid (i.e. enclosing quotes are + * missing or embedded quotes are not doubled) */ // package protected for access by test cases - static String __parsePathname(String reply) - { - String param = reply.substring(REPLY_CODE_LEN + 1); + static String parsePathname(final String reply) { + final String param = reply.substring(REPLY_CODE_LEN + 1); if (param.startsWith("\"")) { - StringBuilder sb = new StringBuilder(); + final StringBuilder sb = new StringBuilder(); boolean quoteSeen = false; // start after initial quote - for(int i=1; i < param.length(); i++) { - char ch = param.charAt(i); - if (ch=='"') { + for (int i = 1; i < param.length(); i++) { + final char ch = param.charAt(i); + if (ch == '"') { if (quoteSeen) { sb.append(ch); - quoteSeen=false; + quoteSeen = false; } else { // don't output yet, in case doubled - quoteSeen=true; + quoteSeen = true; } } else { if (quoteSeen) { // found lone trailing quote within string @@ -555,274 +485,202 @@ implements Configurable return param; } - /** - * @since 3.1 - * @param reply the reply to parse - * @throws MalformedServerReplyException if the server reply does not match (n,n,n,n),(n),(n) - */ - protected void _parsePassiveModeReply(String reply) - throws MalformedServerReplyException - { - java.util.regex.Matcher m = __PARMS_PAT.matcher(reply); - if (!m.find()) { - throw new MalformedServerReplyException( - "Could not parse passive host information.\nServer Reply: " + reply); - } + private int dataConnectionMode; + private Duration dataTimeout; - __passiveHost = m.group(1).replace(',', '.'); // Fix up to look like IP address + private int passivePort; + private String passiveHost; + private final Random random; + private int activeMinPort; + private int activeMaxPort; + private InetAddress activeExternalHost; - try - { - int oct1 = Integer.parseInt(m.group(2)); - int oct2 = Integer.parseInt(m.group(3)); - __passivePort = (oct1 << 8) | oct2; - } - catch (NumberFormatException e) - { - throw new MalformedServerReplyException( - "Could not parse passive port information.\nServer Reply: " + reply); - } + /** overrides __activeExternalHost in EPRT/PORT commands. */ + private InetAddress reportActiveExternalHost; - if (__passiveNatWorkaroundStrategy != null) { - try { - String passiveHost = __passiveNatWorkaroundStrategy.resolve(__passiveHost); - if (!__passiveHost.equals(passiveHost)) { - fireReplyReceived(0, - "[Replacing PASV mode reply address "+__passiveHost+" with "+passiveHost+"]\n"); - __passiveHost = passiveHost; - } - } catch (UnknownHostException e) { // Should not happen as we are passing in an IP address - throw new MalformedServerReplyException( - "Could not parse passive host information.\nServer Reply: " + reply); - } - } - } + /** The address to bind to on passive connections, if necessary. */ + private InetAddress passiveLocalHost; + private int fileType; + @SuppressWarnings("unused") // fields are written, but currently not read + private int fileFormat; + @SuppressWarnings("unused") // field is written, but currently not read + private int fileStructure; + @SuppressWarnings("unused") // field is written, but currently not read + private int fileTransferMode; - protected void _parseExtendedPassiveModeReply(String reply) - throws MalformedServerReplyException - { - reply = reply.substring(reply.indexOf('(') + 1, - reply.indexOf(')')).trim(); + private boolean remoteVerificationEnabled; - char delim1, delim2, delim3, delim4; - delim1 = reply.charAt(0); - delim2 = reply.charAt(1); - delim3 = reply.charAt(2); - delim4 = reply.charAt(reply.length()-1); + private long restartOffset; - if (!(delim1 == delim2) || !(delim2 == delim3) - || !(delim3 == delim4)) { - throw new MalformedServerReplyException( - "Could not parse extended passive host information.\nServer Reply: " + reply); - } + private FTPFileEntryParserFactory parserFactory; - int port; - try - { - port = Integer.parseInt(reply.substring(3, reply.length()-1)); - } - catch (NumberFormatException e) - { - throw new MalformedServerReplyException( - "Could not parse extended passive host information.\nServer Reply: " + reply); - } + private int bufferSize; // buffersize for buffered data streams + private int sendDataSocketBufferSize; - // in EPSV mode, the passive host address is implicit - __passiveHost = getRemoteAddress().getHostAddress(); - __passivePort = port; - } + private int receiveDataSocketBufferSize; - private boolean __storeFile(FTPCmd command, String remote, InputStream local) - throws IOException - { - return _storeFile(command.getCommand(), remote, local); - } + private boolean listHiddenFiles; - /** - * @since 3.1 - * @param command the command to send - * @param remote the remote file name - * @param local the local file name - * @return true if successful - * @throws IOException on error - */ - protected boolean _storeFile(String command, String remote, InputStream local) - throws IOException - { - Socket socket = _openDataConnection_(command, remote); + private boolean useEPSVwithIPv4; // whether to attempt EPSV with an IPv4 connection - if (socket == null) { - return false; - } + // __systemName is a cached value that should not be referenced directly + // except when assigned in getSystemName and __initDefaults. + private String systemName; - final OutputStream output; + // __entryParser is a cached value that should not be referenced directly + // except when assigned in listFiles(String, String) and __initDefaults. + private FTPFileEntryParser entryParser; - if (__fileType == ASCII_FILE_TYPE) { - output = new ToNetASCIIOutputStream(getBufferedOutputStream(socket.getOutputStream())); - } else { - output = getBufferedOutputStream(socket.getOutputStream()); - } + // Key used to create the parser; necessary to ensure that the parser type is not ignored + private String entryParserKey; - CSL csl = null; - if (__controlKeepAliveTimeout > 0) { - csl = new CSL(this, __controlKeepAliveTimeout, __controlKeepAliveReplyTimeout); - } + private FTPClientConfig configuration; - // Treat everything else as binary for now - try - { - Util.copyStream(local, output, getBufferSize(), - CopyStreamEvent.UNKNOWN_STREAM_SIZE, __mergeListeners(csl), - false); - } - catch (IOException e) - { - Util.closeQuietly(socket); // ignore close errors here - if (csl != null) { - csl.cleanUp(); // fetch any outstanding keepalive replies - } - throw e; - } + // Listener used by store/retrieve methods to handle keepalive + private CopyStreamListener copyStreamListener; - output.close(); // ensure the file is fully written - socket.close(); // done writing the file - if (csl != null) { - csl.cleanUp(); // fetch any outstanding keepalive replies - } - // Get the transfer response - boolean ok = completePendingCommand(); - return ok; - } + // How long to wait before sending another control keep-alive message + private Duration controlKeepAliveTimeout = Duration.ZERO; - private OutputStream __storeFileStream(FTPCmd command, String remote) - throws IOException - { - return _storeFileStream(command.getCommand(), remote); - } + // How long to wait for keepalive message replies before continuing + // Most FTP servers don't seem to support concurrent control and data connection usage + private Duration controlKeepAliveReplyTimeout = Duration.ofSeconds(1); + + // Debug counts for NOOP acks + private int[] cslDebug; /** - * @param command the command to send - * @param remote the remote file name - * @return the output stream to write to - * @throws IOException on error - * @since 3.1 + * Enable or disable replacement of internal IP in passive mode. Default enabled using {code NatServerResolverImpl}. */ - protected OutputStream _storeFileStream(String command, String remote) - throws IOException - { - Socket socket = _openDataConnection_(command, remote); + private HostnameResolver passiveNatWorkaroundStrategy = new NatServerResolverImpl(this); - if (socket == null) { - return null; - } + /** Controls the automatic server encoding detection (only UTF-8 supported). */ + private boolean autodetectEncoding; - final OutputStream output; - if (__fileType == ASCII_FILE_TYPE) { - // We buffer ascii transfers because the buffering has to - // be interposed between ToNetASCIIOutputSream and the underlying - // socket output stream. We don't buffer binary transfers - // because we don't want to impose a buffering policy on the - // programmer if possible. Programmers can decide on their - // own if they want to wrap the SocketOutputStream we return - // for file types other than ASCII. - output = new ToNetASCIIOutputStream(getBufferedOutputStream(socket.getOutputStream())); - } else { - output = socket.getOutputStream(); - } - return new org.apache.commons.net.io.SocketOutputStream(socket, output); - } + /** Map of FEAT responses. If null, has not been initialized. */ + private HashMap<String, Set<String>> featuresMap; + private boolean ipAddressFromPasvResponse = Boolean.parseBoolean(System.getProperty(FTPClient.FTP_IP_ADDRESS_FROM_PASV_RESPONSE)); /** - * Establishes a data connection with the FTP server, returning - * a Socket for the connection if successful. If a restart - * offset has been set with {@link #setRestartOffset(long)}, - * a REST command is issued to the server with the offset as - * an argument before establishing the data connection. Active - * mode connections also cause a local PORT command to be issued. + * Default FTPClient constructor. Creates a new FTPClient instance with the data connection mode set to <code> ACTIVE_LOCAL_DATA_CONNECTION_MODE </code>, + * the file type set to <code> FTP.ASCII_FILE_TYPE </code>, the file format set to <code> FTP.NON_PRINT_TEXT_FORMAT </code>, the file structure set to + * <code> FTP.FILE_STRUCTURE </code>, and the transfer mode set to <code> FTP.STREAM_TRANSFER_MODE </code>. + * <p> + * The list parsing auto-detect feature can be configured to use lenient future dates (short dates may be up to one day in the future) as follows: * - * @param command The int representation of the FTP command to send. - * @param arg The arguments to the FTP command. If this parameter is - * set to null, then the command is sent with no argument. - * @return A Socket corresponding to the established data connection. - * Null is returned if an FTP protocol error is reported at - * any point during the establishment and initialization of - * the connection. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @deprecated (3.3) Use {@link #_openDataConnection_(FTPCmd, String)} instead + * <pre> + * FTPClient ftp = new FTPClient(); + * FTPClientConfig config = new FTPClientConfig(); + * config.setLenientFutureDates(true); + * ftp.configure(config); + * </pre> */ - @Deprecated - protected Socket _openDataConnection_(int command, String arg) - throws IOException - { - return _openDataConnection_(FTPCommand.getCommand(command), arg); + public FTPClient() { + initDefaults(); + dataTimeout = Duration.ofMillis(-1); + remoteVerificationEnabled = true; + parserFactory = new DefaultFTPFileEntryParserFactory(); + configuration = null; + listHiddenFiles = false; + useEPSVwithIPv4 = false; + random = new Random(); + passiveLocalHost = null; + } + + @Override + protected void _connectAction_() throws IOException { + _connectAction_(null); } /** - * Establishes a data connection with the FTP server, returning - * a Socket for the connection if successful. If a restart - * offset has been set with {@link #setRestartOffset(long)}, - * a REST command is issued to the server with the offset as - * an argument before establishing the data connection. Active - * mode connections also cause a local PORT command to be issued. - * - * @param command The int representation of the FTP command to send. - * @param arg The arguments to the FTP command. If this parameter is - * set to null, then the command is sent with no argument. - * @return A Socket corresponding to the established data connection. - * Null is returned if an FTP protocol error is reported at - * any point during the establishment and initialization of - * the connection. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @since 3.3 + * @param socketIsReader the reader to reuse (if non-null) + * @throws IOException on error + * @since 3.4 + */ + @Override + protected void _connectAction_(final Reader socketIsReader) throws IOException { + super._connectAction_(socketIsReader); // sets up _input_ and _output_ + initDefaults(); + // must be after super._connectAction_(), because otherwise we get an + // Exception claiming we're not connected + if (autodetectEncoding) { + final ArrayList<String> oldReplyLines = new ArrayList<>(_replyLines); + final int oldReplyCode = _replyCode; + if (hasFeature("UTF8") || hasFeature("UTF-8")) // UTF8 appears to be the default + { + setControlEncoding("UTF-8"); + _controlInput_ = new CRLFLineReader(new InputStreamReader(_input_, getControlEncoding())); + _controlOutput_ = new BufferedWriter(new OutputStreamWriter(_output_, getControlEncoding())); + } + // restore the original reply (server greeting) + _replyLines.clear(); + _replyLines.addAll(oldReplyLines); + _replyCode = oldReplyCode; + _newReplyString = true; + } + } + + /** + * Establishes a data connection with the FTP server, returning a Socket for the connection if successful. If a restart offset has been set with + * {@link #setRestartOffset(long)}, a REST command is issued to the server with the offset as an argument before establishing the data connection. Active + * mode connections also cause a local PORT command to be issued. + * + * @param command The int representation of the FTP command to send. + * @param arg The arguments to the FTP command. If this parameter is set to null, then the command is sent with no argument. + * @return A Socket corresponding to the established data connection. Null is returned if an FTP protocol error is reported at any point during the + * establishment and initialization of the connection. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @since 3.3 */ - protected Socket _openDataConnection_(FTPCmd command, String arg) - throws IOException - { + protected Socket _openDataConnection_(final FTPCmd command, final String arg) throws IOException { return _openDataConnection_(command.getCommand(), arg); } /** - * Establishes a data connection with the FTP server, returning - * a Socket for the connection if successful. If a restart - * offset has been set with {@link #setRestartOffset(long)}, - * a REST command is issued to the server with the offset as - * an argument before establishing the data connection. Active + * Establishes a data connection with the FTP server, returning a Socket for the connection if successful. If a restart offset has been set with + * {@link #setRestartOffset(long)}, a REST command is issued to the server with the offset as an argument before establishing the data connection. Active + * mode connections also cause a local PORT command to be issued. + * + * @deprecated (3.3) Use {@link #_openDataConnection_(FTPCmd, String)} instead + * @param command The int representation of the FTP command to send. + * @param arg The arguments to the FTP command. If this parameter is set to null, then the command is sent with no argument. + * @return A Socket corresponding to the established data connection. Null is returned if an FTP protocol error is reported at any point during the + * establishment and initialization of the connection. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + @Deprecated + protected Socket _openDataConnection_(final int command, final String arg) throws IOException { + return _openDataConnection_(FTPCommand.getCommand(command), arg); + } + + /** + * Establishes a data connection with the FTP server, returning a Socket for the connection if successful. If a restart offset has been set with + * {@link #setRestartOffset(long)}, a REST command is issued to the server with the offset as an argument before establishing the data connection. Active * mode connections also cause a local PORT command to be issued. * - * @param command The text representation of the FTP command to send. - * @param arg The arguments to the FTP command. If this parameter is - * set to null, then the command is sent with no argument. - * @return A Socket corresponding to the established data connection. - * Null is returned if an FTP protocol error is reported at - * any point during the establishment and initialization of - * the connection. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. + * @param command The text representation of the FTP command to send. + * @param arg The arguments to the FTP command. If this parameter is set to null, then the command is sent with no argument. + * @return A Socket corresponding to the established data connection. Null is returned if an FTP protocol error is reported at any point during the + * establishment and initialization of the connection. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. * @since 3.1 */ - protected Socket _openDataConnection_(String command, String arg) - throws IOException - { - if (__dataConnectionMode != ACTIVE_LOCAL_DATA_CONNECTION_MODE && - __dataConnectionMode != PASSIVE_LOCAL_DATA_CONNECTION_MODE) { + protected Socket _openDataConnection_(final String command, final String arg) throws IOException { + if (dataConnectionMode != ACTIVE_LOCAL_DATA_CONNECTION_MODE && dataConnectionMode != PASSIVE_LOCAL_DATA_CONNECTION_MODE) { return null; } final boolean isInet6Address = getRemoteAddress() instanceof Inet6Address; - Socket socket; + final Socket socket; - if (__dataConnectionMode == ACTIVE_LOCAL_DATA_CONNECTION_MODE) - { + final int soTimeoutMillis = DurationUtils.toMillisInt(dataTimeout); + if (dataConnectionMode == ACTIVE_LOCAL_DATA_CONNECTION_MODE) { // if no activePortRange was set (correctly) -> getActivePort() = 0 // -> new ServerSocket(0) -> bind to any free local port - ServerSocket server = _serverSocketFactory_.createServerSocket(getActivePort(), 1, getHostAddress()); - - try { + try (final ServerSocket server = _serverSocketFactory_.createServerSocket(getActivePort(), 1, getHostAddress())) { // Try EPRT only if remote server is over IPv6, if not use PORT, // because EPRT has no advantage over PORT on IPv4. // It could even have the disadvantage, @@ -834,13 +692,11 @@ implements Configurable if (!FTPReply.isPositiveCompletion(eprt(getReportHostAddress(), server.getLocalPort()))) { return null; } - } else { - if (!FTPReply.isPositiveCompletion(port(getReportHostAddress(), server.getLocalPort()))) { - return null; - } + } else if (!FTPReply.isPositiveCompletion(port(getReportHostAddress(), server.getLocalPort()))) { + return null; } - if ((__restartOffset > 0) && !restart(__restartOffset)) { + if ((restartOffset > 0) && !restart(restartOffset)) { return null; } @@ -849,30 +705,26 @@ implements Configurable } // For now, let's just use the data timeout value for waiting for - // the data connection. It may be desirable to let this be a - // separately configurable value. In any case, we really want + // the data connection. It may be desirable to let this be a + // separately configurable value. In any case, we really want // to allow preventing the accept from blocking indefinitely. - if (__dataTimeout >= 0) { - server.setSoTimeout(__dataTimeout); + if (soTimeoutMillis >= 0) { + server.setSoTimeout(soTimeoutMillis); } socket = server.accept(); // Ensure the timeout is set before any commands are issued on the new socket - if (__dataTimeout >= 0) { - socket.setSoTimeout(__dataTimeout); + if (soTimeoutMillis >= 0) { + socket.setSoTimeout(soTimeoutMillis); } - if (__receiveDataSocketBufferSize > 0) { - socket.setReceiveBufferSize(__receiveDataSocketBufferSize); + if (receiveDataSocketBufferSize > 0) { + socket.setReceiveBufferSize(receiveDataSocketBufferSize); } - if (__sendDataSocketBufferSize > 0) { - socket.setSendBufferSize(__sendDataSocketBufferSize); + if (sendDataSocketBufferSize > 0) { + socket.setSendBufferSize(sendDataSocketBufferSize); } - } finally { - server.close(); } - } - else - { // We must be in PASSIVE_LOCAL_DATA_CONNECTION_MODE + } else { // We must be in PASSIVE_LOCAL_DATA_CONNECTION_MODE // Try EPSV command first on IPv6 - and IPv4 if enabled. // When using IPv4 with NAT it has the advantage @@ -881,13 +733,10 @@ implements Configurable // and the client is coming from another internal network. // In that case the data connection after PASV command would fail, // while EPSV would make the client succeed by taking just the port. - boolean attemptEPSV = isUseEPSVwithIPv4() || isInet6Address; - if (attemptEPSV && epsv() == FTPReply.ENTERING_EPSV_MODE) - { + final boolean attemptEPSV = isUseEPSVwithIPv4() || isInet6Address; + if (attemptEPSV && epsv() == FTPReply.ENTERING_EPSV_MODE) { _parseExtendedPassiveModeReply(_replyLines.get(0)); - } - else - { + } else { if (isInet6Address) { return null; // Must use EPSV for IPV6 } @@ -899,1524 +748,1201 @@ implements Configurable } socket = _socketFactory_.createSocket(); - if (__receiveDataSocketBufferSize > 0) { - socket.setReceiveBufferSize(__receiveDataSocketBufferSize); + if (receiveDataSocketBufferSize > 0) { + socket.setReceiveBufferSize(receiveDataSocketBufferSize); } - if (__sendDataSocketBufferSize > 0) { - socket.setSendBufferSize(__sendDataSocketBufferSize); + if (sendDataSocketBufferSize > 0) { + socket.setSendBufferSize(sendDataSocketBufferSize); } - if (__passiveLocalHost != null) { - socket.bind(new InetSocketAddress(__passiveLocalHost, 0)); + if (passiveLocalHost != null) { + socket.bind(new InetSocketAddress(passiveLocalHost, 0)); } // For now, let's just use the data timeout value for waiting for - // the data connection. It may be desirable to let this be a - // separately configurable value. In any case, we really want + // the data connection. It may be desirable to let this be a + // separately configurable value. In any case, we really want // to allow preventing the accept from blocking indefinitely. - if (__dataTimeout >= 0) { - socket.setSoTimeout(__dataTimeout); + if (soTimeoutMillis >= 0) { + socket.setSoTimeout(soTimeoutMillis); } - socket.connect(new InetSocketAddress(__passiveHost, __passivePort), connectTimeout); - if ((__restartOffset > 0) && !restart(__restartOffset)) - { + socket.connect(new InetSocketAddress(passiveHost, passivePort), connectTimeout); + if ((restartOffset > 0) && !restart(restartOffset)) { socket.close(); return null; } - if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) - { + if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) { socket.close(); return null; } } - if (__remoteVerificationEnabled && !verifyRemote(socket)) - { + if (remoteVerificationEnabled && !verifyRemote(socket)) { + // Grab the host before we close the socket to avoid NET-663 + final InetAddress socketHost = socket.getInetAddress(); + socket.close(); throw new IOException( - "Host attempting data connection " + socket.getInetAddress().getHostAddress() + - " is not same as server " + getRemoteAddress().getHostAddress()); + "Host attempting data connection " + socketHost.getHostAddress() + " is not same as server " + getRemoteAddress().getHostAddress()); } return socket; } + protected void _parseExtendedPassiveModeReply(String reply) throws MalformedServerReplyException { + reply = reply.substring(reply.indexOf('(') + 1, reply.indexOf(')')).trim(); - @Override - protected void _connectAction_() throws IOException - { - _connectAction_(null); - } + final char delim1 = reply.charAt(0); + final char delim2 = reply.charAt(1); + final char delim3 = reply.charAt(2); + final char delim4 = reply.charAt(reply.length() - 1); + if ((delim1 != delim2) || (delim2 != delim3) || (delim3 != delim4)) { + throw new MalformedServerReplyException("Could not parse extended passive host information.\nServer Reply: " + reply); + } - /** - * @param socketIsReader the reader to reuse (if non-null) - * @throws IOException on error - * @since 3.4 - */ - @Override - protected void _connectAction_(Reader socketIsReader) throws IOException - { - super._connectAction_(socketIsReader); // sets up _input_ and _output_ - __initDefaults(); - // must be after super._connectAction_(), because otherwise we get an - // Exception claiming we're not connected - if ( __autodetectEncoding ) - { - ArrayList<String> oldReplyLines = new ArrayList<String> (_replyLines); - int oldReplyCode = _replyCode; - if ( hasFeature("UTF8") || hasFeature("UTF-8")) // UTF8 appears to be the default - { - setControlEncoding("UTF-8"); - _controlInput_ = - new CRLFLineReader(new InputStreamReader(_input_, getControlEncoding())); - _controlOutput_ = - new BufferedWriter(new OutputStreamWriter(_output_, getControlEncoding())); - } - // restore the original reply (server greeting) - _replyLines.clear(); - _replyLines.addAll(oldReplyLines); - _replyCode = oldReplyCode; - _newReplyString = true; + final int port; + try { + port = Integer.parseInt(reply.substring(3, reply.length() - 1)); + } catch (final NumberFormatException e) { + throw new MalformedServerReplyException("Could not parse extended passive host information.\nServer Reply: " + reply); } - } + // in EPSV mode, the passive host address is implicit + this.passiveHost = getRemoteAddress().getHostAddress(); + this.passivePort = port; + } /** - * Sets the timeout in milliseconds to use when reading from the - * data connection. This timeout will be set immediately after - * opening the data connection, provided that the value is ≥ 0. - * <p> - * <b>Note:</b> the timeout will also be applied when calling accept() - * whilst establishing an active local data connection. - * @param timeout The default timeout in milliseconds that is used when - * opening a data connection socket. The value 0 means an infinite timeout. + * @since 3.1 + * @param reply the reply to parse + * @throws MalformedServerReplyException if the server reply does not match (n,n,n,n),(n),(n) */ - public void setDataTimeout(int timeout) - { - __dataTimeout = timeout; + protected void _parsePassiveModeReply(final String reply) throws MalformedServerReplyException { + final Matcher m = PARMS_PAT.matcher(reply); + if (!m.find()) { + throw new MalformedServerReplyException("Could not parse passive host information.\nServer Reply: " + reply); + } + + int pasvPort; + // Fix up to look like IP address + String pasvHost = "0,0,0,0".equals(m.group(1)) ? _socket_.getInetAddress().getHostAddress() : m.group(1).replace(',', '.'); + + try { + final int oct1 = Integer.parseInt(m.group(2)); + final int oct2 = Integer.parseInt(m.group(3)); + pasvPort = (oct1 << 8) | oct2; + } catch (final NumberFormatException e) { + throw new MalformedServerReplyException("Could not parse passive port information.\nServer Reply: " + reply); + } + + if (isIpAddressFromPasvResponse()) { + // Pre-3.9.0 behavior + if (passiveNatWorkaroundStrategy != null) { + try { + final String newPassiveHost = passiveNatWorkaroundStrategy.resolve(pasvHost); + if (!pasvHost.equals(newPassiveHost)) { + fireReplyReceived(0, "[Replacing PASV mode reply address " + this.passiveHost + " with " + newPassiveHost + "]\n"); + pasvHost = newPassiveHost; + } + } catch (final UnknownHostException e) { // Should not happen as we are passing in an IP address + throw new MalformedServerReplyException("Could not parse passive host information.\nServer Reply: " + reply); + } + } + } else if (_socket_ == null) { + pasvHost = null; // For unit testing. + } else { + pasvHost = _socket_.getInetAddress().getHostAddress(); + } + this.passiveHost = pasvHost; + this.passivePort = pasvPort; } /** - * set the factory used for parser creation to the supplied factory object. - * - * @param parserFactory - * factory object used to create FTPFileEntryParsers - * - * @see org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory - * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory + * @param command the command to get + * @param remote the remote file name + * @param local The local OutputStream to which to write the file. + * @return true if successful + * @throws IOException on error + * @since 3.1 */ - public void setParserFactory(FTPFileEntryParserFactory parserFactory) { - __parserFactory = parserFactory; - } + protected boolean _retrieveFile(final String command, final String remote, final OutputStream local) throws IOException { + final Socket socket = _openDataConnection_(command, remote); + if (socket == null) { + return false; + } - /** - * Closes the connection to the FTP server and restores - * connection parameters to the default values. - * - * @throws IOException If an error occurs while disconnecting. - */ - @Override - public void disconnect() throws IOException - { - super.disconnect(); - __initDefaults(); - } + InputStream input = null; + CSL csl = null; + try { + try { + if (fileType == ASCII_FILE_TYPE) { + input = new FromNetASCIIInputStream(getBufferedInputStream(socket.getInputStream())); + } else { + input = getBufferedInputStream(socket.getInputStream()); + } + if (DurationUtils.isPositive(controlKeepAliveTimeout)) { + csl = new CSL(this, controlKeepAliveTimeout, controlKeepAliveReplyTimeout); + } - /** - * Enable or disable verification that the remote host taking part - * of a data connection is the same as the host to which the control - * connection is attached. The default is for verification to be - * enabled. You may set this value at any time, whether the - * FTPClient is currently connected or not. - * - * @param enable True to enable verification, false to disable verification. - */ - public void setRemoteVerificationEnabled(boolean enable) - { - __remoteVerificationEnabled = enable; + // Treat everything else as binary for now + Util.copyStream(input, local, getBufferSize(), CopyStreamEvent.UNKNOWN_STREAM_SIZE, mergeListeners(csl), false); + } finally { + Util.closeQuietly(input); + } + // Get the transfer response + return completePendingCommand(); + } finally { + Util.closeQuietly(socket); + if (csl != null) { + cslDebug = csl.cleanUp(); // fetch any outstanding keepalive replies + } + } } /** - * Return whether or not verification of the remote host participating - * in data connections is enabled. The default behavior is for - * verification to be enabled. - * - * @return True if verification is enabled, false if not. + * @param command the command to send + * @param remote the remote file name + * @return the stream from which to read the file + * @throws IOException on error + * @since 3.1 */ - public boolean isRemoteVerificationEnabled() - { - return __remoteVerificationEnabled; + protected InputStream _retrieveFileStream(final String command, final String remote) throws IOException { + final Socket socket = _openDataConnection_(command, remote); + + if (socket == null) { + return null; + } + + final InputStream input; + if (fileType == ASCII_FILE_TYPE) { + // We buffer ascii transfers because the buffering has to + // be interposed between FromNetASCIIOutputSream and the underlying + // socket input stream. We don't buffer binary transfers + // because we don't want to impose a buffering policy on the + // programmer if possible. Programmers can decide on their + // own if they want to wrap the SocketInputStream we return + // for file types other than ASCII. + input = new FromNetASCIIInputStream(getBufferedInputStream(socket.getInputStream())); + } else { + input = socket.getInputStream(); + } + return new org.apache.commons.net.io.SocketInputStream(socket, input); } /** - * Login to the FTP server using the provided username and password. - * - * @param username The username to login under. - * @param password The password to use. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. + * @since 3.1 + * @param command the command to send + * @param remote the remote file name + * @param local The local InputStream from which to read the data to be written/appended to the remote file. + * @return true if successful + * @throws IOException on error */ - public boolean login(String username, String password) throws IOException - { - - user(username); - - if (FTPReply.isPositiveCompletion(_replyCode)) { - return true; - } + protected boolean _storeFile(final String command, final String remote, final InputStream local) throws IOException { + final Socket socket = _openDataConnection_(command, remote); - // If we get here, we either have an error code, or an intermmediate - // reply requesting password. - if (!FTPReply.isPositiveIntermediate(_replyCode)) { + if (socket == null) { return false; } - return FTPReply.isPositiveCompletion(pass(password)); - } - + final OutputStream output; - /** - * Login to the FTP server using the provided username, password, - * and account. If no account is required by the server, only - * the username and password, the account information is not used. - * - * @param username The username to login under. - * @param password The password to use. - * @param account The account to use. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean login(String username, String password, String account) - throws IOException - { - user(username); + if (fileType == ASCII_FILE_TYPE) { + output = new ToNetASCIIOutputStream(getBufferedOutputStream(socket.getOutputStream())); + } else { + output = getBufferedOutputStream(socket.getOutputStream()); + } - if (FTPReply.isPositiveCompletion(_replyCode)) { - return true; + CSL csl = null; + if (DurationUtils.isPositive(controlKeepAliveTimeout)) { + csl = new CSL(this, controlKeepAliveTimeout, controlKeepAliveReplyTimeout); } - // If we get here, we either have an error code, or an intermmediate - // reply requesting password. - if (!FTPReply.isPositiveIntermediate(_replyCode)) { - return false; + // Treat everything else as binary for now + try { + Util.copyStream(local, output, getBufferSize(), CopyStreamEvent.UNKNOWN_STREAM_SIZE, mergeListeners(csl), false); + output.close(); // ensure the file is fully written + socket.close(); // done writing the file + + // Get the transfer response + return completePendingCommand(); + } catch (final IOException e) { + Util.closeQuietly(output); // ignore close errors here + Util.closeQuietly(socket); // ignore close errors here + throw e; + } finally { + if (csl != null) { + cslDebug = csl.cleanUp(); // fetch any outstanding keepalive replies + } } + } - pass(password); + /** + * @param command the command to send + * @param remote the remote file name + * @return the output stream to write to + * @throws IOException on error + * @since 3.1 + */ + protected OutputStream _storeFileStream(final String command, final String remote) throws IOException { + final Socket socket = _openDataConnection_(command, remote); - if (FTPReply.isPositiveCompletion(_replyCode)) { - return true; + if (socket == null) { + return null; } - if (!FTPReply.isPositiveIntermediate(_replyCode)) { - return false; + final OutputStream output; + if (fileType == ASCII_FILE_TYPE) { + // We buffer ascii transfers because the buffering has to + // be interposed between ToNetASCIIOutputSream and the underlying + // socket output stream. We don't buffer binary transfers + // because we don't want to impose a buffering policy on the + // programmer if possible. Programmers can decide on their + // own if they want to wrap the SocketOutputStream we return + // for file types other than ASCII. + output = new ToNetASCIIOutputStream(getBufferedOutputStream(socket.getOutputStream())); + } else { + output = socket.getOutputStream(); } - - return FTPReply.isPositiveCompletion(acct(account)); + return new SocketOutputStream(socket, output); } /** - * Logout of the FTP server by sending the QUIT command. + * Abort a transfer in progress. * * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean logout() throws IOException - { - return FTPReply.isPositiveCompletion(quit()); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean abort() throws IOException { + return FTPReply.isPositiveCompletion(abor()); } - /** - * Change the current working directory of the FTP session. + * Reserve a number of bytes on the server for the next file transfer. * - * @param pathname The new current working directory. + * @param bytes The number of bytes which the server should allocate. * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean changeWorkingDirectory(String pathname) throws IOException - { - return FTPReply.isPositiveCompletion(cwd(pathname)); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean allocate(final int bytes) throws IOException { + return FTPReply.isPositiveCompletion(allo(bytes)); } - /** - * Change to the parent directory of the current working directory. + * Reserve space on the server for the next file transfer. * + * @param bytes The number of bytes which the server should allocate. + * @param recordSize The size of a file record. * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean changeToParentDirectory() throws IOException - { - return FTPReply.isPositiveCompletion(cdup()); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean allocate(final int bytes, final int recordSize) throws IOException { + return FTPReply.isPositiveCompletion(allo(bytes, recordSize)); } - /** - * Issue the FTP SMNT command. + * Reserve a number of bytes on the server for the next file transfer. * - * @param pathname The pathname to mount. + * @param bytes The number of bytes which the server should allocate. * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean structureMount(String pathname) throws IOException - { - return FTPReply.isPositiveCompletion(smnt(pathname)); + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean allocate(final long bytes) throws IOException { + return FTPReply.isPositiveCompletion(allo(bytes)); } /** - * Reinitialize the FTP session. Not all FTP servers support this - * command, which issues the FTP REIN command. + * Reserve space on the server for the next file transfer. * + * @param bytes The number of bytes which the server should allocate. + * @param recordSize The size of a file record. * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @since 3.4 (made public) + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - public boolean reinitialize() throws IOException - { - rein(); - - if (FTPReply.isPositiveCompletion(_replyCode) || - (FTPReply.isPositivePreliminary(_replyCode) && - FTPReply.isPositiveCompletion(getReply()))) - { - - __initDefaults(); - - return true; - } - - return false; + public boolean allocate(final long bytes, final int recordSize) throws IOException { + return FTPReply.isPositiveCompletion(allo(bytes, recordSize)); } - /** - * Set the current data connection mode to - * <code>ACTIVE_LOCAL_DATA_CONNECTION_MODE</code>. No communication - * with the FTP server is conducted, but this causes all future data - * transfers to require the FTP server to connect to the client's - * data port. Additionally, to accommodate differences between socket - * implementations on different platforms, this method causes the - * client to issue a PORT command before every data transfer. - */ - public void enterLocalActiveMode() - { - __dataConnectionMode = ACTIVE_LOCAL_DATA_CONNECTION_MODE; - __passiveHost = null; - __passivePort = -1; + * Appends to a file on the server with the given name, taking input from the given InputStream. This method does NOT close the given InputStream. If the + * current file type is ASCII, line separators in the file are transparently converted to the NETASCII format (i.e., you should not attempt to create a + * special InputStream to do this). + * + * @param remote The name of the remote file. + * @param local The local InputStream from which to read the data to be appended to the remote file. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some + * other reason causing the server to send FTP reply code 421. This exception may be caught either as + * an IOException or independently as itself. + * @throws org.apache.commons.net.io.CopyStreamException If an I/O error occurs while actually transferring the file. The CopyStreamException allows you to + * determine the number of bytes transferred and the IOException causing the error. This exception may + * be caught either as an IOException or independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the + * server. + */ + public boolean appendFile(final String remote, final InputStream local) throws IOException { + return storeFile(FTPCmd.APPE, remote, local); } - /** - * Set the current data connection mode to - * <code> PASSIVE_LOCAL_DATA_CONNECTION_MODE </code>. Use this - * method only for data transfers between the client and server. - * This method causes a PASV (or EPSV) command to be issued to the server - * before the opening of every data connection, telling the server to - * open a data port to which the client will connect to conduct - * data transfers. The FTPClient will stay in - * <code> PASSIVE_LOCAL_DATA_CONNECTION_MODE </code> until the - * mode is changed by calling some other method such as - * {@link #enterLocalActiveMode enterLocalActiveMode() } + * Returns an OutputStream through which data can be written to append to a file on the server with the given name. If the current file type is ASCII, the + * returned OutputStream will convert line separators in the file to the NETASCII format (i.e., you should not attempt to create a special OutputStream to + * do this). You must close the OutputStream when you finish writing to it. The OutputStream itself will take care of closing the parent data connection + * socket upon being closed. * <p> - * <b>N.B.</b> currently calling any connect method will reset the mode to - * ACTIVE_LOCAL_DATA_CONNECTION_MODE. + * <b>To finalize the file transfer you must call {@link #completePendingCommand completePendingCommand } and check its return value to verify success.</b> + * If this is not done, subsequent commands may behave unexpectedly. + * + * @param remote The name of the remote file. + * @return An OutputStream through which the remote file can be appended. If the data connection cannot be opened (e.g., the file does not exist), null is + * returned (in which case you may check the reply code to determine the exact reason for failure). + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - public void enterLocalPassiveMode() - { - __dataConnectionMode = PASSIVE_LOCAL_DATA_CONNECTION_MODE; - // These will be set when just before a data connection is opened - // in _openDataConnection_() - __passiveHost = null; - __passivePort = -1; + public OutputStream appendFileStream(final String remote) throws IOException { + return storeFileStream(FTPCmd.APPE, remote); } - /** - * Set the current data connection mode to - * <code> ACTIVE_REMOTE_DATA_CONNECTION </code>. Use this method only - * for server to server data transfers. This method issues a PORT - * command to the server, indicating the other server and port to which - * it should connect for data transfers. You must call this method - * before EVERY server to server transfer attempt. The FTPClient will - * NOT automatically continue to issue PORT commands. You also - * must remember to call - * {@link #enterLocalActiveMode enterLocalActiveMode() } if you - * wish to return to the normal data connection mode. + * Change to the parent directory of the current working directory. * - * @param host The passive mode server accepting connections for data - * transfers. - * @param port The passive mode server's data port. * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean enterRemoteActiveMode(InetAddress host, int port) - throws IOException - { - if (FTPReply.isPositiveCompletion(port(host, port))) - { - __dataConnectionMode = ACTIVE_REMOTE_DATA_CONNECTION_MODE; - __passiveHost = null; - __passivePort = -1; - return true; - } - return false; + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean changeToParentDirectory() throws IOException { + return FTPReply.isPositiveCompletion(cdup()); } /** - * Set the current data connection mode to - * <code> PASSIVE_REMOTE_DATA_CONNECTION_MODE </code>. Use this - * method only for server to server data transfers. - * This method issues a PASV command to the server, telling it to - * open a data port to which the active server will connect to conduct - * data transfers. You must call this method - * before EVERY server to server transfer attempt. The FTPClient will - * NOT automatically continue to issue PASV commands. You also - * must remember to call - * {@link #enterLocalActiveMode enterLocalActiveMode() } if you - * wish to return to the normal data connection mode. + * Change the current working directory of the FTP session. * + * @param pathname The new current working directory. * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean enterRemotePassiveMode() throws IOException - { - if (pasv() != FTPReply.ENTERING_PASSIVE_MODE) { - return false; - } - - __dataConnectionMode = PASSIVE_REMOTE_DATA_CONNECTION_MODE; - _parsePassiveModeReply(_replyLines.get(0)); - - return true; + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean changeWorkingDirectory(final String pathname) throws IOException { + return FTPReply.isPositiveCompletion(cwd(pathname)); } /** - * Returns the hostname or IP address (in the form of a string) returned - * by the server when entering passive mode. If not in passive mode, - * returns null. This method only returns a valid value AFTER a - * data connection has been opened after a call to - * {@link #enterLocalPassiveMode enterLocalPassiveMode()}. - * This is because FTPClient sends a PASV command to the server only - * just before opening a data connection, and not when you call - * {@link #enterLocalPassiveMode enterLocalPassiveMode()}. + * There are a few FTPClient methods that do not complete the entire sequence of FTP commands to complete a transaction. These commands require some action + * by the programmer after the reception of a positive intermediate command. After the programmer's code completes its actions, it must call this method to + * receive the completion reply from the server and verify the success of the entire transaction. + * <p> + * For example, * - * @return The passive host name if in passive mode, otherwise null. + * <pre> + * InputStream input; + * OutputStream output; + * input = new FileInputStream("foobaz.txt"); + * output = ftp.storeFileStream("foobar.txt") + * if(!FTPReply.isPositiveIntermediate(ftp.getReplyCode())) { + * input.close(); + * output.close(); + * ftp.logout(); + * ftp.disconnect(); + * System.err.println("File transfer failed."); + * System.exit(1); + * } + * Util.copyStream(input, output); + * input.close(); + * output.close(); + * // Must call completePendingCommand() to finish command. + * if(!ftp.completePendingCommand()) { + * ftp.logout(); + * ftp.disconnect(); + * System.err.println("File transfer failed."); + * System.exit(1); + * } + * </pre> + * + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - public String getPassiveHost() - { - return __passiveHost; + public boolean completePendingCommand() throws IOException { + return FTPReply.isPositiveCompletion(getReply()); } /** - * If in passive mode, returns the data port of the passive host. - * This method only returns a valid value AFTER a - * data connection has been opened after a call to - * {@link #enterLocalPassiveMode enterLocalPassiveMode()}. - * This is because FTPClient sends a PASV command to the server only - * just before opening a data connection, and not when you call - * {@link #enterLocalPassiveMode enterLocalPassiveMode()}. + * Implementation of the {@link Configurable Configurable} interface. In the case of this class, configuring merely makes the config object available for + * the factory methods that construct parsers. * - * @return The data port of the passive server. If not in passive - * mode, undefined. + * @param config {@link FTPClientConfig FTPClientConfig} object used to provide non-standard configurations to the parser. + * @since 1.4 */ - public int getPassivePort() - { - return __passivePort; + @Override + public void configure(final FTPClientConfig config) { + this.configuration = config; } + // package access for test purposes + void createParser(final String parserKey) throws IOException { + // We cache the value to avoid creation of a new object every + // time a file listing is generated. + // Note: we don't check against a null parserKey (NET-544) + if (entryParser == null || (parserKey != null && !entryParserKey.equals(parserKey))) { + if (null != parserKey) { + // if a parser key was supplied in the parameters, + // use that to create the parser + entryParser = parserFactory.createFileEntryParser(parserKey); + entryParserKey = parserKey; + + } else // if no parserKey was supplied, check for a configuration + // in the params, and if it has a non-empty system type, use that. + if (null != configuration && configuration.getServerSystemKey().length() > 0) { + entryParser = parserFactory.createFileEntryParser(configuration); + entryParserKey = configuration.getServerSystemKey(); + } else { + // if a parserKey hasn't been supplied, and a configuration + // hasn't been supplied, and the override property is not set + // then autodetect by calling + // the SYST command and use that to choose the parser. + String systemType = System.getProperty(FTP_SYSTEM_TYPE); + if (systemType == null) { + systemType = getSystemType(); // cannot be null + final Properties override = getOverrideProperties(); + if (override != null) { + final String newType = override.getProperty(systemType); + if (newType != null) { + systemType = newType; + } + } + } + if (null != configuration) { // system type must have been empty above + entryParser = parserFactory.createFileEntryParser(new FTPClientConfig(systemType, configuration)); + } else { + entryParser = parserFactory.createFileEntryParser(systemType); + } + entryParserKey = systemType; + } + } + } /** - * Returns the current data connection mode (one of the - * <code> _DATA_CONNECTION_MODE </code> constants. + * Deletes a file on the FTP server. * - * @return The current data connection mode (one of the - * <code> _DATA_CONNECTION_MODE </code> constants. + * @param pathname The pathname of the file to be deleted. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - public int getDataConnectionMode() - { - return __dataConnectionMode; + public boolean deleteFile(final String pathname) throws IOException { + return FTPReply.isPositiveCompletion(dele(pathname)); } /** - * Get the client port for active mode. + * Closes the connection to the FTP server and restores connection parameters to the default values. * - * @return The client port for active mode. + * @throws IOException If an error occurs while disconnecting. */ - private int getActivePort() - { - if (__activeMinPort > 0 && __activeMaxPort >= __activeMinPort) - { - if (__activeMaxPort == __activeMinPort) { - return __activeMaxPort; - } - // Get a random port between the min and max port range - return __random.nextInt(__activeMaxPort - __activeMinPort + 1) + __activeMinPort; - } - else - { - // default port - return 0; - } + @Override + public void disconnect() throws IOException { + super.disconnect(); + initDefaults(); } /** - * Get the host address for active mode; allows the local address to be overridden. + * Issue a command and wait for the reply. + * <p> + * Should only be used with commands that return replies on the command channel - do not use for LIST, NLST, MLSD etc. * - * @return __activeExternalHost if non-null, else getLocalAddress() - * @see #setActiveExternalIPAddress(String) + * @param command The command to invoke + * @param params The parameters string, may be {@code null} + * @return True if successfully completed, false if not, in which case call {@link #getReplyCode()} or {@link #getReplyString()} to get the reason. + * + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @since 3.0 */ - private InetAddress getHostAddress() - { - if (__activeExternalHost != null) - { - return __activeExternalHost; - } - else - { - // default local address - return getLocalAddress(); - } + public boolean doCommand(final String command, final String params) throws IOException { + return FTPReply.isPositiveCompletion(sendCommand(command, params)); } /** - * Get the reported host address for active mode EPRT/PORT commands; - * allows override of {@link #getHostAddress()}. + * Issue a command and wait for the reply, returning it as an array of strings. + * <p> + * Should only be used with commands that return replies on the command channel - do not use for LIST, NLST, MLSD etc. * - * Useful for FTP Client behind Firewall NAT. + * @param command The command to invoke + * @param params The parameters string, may be {@code null} + * @return The array of replies, or {@code null} if the command failed, in which case call {@link #getReplyCode()} or {@link #getReplyString()} to get the + * reason. * - * @return __reportActiveExternalHost if non-null, else getHostAddress(); + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @since 3.0 */ - private InetAddress getReportHostAddress() { - if (__reportActiveExternalHost != null) { - return __reportActiveExternalHost ; - } else { - return getHostAddress(); + public String[] doCommandAsStrings(final String command, final String params) throws IOException { + final boolean success = FTPReply.isPositiveCompletion(sendCommand(command, params)); + if (success) { + return getReplyStrings(); } + return null; } /** - * Set the client side port range in active mode. - * - * @param minPort The lowest available port (inclusive). - * @param maxPort The highest available port (inclusive). - * @since 2.2 + * Set the current data connection mode to <code>ACTIVE_LOCAL_DATA_CONNECTION_MODE</code>. No communication with the FTP server is conducted, but this + * causes all future data transfers to require the FTP server to connect to the client's data port. Additionally, to accommodate differences between socket + * implementations on different platforms, this method causes the client to issue a PORT command before every data transfer. */ - public void setActivePortRange(int minPort, int maxPort) - { - this.__activeMinPort = minPort; - this.__activeMaxPort = maxPort; + public void enterLocalActiveMode() { + dataConnectionMode = ACTIVE_LOCAL_DATA_CONNECTION_MODE; + passiveHost = null; + passivePort = -1; } /** - * Set the external IP address in active mode. - * Useful when there are multiple network cards. - * - * @param ipAddress The external IP address of this machine. - * @throws UnknownHostException if the ipAddress cannot be resolved - * @since 2.2 + * Set the current data connection mode to <code> PASSIVE_LOCAL_DATA_CONNECTION_MODE </code>. Use this method only for data transfers between the client and + * server. This method causes a PASV (or EPSV) command to be issued to the server before the opening of every data connection, telling the server to open a + * data port to which the client will connect to conduct data transfers. The FTPClient will stay in <code> PASSIVE_LOCAL_DATA_CONNECTION_MODE </code> until + * the mode is changed by calling some other method such as {@link #enterLocalActiveMode enterLocalActiveMode() } + * <p> + * <b>N.B.</b> currently calling any connect method will reset the mode to ACTIVE_LOCAL_DATA_CONNECTION_MODE. */ - public void setActiveExternalIPAddress(String ipAddress) throws UnknownHostException - { - this.__activeExternalHost = InetAddress.getByName(ipAddress); + public void enterLocalPassiveMode() { + dataConnectionMode = PASSIVE_LOCAL_DATA_CONNECTION_MODE; + // These will be set when just before a data connection is opened + // in _openDataConnection_() + passiveHost = null; + passivePort = -1; } /** - * Set the local IP address to use in passive mode. - * Useful when there are multiple network cards. + * Set the current data connection mode to <code> ACTIVE_REMOTE_DATA_CONNECTION </code>. Use this method only for server to server data transfers. This + * method issues a PORT command to the server, indicating the other server and port to which it should connect for data transfers. You must call this method + * before EVERY server to server transfer attempt. The FTPClient will NOT automatically continue to issue PORT commands. You also must remember to call + * {@link #enterLocalActiveMode enterLocalActiveMode() } if you wish to return to the normal data connection mode. * - * @param ipAddress The local IP address of this machine. - * @throws UnknownHostException if the ipAddress cannot be resolved - */ - public void setPassiveLocalIPAddress(String ipAddress) throws UnknownHostException - { - this.__passiveLocalHost = InetAddress.getByName(ipAddress); + * @param host The passive mode server accepting connections for data transfers. + * @param port The passive mode server's data port. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean enterRemoteActiveMode(final InetAddress host, final int port) throws IOException { + if (FTPReply.isPositiveCompletion(port(host, port))) { + dataConnectionMode = ACTIVE_REMOTE_DATA_CONNECTION_MODE; + passiveHost = null; + passivePort = -1; + return true; + } + return false; } /** - * Set the local IP address to use in passive mode. - * Useful when there are multiple network cards. + * Set the current data connection mode to <code> PASSIVE_REMOTE_DATA_CONNECTION_MODE </code>. Use this method only for server to server data transfers. + * This method issues a PASV command to the server, telling it to open a data port to which the active server will connect to conduct data transfers. You + * must call this method before EVERY server to server transfer attempt. The FTPClient will NOT automatically continue to issue PASV commands. You also must + * remember to call {@link #enterLocalActiveMode enterLocalActiveMode() } if you wish to return to the normal data connection mode. * - * @param inetAddress The local IP address of this machine. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - public void setPassiveLocalIPAddress(InetAddress inetAddress) - { - this.__passiveLocalHost = inetAddress; + public boolean enterRemotePassiveMode() throws IOException { + if (pasv() != FTPReply.ENTERING_PASSIVE_MODE) { + return false; + } + + dataConnectionMode = PASSIVE_REMOTE_DATA_CONNECTION_MODE; + _parsePassiveModeReply(_replyLines.get(0)); + + return true; } /** - * Set the local IP address in passive mode. - * Useful when there are multiple network cards. + * Queries the server for supported features. The server may reply with a list of server-supported extensions. For example, a typical client-server + * interaction might be (from RFC 2389): * - * @return The local IP address in passive mode. + * <pre> + C> feat + S> 211-Extensions supported: + S> MLST size*;create;modify*;perm;media-type + S> SIZE + S> COMPRESSION + S> MDTM + S> 211 END + * </pre> + * + * @see <a href="http://www.faqs.org/rfcs/rfc2389.html">http://www.faqs.org/rfcs/rfc2389.html</a> + * @return True if successfully completed, false if not. + * @throws IOException on error + * @since 2.2 */ - public InetAddress getPassiveLocalIPAddress() - { - return this.__passiveLocalHost; + public boolean features() throws IOException { + return FTPReply.isPositiveCompletion(feat()); } /** - * Set the external IP address to report in EPRT/PORT commands in active mode. - * Useful when there are multiple network cards. + * Queries the server for a supported feature, and returns the its value (if any). Caches the parsed response to avoid resending the command repeatedly. * - * @param ipAddress The external IP address of this machine. - * @throws UnknownHostException if the ipAddress cannot be resolved - * @since 3.1 - * @see #getReportHostAddress() + * @param feature the feature to check + * + * @return if the feature is present, returns the feature value or the empty string if the feature exists but has no value. Returns {@code null} if the + * feature is not found or the command failed. Check {@link #getReplyCode()} or {@link #getReplyString()} if so. + * @throws IOException on error + * @since 3.0 */ - public void setReportActiveExternalIPAddress(String ipAddress) throws UnknownHostException - { - this.__reportActiveExternalHost = InetAddress.getByName(ipAddress); - } - - - /** - * Sets the file type to be transferred. This should be one of - * <code> FTP.ASCII_FILE_TYPE </code>, <code> FTP.BINARY_FILE_TYPE</code>, - * etc. The file type only needs to be set when you want to change the - * type. After changing it, the new type stays in effect until you change - * it again. The default file type is <code> FTP.ASCII_FILE_TYPE </code> - * if this method is never called. - * <br> - * The server default is supposed to be ASCII (see RFC 959), however many - * ftp servers default to BINARY. <b>To ensure correct operation with all servers, - * always specify the appropriate file type after connecting to the server.</b> - * <br> - * <p> - * <b>N.B.</b> currently calling any connect method will reset the type to - * FTP.ASCII_FILE_TYPE. - * @param fileType The <code> _FILE_TYPE </code> constant indcating the - * type of file. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean setFileType(int fileType) throws IOException - { - if (FTPReply.isPositiveCompletion(type(fileType))) - { - __fileType = fileType; - __fileFormat = FTP.NON_PRINT_TEXT_FORMAT; - return true; + public String featureValue(final String feature) throws IOException { + final String[] values = featureValues(feature); + if (values != null) { + return values[0]; } - return false; + return null; } - /** - * Sets the file type to be transferred and the format. The type should be - * one of <code> FTP.ASCII_FILE_TYPE </code>, - * <code> FTP.BINARY_FILE_TYPE </code>, etc. The file type only needs to - * be set when you want to change the type. After changing it, the new - * type stays in effect until you change it again. The default file type - * is <code> FTP.ASCII_FILE_TYPE </code> if this method is never called. - * <br> - * The server default is supposed to be ASCII (see RFC 959), however many - * ftp servers default to BINARY. <b>To ensure correct operation with all servers, - * always specify the appropriate file type after connecting to the server.</b> - * <br> - * The format should be one of the FTP class <code> TEXT_FORMAT </code> - * constants, or if the type is <code> FTP.LOCAL_FILE_TYPE </code>, the - * format should be the byte size for that type. The default format - * is <code> FTP.NON_PRINT_TEXT_FORMAT </code> if this method is never - * called. - * <p> - * <b>N.B.</b> currently calling any connect method will reset the type to - * FTP.ASCII_FILE_TYPE and the formatOrByteSize to FTP.NON_PRINT_TEXT_FORMAT. + * Queries the server for a supported feature, and returns its values (if any). Caches the parsed response to avoid resending the command repeatedly. * - * @param fileType The <code> _FILE_TYPE </code> constant indcating the - * type of file. - * @param formatOrByteSize The format of the file (one of the - * <code>_FORMAT</code> constants. In the case of - * <code>LOCAL_FILE_TYPE</code>, the byte size. + * @param feature the feature to check * - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean setFileType(int fileType, int formatOrByteSize) - throws IOException - { - if (FTPReply.isPositiveCompletion(type(fileType, formatOrByteSize))) - { - __fileType = fileType; - __fileFormat = formatOrByteSize; - return true; + * @return if the feature is present, returns the feature values (empty array if none) Returns {@code null} if the feature is not found or the command + * failed. Check {@link #getReplyCode()} or {@link #getReplyString()} if so. + * @throws IOException on error + * @since 3.0 + */ + public String[] featureValues(final String feature) throws IOException { + if (!initFeatureMap()) { + return null; } - return false; + final Set<String> entries = featuresMap.get(feature.toUpperCase(Locale.ENGLISH)); + if (entries != null) { + return entries.toArray(NetConstants.EMPTY_STRING_ARRAY); + } + return null; } - /** - * Sets the file structure. The default structure is - * <code> FTP.FILE_STRUCTURE </code> if this method is never called - * or if a connect method is called. + * Get the client port for active mode. * - * @param structure The structure of the file (one of the FTP class - * <code>_STRUCTURE</code> constants). - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean setFileStructure(int structure) throws IOException - { - if (FTPReply.isPositiveCompletion(stru(structure))) - { - __fileStructure = structure; - return true; + * @return The client port for active mode. + */ + int getActivePort() { + if (activeMinPort > 0 && activeMaxPort >= activeMinPort) { + if (activeMaxPort == activeMinPort) { + return activeMaxPort; + } + // Get a random port between the min and max port range + return random.nextInt(activeMaxPort - activeMinPort + 1) + activeMinPort; } - return false; + // default port + return 0; } - /** - * Sets the transfer mode. The default transfer mode - * <code> FTP.STREAM_TRANSFER_MODE </code> if this method is never called - * or if a connect method is called. + * Tells if automatic server encoding detection is enabled or disabled. * - * @param mode The new transfer mode to use (one of the FTP class - * <code>_TRANSFER_MODE</code> constants). - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean setFileTransferMode(int mode) throws IOException - { - if (FTPReply.isPositiveCompletion(mode(mode))) - { - __fileTransferMode = mode; - return true; + * @return true, if automatic server encoding detection is enabled. + */ + public boolean getAutodetectUTF8() { + return autodetectEncoding; + } + + private InputStream getBufferedInputStream(final InputStream inputStream) { + if (bufferSize > 0) { + return new BufferedInputStream(inputStream, bufferSize); } - return false; + return new BufferedInputStream(inputStream); } + private OutputStream getBufferedOutputStream(final OutputStream outputStream) { + if (bufferSize > 0) { + return new BufferedOutputStream(outputStream, bufferSize); + } + return new BufferedOutputStream(outputStream); + } /** - * Initiate a server to server file transfer. This method tells the - * server to which the client is connected to retrieve a given file from - * the other server. + * Retrieve the current internal buffer size for buffered data streams. * - * @param filename The name of the file to retrieve. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean remoteRetrieve(String filename) throws IOException - { - if (__dataConnectionMode == ACTIVE_REMOTE_DATA_CONNECTION_MODE || - __dataConnectionMode == PASSIVE_REMOTE_DATA_CONNECTION_MODE) { - return FTPReply.isPositivePreliminary(retr(filename)); - } - return false; + * @return The current buffer size. + */ + public int getBufferSize() { + return bufferSize; } - /** - * Initiate a server to server file transfer. This method tells the - * server to which the client is connected to store a file on - * the other server using the given filename. The other server must - * have had a <code> remoteRetrieve </code> issued to it by another - * FTPClient. + * Gets how long to wait for control keep-alive message replies. * - * @param filename The name to call the file that is to be stored. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean remoteStore(String filename) throws IOException - { - if (__dataConnectionMode == ACTIVE_REMOTE_DATA_CONNECTION_MODE || - __dataConnectionMode == PASSIVE_REMOTE_DATA_CONNECTION_MODE) { - return FTPReply.isPositivePreliminary(stor(filename)); - } - return false; + * @deprecated Use {@link #getControlKeepAliveReplyTimeoutDuration()}. + * @return wait time in milliseconds. + * @since 3.0 + */ + @Deprecated + public int getControlKeepAliveReplyTimeout() { + return DurationUtils.toMillisInt(controlKeepAliveReplyTimeout); } - /** - * Initiate a server to server file transfer. This method tells the - * server to which the client is connected to store a file on - * the other server using a unique filename based on the given filename. - * The other server must have had a <code> remoteRetrieve </code> issued - * to it by another FTPClient. + * Gets how long to wait for control keep-alive message replies. * - * @param filename The name on which to base the filename of the file - * that is to be stored. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean remoteStoreUnique(String filename) throws IOException - { - if (__dataConnectionMode == ACTIVE_REMOTE_DATA_CONNECTION_MODE || - __dataConnectionMode == PASSIVE_REMOTE_DATA_CONNECTION_MODE) { - return FTPReply.isPositivePreliminary(stou(filename)); - } - return false; + * @return wait time. + * @since 3.9.0 + */ + public Duration getControlKeepAliveReplyTimeoutDuration() { + return controlKeepAliveReplyTimeout; } - /** - * Initiate a server to server file transfer. This method tells the - * server to which the client is connected to store a file on - * the other server using a unique filename. - * The other server must have had a <code> remoteRetrieve </code> issued - * to it by another FTPClient. Many FTP servers require that a base - * filename be given from which the unique filename can be derived. For - * those servers use the other version of <code> remoteStoreUnique</code> + * Gets the time to wait between sending control connection keepalive messages when processing file upload or download. + * <p> + * See the class Javadoc section "Control channel keep-alive feature" + * </p> * - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean remoteStoreUnique() throws IOException - { - if (__dataConnectionMode == ACTIVE_REMOTE_DATA_CONNECTION_MODE || - __dataConnectionMode == PASSIVE_REMOTE_DATA_CONNECTION_MODE) { - return FTPReply.isPositivePreliminary(stou()); - } - return false; + * @deprecated Use {@link #getControlKeepAliveTimeoutDuration()}. + * @return the number of seconds between keepalive messages. + * @since 3.0 + */ + @Deprecated + public long getControlKeepAliveTimeout() { + return controlKeepAliveTimeout.getSeconds(); } - // For server to server transfers /** - * Initiate a server to server file transfer. This method tells the - * server to which the client is connected to append to a given file on - * the other server. The other server must have had a - * <code> remoteRetrieve </code> issued to it by another FTPClient. - * - * @param filename The name of the file to be appended to, or if the - * file does not exist, the name to call the file being stored. + * Gets the time to wait between sending control connection keepalive messages when processing file upload or download. + * <p> + * See the class Javadoc section "Control channel keep-alive feature" + * </p> * - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean remoteAppend(String filename) throws IOException - { - if (__dataConnectionMode == ACTIVE_REMOTE_DATA_CONNECTION_MODE || - __dataConnectionMode == PASSIVE_REMOTE_DATA_CONNECTION_MODE) { - return FTPReply.isPositivePreliminary(appe(filename)); - } - return false; + * @return the duration between keepalive messages. + * @since 3.9.0 + */ + public Duration getControlKeepAliveTimeoutDuration() { + return controlKeepAliveTimeout; } /** - * There are a few FTPClient methods that do not complete the - * entire sequence of FTP commands to complete a transaction. These - * commands require some action by the programmer after the reception - * of a positive intermediate command. After the programmer's code - * completes its actions, it must call this method to receive - * the completion reply from the server and verify the success of the - * entire transaction. - * <p> - * For example, - * <pre> - * InputStream input; - * OutputStream output; - * input = new FileInputStream("foobaz.txt"); - * output = ftp.storeFileStream("foobar.txt") - * if(!FTPReply.isPositiveIntermediate(ftp.getReplyCode())) { - * input.close(); - * output.close(); - * ftp.logout(); - * ftp.disconnect(); - * System.err.println("File transfer failed."); - * System.exit(1); - * } - * Util.copyStream(input, output); - * input.close(); - * output.close(); - * // Must call completePendingCommand() to finish command. - * if(!ftp.completePendingCommand()) { - * ftp.logout(); - * ftp.disconnect(); - * System.err.println("File transfer failed."); - * System.exit(1); - * } - * </pre> + * Obtain the currently active listener. * - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean completePendingCommand() throws IOException - { - return FTPReply.isPositiveCompletion(getReply()); + * @return the listener, may be {@code null} + * @since 3.0 + */ + public CopyStreamListener getCopyStreamListener() { + return copyStreamListener; } - /** - * Retrieves a named file from the server and writes it to the given - * OutputStream. This method does NOT close the given OutputStream. - * If the current file type is ASCII, line separators in the file are - * converted to the local representation. + * Get the CSL debug array. * <p> - * Note: if you have used {@link #setRestartOffset(long)}, - * the file data will start from the selected offset. - * @param remote The name of the remote file. - * @param local The local OutputStream to which to write the file. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws org.apache.commons.net.io.CopyStreamException - * If an I/O error occurs while actually - * transferring the file. The CopyStreamException allows you to - * determine the number of bytes transferred and the IOException - * causing the error. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean retrieveFile(String remote, OutputStream local) - throws IOException - { - return _retrieveFile(FTPCmd.RETR.getCommand(), remote, local); + * <b>For debug use only</b> + * <p> + * Currently contains: + * <ul> + * <li>successfully acked NOOPs at end of transfer</li> + * <li>unanswered NOOPs at end of transfer</li> + * <li>unanswered NOOPs after fetching additional replies</li> + * <li>Number of IOErrors ignored</li> + * </ul> + * + * @deprecated 3.7 For testing only; may be dropped or changed at any time + * @return the debug array + */ + @Deprecated // only for use in testing + public int[] getCslDebug() { + return cslDebug; } /** - * @param command the command to get - * @param remote the remote file name - * @param local the local file name - * @return true if successful - * @throws IOException on error - * @since 3.1 + * Returns the current data connection mode (one of the <code> _DATA_CONNECTION_MODE </code> constants. + * + * @return The current data connection mode (one of the <code> _DATA_CONNECTION_MODE </code> constants. */ - protected boolean _retrieveFile(String command, String remote, OutputStream local) - throws IOException - { - Socket socket = _openDataConnection_(command, remote); + public int getDataConnectionMode() { + return dataConnectionMode; + } - if (socket == null) { - return false; - } + /** + * Gets the timeout to use when reading from the data connection. This timeout will be set immediately after opening the data connection, provided that the + * value is ≥ 0. + * <p> + * <b>Note:</b> the timeout will also be applied when calling accept() whilst establishing an active local data connection. + * </p> + * + * @return The default timeout used when opening a data connection socket. The value 0 means an infinite timeout. + * @since 3.9.0 + */ + public Duration getDataTimeout() { + return dataTimeout; + } - final InputStream input; - if (__fileType == ASCII_FILE_TYPE) { - input = new FromNetASCIIInputStream(getBufferedInputStream(socket.getInputStream())); - } else { - input = getBufferedInputStream(socket.getInputStream()); - } + // Method for use by unit test code only + FTPFileEntryParser getEntryParser() { + return entryParser; + } - CSL csl = null; - if (__controlKeepAliveTimeout > 0) { - csl = new CSL(this, __controlKeepAliveTimeout, __controlKeepAliveReplyTimeout); + /** + * Get the host address for active mode; allows the local address to be overridden. + * + * @return __activeExternalHost if non-null, else getLocalAddress() + * @see #setActiveExternalIPAddress(String) + */ + InetAddress getHostAddress() { + if (activeExternalHost != null) { + return activeExternalHost; } + // default local address + return getLocalAddress(); + } - // Treat everything else as binary for now - try - { - Util.copyStream(input, local, getBufferSize(), - CopyStreamEvent.UNKNOWN_STREAM_SIZE, __mergeListeners(csl), - false); - } finally { - Util.closeQuietly(input); - Util.closeQuietly(socket); - if (csl != null) { - csl.cleanUp(); // fetch any outstanding keepalive replies + /** + * @param pathname the initial pathname + * @return the adjusted string with "-a" added if necessary + * @since 2.0 + */ + protected String getListArguments(final String pathname) { + if (getListHiddenFiles()) { + if (pathname != null) { + final StringBuilder sb = new StringBuilder(pathname.length() + 3); + sb.append("-a "); + sb.append(pathname); + return sb.toString(); } + return "-a"; } - // Get the transfer response - boolean ok = completePendingCommand(); - return ok; + return pathname; } /** - * Returns an InputStream from which a named file from the server - * can be read. If the current file type is ASCII, the returned - * InputStream will convert line separators in the file to - * the local representation. You must close the InputStream when you - * finish reading from it. The InputStream itself will take care of - * closing the parent data connection socket upon being closed. - * <p> - * <b>To finalize the file transfer you must call - * {@link #completePendingCommand completePendingCommand } and - * check its return value to verify success.</b> - * If this is not done, subsequent commands may behave unexpectedly. - * <p> - * Note: if you have used {@link #setRestartOffset(long)}, - * the file data will start from the selected offset. - * - * @param remote The name of the remote file. - * @return An InputStream from which the remote file can be read. If - * the data connection cannot be opened (e.g., the file does not - * exist), null is returned (in which case you may check the reply - * code to determine the exact reason for failure). - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public InputStream retrieveFileStream(String remote) throws IOException - { - return _retrieveFileStream(FTPCmd.RETR.getCommand(), remote); + * @see #setListHiddenFiles(boolean) + * @return the current state + * @since 2.0 + */ + public boolean getListHiddenFiles() { + return this.listHiddenFiles; } /** - * @param command the command to send - * @param remote the remote file name - * @return the stream from which to read the file - * @throws IOException on error - * @since 3.1 + * Issue the FTP MDTM command (not supported by all servers) to retrieve the last modification time of a file. The modification string should be in the ISO + * 3077 form "yyyyMMDDhhmmss(.xxx)?". The timestamp represented should also be in GMT, but not all FTP servers honor this. + * + * @param pathname The file path to query. + * @return A string representing the last file modification time in <code>yyyyMMDDhhmmss</code> format. + * @throws IOException if an I/O error occurs. + * @since 2.0 */ - protected InputStream _retrieveFileStream(String command, String remote) - throws IOException - { - Socket socket = _openDataConnection_(command, remote); - - if (socket == null) { - return null; - } - - final InputStream input; - if (__fileType == ASCII_FILE_TYPE) { - // We buffer ascii transfers because the buffering has to - // be interposed between FromNetASCIIOutputSream and the underlying - // socket input stream. We don't buffer binary transfers - // because we don't want to impose a buffering policy on the - // programmer if possible. Programmers can decide on their - // own if they want to wrap the SocketInputStream we return - // for file types other than ASCII. - input = new FromNetASCIIInputStream(getBufferedInputStream(socket.getInputStream())); - } else { - input = socket.getInputStream(); + public String getModificationTime(final String pathname) throws IOException { + if (FTPReply.isPositiveCompletion(mdtm(pathname))) { + // skip the return code (e.g. 213) and the space + return getReplyString(0).substring(4); } - return new org.apache.commons.net.io.SocketInputStream(socket, input); + return null; } + /** + * Returns the hostname or IP address (in the form of a string) returned by the server when entering passive mode. If not in passive mode, returns null. + * This method only returns a valid value AFTER a data connection has been opened after a call to {@link #enterLocalPassiveMode enterLocalPassiveMode()}. + * This is because FTPClient sends a PASV command to the server only just before opening a data connection, and not when you call + * {@link #enterLocalPassiveMode enterLocalPassiveMode()}. + * + * @return The passive host name if in passive mode, otherwise null. + */ + public String getPassiveHost() { + return passiveHost; + } /** - * Stores a file on the server using the given name and taking input - * from the given InputStream. This method does NOT close the given - * InputStream. If the current file type is ASCII, line separators in - * the file are transparently converted to the NETASCII format (i.e., - * you should not attempt to create a special InputStream to do this). + * Set the local IP address in passive mode. Useful when there are multiple network cards. * - * @param remote The name to give the remote file. - * @param local The local InputStream from which to read the file. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws org.apache.commons.net.io.CopyStreamException - * If an I/O error occurs while actually - * transferring the file. The CopyStreamException allows you to - * determine the number of bytes transferred and the IOException - * causing the error. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean storeFile(String remote, InputStream local) - throws IOException - { - return __storeFile(FTPCmd.STOR, remote, local); - } - - - /** - * Returns an OutputStream through which data can be written to store - * a file on the server using the given name. If the current file type - * is ASCII, the returned OutputStream will convert line separators in - * the file to the NETASCII format (i.e., you should not attempt to - * create a special OutputStream to do this). You must close the - * OutputStream when you finish writing to it. The OutputStream itself - * will take care of closing the parent data connection socket upon being - * closed. - * <p> - * <b>To finalize the file transfer you must call - * {@link #completePendingCommand completePendingCommand } and - * check its return value to verify success.</b> - * If this is not done, subsequent commands may behave unexpectedly. + * @return The local IP address in passive mode. + */ + public InetAddress getPassiveLocalIPAddress() { + return this.passiveLocalHost; + } + + /** + * If in passive mode, returns the data port of the passive host. This method only returns a valid value AFTER a data connection has been opened after a + * call to {@link #enterLocalPassiveMode enterLocalPassiveMode()}. This is because FTPClient sends a PASV command to the server only just before opening a + * data connection, and not when you call {@link #enterLocalPassiveMode enterLocalPassiveMode()}. * - * @param remote The name to give the remote file. - * @return An OutputStream through which the remote file can be written. If - * the data connection cannot be opened (e.g., the file does not - * exist), null is returned (in which case you may check the reply - * code to determine the exact reason for failure). - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public OutputStream storeFileStream(String remote) throws IOException - { - return __storeFileStream(FTPCmd.STOR, remote); - } - - /** - * Appends to a file on the server with the given name, taking input - * from the given InputStream. This method does NOT close the given - * InputStream. If the current file type is ASCII, line separators in - * the file are transparently converted to the NETASCII format (i.e., - * you should not attempt to create a special InputStream to do this). - * - * @param remote The name of the remote file. - * @param local The local InputStream from which to read the data to - * be appended to the remote file. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws org.apache.commons.net.io.CopyStreamException - * If an I/O error occurs while actually - * transferring the file. The CopyStreamException allows you to - * determine the number of bytes transferred and the IOException - * causing the error. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean appendFile(String remote, InputStream local) - throws IOException - { - return __storeFile(FTPCmd.APPE, remote, local); - } - - /** - * Returns an OutputStream through which data can be written to append - * to a file on the server with the given name. If the current file type - * is ASCII, the returned OutputStream will convert line separators in - * the file to the NETASCII format (i.e., you should not attempt to - * create a special OutputStream to do this). You must close the - * OutputStream when you finish writing to it. The OutputStream itself - * will take care of closing the parent data connection socket upon being - * closed. - * <p> - * <b>To finalize the file transfer you must call - * {@link #completePendingCommand completePendingCommand } and - * check its return value to verify success.</b> - * If this is not done, subsequent commands may behave unexpectedly. + * @return The data port of the passive server. If not in passive mode, undefined. + */ + public int getPassivePort() { + return passivePort; + } + + /** + * Retrieve the value to be used for the data socket SO_RCVBUF option. * - * @param remote The name of the remote file. - * @return An OutputStream through which the remote file can be appended. - * If the data connection cannot be opened (e.g., the file does not - * exist), null is returned (in which case you may check the reply - * code to determine the exact reason for failure). - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public OutputStream appendFileStream(String remote) throws IOException - { - return __storeFileStream(FTPCmd.APPE, remote); - } - - /** - * Stores a file on the server using a unique name derived from the - * given name and taking input - * from the given InputStream. This method does NOT close the given - * InputStream. If the current file type is ASCII, line separators in - * the file are transparently converted to the NETASCII format (i.e., - * you should not attempt to create a special InputStream to do this). - * - * @param remote The name on which to base the unique name given to - * the remote file. - * @param local The local InputStream from which to read the file. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws org.apache.commons.net.io.CopyStreamException - * If an I/O error occurs while actually - * transferring the file. The CopyStreamException allows you to - * determine the number of bytes transferred and the IOException - * causing the error. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean storeUniqueFile(String remote, InputStream local) - throws IOException - { - return __storeFile(FTPCmd.STOU, remote, local); - } - - - /** - * Returns an OutputStream through which data can be written to store - * a file on the server using a unique name derived from the given name. - * If the current file type - * is ASCII, the returned OutputStream will convert line separators in - * the file to the NETASCII format (i.e., you should not attempt to - * create a special OutputStream to do this). You must close the - * OutputStream when you finish writing to it. The OutputStream itself - * will take care of closing the parent data connection socket upon being - * closed. - * <p> - * <b>To finalize the file transfer you must call - * {@link #completePendingCommand completePendingCommand } and - * check its return value to verify success.</b> - * If this is not done, subsequent commands may behave unexpectedly. + * @return The current buffer size. + * @since 3.3 + */ + public int getReceiveDataSocketBufferSize() { + return receiveDataSocketBufferSize; + } + + /** + * Get the reported host address for active mode EPRT/PORT commands; allows override of {@link #getHostAddress()}. * - * @param remote The name on which to base the unique name given to - * the remote file. - * @return An OutputStream through which the remote file can be written. If - * the data connection cannot be opened (e.g., the file does not - * exist), null is returned (in which case you may check the reply - * code to determine the exact reason for failure). - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public OutputStream storeUniqueFileStream(String remote) throws IOException - { - return __storeFileStream(FTPCmd.STOU, remote); - } - - /** - * Stores a file on the server using a unique name assigned by the - * server and taking input from the given InputStream. This method does - * NOT close the given - * InputStream. If the current file type is ASCII, line separators in - * the file are transparently converted to the NETASCII format (i.e., - * you should not attempt to create a special InputStream to do this). - * - * @param local The local InputStream from which to read the file. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws org.apache.commons.net.io.CopyStreamException - * If an I/O error occurs while actually - * transferring the file. The CopyStreamException allows you to - * determine the number of bytes transferred and the IOException - * causing the error. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean storeUniqueFile(InputStream local) throws IOException - { - return __storeFile(FTPCmd.STOU, null, local); - } - - /** - * Returns an OutputStream through which data can be written to store - * a file on the server using a unique name assigned by the server. - * If the current file type - * is ASCII, the returned OutputStream will convert line separators in - * the file to the NETASCII format (i.e., you should not attempt to - * create a special OutputStream to do this). You must close the - * OutputStream when you finish writing to it. The OutputStream itself - * will take care of closing the parent data connection socket upon being - * closed. - * <p> - * <b>To finalize the file transfer you must call - * {@link #completePendingCommand completePendingCommand } and - * check its return value to verify success.</b> - * If this is not done, subsequent commands may behave unexpectedly. + * Useful for FTP Client behind Firewall NAT. * - * @return An OutputStream through which the remote file can be written. If - * the data connection cannot be opened (e.g., the file does not - * exist), null is returned (in which case you may check the reply - * code to determine the exact reason for failure). - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. + * @return __reportActiveExternalHost if non-null, else getHostAddress(); */ - public OutputStream storeUniqueFileStream() throws IOException - { - return __storeFileStream(FTPCmd.STOU, null); + InetAddress getReportHostAddress() { + if (reportActiveExternalHost != null) { + return reportActiveExternalHost; + } + return getHostAddress(); } /** - * Reserve a number of bytes on the server for the next file transfer. + * Fetches the restart offset. * - * @param bytes The number of bytes which the server should allocate. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean allocate(int bytes) throws IOException - { - return FTPReply.isPositiveCompletion(allo(bytes)); + * @return offset The offset into the remote file at which to start the next file transfer. + */ + public long getRestartOffset() { + return restartOffset; } /** - * Query the server for supported features. The server may reply with a list of server-supported exensions. - * For example, a typical client-server interaction might be (from RFC 2389): - * <pre> - C> feat - S> 211-Extensions supported: - S> MLST size*;create;modify*;perm;media-type - S> SIZE - S> COMPRESSION - S> MDTM - S> 211 END - * </pre> - * @see <a href="http://www.faqs.org/rfcs/rfc2389.html">http://www.faqs.org/rfcs/rfc2389.html</a> - * @return True if successfully completed, false if not. - * @throws IOException on error - * @since 2.2 + * Retrieve the value to be used for the data socket SO_SNDBUF option. + * + * @return The current buffer size. + * @since 3.3 */ - public boolean features() throws IOException { - return FTPReply.isPositiveCompletion(feat()); + public int getSendDataSocketBufferSize() { + return sendDataSocketBufferSize; } /** - * Query the server for a supported feature, and returns its values (if any). - * Caches the parsed response to avoid resending the command repeatedly. - * @param feature the feature to check + * Issue the FTP SIZE command to the server for a given pathname. This should produce the size of the file. * - * @return if the feature is present, returns the feature values (empty array if none) - * Returns {@code null} if the feature is not found or the command failed. - * Check {@link #getReplyCode()} or {@link #getReplyString()} if so. - * @throws IOException on error - * @since 3.0 + * @param pathname the file name + * + * @return The size information returned by the server; {@code null} if there was an error + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @since 3.7 */ - public String[] featureValues(String feature) throws IOException { - if (!initFeatureMap()) { - return null; + public String getSize(final String pathname) throws IOException { + if (FTPReply.isPositiveCompletion(size(pathname))) { + return getReplyString(0).substring(4); // skip the return code (e.g. 213) and the space } - Set<String> entries = __featuresMap.get(feature.toUpperCase(Locale.ENGLISH)); - if (entries != null) { - return entries.toArray(new String[entries.size()]); + return null; + } + + /** + * Issue the FTP STAT command to the server. + * + * @return The status information returned by the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public String getStatus() throws IOException { + if (FTPReply.isPositiveCompletion(stat())) { + return getReplyString(); } return null; } /** - * Query the server for a supported feature, and returns the its value (if any). - * Caches the parsed response to avoid resending the command repeatedly. - * @param feature the feature to check + * Issue the FTP STAT command to the server for a given pathname. This should produce a listing of the file or directory. * - * @return if the feature is present, returns the feature value or the empty string - * if the feature exists but has no value. - * Returns {@code null} if the feature is not found or the command failed. - * Check {@link #getReplyCode()} or {@link #getReplyString()} if so. - * @throws IOException on error - * @since 3.0 + * @param pathname the file name + * + * @return The status information returned by the server. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - public String featureValue(String feature) throws IOException { - String [] values = featureValues(feature); - if (values != null) { - return values[0]; + public String getStatus(final String pathname) throws IOException { + if (FTPReply.isPositiveCompletion(stat(pathname))) { + return getReplyString(); } return null; } /** - * Query the server for a supported feature. - * Caches the parsed response to avoid resending the command repeatedly. + * @deprecated use {@link #getSystemType()} instead + * @return the name + * @throws IOException on error + */ + @Deprecated + public String getSystemName() throws IOException { + if (systemName == null && FTPReply.isPositiveCompletion(syst())) { + systemName = _replyLines.get(_replyLines.size() - 1).substring(4); + } + return systemName; + } + + /** + * Fetches the system type from the server and returns the string. This value is cached for the duration of the connection after the first call to this + * method. In other words, only the first time that you invoke this method will it issue a SYST command to the FTP server. FTPClient will remember the value + * and return the cached value until a call to disconnect. + * <p> + * If the SYST command fails, and the system property {@link #FTP_SYSTEM_TYPE_DEFAULT} is defined, then this is used instead. + * + * @return The system type obtained from the server. Never null. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server (and the + * default system type property is not defined) + * @since 2.2 + */ + public String getSystemType() throws IOException { + // if (syst() == FTPReply.NAME_SYSTEM_TYPE) + // Technically, we should expect a NAME_SYSTEM_TYPE response, but + // in practice FTP servers deviate, so we soften the condition to + // a positive completion. + if (systemName == null) { + if (FTPReply.isPositiveCompletion(syst())) { + // Assume that response is not empty here (cannot be null) + systemName = _replyLines.get(_replyLines.size() - 1).substring(4); + } else { + // Check if the user has provided a default for when the SYST command fails + final String systDefault = System.getProperty(FTP_SYSTEM_TYPE_DEFAULT); + if (systDefault == null) { + throw new IOException("Unable to determine system type - response: " + getReplyString()); + } + systemName = systDefault; + } + } + return systemName; + } + + /** + * Queries the server for a supported feature. Caches the parsed response to avoid resending the command repeatedly. + * + * @param feature the name of the feature; it is converted to upper case. + * @return {@code true} if the feature is present, {@code false} if the feature is not present or the {@link #feat()} command failed. Check + * {@link #getReplyCode()} or {@link #getReplyString()} if it is necessary to distinguish these cases. + * + * @throws IOException on error + * @since 3.8.0 + */ + public boolean hasFeature(final FTPCmd feature) throws IOException { + return hasFeature(feature.name()); + } + + /** + * Queries the server for a supported feature. Caches the parsed response to avoid resending the command repeatedly. * * @param feature the name of the feature; it is converted to upper case. - * @return {@code true} if the feature is present, {@code false} if the feature is not present - * or the {@link #feat()} command failed. Check {@link #getReplyCode()} or {@link #getReplyString()} - * if it is necessary to distinguish these cases. + * @return {@code true} if the feature is present, {@code false} if the feature is not present or the {@link #feat()} command failed. Check + * {@link #getReplyCode()} or {@link #getReplyString()} if it is necessary to distinguish these cases. * * @throws IOException on error * @since 3.0 */ - public boolean hasFeature(String feature) throws IOException { + public boolean hasFeature(final String feature) throws IOException { if (!initFeatureMap()) { return false; } - return __featuresMap.containsKey(feature.toUpperCase(Locale.ENGLISH)); + return featuresMap.containsKey(feature.toUpperCase(Locale.ENGLISH)); } /** - * Query the server for a supported feature with particular value, - * for example "AUTH SSL" or "AUTH TLS". - * Caches the parsed response to avoid resending the command repeatedly. + * Queries the server for a supported feature with particular value, for example "AUTH SSL" or "AUTH TLS". Caches the parsed response to avoid resending the + * command repeatedly. * * @param feature the name of the feature; it is converted to upper case. - * @param value the value to find. + * @param value the value to find. * - * @return {@code true} if the feature is present, {@code false} if the feature is not present - * or the {@link #feat()} command failed. Check {@link #getReplyCode()} or {@link #getReplyString()} - * if it is necessary to distinguish these cases. + * @return {@code true} if the feature is present, {@code false} if the feature is not present or the {@link #feat()} command failed. Check + * {@link #getReplyCode()} or {@link #getReplyString()} if it is necessary to distinguish these cases. * * @throws IOException on error * @since 3.0 */ - public boolean hasFeature(String feature, String value) throws IOException { + public boolean hasFeature(final String feature, final String value) throws IOException { if (!initFeatureMap()) { return false; } - Set<String> entries = __featuresMap.get(feature.toUpperCase(Locale.ENGLISH)); + final Set<String> entries = featuresMap.get(feature.toUpperCase(Locale.ENGLISH)); if (entries != null) { return entries.contains(value); } return false; } + private void initDefaults() { + dataConnectionMode = ACTIVE_LOCAL_DATA_CONNECTION_MODE; + passiveHost = null; + passivePort = -1; + activeExternalHost = null; + reportActiveExternalHost = null; + activeMinPort = 0; + activeMaxPort = 0; + fileType = FTP.ASCII_FILE_TYPE; + fileStructure = FTP.FILE_STRUCTURE; + fileFormat = FTP.NON_PRINT_TEXT_FORMAT; + fileTransferMode = FTP.STREAM_TRANSFER_MODE; + restartOffset = 0; + systemName = null; + entryParser = null; + entryParserKey = ""; + featuresMap = null; + } + /* * Create the feature map if not already created. */ private boolean initFeatureMap() throws IOException { - if (__featuresMap == null) { + if (featuresMap == null) { // Don't create map here, because next line may throw exception final int replyCode = feat(); if (replyCode == FTPReply.NOT_LOGGED_IN) { // 503 return false; // NET-518; don't create empy map } - boolean success = FTPReply.isPositiveCompletion(replyCode); + final boolean success = FTPReply.isPositiveCompletion(replyCode); // we init the map here, so we don't keep trying if we know the command will fail - __featuresMap = new HashMap<String, Set<String>>(); + featuresMap = new HashMap<>(); if (!success) { return false; } - for (String l : getReplyStrings()) { - if (l.startsWith(" ")) { // it's a FEAT entry + for (final String line : _replyLines) { + if (line.startsWith(" ")) { // it's a FEAT entry String key; - String value=""; - int varsep = l.indexOf(' ', 1); + String value = ""; + final int varsep = line.indexOf(' ', 1); if (varsep > 0) { - key = l.substring(1, varsep); - value = l.substring(varsep+1); + key = line.substring(1, varsep); + value = line.substring(varsep + 1); } else { - key = l.substring(1); + key = line.substring(1); } key = key.toUpperCase(Locale.ENGLISH); - Set<String> entries = __featuresMap.get(key); - if (entries == null) { - entries = new HashSet<String>(); - __featuresMap.put(key, entries); - } + final Set<String> entries = featuresMap.computeIfAbsent(key, k -> new HashSet<>()); entries.add(value); } } @@ -2425,1556 +1951,1468 @@ implements Configurable } /** - * Reserve space on the server for the next file transfer. - * - * @param bytes The number of bytes which the server should allocate. - * @param recordSize The size of a file record. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean allocate(int bytes, int recordSize) throws IOException - { - return FTPReply.isPositiveCompletion(allo(bytes, recordSize)); - } - - - /** - * Issue a command and wait for the reply. + * Using the default autodetect mechanism, initialize an FTPListParseEngine object containing a raw file information for the current working directory on + * the server This information is obtained through the LIST command. This object is then capable of being iterated to return a sequence of FTPFile objects + * with information filled in by the <code> FTPFileEntryParser </code> used. * <p> - * Should only be used with commands that return replies on the - * command channel - do not use for LIST, NLST, MLSD etc. - * - * @param command The command to invoke - * @param params The parameters string, may be {@code null} - * @return True if successfully completed, false if not, in which case - * call {@link #getReplyCode()} or {@link #getReplyString()} - * to get the reason. - * - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @since 3.0 + * This method differs from using the listFiles() methods in that expensive FTPFile objects are not created until needed which may be an advantage on large + * lists. + * + * @return A FTPListParseEngine object that holds the raw information and is capable of providing parsed FTPFile objects, one for each file containing + * information contained in the given path in the format determined by the <code> parser </code> parameter. Null will be returned if a data + * connection cannot be opened. If the current working directory contains no files, an empty array will be the return. + * + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client + * being idle or some other reason causing the server to send FTP reply code 421. + * This exception may be caught either as an IOException or independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving + * a reply from the server. + * @throws org.apache.commons.net.ftp.parser.ParserInitializationException Thrown if the autodetect mechanism cannot resolve the type of system we are + * connected with. + * @see FTPListParseEngine */ - public boolean doCommand(String command, String params) throws IOException - { - return FTPReply.isPositiveCompletion(sendCommand(command, params)); + public FTPListParseEngine initiateListParsing() throws IOException { + return initiateListParsing((String) null); } /** - * Issue a command and wait for the reply, returning it as an array of strings. - * <p> - * Should only be used with commands that return replies on the - * command channel - do not use for LIST, NLST, MLSD etc. - * - * @param command The command to invoke - * @param params The parameters string, may be {@code null} - * @return The array of replies, or {@code null} if the command failed, in which case - * call {@link #getReplyCode()} or {@link #getReplyString()} - * to get the reason. + * private method through which all listFiles() and initiateListParsing methods pass once a parser is determined. * - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @since 3.0 + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @see FTPListParseEngine */ - public String[] doCommandAsStrings(String command, String params) throws IOException - { - boolean success = FTPReply.isPositiveCompletion(sendCommand(command, params)); - if (success){ - return getReplyStrings(); - } else { - return null; + private FTPListParseEngine initiateListParsing(final FTPFileEntryParser parser, final String pathname) throws IOException { + final Socket socket = _openDataConnection_(FTPCmd.LIST, getListArguments(pathname)); + + final FTPListParseEngine engine = new FTPListParseEngine(parser, configuration); + if (socket == null) { + return engine; + } + + try { + engine.readServerList(socket.getInputStream(), getControlEncoding()); + } finally { + Util.closeQuietly(socket); } + + completePendingCommand(); + return engine; } /** - * Get file details using the MLST command + * Using the default autodetect mechanism, initialize an FTPListParseEngine object containing a raw file information for the supplied directory. This + * information is obtained through the LIST command. This object is then capable of being iterated to return a sequence of FTPFile objects with information + * filled in by the <code> FTPFileEntryParser </code> used. + * <p> + * The server may or may not expand glob expressions. You should avoid using glob expressions because the return format for glob listings differs from + * server to server and will likely cause this method to fail. + * <p> + * This method differs from using the listFiles() methods in that expensive FTPFile objects are not created until needed which may be an advantage on large + * lists. * - * @param pathname the file or directory to list, may be {@code null} - * @return the file details, may be {@code null} - * @throws IOException on error - * @since 3.0 + * <pre> + * FTPClient f = FTPClient(); + * f.connect(server); + * f.login(username, password); + * FTPListParseEngine engine = f.initiateListParsing(directory); + * + * while (engine.hasNext()) { + * FTPFile[] files = engine.getNext(25); // "page size" you want + * // do whatever you want with these files, display them, etc. + * // expensive FTPFile objects not created until needed. + * } + * </pre> + * + * @param pathname the starting directory + * + * @return A FTPListParseEngine object that holds the raw information and is capable of providing parsed FTPFile objects, one for each file containing + * information contained in the given path in the format determined by the <code> parser </code> parameter. Null will be returned if a data + * connection cannot be opened. If the current working directory contains no files, an empty array will be the return. + * + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client + * being idle or some other reason causing the server to send FTP reply code 421. + * This exception may be caught either as an IOException or independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving + * a reply from the server. + * @throws org.apache.commons.net.ftp.parser.ParserInitializationException Thrown if the autodetect mechanism cannot resolve the type of system we are + * connected with. + * @see FTPListParseEngine */ - public FTPFile mlistFile(String pathname) throws IOException - { - boolean success = FTPReply.isPositiveCompletion(sendCommand(FTPCmd.MLST, pathname)); - if (success){ - String reply = getReplyStrings()[1]; - /* check the response makes sense. - * Must have space before fact(s) and between fact(s) and filename - * Fact(s) can be absent, so at least 3 chars are needed. - */ - if (reply.length() < 3 || reply.charAt(0) != ' ') { - throw new MalformedServerReplyException("Invalid server reply (MLST): '" + reply + "'"); - } - String entry = reply.substring(1); // skip leading space for parser - return MLSxEntryParser.parseEntry(entry); - } else { - return null; - } + public FTPListParseEngine initiateListParsing(final String pathname) throws IOException { + return initiateListParsing((String) null, pathname); } /** - * Generate a directory listing for the current directory using the MLSD command. - * - * @return the array of file entries - * @throws IOException on error - * @since 3.0 + * Using the supplied parser key, initialize an FTPListParseEngine object containing a raw file information for the supplied directory. This information is + * obtained through the LIST command. This object is then capable of being iterated to return a sequence of FTPFile objects with information filled in by + * the <code> FTPFileEntryParser </code> used. + * <p> + * The server may or may not expand glob expressions. You should avoid using glob expressions because the return format for glob listings differs from + * server to server and will likely cause this method to fail. + * <p> + * This method differs from using the listFiles() methods in that expensive FTPFile objects are not created until needed which may be an advantage on large + * lists. + * + * @param parserKey A string representing a designated code or fully-qualified class name of an <code> FTPFileEntryParser </code> that should be used to + * parse each server file listing. May be {@code null}, in which case the code checks first the system property {@link #FTP_SYSTEM_TYPE}, + * and if that is not defined the SYST command is used to provide the value. To allow for arbitrary system types, the return from the SYST + * command is used to look up an alias for the type in the {@link #SYSTEM_TYPE_PROPERTIES} properties file if it is available. + * @param pathname the starting directory + * + * @return A FTPListParseEngine object that holds the raw information and is capable of providing parsed FTPFile objects, one for each file containing + * information contained in the given path in the format determined by the <code> parser </code> parameter. Null will be returned if a data + * connection cannot be opened. If the current working directory contains no files, an empty array will be the return. + * + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client + * being idle or some other reason causing the server to send FTP reply code 421. + * This exception may be caught either as an IOException or independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving + * a reply from the server. + * @throws org.apache.commons.net.ftp.parser.ParserInitializationException Thrown if the parserKey parameter cannot be resolved by the selected parser + * factory. In the DefaultFTPEntryParserFactory, this will happen when parserKey is + * neither the fully qualified class name of a class implementing the interface + * org.apache.commons.net.ftp.FTPFileEntryParser nor a string containing one of the + * recognized keys mapping to such a parser or if class loader security issues + * prevent its being loaded. + * @see FTPListParseEngine */ - public FTPFile[] mlistDir() throws IOException - { - return mlistDir(null); + public FTPListParseEngine initiateListParsing(final String parserKey, final String pathname) throws IOException { + createParser(parserKey); // create and cache parser + return initiateListParsing(entryParser, pathname); } /** - * Generate a directory listing using the MLSD command. + * Initiate list parsing for MLSD listings in the current working directory. * - * @param pathname the directory name, may be {@code null} - * @return the array of file entries + * @return the engine * @throws IOException on error - * @since 3.0 */ - public FTPFile[] mlistDir(String pathname) throws IOException - { - FTPListParseEngine engine = initiateMListParsing( pathname); - return engine.getFiles(); + public FTPListParseEngine initiateMListParsing() throws IOException { + return initiateMListParsing(null); } /** - * Generate a directory listing using the MLSD command. + * Initiate list parsing for MLSD listings. * - * @param pathname the directory name, may be {@code null} - * @param filter the filter to apply to the responses - * @return the array of file entries + * @param pathname the path from where to MLSD. + * @return the engine. * @throws IOException on error - * @since 3.0 */ - public FTPFile[] mlistDir(String pathname, FTPFileFilter filter) throws IOException - { - FTPListParseEngine engine = initiateMListParsing( pathname); - return engine.getFiles(filter); + public FTPListParseEngine initiateMListParsing(final String pathname) throws IOException { + final Socket socket = _openDataConnection_(FTPCmd.MLSD, pathname); + final FTPListParseEngine engine = new FTPListParseEngine(MLSxEntryParser.getInstance(), configuration); + if (socket == null) { + return engine; + } + + try { + engine.readServerList(socket.getInputStream(), getControlEncoding()); + } finally { + Util.closeQuietly(socket); + completePendingCommand(); + } + return engine; } /** - * Restart a <code>STREAM_TRANSFER_MODE</code> file transfer starting - * from the given offset. This will only work on FTP servers supporting - * the REST comand for the stream transfer mode. However, most FTP - * servers support this. Any subsequent file transfer will start - * reading or writing the remote file from the indicated offset. + * Returns, whether the IP address from the server's response should be used. Until 3.9.0, this has always been the case. Beginning with 3.9.0, that IP + * address will be silently ignored, and replaced with the remote IP address of the control connection, unless this configuration option is given, which + * restores the old behavior. To enable this by default, use the system property {@link FTPClient#FTP_IP_ADDRESS_FROM_PASV_RESPONSE}. * - * @param offset The offset into the remote file at which to start the - * next file transfer. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @since 3.1 (changed from private to protected) + * @return True, if the IP address from the server's response will be used (pre-3.9 compatible behavior), or false (ignore that IP address). + * + * @see FTPClient#FTP_IP_ADDRESS_FROM_PASV_RESPONSE + * @see #setIpAddressFromPasvResponse(boolean) + * @since 3.9.0 */ - protected boolean restart(long offset) throws IOException - { - __restartOffset = 0; - return FTPReply.isPositiveIntermediate(rest(Long.toString(offset))); + public boolean isIpAddressFromPasvResponse() { + return ipAddressFromPasvResponse; } /** - * Sets the restart offset for file transfers. - * <p> - * The restart command is not sent to the server immediately. - * It is sent when a data connection is created as part of a - * subsequent command. - * The restart marker is reset to zero after use. - * </p> - * <p> - * <b>Note: This method should only be invoked immediately prior to - * the transfer to which it applies.</b> + * Return whether or not verification of the remote host participating in data connections is enabled. The default behavior is for verification to be + * enabled. * - * @param offset The offset into the remote file at which to start the - * next file transfer. This must be a value greater than or - * equal to zero. + * @return True if verification is enabled, false if not. */ - public void setRestartOffset(long offset) - { - if (offset >= 0) { - __restartOffset = offset; - } + public boolean isRemoteVerificationEnabled() { + return remoteVerificationEnabled; } /** - * Fetches the restart offset. + * Whether should attempt to use EPSV with IPv4. Default (if not set) is <code>false</code> * - * @return offset The offset into the remote file at which to start the - * next file transfer. + * @return true if should attempt EPSV + * @since 2.2 */ - public long getRestartOffset() - { - return __restartOffset; + public boolean isUseEPSVwithIPv4() { + return useEPSVwithIPv4; } - + /** + * Using the default system autodetect mechanism, obtain a list of directories contained in the current working directory. + * <p> + * This information is obtained through the LIST command. The contents of the returned array is determined by the<code> FTPFileEntryParser </code> used. + * <p> + * N.B. the LIST command does not generally return very precise timestamps. For recent files, the response usually contains hours and minutes (not seconds). + * For older files, the output may only contain a date. If the server supports it, the MLSD command returns timestamps with a precision of seconds, and may + * include milliseconds. See {@link #mlistDir()} + * + * @return The list of directories contained in the current directory in the format determined by the autodetection mechanism. + * + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client + * being idle or some other reason causing the server to send FTP reply code 421. + * This exception may be caught either as an IOException or independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving + * a reply from the server. + * @throws org.apache.commons.net.ftp.parser.ParserInitializationException Thrown if the parserKey parameter cannot be resolved by the selected parser + * factory. In the DefaultFTPEntryParserFactory, this will happen when parserKey is + * neither the fully qualified class name of a class implementing the interface + * org.apache.commons.net.ftp.FTPFileEntryParser nor a string containing one of the + * recognized keys mapping to such a parser or if class loader security issues + * prevent its being loaded. + * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.FTPFileEntryParser + * @since 3.0 + */ + public FTPFile[] listDirectories() throws IOException { + return listDirectories((String) null); + } /** - * Renames a remote file. + * Using the default system autodetect mechanism, obtain a list of directories contained in the specified directory. + * <p> + * This information is obtained through the LIST command. The contents of the returned array is determined by the<code> FTPFileEntryParser </code> used. + * <p> + * N.B. the LIST command does not generally return very precise timestamps. For recent files, the response usually contains hours and minutes (not seconds). + * For older files, the output may only contain a date. If the server supports it, the MLSD command returns timestamps with a precision of seconds, and may + * include milliseconds. See {@link #mlistDir()} * - * @param from The name of the remote file to rename. - * @param to The new name of the remote file. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean rename(String from, String to) throws IOException - { - if (!FTPReply.isPositiveIntermediate(rnfr(from))) { - return false; - } - - return FTPReply.isPositiveCompletion(rnto(to)); + * @param parent the starting directory + * + * @return The list of directories contained in the specified directory in the format determined by the autodetection mechanism. + * + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client + * being idle or some other reason causing the server to send FTP reply code 421. + * This exception may be caught either as an IOException or independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving + * a reply from the server. + * @throws org.apache.commons.net.ftp.parser.ParserInitializationException Thrown if the parserKey parameter cannot be resolved by the selected parser + * factory. In the DefaultFTPEntryParserFactory, this will happen when parserKey is + * neither the fully qualified class name of a class implementing the interface + * org.apache.commons.net.ftp.FTPFileEntryParser nor a string containing one of the + * recognized keys mapping to such a parser or if class loader security issues + * prevent its being loaded. + * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.FTPFileEntryParser + * @since 3.0 + */ + public FTPFile[] listDirectories(final String parent) throws IOException { + return listFiles(parent, FTPFileFilters.DIRECTORIES); } + /** + * Using the default system autodetect mechanism, obtain a list of file information for the current working directory. + * <p> + * This information is obtained through the LIST command. The contents of the returned array is determined by the<code> FTPFileEntryParser </code> used. + * <p> + * N.B. the LIST command does not generally return very precise timestamps. For recent files, the response usually contains hours and minutes (not seconds). + * For older files, the output may only contain a date. If the server supports it, the MLSD command returns timestamps with a precision of seconds, and may + * include milliseconds. See {@link #mlistDir()} + * + * @return The list of file information contained in the current directory in the format determined by the autodetection mechanism. + * <p> + * <b> NOTE:</b> This array may contain null members if any of the individual file listings failed to parse. The caller should check each entry for + * null before referencing it. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client + * being idle or some other reason causing the server to send FTP reply code 421. + * This exception may be caught either as an IOException or independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving + * a reply from the server. + * @throws org.apache.commons.net.ftp.parser.ParserInitializationException Thrown if the parserKey parameter cannot be resolved by the selected parser + * factory. In the DefaultFTPEntryParserFactory, this will happen when parserKey is + * neither the fully qualified class name of a class implementing the interface + * org.apache.commons.net.ftp.FTPFileEntryParser nor a string containing one of the + * recognized keys mapping to such a parser or if class loader security issues + * prevent its being loaded. + * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.FTPFileEntryParser + */ + public FTPFile[] listFiles() throws IOException { + return listFiles((String) null); + } /** - * Abort a transfer in progress. - * - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean abort() throws IOException - { - return FTPReply.isPositiveCompletion(abor()); + * Using the default system autodetect mechanism, obtain a list of file information for the current working directory or for just a single file. + * <p> + * This information is obtained through the LIST command. The contents of the returned array is determined by the<code> FTPFileEntryParser </code> used. + * <p> + * N.B. the LIST command does not generally return very precise timestamps. For recent files, the response usually contains hours and minutes (not seconds). + * For older files, the output may only contain a date. If the server supports it, the MLSD command returns timestamps with a precision of seconds, and may + * include milliseconds. See {@link #mlistDir()} + * + * @param pathname The file or directory to list. Since the server may or may not expand glob expressions, using them here is not recommended and may well + * cause this method to fail. Also, some servers treat a leading '-' as being an option. To avoid this interpretation, use an absolute + * pathname or prefix the pathname with ./ (unix style servers). Some servers may support "--" as meaning end of options, in which case "-- + * -xyz" should work. + * + * @return The list of file information contained in the given path in the format determined by the autodetection mechanism + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client + * being idle or some other reason causing the server to send FTP reply code 421. + * This exception may be caught either as an IOException or independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving + * a reply from the server. + * @throws org.apache.commons.net.ftp.parser.ParserInitializationException Thrown if the parserKey parameter cannot be resolved by the selected parser + * factory. In the DefaultFTPEntryParserFactory, this will happen when parserKey is + * neither the fully qualified class name of a class implementing the interface + * org.apache.commons.net.ftp.FTPFileEntryParser nor a string containing one of the + * recognized keys mapping to such a parser or if class loader security issues + * prevent its being loaded. + * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.FTPFileEntryParser + */ + public FTPFile[] listFiles(final String pathname) throws IOException { + return initiateListParsing((String) null, pathname).getFiles(); } /** - * Deletes a file on the FTP server. + * Version of {@link #listFiles(String)} which allows a filter to be provided. For example: <code>listFiles("site", FTPFileFilters.DIRECTORY);</code> * - * @param pathname The pathname of the file to be deleted. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean deleteFile(String pathname) throws IOException - { - return FTPReply.isPositiveCompletion(dele(pathname)); + * @param pathname the initial path, may be null + * @param filter the filter, non-null + * @return the list of FTPFile entries. + * @throws IOException on error + * @since 2.2 + */ + public FTPFile[] listFiles(final String pathname, final FTPFileFilter filter) throws IOException { + return initiateListParsing((String) null, pathname).getFiles(filter); } - /** - * Removes a directory on the FTP server (if empty). + * Fetches the system help information from the server and returns the full string. * - * @param pathname The pathname of the directory to remove. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean removeDirectory(String pathname) throws IOException - { - return FTPReply.isPositiveCompletion(rmd(pathname)); + * @return The system help string obtained from the server. null if the information could not be obtained. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public String listHelp() throws IOException { + return FTPReply.isPositiveCompletion(help()) ? getReplyString() : null; } - /** - * Creates a new subdirectory on the FTP server in the current directory - * (if a relative pathname is given) or where specified (if an absolute - * pathname is given). + * Fetches the help information for a given command from the server and returns the full string. * - * @param pathname The pathname of the directory to create. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean makeDirectory(String pathname) throws IOException - { - return FTPReply.isPositiveCompletion(mkd(pathname)); + * @param command The command on which to ask for help. + * @return The command help string obtained from the server. null if the information could not be obtained. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public String listHelp(final String command) throws IOException { + return FTPReply.isPositiveCompletion(help(command)) ? getReplyString() : null; } - /** - * Returns the pathname of the current working directory. + * Obtain a list of file names in the current working directory This information is obtained through the NLST command. If the current directory contains no + * files, a zero length array is returned only if the FTP server returned a positive completion code, otherwise, null is returned (the FTP server returned a + * 550 error No files found.). If the directory is not empty, an array of file names in the directory is returned. * - * @return The pathname of the current working directory. If it cannot - * be obtained, returns null. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public String printWorkingDirectory() throws IOException - { - if (pwd() != FTPReply.PATHNAME_CREATED) { - return null; - } - - return __parsePathname(_replyLines.get( _replyLines.size() - 1)); + * @return The list of file names contained in the current working directory. null if the list could not be obtained. If there are no file names in the + * directory, a zero-length array is returned. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public String[] listNames() throws IOException { + return listNames(null); } - /** - * Send a site specific command. - * @param arguments The site specific command and arguments. - * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean sendSiteCommand(String arguments) throws IOException - { - return FTPReply.isPositiveCompletion(site(arguments)); - } + * Obtain a list of file names in a directory (or just the name of a given file, which is not particularly useful). This information is obtained through the + * NLST command. If the given pathname is a directory and contains no files, a zero length array is returned only if the FTP server returned a positive + * completion code, otherwise null is returned (the FTP server returned a 550 error No files found.). If the directory is not empty, an array of file names + * in the directory is returned. If the pathname corresponds to a file, only that file will be listed. The server may or may not expand glob expressions. + * + * @param pathname The file or directory to list. Warning: the server may treat a leading '-' as an option introducer. If so, try using an absolute path, or + * prefix the path with ./ (unix style servers). Some servers may support "--" as meaning end of options, in which case "-- -xyz" should + * work. + * @return The list of file names contained in the given path. null if the list could not be obtained. If there are no file names in the directory, a + * zero-length array is returned. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public String[] listNames(final String pathname) throws IOException { + final ArrayList<String> results = new ArrayList<>(); + try (final Socket socket = _openDataConnection_(FTPCmd.NLST, getListArguments(pathname))) { + if (socket == null) { + return null; + } - /** - * Fetches the system type from the server and returns the string. - * This value is cached for the duration of the connection after the - * first call to this method. In other words, only the first time - * that you invoke this method will it issue a SYST command to the - * FTP server. FTPClient will remember the value and return the - * cached value until a call to disconnect. - * <p> - * If the SYST command fails, and the system property - * {@link #FTP_SYSTEM_TYPE_DEFAULT} is defined, then this is used instead. - * @return The system type obtained from the server. Never null. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server (and the default - * system type property is not defined) - * @since 2.2 - */ - public String getSystemType() throws IOException - { - //if (syst() == FTPReply.NAME_SYSTEM_TYPE) - // Technically, we should expect a NAME_SYSTEM_TYPE response, but - // in practice FTP servers deviate, so we soften the condition to - // a positive completion. - if (__systemName == null){ - if (FTPReply.isPositiveCompletion(syst())) { - // Assume that response is not empty here (cannot be null) - __systemName = _replyLines.get(_replyLines.size() - 1).substring(4); - } else { - // Check if the user has provided a default for when the SYST command fails - String systDefault = System.getProperty(FTP_SYSTEM_TYPE_DEFAULT); - if (systDefault != null) { - __systemName = systDefault; - } else { - throw new IOException("Unable to determine system type - response: " + getReplyString()); + try (final BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), getControlEncoding()))) { + + String line; + while ((line = reader.readLine()) != null) { + results.add(line); } } } - return __systemName; - } + if (completePendingCommand()) { + return results.toArray(NetConstants.EMPTY_STRING_ARRAY); + } + + return null; + } /** - * Fetches the system help information from the server and returns the - * full string. + * Login to the FTP server using the provided username and password. * - * @return The system help string obtained from the server. null if the - * information could not be obtained. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. + * @param username The username to login under. + * @param password The password to use. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - public String listHelp() throws IOException - { - if (FTPReply.isPositiveCompletion(help())) { - return getReplyString(); - } - return null; - } + public boolean login(final String username, final String password) throws IOException { + user(username); - /** - * Fetches the help information for a given command from the server and - * returns the full string. - * @param command The command on which to ask for help. - * @return The command help string obtained from the server. null if the - * information could not be obtained. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public String listHelp(String command) throws IOException - { - if (FTPReply.isPositiveCompletion(help(command))) { - return getReplyString(); + if (FTPReply.isPositiveCompletion(_replyCode)) { + return true; + } + + // If we get here, we either have an error code, or an intermmediate + // reply requesting password. + if (!FTPReply.isPositiveIntermediate(_replyCode)) { + return false; } - return null; - } + return FTPReply.isPositiveCompletion(pass(password)); + } /** - * Sends a NOOP command to the FTP server. This is useful for preventing - * server timeouts. + * Login to the FTP server using the provided username, password, and account. If no account is required by the server, only the username and password, the + * account information is not used. * + * @param username The username to login under. + * @param password The password to use. + * @param account The account to use. * @return True if successfully completed, false if not. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public boolean sendNoOp() throws IOException - { - return FTPReply.isPositiveCompletion(noop()); - } - + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean login(final String username, final String password, final String account) throws IOException { + user(username); - /** - * Obtain a list of filenames in a directory (or just the name of a given - * file, which is not particularly useful). This information is obtained - * through the NLST command. If the given pathname is a directory and - * contains no files, a zero length array is returned only - * if the FTP server returned a positive completion code, otherwise - * null is returned (the FTP server returned a 550 error No files found.). - * If the directory is not empty, an array of filenames in the directory is - * returned. If the pathname corresponds - * to a file, only that file will be listed. The server may or may not - * expand glob expressions. - * - * @param pathname The file or directory to list. - * Warning: the server may treat a leading '-' as an - * option introducer. If so, try using an absolute path, - * or prefix the path with ./ (unix style servers). - * Some servers may support "--" as meaning end of options, - * in which case "-- -xyz" should work. - * @return The list of filenames contained in the given path. null if - * the list could not be obtained. If there are no filenames in - * the directory, a zero-length array is returned. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public String[] listNames(String pathname) throws IOException - { - Socket socket = _openDataConnection_(FTPCmd.NLST, getListArguments(pathname)); + if (FTPReply.isPositiveCompletion(_replyCode)) { + return true; + } - if (socket == null) { - return null; + // If we get here, we either have an error code, or an intermmediate + // reply requesting password. + if (!FTPReply.isPositiveIntermediate(_replyCode)) { + return false; } - BufferedReader reader = - new BufferedReader(new InputStreamReader(socket.getInputStream(), getControlEncoding())); + pass(password); - ArrayList<String> results = new ArrayList<String>(); - String line; - while ((line = reader.readLine()) != null) { - results.add(line); + if (FTPReply.isPositiveCompletion(_replyCode)) { + return true; } - reader.close(); - socket.close(); - - if (completePendingCommand()) - { - String[] names = new String[ results.size() ]; - return results.toArray(names); + if (!FTPReply.isPositiveIntermediate(_replyCode)) { + return false; } - return null; + return FTPReply.isPositiveCompletion(acct(account)); } - /** - * Obtain a list of filenames in the current working directory - * This information is obtained through the NLST command. If the current - * directory contains no files, a zero length array is returned only - * if the FTP server returned a positive completion code, otherwise, - * null is returned (the FTP server returned a 550 error No files found.). - * If the directory is not empty, an array of filenames in the directory is - * returned. + * Logout of the FTP server by sending the QUIT command. * - * @return The list of filenames contained in the current working - * directory. null if the list could not be obtained. - * If there are no filenames in the directory, a zero-length array - * is returned. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - public String[] listNames() throws IOException - { - return listNames(null); + public boolean logout() throws IOException { + return FTPReply.isPositiveCompletion(quit()); } - - /** - * Using the default system autodetect mechanism, obtain a - * list of file information for the current working directory - * or for just a single file. - * <p> - * This information is obtained through the LIST command. The contents of - * the returned array is determined by the<code> FTPFileEntryParser </code> - * used. - * <p> - * N.B. the LIST command does not generally return very precise timestamps. - * For recent files, the response usually contains hours and minutes (not seconds). - * For older files, the output may only contain a date. - * If the server supports it, the MLSD command returns timestamps with a precision - * of seconds, and may include milliseconds. See {@link #mlistDir()} - * - * @param pathname The file or directory to list. Since the server may - * or may not expand glob expressions, using them here - * is not recommended and may well cause this method to - * fail. - * Also, some servers treat a leading '-' as being an option. - * To avoid this interpretation, use an absolute pathname - * or prefix the pathname with ./ (unix style servers). - * Some servers may support "--" as meaning end of options, - * in which case "-- -xyz" should work. - * - * @return The list of file information contained in the given path in - * the format determined by the autodetection mechanism - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection - * as a result of the client being idle or some other - * reason causing the server to send FTP reply code 421. - * This exception may be caught either as an IOException - * or independently as itself. - * @throws IOException - * If an I/O error occurs while either sending a - * command to the server or receiving a reply - * from the server. - * @throws org.apache.commons.net.ftp.parser.ParserInitializationException - * Thrown if the parserKey parameter cannot be - * resolved by the selected parser factory. - * In the DefaultFTPEntryParserFactory, this will - * happen when parserKey is neither - * the fully qualified class name of a class - * implementing the interface - * org.apache.commons.net.ftp.FTPFileEntryParser - * nor a string containing one of the recognized keys - * mapping to such a parser or if class loader - * security issues prevent its being loaded. - * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory - * @see org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory - * @see org.apache.commons.net.ftp.FTPFileEntryParser + * Creates a new subdirectory on the FTP server in the current directory (if a relative pathname is given) or where specified (if an absolute pathname is + * given). + * + * @param pathname The pathname of the directory to create. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - public FTPFile[] listFiles(String pathname) - throws IOException - { - FTPListParseEngine engine = initiateListParsing((String) null, pathname); - return engine.getFiles(); - + public boolean makeDirectory(final String pathname) throws IOException { + return FTPReply.isPositiveCompletion(mkd(pathname)); } /** - * Using the default system autodetect mechanism, obtain a - * list of file information for the current working directory. - * <p> - * This information is obtained through the LIST command. The contents of - * the returned array is determined by the<code> FTPFileEntryParser </code> - * used. - * <p> - * N.B. the LIST command does not generally return very precise timestamps. - * For recent files, the response usually contains hours and minutes (not seconds). - * For older files, the output may only contain a date. - * If the server supports it, the MLSD command returns timestamps with a precision - * of seconds, and may include milliseconds. See {@link #mlistDir()} - * - * @return The list of file information contained in the current directory - * in the format determined by the autodetection mechanism. - * <p><b> - * NOTE:</b> This array may contain null members if any of the - * individual file listings failed to parse. The caller should - * check each entry for null before referencing it. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection - * as a result of the client being idle or some other - * reason causing the server to send FTP reply code 421. - * This exception may be caught either as an IOException - * or independently as itself. - * @throws IOException - * If an I/O error occurs while either sending a - * command to the server or receiving a reply - * from the server. - * @throws org.apache.commons.net.ftp.parser.ParserInitializationException - * Thrown if the parserKey parameter cannot be - * resolved by the selected parser factory. - * In the DefaultFTPEntryParserFactory, this will - * happen when parserKey is neither - * the fully qualified class name of a class - * implementing the interface - * org.apache.commons.net.ftp.FTPFileEntryParser - * nor a string containing one of the recognized keys - * mapping to such a parser or if class loader - * security issues prevent its being loaded. - * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory - * @see org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory - * @see org.apache.commons.net.ftp.FTPFileEntryParser + * Issue the FTP MDTM command (not supported by all servers) to retrieve the last modification time of a file. The modification string should be in the ISO + * 3077 form "yyyyMMDDhhmmss(.xxx)?". The timestamp represented should also be in GMT, but not all FTP servers honor this. + * + * @param pathname The file path to query. + * @return A Calendar representing the last file modification time, may be {@code null}. The Calendar timestamp will be null if a parse error occurs. + * @throws IOException if an I/O error occurs. + * @since 3.8.0 */ - public FTPFile[] listFiles() - throws IOException - { - return listFiles((String) null); + public Calendar mdtmCalendar(final String pathname) throws IOException { + final String modificationTime = getModificationTime(pathname); + if (modificationTime != null) { + return MLSxEntryParser.parseGMTdateTime(modificationTime); + } + return null; } /** - * Version of {@link #listFiles(String)} which allows a filter to be provided. - * For example: <code>listFiles("site", FTPFileFilters.DIRECTORY);</code> - * @param pathname the initial path, may be null - * @param filter the filter, non-null - * @return the list of FTPFile entries. - * @throws IOException on error - * @since 2.2 + * Issue the FTP MDTM command (not supported by all servers) to retrieve the last modification time of a file. The modification string should be in the ISO + * 3077 form "yyyyMMDDhhmmss(.xxx)?". The timestamp represented should also be in GMT, but not all FTP servers honor this. + * + * @param pathname The file path to query. + * @return A FTPFile representing the last file modification time, may be {@code null}. The FTPFile timestamp will be null if a parse error occurs. + * @throws IOException if an I/O error occurs. + * @since 3.4 */ - public FTPFile[] listFiles(String pathname, FTPFileFilter filter) - throws IOException - { - FTPListParseEngine engine = initiateListParsing((String) null, pathname); - return engine.getFiles(filter); - + public FTPFile mdtmFile(final String pathname) throws IOException { + final String modificationTime = getModificationTime(pathname); + if (modificationTime != null) { + final FTPFile file = new FTPFile(); + file.setName(pathname); + file.setRawListing(modificationTime); + file.setTimestamp(MLSxEntryParser.parseGMTdateTime(modificationTime)); + return file; + } + return null; } /** - * Using the default system autodetect mechanism, obtain a - * list of directories contained in the current working directory. - * <p> - * This information is obtained through the LIST command. The contents of - * the returned array is determined by the<code> FTPFileEntryParser </code> - * used. - * <p> - * N.B. the LIST command does not generally return very precise timestamps. - * For recent files, the response usually contains hours and minutes (not seconds). - * For older files, the output may only contain a date. - * If the server supports it, the MLSD command returns timestamps with a precision - * of seconds, and may include milliseconds. See {@link #mlistDir()} - * - * @return The list of directories contained in the current directory - * in the format determined by the autodetection mechanism. - * - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection - * as a result of the client being idle or some other - * reason causing the server to send FTP reply code 421. - * This exception may be caught either as an IOException - * or independently as itself. - * @throws IOException - * If an I/O error occurs while either sending a - * command to the server or receiving a reply - * from the server. - * @throws org.apache.commons.net.ftp.parser.ParserInitializationException - * Thrown if the parserKey parameter cannot be - * resolved by the selected parser factory. - * In the DefaultFTPEntryParserFactory, this will - * happen when parserKey is neither - * the fully qualified class name of a class - * implementing the interface - * org.apache.commons.net.ftp.FTPFileEntryParser - * nor a string containing one of the recognized keys - * mapping to such a parser or if class loader - * security issues prevent its being loaded. - * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory - * @see org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory - * @see org.apache.commons.net.ftp.FTPFileEntryParser - * @since 3.0 + * Issue the FTP MDTM command (not supported by all servers) to retrieve the last modification time of a file. The modification string should be in the ISO + * 3077 form "yyyyMMDDhhmmss(.xxx)?". The timestamp represented should also be in GMT, but not all FTP servers honor this. + * + * @param pathname The file path to query. + * @return An Instant representing the last file modification time, may be {@code null}. The Instant timestamp will be null if a parse error occurs. + * @throws IOException if an I/O error occurs. + * @since 3.9.0 */ - public FTPFile[] listDirectories() throws IOException { - return listDirectories((String) null); + public Instant mdtmInstant(final String pathname) throws IOException { + final String modificationTime = getModificationTime(pathname); + if (modificationTime != null) { + return MLSxEntryParser.parseGmtInstant(modificationTime); + } + return null; } /** - * Using the default system autodetect mechanism, obtain a - * list of directories contained in the specified directory. - * <p> - * This information is obtained through the LIST command. The contents of - * the returned array is determined by the<code> FTPFileEntryParser </code> - * used. - * <p> - * N.B. the LIST command does not generally return very precise timestamps. - * For recent files, the response usually contains hours and minutes (not seconds). - * For older files, the output may only contain a date. - * If the server supports it, the MLSD command returns timestamps with a precision - * of seconds, and may include milliseconds. See {@link #mlistDir()} - * @param parent the starting directory + * Merge two copystream listeners, either or both of which may be null. * - * @return The list of directories contained in the specified directory - * in the format determined by the autodetection mechanism. - * - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection - * as a result of the client being idle or some other - * reason causing the server to send FTP reply code 421. - * This exception may be caught either as an IOException - * or independently as itself. - * @throws IOException - * If an I/O error occurs while either sending a - * command to the server or receiving a reply - * from the server. - * @throws org.apache.commons.net.ftp.parser.ParserInitializationException - * Thrown if the parserKey parameter cannot be - * resolved by the selected parser factory. - * In the DefaultFTPEntryParserFactory, this will - * happen when parserKey is neither - * the fully qualified class name of a class - * implementing the interface - * org.apache.commons.net.ftp.FTPFileEntryParser - * nor a string containing one of the recognized keys - * mapping to such a parser or if class loader - * security issues prevent its being loaded. - * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory - * @see org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory - * @see org.apache.commons.net.ftp.FTPFileEntryParser + * @param local the listener used by this class, may be null + * @return a merged listener or a single listener or null * @since 3.0 */ - public FTPFile[] listDirectories(String parent) throws IOException { - return listFiles(parent, FTPFileFilters.DIRECTORIES); + private CopyStreamListener mergeListeners(final CopyStreamListener local) { + if (local == null) { + return copyStreamListener; + } + if (copyStreamListener == null) { + return local; + } + // Both are non-null + final CopyStreamAdapter merged = new CopyStreamAdapter(); + merged.addCopyStreamListener(local); + merged.addCopyStreamListener(copyStreamListener); + return merged; } /** - * Using the default autodetect mechanism, initialize an FTPListParseEngine - * object containing a raw file information for the current working - * directory on the server - * This information is obtained through the LIST command. This object - * is then capable of being iterated to return a sequence of FTPFile - * objects with information filled in by the - * <code> FTPFileEntryParser </code> used. - * <p> - * This method differs from using the listFiles() methods in that - * expensive FTPFile objects are not created until needed which may be - * an advantage on large lists. - * - * @return A FTPListParseEngine object that holds the raw information and - * is capable of providing parsed FTPFile objects, one for each file - * containing information contained in the given path in the format - * determined by the <code> parser </code> parameter. Null will be - * returned if a data connection cannot be opened. If the current working - * directory contains no files, an empty array will be the return. - * - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException - * If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @throws org.apache.commons.net.ftp.parser.ParserInitializationException - * Thrown if the autodetect mechanism cannot - * resolve the type of system we are connected with. - * @see FTPListParseEngine + * Generate a directory listing for the current directory using the MLSD command. + * + * @return the array of file entries + * @throws IOException on error + * @since 3.0 */ - public FTPListParseEngine initiateListParsing() - throws IOException - { - return initiateListParsing((String) null); + public FTPFile[] mlistDir() throws IOException { + return mlistDir(null); } /** - * Using the default autodetect mechanism, initialize an FTPListParseEngine - * object containing a raw file information for the supplied directory. - * This information is obtained through the LIST command. This object - * is then capable of being iterated to return a sequence of FTPFile - * objects with information filled in by the - * <code> FTPFileEntryParser </code> used. - * <p> - * The server may or may not expand glob expressions. You should avoid - * using glob expressions because the return format for glob listings - * differs from server to server and will likely cause this method to fail. - * <p> - * This method differs from using the listFiles() methods in that - * expensive FTPFile objects are not created until needed which may be - * an advantage on large lists. - * - * <pre> - * FTPClient f=FTPClient(); - * f.connect(server); - * f.login(username, password); - * FTPListParseEngine engine = f.initiateListParsing(directory); - * - * while (engine.hasNext()) { - * FTPFile[] files = engine.getNext(25); // "page size" you want - * //do whatever you want with these files, display them, etc. - * //expensive FTPFile objects not created until needed. - * } - * </pre> - * @param pathname the starting directory + * Generate a directory listing using the MLSD command. * - * @return A FTPListParseEngine object that holds the raw information and - * is capable of providing parsed FTPFile objects, one for each file - * containing information contained in the given path in the format - * determined by the <code> parser </code> parameter. Null will be - * returned if a data connection cannot be opened. If the current working - * directory contains no files, an empty array will be the return. - * - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException - * If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @throws org.apache.commons.net.ftp.parser.ParserInitializationException - * Thrown if the autodetect mechanism cannot - * resolve the type of system we are connected with. - * @see FTPListParseEngine + * @param pathname the directory name, may be {@code null} + * @return the array of file entries + * @throws IOException on error + * @since 3.0 */ - public FTPListParseEngine initiateListParsing(String pathname) - throws IOException - { - return initiateListParsing((String) null, pathname); + public FTPFile[] mlistDir(final String pathname) throws IOException { + return initiateMListParsing(pathname).getFiles(); } /** - * Using the supplied parser key, initialize an FTPListParseEngine - * object containing a raw file information for the supplied directory. - * This information is obtained through the LIST command. This object - * is then capable of being iterated to return a sequence of FTPFile - * objects with information filled in by the - * <code> FTPFileEntryParser </code> used. - * <p> - * The server may or may not expand glob expressions. You should avoid - * using glob expressions because the return format for glob listings - * differs from server to server and will likely cause this method to fail. - * <p> - * This method differs from using the listFiles() methods in that - * expensive FTPFile objects are not created until needed which may be - * an advantage on large lists. - * - * @param parserKey A string representing a designated code or fully-qualified - * class name of an <code> FTPFileEntryParser </code> that should be - * used to parse each server file listing. - * May be {@code null}, in which case the code checks first - * the system property {@link #FTP_SYSTEM_TYPE}, and if that is - * not defined the SYST command is used to provide the value. - * To allow for arbitrary system types, the return from the - * SYST command is used to look up an alias for the type in the - * {@link #SYSTEM_TYPE_PROPERTIES} properties file if it is available. - * @param pathname the starting directory + * Generate a directory listing using the MLSD command. * - * @return A FTPListParseEngine object that holds the raw information and - * is capable of providing parsed FTPFile objects, one for each file - * containing information contained in the given path in the format - * determined by the <code> parser </code> parameter. Null will be - * returned if a data connection cannot be opened. If the current working - * directory contains no files, an empty array will be the return. - * - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException - * If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @throws org.apache.commons.net.ftp.parser.ParserInitializationException - * Thrown if the parserKey parameter cannot be - * resolved by the selected parser factory. - * In the DefaultFTPEntryParserFactory, this will - * happen when parserKey is neither - * the fully qualified class name of a class - * implementing the interface - * org.apache.commons.net.ftp.FTPFileEntryParser - * nor a string containing one of the recognized keys - * mapping to such a parser or if class loader - * security issues prevent its being loaded. - * @see FTPListParseEngine + * @param pathname the directory name, may be {@code null} + * @param filter the filter to apply to the responses + * @return the array of file entries + * @throws IOException on error + * @since 3.0 */ - public FTPListParseEngine initiateListParsing( - String parserKey, String pathname) - throws IOException - { - __createParser(parserKey); // create and cache parser - return initiateListParsing(__entryParser, pathname); + public FTPFile[] mlistDir(final String pathname, final FTPFileFilter filter) throws IOException { + return initiateMListParsing(pathname).getFiles(filter); } - // package access for test purposes - void __createParser(String parserKey) throws IOException { - // We cache the value to avoid creation of a new object every - // time a file listing is generated. - // Note: we don't check against a null parserKey (NET-544) - if(__entryParser == null || (parserKey != null && ! __entryParserKey.equals(parserKey))) { - if (null != parserKey) { - // if a parser key was supplied in the parameters, - // use that to create the parser - __entryParser = - __parserFactory.createFileEntryParser(parserKey); - __entryParserKey = parserKey; - - } else { - // if no parserKey was supplied, check for a configuration - // in the params, and if it has a non-empty system type, use that. - if (null != __configuration && __configuration.getServerSystemKey().length() > 0) { - __entryParser = - __parserFactory.createFileEntryParser(__configuration); - __entryParserKey = __configuration.getServerSystemKey(); - } else { - // if a parserKey hasn't been supplied, and a configuration - // hasn't been supplied, and the override property is not set - // then autodetect by calling - // the SYST command and use that to choose the parser. - String systemType = System.getProperty(FTP_SYSTEM_TYPE); - if (systemType == null) { - systemType = getSystemType(); // cannot be null - Properties override = getOverrideProperties(); - if (override != null) { - String newType = override.getProperty(systemType); - if (newType != null) { - systemType = newType; - } - } - } - if (null != __configuration) { // system type must have been empty above - __entryParser = __parserFactory.createFileEntryParser(new FTPClientConfig(systemType, __configuration)); - } else { - __entryParser = __parserFactory.createFileEntryParser(systemType); - } - __entryParserKey = systemType; - } + /** + * Get file details using the MLST command + * + * @param pathname the file or directory to list, may be {@code null} + * @return the file details, may be {@code null} + * @throws IOException on error + * @since 3.0 + */ + public FTPFile mlistFile(final String pathname) throws IOException { + final boolean success = FTPReply.isPositiveCompletion(sendCommand(FTPCmd.MLST, pathname)); + if (success) { + String reply = getReplyString(1); + // some FTP server reply not contains space before fact(s) + if (reply.charAt(0) != ' ') { + reply = " " + reply; } + /* + * check the response makes sense. Must have space before fact(s) and between fact(s) and file name Fact(s) can be absent, so at least 3 chars are + * needed. + */ + if (reply.length() < 3) { + throw new MalformedServerReplyException("Invalid server reply (MLST): '" + reply + "'"); + } + // some FTP server reply contains more than one space before fact(s) + final String entry = reply.replaceAll("^\\s+", ""); // skip leading space for parser + return MLSxEntryParser.parseEntry(entry); } + return null; + } + /** + * Returns the pathname of the current working directory. + * + * @return The pathname of the current working directory. If it cannot be obtained, returns null. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public String printWorkingDirectory() throws IOException { + if (pwd() != FTPReply.PATHNAME_CREATED) { + return null; + } + return parsePathname(_replyLines.get(_replyLines.size() - 1)); } /** - * private method through which all listFiles() and - * initiateListParsing methods pass once a parser is determined. + * Reinitialize the FTP session. Not all FTP servers support this command, which issues the FTP REIN command. * - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException - * If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @see FTPListParseEngine + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @since 3.4 (made public) */ - private FTPListParseEngine initiateListParsing( - FTPFileEntryParser parser, String pathname) - throws IOException - { - Socket socket = _openDataConnection_(FTPCmd.LIST, getListArguments(pathname)); + public boolean reinitialize() throws IOException { + rein(); - FTPListParseEngine engine = new FTPListParseEngine(parser, __configuration); - if (socket == null) - { - return engine; - } + if (FTPReply.isPositiveCompletion(_replyCode) || (FTPReply.isPositivePreliminary(_replyCode) && FTPReply.isPositiveCompletion(getReply()))) { - try { - engine.readServerList(socket.getInputStream(), getControlEncoding()); - } - finally { - Util.closeQuietly(socket); + initDefaults(); + + return true; } - completePendingCommand(); - return engine; + return false; } + // For server to server transfers /** - * Initiate list parsing for MLSD listings. + * Initiate a server to server file transfer. This method tells the server to which the client is connected to append to a given file on the other server. + * The other server must have had a <code> remoteRetrieve </code> issued to it by another FTPClient. * - * @param pathname - * @return the engine - * @throws IOException - */ - private FTPListParseEngine initiateMListParsing(String pathname) throws IOException - { - Socket socket = _openDataConnection_(FTPCmd.MLSD, pathname); - FTPListParseEngine engine = new FTPListParseEngine(MLSxEntryParser.getInstance(), __configuration); - if (socket == null) - { - return engine; + * @param fileName The name of the file to be appended to, or if the file does not exist, the name to call the file being stored. + * + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean remoteAppend(final String fileName) throws IOException { + if (dataConnectionMode == ACTIVE_REMOTE_DATA_CONNECTION_MODE || dataConnectionMode == PASSIVE_REMOTE_DATA_CONNECTION_MODE) { + return FTPReply.isPositivePreliminary(appe(fileName)); } + return false; + } - try { - engine.readServerList(socket.getInputStream(), getControlEncoding()); - } - finally { - Util.closeQuietly(socket); - completePendingCommand(); + /** + * Initiate a server to server file transfer. This method tells the server to which the client is connected to retrieve a given file from the other server. + * + * @param fileName The name of the file to retrieve. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean remoteRetrieve(final String fileName) throws IOException { + if (dataConnectionMode == ACTIVE_REMOTE_DATA_CONNECTION_MODE || dataConnectionMode == PASSIVE_REMOTE_DATA_CONNECTION_MODE) { + return FTPReply.isPositivePreliminary(retr(fileName)); } - return engine; + return false; } /** - * @param pathname the initial pathname - * @return the adjusted string with "-a" added if necessary - * @since 2.0 + * Initiate a server to server file transfer. This method tells the server to which the client is connected to store a file on the other server using the + * given file name. The other server must have had a <code> remoteRetrieve </code> issued to it by another FTPClient. + * + * @param fileName The name to call the file that is to be stored. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - protected String getListArguments(String pathname) { - if (getListHiddenFiles()) - { - if (pathname != null) - { - StringBuilder sb = new StringBuilder(pathname.length() + 3); - sb.append("-a "); - sb.append(pathname); - return sb.toString(); - } - else - { - return "-a"; - } + public boolean remoteStore(final String fileName) throws IOException { + if (dataConnectionMode == ACTIVE_REMOTE_DATA_CONNECTION_MODE || dataConnectionMode == PASSIVE_REMOTE_DATA_CONNECTION_MODE) { + return FTPReply.isPositivePreliminary(stor(fileName)); } - - return pathname; + return false; } - /** - * Issue the FTP STAT command to the server. + * Initiate a server to server file transfer. This method tells the server to which the client is connected to store a file on the other server using a + * unique file name. The other server must have had a <code> remoteRetrieve </code> issued to it by another FTPClient. Many FTP servers require that a base + * file name be given from which the unique file name can be derived. For those servers use the other version of <code> remoteStoreUnique</code> * - * @return The status information returned by the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public String getStatus() throws IOException - { - if (FTPReply.isPositiveCompletion(stat())) { - return getReplyString(); + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean remoteStoreUnique() throws IOException { + if (dataConnectionMode == ACTIVE_REMOTE_DATA_CONNECTION_MODE || dataConnectionMode == PASSIVE_REMOTE_DATA_CONNECTION_MODE) { + return FTPReply.isPositivePreliminary(stou()); } - return null; + return false; } - /** - * Issue the FTP STAT command to the server for a given pathname. This - * should produce a listing of the file or directory. - * @param pathname the filename + * Initiate a server to server file transfer. This method tells the server to which the client is connected to store a file on the other server using a + * unique file name based on the given file name. The other server must have had a <code> remoteRetrieve </code> issued to it by another FTPClient. * - * @return The status information returned by the server. - * @throws FTPConnectionClosedException - * If the FTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send FTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - */ - public String getStatus(String pathname) throws IOException - { - if (FTPReply.isPositiveCompletion(stat(pathname))) { - return getReplyString(); + * @param fileName The name on which to base the file name of the file that is to be stored. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean remoteStoreUnique(final String fileName) throws IOException { + if (dataConnectionMode == ACTIVE_REMOTE_DATA_CONNECTION_MODE || dataConnectionMode == PASSIVE_REMOTE_DATA_CONNECTION_MODE) { + return FTPReply.isPositivePreliminary(stou(fileName)); } - return null; + return false; } + /** + * Removes a directory on the FTP server (if empty). + * + * @param pathname The pathname of the directory to remove. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean removeDirectory(final String pathname) throws IOException { + return FTPReply.isPositiveCompletion(rmd(pathname)); + } /** - * Issue the FTP MDTM command (not supported by all servers) to retrieve the last - * modification time of a file. The modification string should be in the - * ISO 3077 form "YYYYMMDDhhmmss(.xxx)?". The timestamp represented should also be in - * GMT, but not all FTP servers honour this. + * Renames a remote file. * - * @param pathname The file path to query. - * @return A string representing the last file modification time in <code>YYYYMMDDhhmmss</code> format. - * @throws IOException if an I/O error occurs. - * @since 2.0 + * @param from The name of the remote file to rename. + * @param to The new name of the remote file. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - public String getModificationTime(String pathname) throws IOException { - if (FTPReply.isPositiveCompletion(mdtm(pathname))) { - return getReplyStrings()[0].substring(4); // skip the return code (e.g. 213) and the space + public boolean rename(final String from, final String to) throws IOException { + if (!FTPReply.isPositiveIntermediate(rnfr(from))) { + return false; } - return null; + + return FTPReply.isPositiveCompletion(rnto(to)); } + /** + * Restart a <code>STREAM_TRANSFER_MODE</code> file transfer starting from the given offset. This will only work on FTP servers supporting the REST comand + * for the stream transfer mode. However, most FTP servers support this. Any subsequent file transfer will start reading or writing the remote file from the + * indicated offset. + * + * @param offset The offset into the remote file at which to start the next file transfer. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @since 3.1 (changed from private to protected) + */ + protected boolean restart(final long offset) throws IOException { + restartOffset = 0; + return FTPReply.isPositiveIntermediate(rest(Long.toString(offset))); + } /** - * Issue the FTP MDTM command (not supported by all servers) to retrieve the last - * modification time of a file. The modification string should be in the - * ISO 3077 form "YYYYMMDDhhmmss(.xxx)?". The timestamp represented should also be in - * GMT, but not all FTP servers honour this. + * Retrieves a named file from the server and writes it to the given OutputStream. This method does NOT close the given OutputStream. If the current file + * type is ASCII, line separators in the file are converted to the local representation. + * <p> + * Note: if you have used {@link #setRestartOffset(long)}, the file data will start from the selected offset. * - * @param pathname The file path to query. - * @return A FTPFile representing the last file modification time, may be {@code null}. - * The FTPFile timestamp will be null if a parse error occurs. - * @throws IOException if an I/O error occurs. - * @since 3.4 + * @param remote The name of the remote file. + * @param local The local OutputStream to which to write the file. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some + * other reason causing the server to send FTP reply code 421. This exception may be caught either as + * an IOException or independently as itself. + * @throws org.apache.commons.net.io.CopyStreamException If an I/O error occurs while actually transferring the file. The CopyStreamException allows you to + * determine the number of bytes transferred and the IOException causing the error. This exception may + * be caught either as an IOException or independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the + * server. + */ + public boolean retrieveFile(final String remote, final OutputStream local) throws IOException { + return _retrieveFile(FTPCmd.RETR.getCommand(), remote, local); + } + + /** + * Returns an InputStream from which a named file from the server can be read. If the current file type is ASCII, the returned InputStream will convert line + * separators in the file to the local representation. You must close the InputStream when you finish reading from it. The InputStream itself will take care + * of closing the parent data connection socket upon being closed. + * <p> + * <b>To finalize the file transfer you must call {@link #completePendingCommand completePendingCommand } and check its return value to verify success.</b> + * If this is not done, subsequent commands may behave unexpectedly. + * <p> + * Note: if you have used {@link #setRestartOffset(long)}, the file data will start from the selected offset. + * + * @param remote The name of the remote file. + * @return An InputStream from which the remote file can be read. If the data connection cannot be opened (e.g., the file does not exist), null is returned + * (in which case you may check the reply code to determine the exact reason for failure). + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - public FTPFile mdtmFile(String pathname) throws IOException { - if (FTPReply.isPositiveCompletion(mdtm(pathname))) { - String reply = getReplyStrings()[0].substring(4); // skip the return code (e.g. 213) and the space - FTPFile file = new FTPFile(); - file.setName(pathname); - file.setRawListing(reply); - file.setTimestamp(MLSxEntryParser.parseGMTdateTime(reply)); - return file; - } - return null; + public InputStream retrieveFileStream(final String remote) throws IOException { + return _retrieveFileStream(FTPCmd.RETR.getCommand(), remote); } + /** + * Sends a NOOP command to the FTP server. This is useful for preventing server timeouts. + * + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean sendNoOp() throws IOException { + return FTPReply.isPositiveCompletion(noop()); + } /** - * Issue the FTP MFMT command (not supported by all servers) which sets the last - * modified time of a file. + * Send a site specific command. * - * The timestamp should be in the form <code>YYYYMMDDhhmmss</code>. It should also - * be in GMT, but not all servers honour this. + * @param arguments The site specific command and arguments. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean sendSiteCommand(final String arguments) throws IOException { + return FTPReply.isPositiveCompletion(site(arguments)); + } + + /** + * Set the external IP address in active mode. Useful when there are multiple network cards. * - * An FTP server would indicate its support of this feature by including "MFMT" - * in its response to the FEAT command, which may be retrieved by FTPClient.features() + * @param ipAddress The external IP address of this machine. + * @throws UnknownHostException if the ipAddress cannot be resolved + * @since 2.2 + */ + public void setActiveExternalIPAddress(final String ipAddress) throws UnknownHostException { + this.activeExternalHost = InetAddress.getByName(ipAddress); + } + + /** + * Set the client side port range in active mode. * - * @param pathname The file path for which last modified time is to be changed. - * @param timeval The timestamp to set to, in <code>YYYYMMDDhhmmss</code> format. - * @return true if successfully set, false if not - * @throws IOException if an I/O error occurs. + * @param minPort The lowest available port (inclusive). + * @param maxPort The highest available port (inclusive). * @since 2.2 - * @see <a href="http://tools.ietf.org/html/draft-somers-ftp-mfxx-04">http://tools.ietf.org/html/draft-somers-ftp-mfxx-04</a> */ - public boolean setModificationTime(String pathname, String timeval) throws IOException { - return (FTPReply.isPositiveCompletion(mfmt(pathname, timeval))); + public void setActivePortRange(final int minPort, final int maxPort) { + this.activeMinPort = minPort; + this.activeMaxPort = maxPort; + } + + /** + * Enables or disables automatic server encoding detection (only UTF-8 supported). + * <p> + * Does not affect existing connections; must be invoked before a connection is established. + * + * @param autodetect If true, automatic server encoding detection will be enabled. + */ + public void setAutodetectUTF8(final boolean autodetect) { + autodetectEncoding = autodetect; } - /** * Set the internal buffer size for buffered data streams. * * @param bufSize The size of the buffer. Use a non-positive value to use the default. */ - public void setBufferSize(int bufSize) { - __bufferSize = bufSize; + public void setBufferSize(final int bufSize) { + bufferSize = bufSize; } /** - * Retrieve the current internal buffer size for buffered data streams. - * @return The current buffer size. + * Sets how long to wait for control keep-alive message replies. + * + * @param timeout number of milliseconds to wait (defaults to 1000) + * @since 3.0 + * @see #setControlKeepAliveTimeout(Duration) */ - public int getBufferSize() { - return __bufferSize; + public void setControlKeepAliveReplyTimeout(final Duration timeout) { + controlKeepAliveReplyTimeout = DurationUtils.zeroIfNull(timeout); } /** - * Sets the value to be used for the data socket SO_SNDBUF option. - * If the value is positive, the option will be set when the data socket has been created. + * Sets how long to wait for control keep-alive message replies. * - * @param bufSize The size of the buffer, zero or negative means the value is ignored. - * @since 3.3 - */ - public void setSendDataSocketBufferSize(int bufSize) { - __sendDataSocketBufferSize = bufSize; + * @deprecated Use {@link #setControlKeepAliveReplyTimeout(Duration)}. + * @param timeoutMillis number of milliseconds to wait (defaults to 1000) + * @since 3.0 + * @see #setControlKeepAliveTimeout(long) + */ + @Deprecated + public void setControlKeepAliveReplyTimeout(final int timeoutMillis) { + controlKeepAliveReplyTimeout = Duration.ofMillis(timeoutMillis); } /** - * Retrieve the value to be used for the data socket SO_SNDBUF option. - * @return The current buffer size. - * @since 3.3 + * Sets the time to wait between sending control connection keepalive messages when processing file upload or download. + * <p> + * See the class Javadoc section "Control channel keep-alive feature" + * </p> + * + * @param controlIdle the wait between keepalive messages. Zero (or less) disables. + * @since 3.9.0 + * @see #setControlKeepAliveReplyTimeout(Duration) */ - public int getSendDataSocketBufferSize() { - return __sendDataSocketBufferSize; + public void setControlKeepAliveTimeout(final Duration controlIdle) { + controlKeepAliveTimeout = DurationUtils.zeroIfNull(controlIdle); } /** - * Sets the value to be used for the data socket SO_RCVBUF option. - * If the value is positive, the option will be set when the data socket has been created. + * Sets the time to wait between sending control connection keepalive messages when processing file upload or download. + * <p> + * See the class Javadoc section "Control channel keep-alive feature" + * </p> * - * @param bufSize The size of the buffer, zero or negative means the value is ignored. - * @since 3.3 + * @deprecated Use {@link #setControlKeepAliveTimeout(Duration)}. + * @param controlIdleSeconds the wait (in seconds) between keepalive messages. Zero (or less) disables. + * @since 3.0 + * @see #setControlKeepAliveReplyTimeout(int) */ - public void setReceieveDataSocketBufferSize(int bufSize) { - __receiveDataSocketBufferSize = bufSize; + @Deprecated + public void setControlKeepAliveTimeout(final long controlIdleSeconds) { + controlKeepAliveTimeout = Duration.ofSeconds(controlIdleSeconds); } /** - * Retrieve the value to be used for the data socket SO_RCVBUF option. - * @return The current buffer size. - * @since 3.3 + * Set the listener to be used when performing store/retrieve operations. The default value (if not set) is {@code null}. + * + * @param listener to be used, may be {@code null} to disable + * @since 3.0 */ - public int getReceiveDataSocketBufferSize() { - return __receiveDataSocketBufferSize; + public void setCopyStreamListener(final CopyStreamListener listener) { + copyStreamListener = listener; } /** - * Implementation of the {@link Configurable Configurable} interface. - * In the case of this class, configuring merely makes the config object available for the - * factory methods that construct parsers. - * @param config {@link FTPClientConfig FTPClientConfig} object used to - * provide non-standard configurations to the parser. - * @since 1.4 + * Sets the timeout to use when reading from the data connection. This timeout will be set immediately after opening the data connection, provided that the + * value is ≥ 0. + * <p> + * <b>Note:</b> the timeout will also be applied when calling accept() whilst establishing an active local data connection. + * + * @param timeout The default timeout that is used when opening a data connection socket. The value 0 (or null) means an infinite timeout. + * @since 3.9.0 */ - @Override - public void configure(FTPClientConfig config) { - this.__configuration = config; + public void setDataTimeout(final Duration timeout) { + dataTimeout = DurationUtils.zeroIfNull(timeout); } /** - * You can set this to true if you would like to get hidden files when {@link #listFiles} too. - * A <code>LIST -a</code> will be issued to the ftp server. - * It depends on your ftp server if you need to call this method, also dont expect to get rid - * of hidden files if you call this method with "false". + * Sets the timeout in milliseconds to use when reading from the data connection. This timeout will be set immediately after opening the data connection, + * provided that the value is ≥ 0. + * <p> + * <b>Note:</b> the timeout will also be applied when calling accept() whilst establishing an active local data connection. + * </p> * - * @param listHiddenFiles true if hidden files should be listed - * @since 2.0 + * @deprecated Use {@link #setDataTimeout(Duration)}. + * @param timeoutMillis The default timeout in milliseconds that is used when opening a data connection socket. The value 0 means an infinite timeout. */ - public void setListHiddenFiles(boolean listHiddenFiles) { - this.__listHiddenFiles = listHiddenFiles; + @Deprecated + public void setDataTimeout(final int timeoutMillis) { + dataTimeout = Duration.ofMillis(timeoutMillis); } /** - * @see #setListHiddenFiles(boolean) - * @return the current state - * @since 2.0 - */ - public boolean getListHiddenFiles() { - return this.__listHiddenFiles; + * Sets the file structure. The default structure is <code> FTP.FILE_STRUCTURE </code> if this method is never called or if a connect method is called. + * + * @param structure The structure of the file (one of the FTP class <code>_STRUCTURE</code> constants). + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean setFileStructure(final int structure) throws IOException { + if (FTPReply.isPositiveCompletion(stru(structure))) { + fileStructure = structure; + return true; + } + return false; } /** - * Whether should attempt to use EPSV with IPv4. - * Default (if not set) is <code>false</code> - * @return true if should attempt EPSV - * @since 2.2 - */ - public boolean isUseEPSVwithIPv4() { - return __useEPSVwithIPv4; + * Sets the transfer mode. The default transfer mode <code> FTP.STREAM_TRANSFER_MODE </code> if this method is never called or if a connect method is + * called. + * + * @param mode The new transfer mode to use (one of the FTP class <code>_TRANSFER_MODE</code> constants). + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean setFileTransferMode(final int mode) throws IOException { + if (FTPReply.isPositiveCompletion(mode(mode))) { + fileTransferMode = mode; + return true; + } + return false; } + /** + * Sets the file type to be transferred. This should be one of <code> FTP.ASCII_FILE_TYPE </code>, <code> FTP.BINARY_FILE_TYPE</code>, etc. The file type + * only needs to be set when you want to change the type. After changing it, the new type stays in effect until you change it again. The default file type + * is <code> FTP.ASCII_FILE_TYPE </code> if this method is never called. <br> + * The server default is supposed to be ASCII (see RFC 959), however many ftp servers default to BINARY. <b>To ensure correct operation with all servers, + * always specify the appropriate file type after connecting to the server.</b> <br> + * <p> + * <b>N.B.</b> currently calling any connect method will reset the type to FTP.ASCII_FILE_TYPE. + * + * @param fileType The <code> _FILE_TYPE </code> constant indicating the type of file. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean setFileType(final int fileType) throws IOException { + if (FTPReply.isPositiveCompletion(type(fileType))) { + this.fileType = fileType; + this.fileFormat = FTP.NON_PRINT_TEXT_FORMAT; + return true; + } + return false; + } /** - * Set whether to use EPSV with IPv4. - * Might be worth enabling in some circumstances. + * Sets the file type to be transferred and the format. The type should be one of <code> FTP.ASCII_FILE_TYPE </code>, <code> FTP.BINARY_FILE_TYPE </code>, + * etc. The file type only needs to be set when you want to change the type. After changing it, the new type stays in effect until you change it again. The + * default file type is <code> FTP.ASCII_FILE_TYPE </code> if this method is never called. <br> + * The server default is supposed to be ASCII (see RFC 959), however many ftp servers default to BINARY. <b>To ensure correct operation with all servers, + * always specify the appropriate file type after connecting to the server.</b> <br> + * The format should be one of the FTP class <code> TEXT_FORMAT </code> constants, or if the type is <code> FTP.LOCAL_FILE_TYPE </code>, the format should + * be the byte size for that type. The default format is <code> FTP.NON_PRINT_TEXT_FORMAT </code> if this method is never called. + * <p> + * <b>N.B.</b> currently calling any connect method will reset the type to FTP.ASCII_FILE_TYPE and the formatOrByteSize to FTP.NON_PRINT_TEXT_FORMAT. * - * For example, when using IPv4 with NAT it - * may work with some rare configurations. - * E.g. if FTP server has a static PASV address (external network) - * and the client is coming from another internal network. - * In that case the data connection after PASV command would fail, - * while EPSV would make the client succeed by taking just the port. + * @param fileType The <code> _FILE_TYPE </code> constant indicating the type of file. + * @param formatOrByteSize The format of the file (one of the <code>_FORMAT</code> constants. In the case of <code>LOCAL_FILE_TYPE</code>, the byte size. * - * @param selected value to set. - * @since 2.2 - */ - public void setUseEPSVwithIPv4(boolean selected) { - this.__useEPSVwithIPv4 = selected; + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean setFileType(final int fileType, final int formatOrByteSize) throws IOException { + if (FTPReply.isPositiveCompletion(type(fileType, formatOrByteSize))) { + this.fileType = fileType; + this.fileFormat = formatOrByteSize; + return true; + } + return false; } /** - * Set the listener to be used when performing store/retrieve operations. - * The default value (if not set) is {@code null}. + * Sets whether the IP address from the server's response should be used. Until 3.9.0, this has always been the case. Beginning with 3.9.0, that IP address + * will be silently ignored, and replaced with the remote IP address of the control connection, unless this configuration option is given, which restores + * the old behavior. To enable this by default, use the system property {@link FTPClient#FTP_IP_ADDRESS_FROM_PASV_RESPONSE}. * - * @param listener to be used, may be {@code null} to disable - * @since 3.0 + * @param usingIpAddressFromPasvResponse True, if the IP address from the server's response should be used (pre-3.9.0 compatible behavior), or false (ignore + * that IP address). + * @see FTPClient#FTP_IP_ADDRESS_FROM_PASV_RESPONSE + * @see #isIpAddressFromPasvResponse + * @since 3.9.0 */ - public void setCopyStreamListener(CopyStreamListener listener){ - __copyStreamListener = listener; + public void setIpAddressFromPasvResponse(final boolean usingIpAddressFromPasvResponse) { + this.ipAddressFromPasvResponse = usingIpAddressFromPasvResponse; } /** - * Obtain the currently active listener. + * You can set this to true if you would like to get hidden files when {@link #listFiles} too. A <code>LIST -a</code> will be issued to the ftp server. It + * depends on your ftp server if you need to call this method, also dont expect to get rid of hidden files if you call this method with "false". * - * @return the listener, may be {@code null} - * @since 3.0 + * @param listHiddenFiles true if hidden files should be listed + * @since 2.0 */ - public CopyStreamListener getCopyStreamListener(){ - return __copyStreamListener; + public void setListHiddenFiles(final boolean listHiddenFiles) { + this.listHiddenFiles = listHiddenFiles; } /** - * Set the time to wait between sending control connection keepalive messages - * when processing file upload or download. + * Issue the FTP MFMT command (not supported by all servers) which sets the last modified time of a file. * - * @param controlIdle the wait (in secs) between keepalive messages. Zero (or less) disables. - * @since 3.0 - * @see #setControlKeepAliveReplyTimeout(int) + * The timestamp should be in the form <code>yyyyMMDDhhmmss</code>. It should also be in GMT, but not all servers honor this. + * + * An FTP server would indicate its support of this feature by including "MFMT" in its response to the FEAT command, which may be retrieved by + * FTPClient.features() + * + * @param pathname The file path for which last modified time is to be changed. + * @param timeval The timestamp to set to, in <code>yyyyMMDDhhmmss</code> format. + * @return true if successfully set, false if not + * @throws IOException if an I/O error occurs. + * @since 2.2 + * @see <a href="http://tools.ietf.org/html/draft-somers-ftp-mfxx-04">http://tools.ietf.org/html/draft-somers-ftp-mfxx-04</a> */ - public void setControlKeepAliveTimeout(long controlIdle){ - __controlKeepAliveTimeout = controlIdle * 1000; + public boolean setModificationTime(final String pathname, final String timeval) throws IOException { + return (FTPReply.isPositiveCompletion(mfmt(pathname, timeval))); } /** - * Get the time to wait between sending control connection keepalive messages. - * @return the number of seconds between keepalive messages. - * @since 3.0 + * set the factory used for parser creation to the supplied factory object. + * + * @param parserFactory factory object used to create FTPFileEntryParsers + * + * @see org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory + * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory */ - public long getControlKeepAliveTimeout() { - return __controlKeepAliveTimeout / 1000; + public void setParserFactory(final FTPFileEntryParserFactory parserFactory) { + this.parserFactory = parserFactory; } /** - * Set how long to wait for control keep-alive message replies. + * Set the local IP address to use in passive mode. Useful when there are multiple network cards. * - * @param timeout number of milliseconds to wait (defaults to 1000) - * @since 3.0 - * @see #setControlKeepAliveTimeout(long) + * @param inetAddress The local IP address of this machine. */ - public void setControlKeepAliveReplyTimeout(int timeout) { - __controlKeepAliveReplyTimeout = timeout; + public void setPassiveLocalIPAddress(final InetAddress inetAddress) { + this.passiveLocalHost = inetAddress; } /** - * Get how long to wait for control keep-alive message replies. - * @return wait time in msec - * @since 3.0 + * Set the local IP address to use in passive mode. Useful when there are multiple network cards. + * + * @param ipAddress The local IP address of this machine. + * @throws UnknownHostException if the ipAddress cannot be resolved */ - public int getControlKeepAliveReplyTimeout() { - return __controlKeepAliveReplyTimeout; + public void setPassiveLocalIPAddress(final String ipAddress) throws UnknownHostException { + this.passiveLocalHost = InetAddress.getByName(ipAddress); } /** - * Enable or disable passive mode NAT workaround. - * If enabled, a site-local PASV mode reply address will be replaced with the - * remote host address to which the PASV mode request was sent - * (unless that is also a site local address). - * This gets around the problem that some NAT boxes may change the - * reply. - * + * Enables or disables passive mode NAT workaround. If enabled, a site-local PASV mode reply address will be replaced with the remote host address to which + * the PASV mode request was sent (unless that is also a site local address). This gets around the problem that some NAT boxes may change the reply. + * <p> * The default is true, i.e. site-local replies are replaced. - * @param enabled true to enable replacing internal IP's in passive - * mode. + * </p> + * * @deprecated (3.6) use {@link #setPassiveNatWorkaroundStrategy(HostnameResolver)} instead + * @param enabled true to enable replacing internal IP's in passive mode. */ @Deprecated - public void setPassiveNatWorkaround(boolean enabled) { - if (enabled) { - this.__passiveNatWorkaroundStrategy = new NatServerResolverImpl(this); - } else { - this.__passiveNatWorkaroundStrategy = null; - } + public void setPassiveNatWorkaround(final boolean enabled) { + this.passiveNatWorkaroundStrategy = enabled ? new NatServerResolverImpl(this) : null; } /** - * Set the workaround strategy to replace the PASV mode reply addresses. - * This gets around the problem that some NAT boxes may change the reply. + * Sets the workaround strategy to replace the PASV mode reply addresses. This gets around the problem that some NAT boxes may change the reply. + * + * The default implementation is {@code NatServerResolverImpl}, i.e. site-local replies are replaced. * - * The default implementation is {@code NatServerResolverImpl}, i.e. site-local - * replies are replaced. - * @param resolver strategy to replace internal IP's in passive mode - * or null to disable the workaround (i.e. use PASV mode reply address.) + * @param resolver strategy to replace internal IP's in passive mode or null to disable the workaround (i.e. use PASV mode reply address.) * @since 3.6 */ - public void setPassiveNatWorkaroundStrategy(HostnameResolver resolver) { - this.__passiveNatWorkaroundStrategy = resolver; + public void setPassiveNatWorkaroundStrategy(final HostnameResolver resolver) { + this.passiveNatWorkaroundStrategy = resolver; } /** - * Strategy interface for updating host names received from FTP server - * for passive NAT workaround. + * Sets the value to be used for the data socket SO_RCVBUF option. If the value is positive, the option will be set when the data socket has been created. * - * @since 3.6 + * @param bufSize The size of the buffer, zero or negative means the value is ignored. + * @since 3.3 */ - public static interface HostnameResolver { - String resolve(String hostname) throws UnknownHostException; + public void setReceieveDataSocketBufferSize(final int bufSize) { + receiveDataSocketBufferSize = bufSize; } /** - * Default strategy for passive NAT workaround (site-local - * replies are replaced.) - * @since 3.6 + * Enable or disable verification that the remote host taking part of a data connection is the same as the host to which the control connection is attached. + * The default is for verification to be enabled. You may set this value at any time, whether the FTPClient is currently connected or not. + * + * @param enable True to enable verification, false to disable verification. */ - public static class NatServerResolverImpl implements HostnameResolver { - private FTPClient client; - - public NatServerResolverImpl(FTPClient client) { - this.client = client; - } - - @Override - public String resolve(String hostname) throws UnknownHostException { - String newHostname = hostname; - InetAddress host = InetAddress.getByName(newHostname); - // reply is a local address, but target is not - assume NAT box changed the PASV reply - if (host.isSiteLocalAddress()) { - InetAddress remote = this.client.getRemoteAddress(); - if (!remote.isSiteLocalAddress()){ - newHostname = remote.getHostAddress(); - } - } - return newHostname; - } + public void setRemoteVerificationEnabled(final boolean enable) { + remoteVerificationEnabled = enable; } - private OutputStream getBufferedOutputStream(OutputStream outputStream) { - if (__bufferSize > 0) { - return new BufferedOutputStream(outputStream, __bufferSize); - } - return new BufferedOutputStream(outputStream); + /** + * Sets the external IP address to report in EPRT/PORT commands in active mode. Useful when there are multiple network cards. + * + * @param ipAddress The external IP address of this machine. + * @throws UnknownHostException if the ipAddress cannot be resolved + * @since 3.1 + * @see #getReportHostAddress() + */ + public void setReportActiveExternalIPAddress(final String ipAddress) throws UnknownHostException { + this.reportActiveExternalHost = InetAddress.getByName(ipAddress); } - private InputStream getBufferedInputStream(InputStream inputStream) { - if (__bufferSize > 0) { - return new BufferedInputStream(inputStream, __bufferSize); + /** + * Sets the restart offset for file transfers. + * <p> + * The restart command is not sent to the server immediately. It is sent when a data connection is created as part of a subsequent command. The restart + * marker is reset to zero after use. + * </p> + * <p> + * <b>Note: This method should only be invoked immediately prior to the transfer to which it applies.</b> + * + * @param offset The offset into the remote file at which to start the next file transfer. This must be a value greater than or equal to zero. + */ + public void setRestartOffset(final long offset) { + if (offset >= 0) { + restartOffset = offset; } - return new BufferedInputStream(inputStream); } - // @since 3.0 - private static class CSL implements CopyStreamListener { - - private final FTPClient parent; - private final long idle; - private final int currentSoTimeout; - - private long time = System.currentTimeMillis(); - private int notAcked; - - CSL(FTPClient parent, long idleTime, int maxWait) throws SocketException { - this.idle = idleTime; - this.parent = parent; - this.currentSoTimeout = parent.getSoTimeout(); - parent.setSoTimeout(maxWait); - } + /** + * Sets the value to be used for the data socket SO_SNDBUF option. If the value is positive, the option will be set when the data socket has been created. + * + * @param bufSize The size of the buffer, zero or negative means the value is ignored. + * @since 3.3 + */ + public void setSendDataSocketBufferSize(final int bufSize) { + sendDataSocketBufferSize = bufSize; + } - @Override - public void bytesTransferred(CopyStreamEvent event) { - bytesTransferred(event.getTotalBytesTransferred(), event.getBytesTransferred(), event.getStreamSize()); - } + /** + * Set whether to use EPSV with IPv4. Might be worth enabling in some circumstances. + * + * For example, when using IPv4 with NAT it may work with some rare configurations. E.g. if FTP server has a static PASV address (external network) and the + * client is coming from another internal network. In that case the data connection after PASV command would fail, while EPSV would make the client succeed + * by taking just the port. + * + * @param selected value to set. + * @since 2.2 + */ + public void setUseEPSVwithIPv4(final boolean selected) { + this.useEPSVwithIPv4 = selected; + } - @Override - public void bytesTransferred(long totalBytesTransferred, - int bytesTransferred, long streamSize) { - long now = System.currentTimeMillis(); - if (now - time > idle) { - try { - parent.__noop(); - } catch (SocketTimeoutException e) { - notAcked++; - } catch (IOException e) { - // Ignored - } - time = now; - } - } + private boolean storeFile(final FTPCmd command, final String remote, final InputStream local) throws IOException { + return _storeFile(command.getCommand(), remote, local); + } - void cleanUp() throws IOException { - try { - while(notAcked-- > 0) { - parent.__getReplyNoReport(); - } - } finally { - parent.setSoTimeout(currentSoTimeout); - } - } + /** + * Stores a file on the server using the given name and taking input from the given InputStream. This method does NOT close the given InputStream. If the + * current file type is ASCII, line separators in the file are transparently converted to the NETASCII format (i.e., you should not attempt to create a + * special InputStream to do this). + * + * @param remote The name to give the remote file. + * @param local The local InputStream from which to read the file. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some + * other reason causing the server to send FTP reply code 421. This exception may be caught either as + * an IOException or independently as itself. + * @throws org.apache.commons.net.io.CopyStreamException If an I/O error occurs while actually transferring the file. The CopyStreamException allows you to + * determine the number of bytes transferred and the IOException causing the error. This exception may + * be caught either as an IOException or independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the + * server. + */ + public boolean storeFile(final String remote, final InputStream local) throws IOException { + return storeFile(FTPCmd.STOR, remote, local); + } + private OutputStream storeFileStream(final FTPCmd command, final String remote) throws IOException { + return _storeFileStream(command.getCommand(), remote); } /** - * Merge two copystream listeners, either or both of which may be null. + * Returns an OutputStream through which data can be written to store a file on the server using the given name. If the current file type is ASCII, the + * returned OutputStream will convert line separators in the file to the NETASCII format (i.e., you should not attempt to create a special OutputStream to + * do this). You must close the OutputStream when you finish writing to it. The OutputStream itself will take care of closing the parent data connection + * socket upon being closed. + * <p> + * <b>To finalize the file transfer you must call {@link #completePendingCommand completePendingCommand } and check its return value to verify success.</b> + * If this is not done, subsequent commands may behave unexpectedly. * - * @param local the listener used by this class, may be null - * @return a merged listener or a single listener or null - * @since 3.0 + * @param remote The name to give the remote file. + * @return An OutputStream through which the remote file can be written. If the data connection cannot be opened (e.g., the file does not exist), null is + * returned (in which case you may check the reply code to determine the exact reason for failure). + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - private CopyStreamListener __mergeListeners(CopyStreamListener local) { - if (local == null) { - return __copyStreamListener; - } - if (__copyStreamListener == null) { - return local; - } - // Both are non-null - CopyStreamAdapter merged = new CopyStreamAdapter(); - merged.addCopyStreamListener(local); - merged.addCopyStreamListener(__copyStreamListener); - return merged; + public OutputStream storeFileStream(final String remote) throws IOException { + return storeFileStream(FTPCmd.STOR, remote); } /** - * Enables or disables automatic server encoding detection (only UTF-8 supported). - * <p> - * Does not affect existing connections; must be invoked before a connection is established. + * Stores a file on the server using a unique name assigned by the server and taking input from the given InputStream. This method does NOT close the given + * InputStream. If the current file type is ASCII, line separators in the file are transparently converted to the NETASCII format (i.e., you should not + * attempt to create a special InputStream to do this). * - * @param autodetect If true, automatic server encoding detection will be enabled. + * @param local The local InputStream from which to read the file. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some + * other reason causing the server to send FTP reply code 421. This exception may be caught either as + * an IOException or independently as itself. + * @throws org.apache.commons.net.io.CopyStreamException If an I/O error occurs while actually transferring the file. The CopyStreamException allows you to + * determine the number of bytes transferred and the IOException causing the error. This exception may + * be caught either as an IOException or independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the + * server. */ - public void setAutodetectUTF8(boolean autodetect) - { - __autodetectEncoding = autodetect; + public boolean storeUniqueFile(final InputStream local) throws IOException { + return storeFile(FTPCmd.STOU, null, local); } /** - * Tells if automatic server encoding detection is enabled or disabled. - * @return true, if automatic server encoding detection is enabled. + * Stores a file on the server using a unique name derived from the given name and taking input from the given InputStream. This method does NOT close the + * given InputStream. If the current file type is ASCII, line separators in the file are transparently converted to the NETASCII format (i.e., you should + * not attempt to create a special InputStream to do this). + * + * @param remote The name on which to base the unique name given to the remote file. + * @param local The local InputStream from which to read the file. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some + * other reason causing the server to send FTP reply code 421. This exception may be caught either as + * an IOException or independently as itself. + * @throws org.apache.commons.net.io.CopyStreamException If an I/O error occurs while actually transferring the file. The CopyStreamException allows you to + * determine the number of bytes transferred and the IOException causing the error. This exception may + * be caught either as an IOException or independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the + * server. */ - public boolean getAutodetectUTF8() - { - return __autodetectEncoding; + public boolean storeUniqueFile(final String remote, final InputStream local) throws IOException { + return storeFile(FTPCmd.STOU, remote, local); } - // Method for use by unit test code only - FTPFileEntryParser getEntryParser() { - return __entryParser; + /** + * Returns an OutputStream through which data can be written to store a file on the server using a unique name assigned by the server. If the current file + * type is ASCII, the returned OutputStream will convert line separators in the file to the NETASCII format (i.e., you should not attempt to create a + * special OutputStream to do this). You must close the OutputStream when you finish writing to it. The OutputStream itself will take care of closing the + * parent data connection socket upon being closed. + * <p> + * <b>To finalize the file transfer you must call {@link #completePendingCommand completePendingCommand } and check its return value to verify success.</b> + * If this is not done, subsequent commands may behave unexpectedly. + * + * @return An OutputStream through which the remote file can be written. If the data connection cannot be opened (e.g., the file does not exist), null is + * returned (in which case you may check the reply code to determine the exact reason for failure). + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public OutputStream storeUniqueFileStream() throws IOException { + return storeFileStream(FTPCmd.STOU, null); } - // DEPRECATED METHODS - for API compatibility only - DO NOT USE + /** + * Returns an OutputStream through which data can be written to store a file on the server using a unique name derived from the given name. If the current + * file type is ASCII, the returned OutputStream will convert line separators in the file to the NETASCII format (i.e., you should not attempt to create a + * special OutputStream to do this). You must close the OutputStream when you finish writing to it. The OutputStream itself will take care of closing the + * parent data connection socket upon being closed. + * <p> + * <b>To finalize the file transfer you must call {@link #completePendingCommand completePendingCommand } and check its return value to verify success.</b> + * If this is not done, subsequent commands may behave unexpectedly. + * + * @param remote The name on which to base the unique name given to the remote file. + * @return An OutputStream through which the remote file can be written. If the data connection cannot be opened (e.g., the file does not exist), null is + * returned (in which case you may check the reply code to determine the exact reason for failure). + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public OutputStream storeUniqueFileStream(final String remote) throws IOException { + return storeFileStream(FTPCmd.STOU, remote); + } /** - * @return the name - * @throws IOException on error - * @deprecated use {@link #getSystemType()} instead + * Issue the FTP SMNT command. + * + * @param pathname The pathname to mount. + * @return True if successfully completed, false if not. + * @throws FTPConnectionClosedException If the FTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send FTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - @Deprecated - public String getSystemName() throws IOException - { - if (__systemName == null && FTPReply.isPositiveCompletion(syst())) { - __systemName = _replyLines.get(_replyLines.size() - 1).substring(4); - } - return __systemName; + public boolean structureMount(final String pathname) throws IOException { + return FTPReply.isPositiveCompletion(smnt(pathname)); } } - -/* Emacs configuration - * Local variables: ** - * mode: java ** - * c-basic-offset: 4 ** - * indent-tabs-mode: nil ** - * End: ** - */ -/* kate: indent-width 4; replace-tabs on; */ diff --git a/src/main/java/org/apache/commons/net/ftp/FTPClientConfig.java b/src/main/java/org/apache/commons/net/ftp/FTPClientConfig.java index 6604dab..0714f6c 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPClientConfig.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPClientConfig.java @@ -26,107 +26,94 @@ import java.util.TreeMap; /** * <p> - * This class implements an alternate means of configuring the - * {@link org.apache.commons.net.ftp.FTPClient FTPClient} object and - * also subordinate objects which it uses. Any class implementing the - * {@link org.apache.commons.net.ftp.Configurable Configurable } - * interface can be configured by this object. - * </p><p> - * In particular this class was designed primarily to support configuration - * of FTP servers which express file timestamps in formats and languages - * other than those for the US locale, which although it is the most common - * is not universal. Unfortunately, nothing in the FTP spec allows this to - * be determined in an automated way, so manual configuration such as this - * is necessary. - * </p><p> - * This functionality was designed to allow existing clients to work exactly - * as before without requiring use of this component. This component should - * only need to be explicitly invoked by the user of this package for problem - * cases that previous implementations could not solve. + * This class implements an alternate means of configuring the {@link org.apache.commons.net.ftp.FTPClient FTPClient} object and also subordinate objects which + * it uses. Any class implementing the {@link org.apache.commons.net.ftp.Configurable Configurable } interface can be configured by this object. * </p> - * <h3>Examples of use of FTPClientConfig</h3> - * Use cases: - * You are trying to access a server that + * <p> + * In particular this class was designed primarily to support configuration of FTP servers which express file timestamps in formats and languages other than + * those for the US locale, which although it is the most common is not universal. Unfortunately, nothing in the FTP spec allows this to be determined in an + * automated way, so manual configuration such as this is necessary. + * </p> + * <p> + * This functionality was designed to allow existing clients to work exactly as before without requiring use of this component. This component should only need + * to be explicitly invoked by the user of this package for problem cases that previous implementations could not solve. + * </p> + * <h2>Examples of use of FTPClientConfig</h2> Use cases: You are trying to access a server that * <ul> - * <li>lists files with timestamps that use month names in languages other - * than English</li> - * <li>lists files with timestamps that use date formats other - * than the American English "standard" <code>MM dd yyyy</code></li> - * <li>is in different timezone and you need accurate timestamps for - * dependency checking as in Ant</li> + * <li>lists files with timestamps that use month names in languages other than English</li> + * <li>lists files with timestamps that use date formats other than the American English "standard" <code>MM dd yyyy</code></li> + * <li>is in different time zone and you need accurate timestamps for dependency checking as in Ant</li> * </ul> * <p> - * Unpaged (whole list) access on a UNIX server that uses French month names - * but uses the "standard" <code>MMM d yyyy</code> date formatting + * Unpaged (whole list) access on a UNIX server that uses French month names but uses the "standard" <code>MMM d yyyy</code> date formatting + * * <pre> - * FTPClient f=FTPClient(); - * FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX); - * conf.setServerLanguageCode("fr"); - * f.configure(conf); - * f.connect(server); - * f.login(username, password); - * FTPFile[] files = listFiles(directory); + * FTPClient f = FTPClient(); + * FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX); + * conf.setServerLanguageCode("fr"); + * f.configure(conf); + * f.connect(server); + * f.login(username, password); + * FTPFile[] files = listFiles(directory); * </pre> * <p> - * Paged access on a UNIX server that uses Danish month names - * and "European" date formatting in Denmark's time zone, when you - * are in some other time zone. + * Paged access on a UNIX server that uses Danish month names and "European" date formatting in Denmark's time zone, when you are in some other time zone. + * * <pre> - * FTPClient f=FTPClient(); - * FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX); - * conf.setServerLanguageCode("da"); - * conf.setDefaultDateFormat("d MMM yyyy"); - * conf.setRecentDateFormat("d MMM HH:mm"); - * conf.setTimeZoneId("Europe/Copenhagen"); - * f.configure(conf); - * f.connect(server); - * f.login(username, password); - * FTPListParseEngine engine = - * f.initiateListParsing("com.whatever.YourOwnParser", directory); + * FTPClient f = FTPClient(); + * FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX); + * conf.setServerLanguageCode("da"); + * conf.setDefaultDateFormat("d MMM yyyy"); + * conf.setRecentDateFormat("d MMM HH:mm"); + * conf.setTimeZoneId("Europe/Copenhagen"); + * f.configure(conf); + * f.connect(server); + * f.login(username, password); + * FTPListParseEngine engine = f.initiateListParsing("com.whatever.YourOwnParser", directory); * - * while (engine.hasNext()) { - * FTPFile[] files = engine.getNext(25); // "page size" you want - * //do whatever you want with these files, display them, etc. - * //expensive FTPFile objects not created until needed. - * } + * while (engine.hasNext()) { + * FTPFile[] files = engine.getNext(25); // "page size" you want + * // do whatever you want with these files, display them, etc. + * // expensive FTPFile objects not created until needed. + * } * </pre> * <p> - * Unpaged (whole list) access on a VMS server that uses month names - * in a language not {@link #getSupportedLanguageCodes() supported} by the system. - * but uses the "standard" <code>MMM d yyyy</code> date formatting + * Unpaged (whole list) access on a VMS server that uses month names in a language not {@link #getSupportedLanguageCodes() supported} by the system. but uses + * the "standard" <code>MMM d yyyy</code> date formatting + * * <pre> - * FTPClient f=FTPClient(); - * FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_VMS); - * conf.setShortMonthNames( - * "jan|feb|mar|apr|ma\u00ED|j\u00FAn|j\u00FAl|\u00e1g\u00FA|sep|okt|n\u00F3v|des"); - * f.configure(conf); - * f.connect(server); - * f.login(username, password); - * FTPFile[] files = listFiles(directory); + * FTPClient f = FTPClient(); + * FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_VMS); + * conf.setShortMonthNames("jan|feb|mar|apr|ma\u00ED|j\u00FAn|j\u00FAl|\u00e1g\u00FA|sep|okt|n\u00F3v|des"); + * f.configure(conf); + * f.connect(server); + * f.login(username, password); + * FTPFile[] files = listFiles(directory); * </pre> * <p> - * Unpaged (whole list) access on a Windows-NT server in a different time zone. - * (Note, since the NT Format uses numeric date formatting, language issues - * are irrelevant here). + * Unpaged (whole list) access on a Windows-NT server in a different time zone. (Note, since the NT Format uses numeric date formatting, language issues are + * irrelevant here). + * * <pre> - * FTPClient f=FTPClient(); - * FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_NT); - * conf.setTimeZoneId("America/Denver"); - * f.configure(conf); - * f.connect(server); - * f.login(username, password); - * FTPFile[] files = listFiles(directory); + * FTPClient f = FTPClient(); + * FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_NT); + * conf.setTimeZoneId("America/Denver"); + * f.configure(conf); + * f.connect(server); + * f.login(username, password); + * FTPFile[] files = listFiles(directory); * </pre> - * Unpaged (whole list) access on a Windows-NT server in a different time zone - * but which has been configured to use a unix-style listing format. + * + * Unpaged (whole list) access on a Windows-NT server in a different time zone but which has been configured to use a unix-style listing format. + * * <pre> - * FTPClient f=FTPClient(); - * FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX); - * conf.setTimeZoneId("America/Denver"); - * f.configure(conf); - * f.connect(server); - * f.login(username, password); - * FTPFile[] files = listFiles(directory); + * FTPClient f = FTPClient(); + * FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX); + * conf.setTimeZoneId("America/Denver"); + * f.configure(conf); + * f.connect(server); + * f.login(username, password); + * FTPFile[] files = listFiles(directory); * </pre> * * @since 1.4 @@ -135,213 +122,193 @@ import java.util.TreeMap; * @see org.apache.commons.net.ftp.parser.FTPTimestampParserImpl#configure(FTPClientConfig) * @see org.apache.commons.net.ftp.parser.ConfigurableFTPFileEntryParserImpl */ -public class FTPClientConfig -{ +public class FTPClientConfig { /** - * Identifier by which a unix-based ftp server is known throughout - * the commons-net ftp system. + * Identifier by which a unix-based ftp server is known throughout the commons-net ftp system. */ - public static final String SYST_UNIX = "UNIX"; + public static final String SYST_UNIX = "UNIX"; /** - * Identifier for alternate UNIX parser; same as {@link #SYST_UNIX} but leading spaces are - * trimmed from file names. This is to maintain backwards compatibility with - * the original behaviour of the parser which ignored multiple spaces between the date - * and the start of the file name. + * Identifier for alternate UNIX parser; same as {@link #SYST_UNIX} but leading spaces are trimmed from file names. This is to maintain backwards + * compatibility with the original behavior of the parser which ignored multiple spaces between the date and the start of the file name. + * * @since 3.4 */ - public static final String SYST_UNIX_TRIM_LEADING = "UNIX_LTRIM"; + public static final String SYST_UNIX_TRIM_LEADING = "UNIX_LTRIM"; /** - * Identifier by which a vms-based ftp server is known throughout - * the commons-net ftp system. + * Identifier by which a vms-based ftp server is known throughout the commons-net ftp system. */ - public static final String SYST_VMS = "VMS"; + public static final String SYST_VMS = "VMS"; /** - * Identifier by which a WindowsNT-based ftp server is known throughout - * the commons-net ftp system. + * Identifier by which a WindowsNT-based ftp server is known throughout the commons-net ftp system. */ - public static final String SYST_NT = "WINDOWS"; + public static final String SYST_NT = "WINDOWS"; /** - * Identifier by which an OS/2-based ftp server is known throughout - * the commons-net ftp system. + * Identifier by which an OS/2-based ftp server is known throughout the commons-net ftp system. */ - public static final String SYST_OS2 = "OS/2"; + public static final String SYST_OS2 = "OS/2"; /** - * Identifier by which an OS/400-based ftp server is known throughout - * the commons-net ftp system. + * Identifier by which an OS/400-based ftp server is known throughout the commons-net ftp system. */ public static final String SYST_OS400 = "OS/400"; /** - * Identifier by which an AS/400-based ftp server is known throughout - * the commons-net ftp system. + * Identifier by which an AS/400-based ftp server is known throughout the commons-net ftp system. */ public static final String SYST_AS400 = "AS/400"; /** - * Identifier by which an MVS-based ftp server is known throughout - * the commons-net ftp system. + * Identifier by which an MVS-based ftp server is known throughout the commons-net ftp system. */ public static final String SYST_MVS = "MVS"; /** - * Some servers return an "UNKNOWN Type: L8" message - * in response to the SYST command. We set these to be a Unix-type system. - * This may happen if the ftpd in question was compiled without system - * information. + * Some servers return an "UNKNOWN Type: L8" message in response to the SYST command. We set these to be a Unix-type system. This may happen if the ftpd in + * question was compiled without system information. * - * NET-230 - Updated to be UPPERCASE so that the check done in - * createFileEntryParser will succeed. + * NET-230 - Updated to be UPPERCASE so that the check done in createFileEntryParser will succeed. * * @since 1.5 */ public static final String SYST_L8 = "TYPE: L8"; /** - * Identifier by which an Netware-based ftp server is known throughout - * the commons-net ftp system. + * Identifier by which an Netware-based ftp server is known throughout the commons-net ftp system. * * @since 1.5 */ public static final String SYST_NETWARE = "NETWARE"; /** - * Identifier by which a Mac pre OS-X -based ftp server is known throughout - * the commons-net ftp system. + * Identifier by which a Mac pre OS-X -based ftp server is known throughout the commons-net ftp system. * * @since 3.1 */ // Full string is "MACOS Peter's Server"; the substring below should be enough - public static final String SYST_MACOS_PETER = "MACOS PETER"; // NET-436 + public static final String SYST_MACOS_PETER = "MACOS PETER"; // NET-436 - private final String serverSystemKey; - private String defaultDateFormatStr = null; - private String recentDateFormatStr = null; - private boolean lenientFutureDates = true; // NET-407 - private String serverLanguageCode = null; - private String shortMonthNames = null; - private String serverTimeZoneId = null; - private boolean saveUnparseableEntries = false; + private static final Map<String, Object> LANGUAGE_CODE_MAP = new TreeMap<>(); + static { + // if there are other commonly used month name encodings which + // correspond to particular locales, please add them here. + + // many locales code short names for months as all three letters + // these we handle simply. + LANGUAGE_CODE_MAP.put("en", Locale.ENGLISH); + LANGUAGE_CODE_MAP.put("de", Locale.GERMAN); + LANGUAGE_CODE_MAP.put("it", Locale.ITALIAN); + LANGUAGE_CODE_MAP.put("es", new Locale("es", "", "")); // spanish + LANGUAGE_CODE_MAP.put("pt", new Locale("pt", "", "")); // portuguese + LANGUAGE_CODE_MAP.put("da", new Locale("da", "", "")); // danish + LANGUAGE_CODE_MAP.put("sv", new Locale("sv", "", "")); // swedish + LANGUAGE_CODE_MAP.put("no", new Locale("no", "", "")); // norwegian + LANGUAGE_CODE_MAP.put("nl", new Locale("nl", "", "")); // dutch + LANGUAGE_CODE_MAP.put("ro", new Locale("ro", "", "")); // romanian + LANGUAGE_CODE_MAP.put("sq", new Locale("sq", "", "")); // albanian + LANGUAGE_CODE_MAP.put("sh", new Locale("sh", "", "")); // serbo-croatian + LANGUAGE_CODE_MAP.put("sk", new Locale("sk", "", "")); // slovak + LANGUAGE_CODE_MAP.put("sl", new Locale("sl", "", "")); // slovenian + + // some don't + LANGUAGE_CODE_MAP.put("fr", "jan|f\u00e9v|mar|avr|mai|jun|jui|ao\u00fb|sep|oct|nov|d\u00e9c"); // french + + } /** - * The main constructor for an FTPClientConfig object - * @param systemKey key representing system type of the server being - * connected to. See {@link #getServerSystemKey() serverSystemKey} - * If set to the empty string, then FTPClient uses the system type returned by the server. - * However this is not recommended for general use; - * the correct system type should be set if it is known. + * Returns a DateFormatSymbols object configured with short month names as in the supplied string + * + * @param shortmonths This should be as described in {@link #setShortMonthNames(String) shortMonthNames} + * @return a DateFormatSymbols object configured with short month names as in the supplied string */ - public FTPClientConfig(String systemKey) { - this.serverSystemKey = systemKey; + public static DateFormatSymbols getDateFormatSymbols(final String shortmonths) { + final String[] months = splitShortMonthString(shortmonths); + final DateFormatSymbols dfs = new DateFormatSymbols(Locale.US); + dfs.setShortMonths(months); + return dfs; } /** - * Convenience constructor mainly for use in testing. - * Constructs a UNIX configuration. + * Returns a Collection of all the language codes currently supported by this class. See {@link #setServerLanguageCode(String) serverLanguageCode} for a + * functional descrption of language codes within this system. + * + * @return a Collection of all the language codes currently supported by this class */ - public FTPClientConfig() { - this(SYST_UNIX); + public static Collection<String> getSupportedLanguageCodes() { + return LANGUAGE_CODE_MAP.keySet(); } /** - * Constructor which allows setting of the format string member fields - * @param systemKey key representing system type of the server being - * connected to. See - * {@link #getServerSystemKey() serverSystemKey} - * @param defaultDateFormatStr See - * {@link #setDefaultDateFormatStr(String) defaultDateFormatStr} - * @param recentDateFormatStr See - * {@link #setRecentDateFormatStr(String) recentDateFormatStr} - * @since 3.6 - */ - public FTPClientConfig(String systemKey, - String defaultDateFormatStr, - String recentDateFormatStr) - { - this(systemKey); - this.defaultDateFormatStr = defaultDateFormatStr; - this.recentDateFormatStr = recentDateFormatStr; + * Looks up the supplied language code in the internally maintained table of language codes. Returns a DateFormatSymbols object configured with short month + * names corresponding to the code. If there is no corresponding entry in the table, the object returned will be that for <code>Locale.US</code> + * + * @param languageCode See {@link #setServerLanguageCode(String) serverLanguageCode} + * @return a DateFormatSymbols object configured with short month names corresponding to the supplied code, or with month names for <code>Locale.US</code> + * if there is no corresponding entry in the internal table. + */ + public static DateFormatSymbols lookupDateFormatSymbols(final String languageCode) { + final Object lang = LANGUAGE_CODE_MAP.get(languageCode); + if (lang != null) { + if (lang instanceof Locale) { + return new DateFormatSymbols((Locale) lang); + } + if (lang instanceof String) { + return getDateFormatSymbols((String) lang); + } + } + return new DateFormatSymbols(Locale.US); } - /** - * Constructor which allows setting of most member fields - * @param systemKey key representing system type of the server being - * connected to. See - * {@link #getServerSystemKey() serverSystemKey} - * @param defaultDateFormatStr See - * {@link #setDefaultDateFormatStr(String) defaultDateFormatStr} - * @param recentDateFormatStr See - * {@link #setRecentDateFormatStr(String) recentDateFormatStr} - * @param serverLanguageCode See - * {@link #setServerLanguageCode(String) serverLanguageCode} - * @param shortMonthNames See - * {@link #setShortMonthNames(String) shortMonthNames} - * @param serverTimeZoneId See - * {@link #setServerTimeZoneId(String) serverTimeZoneId} - */ - public FTPClientConfig(String systemKey, - String defaultDateFormatStr, - String recentDateFormatStr, - String serverLanguageCode, - String shortMonthNames, - String serverTimeZoneId) - { - this(systemKey); - this.defaultDateFormatStr = defaultDateFormatStr; - this.recentDateFormatStr = recentDateFormatStr; - this.serverLanguageCode = serverLanguageCode; - this.shortMonthNames = shortMonthNames; - this.serverTimeZoneId = serverTimeZoneId; + private static String[] splitShortMonthString(final String shortmonths) { + final StringTokenizer st = new StringTokenizer(shortmonths, "|"); + final int monthcnt = st.countTokens(); + if (12 != monthcnt) { + throw new IllegalArgumentException("expecting a pipe-delimited string containing 12 tokens"); + } + final String[] months = new String[13]; + int pos = 0; + while (st.hasMoreTokens()) { + months[pos++] = st.nextToken(); + } + months[pos] = ""; + return months; } + private final String serverSystemKey; + private String defaultDateFormatStr; + + private String recentDateFormatStr; + + private boolean lenientFutureDates = true; // NET-407 + + private String serverLanguageCode; + + private String shortMonthNames; + + private String serverTimeZoneId; + + private boolean saveUnparseableEntries; + /** - * Constructor which allows setting of all member fields - * @param systemKey key representing system type of the server being - * connected to. See - * {@link #getServerSystemKey() serverSystemKey} - * @param defaultDateFormatStr See - * {@link #setDefaultDateFormatStr(String) defaultDateFormatStr} - * @param recentDateFormatStr See - * {@link #setRecentDateFormatStr(String) recentDateFormatStr} - * @param serverLanguageCode See - * {@link #setServerLanguageCode(String) serverLanguageCode} - * @param shortMonthNames See - * {@link #setShortMonthNames(String) shortMonthNames} - * @param serverTimeZoneId See - * {@link #setServerTimeZoneId(String) serverTimeZoneId} - * @param lenientFutureDates See - * {@link #setLenientFutureDates(boolean) lenientFutureDates} - * @param saveUnparseableEntries See - * {@link #setUnparseableEntries(boolean) saveUnparseableEntries} - */ - public FTPClientConfig(String systemKey, - String defaultDateFormatStr, - String recentDateFormatStr, - String serverLanguageCode, - String shortMonthNames, - String serverTimeZoneId, - boolean lenientFutureDates, - boolean saveUnparseableEntries) - { - this(systemKey); - this.defaultDateFormatStr = defaultDateFormatStr; - this.lenientFutureDates = lenientFutureDates; - this.recentDateFormatStr = recentDateFormatStr; - this.saveUnparseableEntries = saveUnparseableEntries; - this.serverLanguageCode = serverLanguageCode; - this.shortMonthNames = shortMonthNames; - this.serverTimeZoneId = serverTimeZoneId; + * Convenience constructor mainly for use in testing. Constructs a UNIX configuration. + */ + public FTPClientConfig() { + this(SYST_UNIX); } - // Copy constructor, intended for use by FTPClient only - FTPClientConfig(String systemKey, FTPClientConfig config) { - this.serverSystemKey = systemKey; + /** + * Copy constructor + * + * @param config source + * @since 3.6 + */ + public FTPClientConfig(final FTPClientConfig config) { + this.serverSystemKey = config.serverSystemKey; this.defaultDateFormatStr = config.defaultDateFormatStr; this.lenientFutureDates = config.lenientFutureDates; this.recentDateFormatStr = config.recentDateFormatStr; @@ -352,12 +319,19 @@ public class FTPClientConfig } /** - * Copy constructor - * @param config source - * @since 3.6 + * The main constructor for an FTPClientConfig object + * + * @param systemKey key representing system type of the server being connected to. See {@link #getServerSystemKey() serverSystemKey} If set to the empty + * string, then FTPClient uses the system type returned by the server. However this is not recommended for general use; the correct system + * type should be set if it is known. */ - public FTPClientConfig(FTPClientConfig config) { - this.serverSystemKey = config.serverSystemKey; + public FTPClientConfig(final String systemKey) { + this.serverSystemKey = systemKey; + } + + // Copy constructor, intended for use by FTPClient only + FTPClientConfig(final String systemKey, final FTPClientConfig config) { + this.serverSystemKey = systemKey; this.defaultDateFormatStr = config.defaultDateFormatStr; this.lenientFutureDates = config.lenientFutureDates; this.recentDateFormatStr = config.recentDateFormatStr; @@ -367,54 +341,67 @@ public class FTPClientConfig this.shortMonthNames = config.shortMonthNames; } - private static final Map<String, Object> LANGUAGE_CODE_MAP = new TreeMap<String, Object>(); - static { - - // if there are other commonly used month name encodings which - // correspond to particular locales, please add them here. - - - - // many locales code short names for months as all three letters - // these we handle simply. - LANGUAGE_CODE_MAP.put("en", Locale.ENGLISH); - LANGUAGE_CODE_MAP.put("de",Locale.GERMAN); - LANGUAGE_CODE_MAP.put("it",Locale.ITALIAN); - LANGUAGE_CODE_MAP.put("es", new Locale("es", "", "")); // spanish - LANGUAGE_CODE_MAP.put("pt", new Locale("pt", "", "")); // portuguese - LANGUAGE_CODE_MAP.put("da", new Locale("da", "", "")); // danish - LANGUAGE_CODE_MAP.put("sv", new Locale("sv", "", "")); // swedish - LANGUAGE_CODE_MAP.put("no", new Locale("no", "", "")); // norwegian - LANGUAGE_CODE_MAP.put("nl", new Locale("nl", "", "")); // dutch - LANGUAGE_CODE_MAP.put("ro", new Locale("ro", "", "")); // romanian - LANGUAGE_CODE_MAP.put("sq", new Locale("sq", "", "")); // albanian - LANGUAGE_CODE_MAP.put("sh", new Locale("sh", "", "")); // serbo-croatian - LANGUAGE_CODE_MAP.put("sk", new Locale("sk", "", "")); // slovak - LANGUAGE_CODE_MAP.put("sl", new Locale("sl", "", "")); // slovenian - - - // some don't - LANGUAGE_CODE_MAP.put("fr", - "jan|f\u00e9v|mar|avr|mai|jun|jui|ao\u00fb|sep|oct|nov|d\u00e9c"); //french + /** + * Constructor which allows setting of the format string member fields + * + * @param systemKey key representing system type of the server being connected to. See {@link #getServerSystemKey() serverSystemKey} + * @param defaultDateFormatStr See {@link #setDefaultDateFormatStr(String) defaultDateFormatStr} + * @param recentDateFormatStr See {@link #setRecentDateFormatStr(String) recentDateFormatStr} + * @since 3.6 + */ + public FTPClientConfig(final String systemKey, final String defaultDateFormatStr, final String recentDateFormatStr) { + this(systemKey); + this.defaultDateFormatStr = defaultDateFormatStr; + this.recentDateFormatStr = recentDateFormatStr; + } + /** + * Constructor which allows setting of most member fields + * + * @param systemKey key representing system type of the server being connected to. See {@link #getServerSystemKey() serverSystemKey} + * @param defaultDateFormatStr See {@link #setDefaultDateFormatStr(String) defaultDateFormatStr} + * @param recentDateFormatStr See {@link #setRecentDateFormatStr(String) recentDateFormatStr} + * @param serverLanguageCode See {@link #setServerLanguageCode(String) serverLanguageCode} + * @param shortMonthNames See {@link #setShortMonthNames(String) shortMonthNames} + * @param serverTimeZoneId See {@link #setServerTimeZoneId(String) serverTimeZoneId} + */ + public FTPClientConfig(final String systemKey, final String defaultDateFormatStr, final String recentDateFormatStr, final String serverLanguageCode, + final String shortMonthNames, final String serverTimeZoneId) { + this(systemKey); + this.defaultDateFormatStr = defaultDateFormatStr; + this.recentDateFormatStr = recentDateFormatStr; + this.serverLanguageCode = serverLanguageCode; + this.shortMonthNames = shortMonthNames; + this.serverTimeZoneId = serverTimeZoneId; } /** - * Getter for the serverSystemKey property. This property - * specifies the general type of server to which the client connects. - * Should be either one of the <code>FTPClientConfig.SYST_*</code> codes - * or else the fully qualified class name of a parser implementing both - * the <code>FTPFileEntryParser</code> and <code>Configurable</code> - * interfaces. - * @return Returns the serverSystemKey property. - */ - public String getServerSystemKey() { - return serverSystemKey; + * Constructor which allows setting of all member fields + * + * @param systemKey key representing system type of the server being connected to. See {@link #getServerSystemKey() serverSystemKey} + * @param defaultDateFormatStr See {@link #setDefaultDateFormatStr(String) defaultDateFormatStr} + * @param recentDateFormatStr See {@link #setRecentDateFormatStr(String) recentDateFormatStr} + * @param serverLanguageCode See {@link #setServerLanguageCode(String) serverLanguageCode} + * @param shortMonthNames See {@link #setShortMonthNames(String) shortMonthNames} + * @param serverTimeZoneId See {@link #setServerTimeZoneId(String) serverTimeZoneId} + * @param lenientFutureDates See {@link #setLenientFutureDates(boolean) lenientFutureDates} + * @param saveUnparseableEntries See {@link #setUnparseableEntries(boolean) saveUnparseableEntries} + */ + public FTPClientConfig(final String systemKey, final String defaultDateFormatStr, final String recentDateFormatStr, final String serverLanguageCode, + final String shortMonthNames, final String serverTimeZoneId, final boolean lenientFutureDates, final boolean saveUnparseableEntries) { + this(systemKey); + this.defaultDateFormatStr = defaultDateFormatStr; + this.lenientFutureDates = lenientFutureDates; + this.recentDateFormatStr = recentDateFormatStr; + this.saveUnparseableEntries = saveUnparseableEntries; + this.serverLanguageCode = serverLanguageCode; + this.shortMonthNames = shortMonthNames; + this.serverTimeZoneId = serverTimeZoneId; } /** - * getter for the {@link #setDefaultDateFormatStr(String) defaultDateFormatStr} - * property. + * getter for the {@link #setDefaultDateFormatStr(String) defaultDateFormatStr} property. + * * @return Returns the defaultDateFormatStr property. */ public String getDefaultDateFormatStr() { @@ -422,7 +409,8 @@ public class FTPClientConfig } /** - * getter for the {@link #setRecentDateFormatStr(String) recentDateFormatStr} property. + * getter for the {@link #setRecentDateFormatStr(String) recentDateFormatStr} property. + * * @return Returns the recentDateFormatStr property. */ @@ -431,7 +419,30 @@ public class FTPClientConfig } /** - * getter for the {@link #setServerTimeZoneId(String) serverTimeZoneId} property. + * <p> + * getter for the {@link #setServerLanguageCode(String) serverLanguageCode} property. + * </p> + * + * @return Returns the serverLanguageCode property. + */ + public String getServerLanguageCode() { + return serverLanguageCode; + } + + /** + * Getter for the serverSystemKey property. This property specifies the general type of server to which the client connects. Should be either one of the + * <code>FTPClientConfig.SYST_*</code> codes or else the fully qualified class name of a parser implementing both the <code>FTPFileEntryParser</code> and + * <code>Configurable</code> interfaces. + * + * @return Returns the serverSystemKey property. + */ + public String getServerSystemKey() { + return serverSystemKey; + } + + /** + * getter for the {@link #setServerTimeZoneId(String) serverTimeZoneId} property. + * * @return Returns the serverTimeZoneId property. */ public String getServerTimeZoneId() { @@ -440,9 +451,9 @@ public class FTPClientConfig /** * <p> - * getter for the {@link #setShortMonthNames(String) shortMonthNames} - * property. + * getter for the {@link #setShortMonthNames(String) shortMonthNames} property. * </p> + * * @return Returns the shortMonthNames. */ public String getShortMonthNames() { @@ -450,260 +461,152 @@ public class FTPClientConfig } /** - * <p> - * getter for the {@link #setServerLanguageCode(String) serverLanguageCode} property. - * </p> - * @return Returns the serverLanguageCode property. + * @return true if list parsing should return FTPFile entries even for unparseable response lines + * <p> + * If true, the FTPFile for any unparseable entries will contain only the unparsed entry {@link FTPFile#getRawListing()} and + * {@link FTPFile#isValid()} will return {@code false} + * @since 3.4 */ - public String getServerLanguageCode() { - return serverLanguageCode; + public boolean getUnparseableEntries() { + return this.saveUnparseableEntries; } /** * <p> - * getter for the {@link #setLenientFutureDates(boolean) lenientFutureDates} property. + * getter for the {@link #setLenientFutureDates(boolean) lenientFutureDates} property. * </p> - * @return Returns the lenientFutureDates. + * + * @return Returns the lenientFutureDates (default true). * @since 1.5 */ public boolean isLenientFutureDates() { return lenientFutureDates; } + /** * <p> - * setter for the defaultDateFormatStr property. This property - * specifies the main date format that will be used by a parser configured - * by this configuration to parse file timestamps. If this is not - * specified, such a parser will use as a default value, the most commonly - * used format which will be in as used in <code>en_US</code> locales. - * </p><p> - * This should be in the format described for - * <code>java.text.SimpleDateFormat</code>. - * property. + * setter for the defaultDateFormatStr property. This property specifies the main date format that will be used by a parser configured by this configuration + * to parse file timestamps. If this is not specified, such a parser will use as a default value, the most commonly used format which will be in as used in + * <code>en_US</code> locales. + * </p> + * <p> + * This should be in the format described for <code>java.text.SimpleDateFormat</code>. property. * </p> + * * @param defaultDateFormatStr The defaultDateFormatStr to set. */ - public void setDefaultDateFormatStr(String defaultDateFormatStr) { + public void setDefaultDateFormatStr(final String defaultDateFormatStr) { this.defaultDateFormatStr = defaultDateFormatStr; } /** * <p> - * setter for the recentDateFormatStr property. This property - * specifies a secondary date format that will be used by a parser - * configured by this configuration to parse file timestamps, typically - * those less than a year old. If this is not specified, such a parser - * will not attempt to parse using an alternate format. + * setter for the lenientFutureDates property. This boolean property (default: true) only has meaning when a {@link #setRecentDateFormatStr(String) + * recentDateFormatStr} property has been set. In that case, if this property is set true, then the parser, when it encounters a listing parseable with the + * recent date format, will only consider a date to belong to the previous year if it is more than one day in the future. This will allow all out-of-synch + * situations (whether based on "slop" - i.e. servers simply out of synch with one another or because of time zone differences - but in the latter case it + * is highly recommended to use the {@link #setServerTimeZoneId(String) serverTimeZoneId} property instead) to resolve correctly. * </p> * <p> * This is used primarily in unix-based systems. * </p> - * <p> - * This should be in the format described for - * <code>java.text.SimpleDateFormat</code>. - * </p> - * @param recentDateFormatStr The recentDateFormatStr to set. + * + * @param lenientFutureDates set true to compensate for out-of-synch conditions. */ - public void setRecentDateFormatStr(String recentDateFormatStr) { - this.recentDateFormatStr = recentDateFormatStr; + public void setLenientFutureDates(final boolean lenientFutureDates) { + this.lenientFutureDates = lenientFutureDates; } /** * <p> - * setter for the lenientFutureDates property. This boolean property - * (default: false) only has meaning when a - * {@link #setRecentDateFormatStr(String) recentDateFormatStr} property - * has been set. In that case, if this property is set true, then the - * parser, when it encounters a listing parseable with the recent date - * format, will only consider a date to belong to the previous year if - * it is more than one day in the future. This will allow all - * out-of-synch situations (whether based on "slop" - i.e. servers simply - * out of synch with one another or because of time zone differences - - * but in the latter case it is highly recommended to use the - * {@link #setServerTimeZoneId(String) serverTimeZoneId} property - * instead) to resolve correctly. - * </p><p> - * This is used primarily in unix-based systems. + * setter for the recentDateFormatStr property. This property specifies a secondary date format that will be used by a parser configured by this + * configuration to parse file timestamps, typically those less than a year old. If this is not specified, such a parser will not attempt to parse using an + * alternate format. * </p> - * @param lenientFutureDates set true to compensate for out-of-synch - * conditions. - */ - public void setLenientFutureDates(boolean lenientFutureDates) { - this.lenientFutureDates = lenientFutureDates; - } - /** * <p> - * setter for the serverTimeZoneId property. This property - * allows a time zone to be specified corresponding to that known to be - * used by an FTP server in file listings. This might be particularly - * useful to clients such as Ant that try to use these timestamps for - * dependency checking. - * </p><p> - * This should be one of the identifiers used by - * <code>java.util.TimeZone</code> to refer to time zones, for example, - * <code>America/Chicago</code> or <code>Asia/Rangoon</code>. + * This is used primarily in unix-based systems. * </p> - * @param serverTimeZoneId The serverTimeZoneId to set. - */ - public void setServerTimeZoneId(String serverTimeZoneId) { - this.serverTimeZoneId = serverTimeZoneId; - } - - /** * <p> - * setter for the shortMonthNames property. - * This property allows the user to specify a set of month names - * used by the server that is different from those that may be - * specified using the {@link #setServerLanguageCode(String) serverLanguageCode} - * property. - * </p><p> - * This should be a string containing twelve strings each composed of - * three characters, delimited by pipe (|) characters. Currently, - * only 8-bit ASCII characters are known to be supported. For example, - * a set of month names used by a hypothetical Icelandic FTP server might - * conceivably be specified as - * <code>"jan|feb|mar|apr|maí|jún|júl|ágú|sep|okt|nóv|des"</code>. + * This should be in the format described for <code>java.text.SimpleDateFormat</code>. * </p> - * @param shortMonthNames The value to set to the shortMonthNames property. + * + * @param recentDateFormatStr The recentDateFormatStr to set. */ - public void setShortMonthNames(String shortMonthNames) { - this.shortMonthNames = shortMonthNames; + public void setRecentDateFormatStr(final String recentDateFormatStr) { + this.recentDateFormatStr = recentDateFormatStr; } /** * <p> - * setter for the serverLanguageCode property. This property allows - * user to specify a - * <a href="http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt"> - * two-letter ISO-639 language code</a> that will be used to - * configure the set of month names used by the file timestamp parser. - * If neither this nor the {@link #setShortMonthNames(String) shortMonthNames} - * is specified, parsing will assume English month names, which may or - * may not be significant, depending on whether the date format(s) - * specified via {@link #setDefaultDateFormatStr(String) defaultDateFormatStr} - * and/or {@link #setRecentDateFormatStr(String) recentDateFormatStr} are using - * numeric or alphabetic month names. + * setter for the serverLanguageCode property. This property allows user to specify a <a href="http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt"> + * two-letter ISO-639 language code</a> that will be used to configure the set of month names used by the file timestamp parser. If neither this nor the + * {@link #setShortMonthNames(String) shortMonthNames} is specified, parsing will assume English month names, which may or may not be significant, depending + * on whether the date format(s) specified via {@link #setDefaultDateFormatStr(String) defaultDateFormatStr} and/or {@link #setRecentDateFormatStr(String) + * recentDateFormatStr} are using numeric or alphabetic month names. * </p> - * <p>If the code supplied is not supported here, <code>en_US</code> - * month names will be used. We are supporting here those language - * codes which, when a <code> java.util.Locale</code> is constucted - * using it, and a <code>java.text.SimpleDateFormat</code> is - * constructed using that Locale, the array returned by the - * SimpleDateFormat's <code>getShortMonths()</code> method consists - * solely of three 8-bit ASCII character strings. Additionally, - * languages which do not meet this requirement are included if a - * common alternative set of short month names is known to be used. - * This means that users who can tell us of additional such encodings - * may get them added to the list of supported languages by contacting - * the Apache Commons Net team. + * <p> + * If the code supplied is not supported here, <code>en_US</code> month names will be used. We are supporting here those language codes which, when a + * <code> java.util.Locale</code> is constucted using it, and a <code>java.text.SimpleDateFormat</code> is constructed using that Locale, the array returned + * by the SimpleDateFormat's <code>getShortMonths()</code> method consists solely of three 8-bit ASCII character strings. Additionally, languages which do + * not meet this requirement are included if a common alternative set of short month names is known to be used. This means that users who can tell us of + * additional such encodings may get them added to the list of supported languages by contacting the Apache Commons Net team. * </p> - * <p><strong> - * Please note that this attribute will NOT be used to determine a - * locale-based date format for the language. </strong> - * Experience has shown that many if not most FTP servers outside the - * United States employ the standard <code>en_US</code> date format - * orderings of <code>MMM d yyyy</code> and <code>MMM d HH:mm</code> - * and attempting to deduce this automatically here would cause more - * problems than it would solve. The date format must be changed - * via the {@link #setDefaultDateFormatStr(String) defaultDateFormatStr} and/or - * {@link #setRecentDateFormatStr(String) recentDateFormatStr} parameters. + * <p> + * <strong> Please note that this attribute will NOT be used to determine a locale-based date format for the language. </strong> Experience has shown that + * many if not most FTP servers outside the United States employ the standard <code>en_US</code> date format orderings of <code>MMM d yyyy</code> and + * <code>MMM d HH:mm</code> and attempting to deduce this automatically here would cause more problems than it would solve. The date format must be changed + * via the {@link #setDefaultDateFormatStr(String) defaultDateFormatStr} and/or {@link #setRecentDateFormatStr(String) recentDateFormatStr} parameters. * </p> + * * @param serverLanguageCode The value to set to the serverLanguageCode property. */ - public void setServerLanguageCode(String serverLanguageCode) { + public void setServerLanguageCode(final String serverLanguageCode) { this.serverLanguageCode = serverLanguageCode; } /** - * Looks up the supplied language code in the internally maintained table of - * language codes. Returns a DateFormatSymbols object configured with - * short month names corresponding to the code. If there is no corresponding - * entry in the table, the object returned will be that for - * <code>Locale.US</code> - * @param languageCode See {@link #setServerLanguageCode(String) serverLanguageCode} - * @return a DateFormatSymbols object configured with short month names - * corresponding to the supplied code, or with month names for - * <code>Locale.US</code> if there is no corresponding entry in the internal - * table. - */ - public static DateFormatSymbols lookupDateFormatSymbols(String languageCode) - { - Object lang = LANGUAGE_CODE_MAP.get(languageCode); - if (lang != null) { - if (lang instanceof Locale) { - return new DateFormatSymbols((Locale) lang); - } else if (lang instanceof String){ - return getDateFormatSymbols((String) lang); - } - } - return new DateFormatSymbols(Locale.US); - } - - /** - * Returns a DateFormatSymbols object configured with short month names - * as in the supplied string - * @param shortmonths This should be as described in - * {@link #setShortMonthNames(String) shortMonthNames} - * @return a DateFormatSymbols object configured with short month names - * as in the supplied string + * <p> + * setter for the serverTimeZoneId property. This property allows a time zone to be specified corresponding to that known to be used by an FTP server in + * file listings. This might be particularly useful to clients such as Ant that try to use these timestamps for dependency checking. + * </p> + * <p> + * This should be one of the identifiers used by <code>java.util.TimeZone</code> to refer to time zones, for example, <code>America/Chicago</code> or + * <code>Asia/Rangoon</code>. + * </p> + * + * @param serverTimeZoneId The serverTimeZoneId to set. */ - public static DateFormatSymbols getDateFormatSymbols(String shortmonths) - { - String[] months = splitShortMonthString(shortmonths); - DateFormatSymbols dfs = new DateFormatSymbols(Locale.US); - dfs.setShortMonths(months); - return dfs; - } - - private static String[] splitShortMonthString(String shortmonths) { - StringTokenizer st = new StringTokenizer(shortmonths, "|"); - int monthcnt = st.countTokens(); - if (12 != monthcnt) { - throw new IllegalArgumentException( - "expecting a pipe-delimited string containing 12 tokens"); - } - String[] months = new String[13]; - int pos = 0; - while(st.hasMoreTokens()) { - months[pos++] = st.nextToken(); - } - months[pos]=""; - return months; + public void setServerTimeZoneId(final String serverTimeZoneId) { + this.serverTimeZoneId = serverTimeZoneId; } /** - * Returns a Collection of all the language codes currently supported - * by this class. See {@link #setServerLanguageCode(String) serverLanguageCode} - * for a functional descrption of language codes within this system. + * <p> + * setter for the shortMonthNames property. This property allows the user to specify a set of month names used by the server that is different from those + * that may be specified using the {@link #setServerLanguageCode(String) serverLanguageCode} property. + * </p> + * <p> + * This should be a string containing twelve strings each composed of three characters, delimited by pipe (|) characters. Currently, only 8-bit ASCII + * characters are known to be supported. For example, a set of month names used by a hypothetical Icelandic FTP server might conceivably be specified as + * <code>"jan|feb|mar|apr|maí|jún|júl|ágú|sep|okt|nóv|des"</code>. + * </p> * - * @return a Collection of all the language codes currently supported - * by this class + * @param shortMonthNames The value to set to the shortMonthNames property. */ - public static Collection<String> getSupportedLanguageCodes() { - return LANGUAGE_CODE_MAP.keySet(); + public void setShortMonthNames(final String shortMonthNames) { + this.shortMonthNames = shortMonthNames; } /** * Allow list parsing methods to create basic FTPFile entries if parsing fails. * <p> - * In this case, the FTPFile will contain only the unparsed entry {@link FTPFile#getRawListing()} - * and {@link FTPFile#isValid()} will return {@code false} + * In this case, the FTPFile will contain only the unparsed entry {@link FTPFile#getRawListing()} and {@link FTPFile#isValid()} will return {@code false} + * * @param saveUnparseable if true, then create FTPFile entries if parsing fails * @since 3.4 */ - public void setUnparseableEntries(boolean saveUnparseable) { + public void setUnparseableEntries(final boolean saveUnparseable) { this.saveUnparseableEntries = saveUnparseable; } - /** - * @return true if list parsing should return FTPFile entries even for unparseable response lines - * <p> - * If true, the FTPFile for any unparseable entries will contain only the unparsed entry - * {@link FTPFile#getRawListing()} and {@link FTPFile#isValid()} will return {@code false} - * @since 3.4 - */ - public boolean getUnparseableEntries() { - return this.saveUnparseableEntries; - } - } diff --git a/src/main/java/org/apache/commons/net/ftp/FTPCmd.java b/src/main/java/org/apache/commons/net/ftp/FTPCmd.java index bd020ed..ed58aa5 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPCmd.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPCmd.java @@ -19,50 +19,13 @@ package org.apache.commons.net.ftp; /** -* @since 3.3 + * @since 3.3 */ public enum FTPCmd { - ABOR, - ACCT, - ALLO, - APPE, - CDUP, - CWD, - DELE, - EPRT, - EPSV, - FEAT, - HELP, - LIST, - MDTM, - MFMT, - MKD, - MLSD, - MLST, - MODE, - NLST, - NOOP, - PASS, - PASV, - PORT, - PWD, - QUIT, - REIN, - REST, - RETR, - RMD, - RNFR, - RNTO, - SITE, - SMNT, - STAT, - STOR, - STOU, - STRU, - SYST, - TYPE, - USER, - ; + ABOR, ACCT, ALLO, APPE, CDUP, CWD, DELE, EPRT, EPSV, FEAT, HELP, LIST, MDTM, MFMT, MKD, MLSD, MLST, MODE, NLST, NOOP, PASS, PASV, PORT, PWD, QUIT, REIN, + REST, RETR, RMD, RNFR, RNTO, SITE, + /** @since 3.7 */ + SIZE, SMNT, STAT, STOR, STOU, STRU, SYST, TYPE, USER,; // Aliases @@ -102,14 +65,11 @@ public enum FTPCmd { public static final FTPCmd USERNAME = USER; /** - * Retrieve the FTP protocol command string corresponding to a specified - * command code. + * Retrieve the FTP protocol command string corresponding to a specified command code. * - * @return The FTP protcol command string corresponding to a specified - * command code. + * @return The FTP protcol command string corresponding to a specified command code. */ - public final String getCommand() - { + public final String getCommand() { return this.name(); } diff --git a/src/main/java/org/apache/commons/net/ftp/FTPCommand.java b/src/main/java/org/apache/commons/net/ftp/FTPCommand.java index c5739d8..740cdfa 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPCommand.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPCommand.java @@ -18,18 +18,14 @@ package org.apache.commons.net.ftp; /** - * FTPCommand stores a set of constants for FTP command codes. To interpret - * the meaning of the codes, familiarity with RFC 959 is assumed. - * The mnemonic constant names are transcriptions from the code descriptions - * of RFC 959. For those who think in terms of the actual FTP commands, - * a set of constants such as {@link #USER USER } are provided - * where the constant name is the same as the FTP command. + * FTPCommand stores a set of constants for FTP command codes. To interpret the meaning of the codes, familiarity with RFC 959 is assumed. The mnemonic constant + * names are transcriptions from the code descriptions of RFC 959. For those who think in terms of the actual FTP commands, a set of constants such as + * {@link #USER USER } are provided where the constant name is the same as the FTP command. * * @deprecated use {@link FTPCmd} instead */ @Deprecated -public final class FTPCommand -{ +public final class FTPCommand { public static final int USER = 0; public static final int PASS = 1; @@ -76,13 +72,15 @@ public final class FTPCommand public static final int EPRT = 37; /** - * Machine parseable list for a directory + * Machine parseable list for a directory + * * @since 3.0 */ public static final int MLSD = 38; /** * Machine parseable list for a single file + * * @since 3.0 */ public static final int MLST = 39; @@ -116,13 +114,13 @@ public final class FTPCommand public static final int REMOVE_DIRECTORY = RMD; public static final int MAKE_DIRECTORY = MKD; public static final int PRINT_WORKING_DIRECTORY = PWD; - // public static final int LIST = LIST; + // public static final int LIST = LIST; public static final int NAME_LIST = NLST; public static final int SITE_PARAMETERS = SITE; public static final int SYSTEM = SYST; public static final int STATUS = STAT; - //public static final int HELP = HELP; - //public static final int NOOP = NOOP; + // public static final int HELP = HELP; + // public static final int NOOP = NOOP; /** @since 2.0 */ public static final int MOD_TIME = MDTM; @@ -134,38 +132,29 @@ public final class FTPCommand /** @since 2.2 */ public static final int SET_MOD_TIME = MFMT; - // Cannot be instantiated - private FTPCommand() - {} - - private static final String[] _commands = { - "USER", "PASS", "ACCT", "CWD", "CDUP", "SMNT", "REIN", "QUIT", "PORT", - "PASV", "TYPE", "STRU", "MODE", "RETR", "STOR", "STOU", "APPE", "ALLO", - "REST", "RNFR", "RNTO", "ABOR", "DELE", "RMD", "MKD", "PWD", "LIST", - "NLST", "SITE", "SYST", "STAT", "HELP", "NOOP", "MDTM", "FEAT", "MFMT", - "EPSV", "EPRT", "MLSD", "MLST" }; - - + private static final String[] COMMANDS = { "USER", "PASS", "ACCT", "CWD", "CDUP", "SMNT", "REIN", "QUIT", "PORT", "PASV", "TYPE", "STRU", "MODE", "RETR", + "STOR", "STOU", "APPE", "ALLO", "REST", "RNFR", "RNTO", "ABOR", "DELE", "RMD", "MKD", "PWD", "LIST", "NLST", "SITE", "SYST", "STAT", "HELP", "NOOP", + "MDTM", "FEAT", "MFMT", "EPSV", "EPRT", "MLSD", "MLST" }; // default access needed for Unit test - static void checkArray(){ - int expectedLength = LAST+1; - if (_commands.length != expectedLength) { - throw new RuntimeException("Incorrect _commands array. Should have length " - +expectedLength+" found "+_commands.length); + static void checkArray() { + final int expectedLength = LAST + 1; + if (COMMANDS.length != expectedLength) { + throw new RuntimeException("Incorrect _commands array. Should have length " + expectedLength + " found " + COMMANDS.length); } } /** - * Retrieve the FTP protocol command string corresponding to a specified - * command code. + * Retrieve the FTP protocol command string corresponding to a specified command code. * * @param command The command code. - * @return The FTP protcol command string corresponding to a specified - * command code. + * @return The FTP protcol command string corresponding to a specified command code. */ - public static final String getCommand(int command) - { - return _commands[command]; + public static String getCommand(final int command) { + return COMMANDS[command]; + } + + // Cannot be instantiated + private FTPCommand() { } } diff --git a/src/main/java/org/apache/commons/net/ftp/FTPConnectionClosedException.java b/src/main/java/org/apache/commons/net/ftp/FTPConnectionClosedException.java index 079f088..2fb44ce 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPConnectionClosedException.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPConnectionClosedException.java @@ -16,39 +16,32 @@ */ package org.apache.commons.net.ftp; + import java.io.IOException; -/*** - * FTPConnectionClosedException is used to indicate the premature or - * unexpected closing of an FTP connection resulting from a - * {@link org.apache.commons.net.ftp.FTPReply#SERVICE_NOT_AVAILABLE FTPReply.SERVICE_NOT_AVAILABLE } - * response (FTP reply code 421) to a - * failed FTP command. This exception is derived from IOException and - * therefore may be caught either as an IOException or specifically as an - * FTPConnectionClosedException. +/** + * FTPConnectionClosedException is used to indicate the premature or unexpected closing of an FTP connection resulting from a + * {@link org.apache.commons.net.ftp.FTPReply#SERVICE_NOT_AVAILABLE FTPReply.SERVICE_NOT_AVAILABLE } response (FTP reply code 421) to a failed FTP command. This + * exception is derived from IOException and therefore may be caught either as an IOException or specifically as an FTPConnectionClosedException. * * @see FTP * @see FTPClient - ***/ + */ -public class FTPConnectionClosedException extends IOException -{ +public class FTPConnectionClosedException extends IOException { private static final long serialVersionUID = 3500547241659379952L; - /*** Constructs a FTPConnectionClosedException with no message ***/ - public FTPConnectionClosedException() - { - super(); + /** Constructs a FTPConnectionClosedException with no message */ + public FTPConnectionClosedException() { } - /*** + /** * Constructs a FTPConnectionClosedException with a specified message. * - * @param message The message explaining the reason for the exception. - ***/ - public FTPConnectionClosedException(String message) - { + * @param message The message explaining the reason for the exception. + */ + public FTPConnectionClosedException(final String message) { super(message); } diff --git a/src/main/java/org/apache/commons/net/ftp/FTPFile.java b/src/main/java/org/apache/commons/net/ftp/FTPFile.java index b208e27..19beda7 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPFile.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPFile.java @@ -16,521 +16,466 @@ */ package org.apache.commons.net.ftp; + import java.io.Serializable; +import java.time.Instant; import java.util.Calendar; import java.util.Date; import java.util.Formatter; import java.util.TimeZone; -/*** - * The FTPFile class is used to represent information about files stored - * on an FTP server. +/** + * The FTPFile class is used to represent information about files stored on an FTP server. * * @see FTPFileEntryParser * @see FTPClient#listFiles - ***/ + */ +public class FTPFile implements Serializable { -public class FTPFile implements Serializable -{ private static final long serialVersionUID = 9010790363003271996L; - /** A constant indicating an FTPFile is a file. ***/ + /** A constant indicating an FTPFile is a file. */ public static final int FILE_TYPE = 0; - /** A constant indicating an FTPFile is a directory. ***/ + + /** A constant indicating an FTPFile is a directory. */ public static final int DIRECTORY_TYPE = 1; - /** A constant indicating an FTPFile is a symbolic link. ***/ + + /** A constant indicating an FTPFile is a symbolic link. */ public static final int SYMBOLIC_LINK_TYPE = 2; - /** A constant indicating an FTPFile is of unknown type. ***/ + + /** A constant indicating an FTPFile is of unknown type. */ public static final int UNKNOWN_TYPE = 3; - /** A constant indicating user access permissions. ***/ + /** A constant indicating user access permissions. */ public static final int USER_ACCESS = 0; - /** A constant indicating group access permissions. ***/ + + /** A constant indicating group access permissions. */ public static final int GROUP_ACCESS = 1; - /** A constant indicating world access permissions. ***/ + + /** A constant indicating world access permissions. */ public static final int WORLD_ACCESS = 2; - /** A constant indicating file/directory read permission. ***/ + /** A constant indicating file/directory read permission. */ public static final int READ_PERMISSION = 0; - /** A constant indicating file/directory write permission. ***/ + + /** A constant indicating file/directory write permission. */ public static final int WRITE_PERMISSION = 1; /** - * A constant indicating file execute permission or directory listing - * permission. - ***/ + * A constant indicating file execute permission or directory listing permission. + */ public static final int EXECUTE_PERMISSION = 2; - private int _type, _hardLinkCount; - private long _size; - private String _rawListing, _user, _group, _name, _link; - private Calendar _date; - // If this is null, then list entry parsing failed - private final boolean[] _permissions[]; // e.g. _permissions[USER_ACCESS][READ_PERMISSION] - - /*** Creates an empty FTPFile. ***/ - public FTPFile() - { - _permissions = new boolean[3][3]; - _type = UNKNOWN_TYPE; - // init these to values that do not occur in listings - // so can distinguish which fields are unset - _hardLinkCount = 0; // 0 is invalid as a link count - _size = -1; // 0 is valid, so use -1 - _user = ""; - _group = ""; - _date = null; - _name = null; + private int type = UNKNOWN_TYPE; + + /** 0 is invalid as a link count. */ + private int hardLinkCount; + + /** 0 is valid, so use -1. */ + private long size = -1; + private String rawListing; + private String user = ""; + private String group = ""; + private String name; + private String link; + + // TODO Consider changing internal representation to java.time. + private Calendar calendar; + + /** If this is null, then list entry parsing failed. */ + private final boolean[][] permissions; // e.g. _permissions[USER_ACCESS][READ_PERMISSION] + + /** Creates an empty FTPFile. */ + public FTPFile() { + permissions = new boolean[3][3]; } /** - * Constructor for use by {@link FTPListParseEngine} only. - * Used to create FTPFile entries for failed parses + * Constructor for use by {@link FTPListParseEngine} only. Used to create FTPFile entries for failed parses + * * @param rawListing line that could not be parsed. * @since 3.4 */ - FTPFile(String rawListing) - { - _permissions = null; // flag that entry is invalid - _rawListing = rawListing; - _type = UNKNOWN_TYPE; - // init these to values that do not occur in listings - // so can distinguish which fields are unset - _hardLinkCount = 0; // 0 is invalid as a link count - _size = -1; // 0 is valid, so use -1 - _user = ""; - _group = ""; - _date = null; - _name = null; + FTPFile(final String rawListing) { + this.permissions = null; // flag that entry is invalid + this.rawListing = rawListing; + } + + private char formatType() { + switch (type) { + case FILE_TYPE: + return '-'; + case DIRECTORY_TYPE: + return 'd'; + case SYMBOLIC_LINK_TYPE: + return 'l'; + default: + return '?'; + } } - - /*** - * Set the original FTP server raw listing from which the FTPFile was - * created. + /** + * Gets the name of the group owning the file. Sometimes this will be a string representation of the group number. * - * @param rawListing The raw FTP server listing. - ***/ - public void setRawListing(String rawListing) - { - _rawListing = rawListing; + * @return The name of the group owning the file. + */ + public String getGroup() { + return group; } - /*** - * Get the original FTP server raw listing used to initialize the FTPFile. + /** + * Gets the number of hard links to this file. This is not to be confused with symbolic links. * - * @return The original FTP server raw listing used to initialize the - * FTPFile. - ***/ - public String getRawListing() - { - return _rawListing; + * @return The number of hard links to this file. + */ + public int getHardLinkCount() { + return hardLinkCount; } - - /*** - * Determine if the file is a directory. + /** + * If the FTPFile is a symbolic link, this method returns the name of the file being pointed to by the symbolic link. Otherwise it returns null. * - * @return True if the file is of type <code>DIRECTORY_TYPE</code>, false if - * not. - ***/ - public boolean isDirectory() - { - return (_type == DIRECTORY_TYPE); + * @return The file pointed to by the symbolic link (null if the FTPFile is not a symbolic link). + */ + public String getLink() { + return link; } - /*** - * Determine if the file is a regular file. + /** + * Gets the name of the file. * - * @return True if the file is of type <code>FILE_TYPE</code>, false if - * not. - ***/ - public boolean isFile() - { - return (_type == FILE_TYPE); + * @return The name of the file. + */ + public String getName() { + return name; } - /*** - * Determine if the file is a symbolic link. + /** + * Gets the original FTP server raw listing used to initialize the FTPFile. * - * @return True if the file is of type <code>UNKNOWN_TYPE</code>, false if - * not. - ***/ - public boolean isSymbolicLink() - { - return (_type == SYMBOLIC_LINK_TYPE); + * @return The original FTP server raw listing used to initialize the FTPFile. + */ + public String getRawListing() { + return rawListing; } - /*** - * Determine if the type of the file is unknown. + /** + * Gets the file size in bytes. * - * @return True if the file is of type <code>UNKNOWN_TYPE</code>, false if - * not. - ***/ - public boolean isUnknown() - { - return (_type == UNKNOWN_TYPE); + * @return The file size in bytes. + */ + public long getSize() { + return size; } /** - * Used to indicate whether an entry is valid or not. - * If the entry is invalid, only the {@link #getRawListing()} method will be useful. - * Other methods may fail. + * Gets the file timestamp. This usually the last modification time. * - * Used in conjunction with list parsing that preseverves entries that failed to parse. - * @see FTPClientConfig#setUnparseableEntries(boolean) - * @return true if the entry is valid - * @since 3.4 + * @return A Calendar instance representing the file timestamp. */ - public boolean isValid() { - return (_permissions != null); + public Calendar getTimestamp() { + return calendar; } - /*** - * Set the type of the file (<code>DIRECTORY_TYPE</code>, - * <code>FILE_TYPE</code>, etc.). + /** + * Gets the file timestamp. This usually the last modification time. * - * @param type The integer code representing the type of the file. - ***/ - public void setType(int type) - { - _type = type; + * @return A Calendar instance representing the file timestamp. + * @since 3.9.0 + */ + public Instant getTimestampInstant() { + return calendar == null ? null : calendar.toInstant(); } - - /*** - * Return the type of the file (one of the <code>_TYPE</code> constants), - * e.g., if it is a directory, a regular file, or a symbolic link. + /** + * Gets the type of the file (one of the <code>_TYPE</code> constants), e.g., if it is a directory, a regular file, or a symbolic link. * * @return The type of the file. - ***/ - public int getType() - { - return _type; + */ + public int getType() { + return type; } - - /*** - * Set the name of the file. + /** + * Gets the name of the user owning the file. Sometimes this will be a string representation of the user number. * - * @param name The name of the file. - ***/ - public void setName(String name) - { - _name = name; + * @return The name of the user owning the file. + */ + public String getUser() { + return user; } - /*** - * Return the name of the file. + /** + * Tests if the given access group (one of the <code> _ACCESS </code> constants) has the given access permission (one of the <code> _PERMISSION </code> + * constants) to the file. * - * @return The name of the file. - ***/ - public String getName() - { - return _name; + * @param access The access group (one of the <code> _ACCESS </code> constants) + * @param permission The access permission (one of the <code> _PERMISSION </code> constants) + * @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range + * @return true if {@link #isValid()} is {@code true &&} the associated permission is set; {@code false} otherwise. + */ + public boolean hasPermission(final int access, final int permission) { + if (permissions == null) { + return false; + } + return permissions[access][permission]; } - /** - * Set the file size in bytes. - * @param size The file size in bytes. + * Tests if the file is a directory. + * + * @return True if the file is of type <code>DIRECTORY_TYPE</code>, false if not. */ - public void setSize(long size) - { - _size = size; + public boolean isDirectory() { + return type == DIRECTORY_TYPE; } - - /*** - * Return the file size in bytes. + /** + * Tests if the file is a regular file. * - * @return The file size in bytes. - ***/ - public long getSize() - { - return _size; + * @return True if the file is of type <code>FILE_TYPE</code>, false if not. + */ + public boolean isFile() { + return type == FILE_TYPE; } - - /*** - * Set the number of hard links to this file. This is not to be - * confused with symbolic links. + /** + * Tests if the file is a symbolic link. * - * @param links The number of hard links to this file. - ***/ - public void setHardLinkCount(int links) - { - _hardLinkCount = links; + * @return True if the file is of type <code>UNKNOWN_TYPE</code>, false if not. + */ + public boolean isSymbolicLink() { + return type == SYMBOLIC_LINK_TYPE; } - - /*** - * Return the number of hard links to this file. This is not to be - * confused with symbolic links. + /** + * Tests if the type of the file is unknown. * - * @return The number of hard links to this file. - ***/ - public int getHardLinkCount() - { - return _hardLinkCount; + * @return True if the file is of type <code>UNKNOWN_TYPE</code>, false if not. + */ + public boolean isUnknown() { + return type == UNKNOWN_TYPE; } - - /*** - * Set the name of the group owning the file. This may be - * a string representation of the group number. + /** + * Tests whether an entry is valid or not. If the entry is invalid, only the {@link #getRawListing()} method will be useful. Other methods may fail. * - * @param group The name of the group owning the file. - ***/ - public void setGroup(String group) - { - _group = group; + * Used in conjunction with list parsing that preseverves entries that failed to parse. + * + * @see FTPClientConfig#setUnparseableEntries(boolean) + * @return true if the entry is valid + * @since 3.4 + */ + public boolean isValid() { + return permissions != null; } - - /*** - * Returns the name of the group owning the file. Sometimes this will be - * a string representation of the group number. - * - * @return The name of the group owning the file. - ***/ - public String getGroup() - { - return _group; + private String permissionToString(final int access) { + final StringBuilder sb = new StringBuilder(); + if (hasPermission(access, READ_PERMISSION)) { + sb.append('r'); + } else { + sb.append('-'); + } + if (hasPermission(access, WRITE_PERMISSION)) { + sb.append('w'); + } else { + sb.append('-'); + } + if (hasPermission(access, EXECUTE_PERMISSION)) { + sb.append('x'); + } else { + sb.append('-'); + } + return sb.toString(); } + private void readObject(final java.io.ObjectInputStream in) { + throw new UnsupportedOperationException("Serialization is not supported"); + } - /*** - * Set the name of the user owning the file. This may be - * a string representation of the user number; + /** + * Sets the name of the group owning the file. This may be a string representation of the group number. * - * @param user The name of the user owning the file. - ***/ - public void setUser(String user) - { - _user = user; + * @param group The name of the group owning the file. + */ + public void setGroup(final String group) { + this.group = group; } - /*** - * Returns the name of the user owning the file. Sometimes this will be - * a string representation of the user number. + /** + * Sets the number of hard links to this file. This is not to be confused with symbolic links. * - * @return The name of the user owning the file. - ***/ - public String getUser() - { - return _user; + * @param links The number of hard links to this file. + */ + public void setHardLinkCount(final int links) { + this.hardLinkCount = links; } - - /*** - * If the FTPFile is a symbolic link, use this method to set the name of the - * file being pointed to by the symbolic link. + /** + * If the FTPFile is a symbolic link, use this method to set the name of the file being pointed to by the symbolic link. * - * @param link The file pointed to by the symbolic link. - ***/ - public void setLink(String link) - { - _link = link; + * @param link The file pointed to by the symbolic link. + */ + public void setLink(final String link) { + this.link = link; } - - /*** - * If the FTPFile is a symbolic link, this method returns the name of the - * file being pointed to by the symbolic link. Otherwise it returns null. + /** + * Sets the name of the file. * - * @return The file pointed to by the symbolic link (null if the FTPFile - * is not a symbolic link). - ***/ - public String getLink() - { - return _link; + * @param name The name of the file. + */ + public void setName(final String name) { + this.name = name; } - - /*** - * Set the file timestamp. This usually the last modification time. - * The parameter is not cloned, so do not alter its value after calling - * this method. + /** + * Sets if the given access group (one of the <code> _ACCESS </code> constants) has the given access permission (one of the <code> _PERMISSION </code> + * constants) to the file. * - * @param date A Calendar instance representing the file timestamp. - ***/ - public void setTimestamp(Calendar date) - { - _date = date; + * @param access The access group (one of the <code> _ACCESS </code> constants) + * @param permission The access permission (one of the <code> _PERMISSION </code> constants) + * @param value True if permission is allowed, false if not. + * @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range + */ + public void setPermission(final int access, final int permission, final boolean value) { + permissions[access][permission] = value; } - - /*** - * Returns the file timestamp. This usually the last modification time. + /** + * Sets the original FTP server raw listing from which the FTPFile was created. * - * @return A Calendar instance representing the file timestamp. - ***/ - public Calendar getTimestamp() - { - return _date; + * @param rawListing The raw FTP server listing. + */ + public void setRawListing(final String rawListing) { + this.rawListing = rawListing; } - - /*** - * Set if the given access group (one of the <code> _ACCESS </code> - * constants) has the given access permission (one of the - * <code> _PERMISSION </code> constants) to the file. + /** + * Sets the file size in bytes. * - * @param access The access group (one of the <code> _ACCESS </code> - * constants) - * @param permission The access permission (one of the - * <code> _PERMISSION </code> constants) - * @param value True if permission is allowed, false if not. - * @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range - ***/ - public void setPermission(int access, int permission, boolean value) - { - _permissions[access][permission] = value; + * @param size The file size in bytes. + */ + public void setSize(final long size) { + this.size = size; } + /** + * Sets the file timestamp. This usually the last modification time. The parameter is not cloned, so do not alter its value after calling this method. + * + * @param date A Calendar instance representing the file timestamp. + */ + public void setTimestamp(final Calendar date) { + this.calendar = date; + } - /*** - * Determines if the given access group (one of the <code> _ACCESS </code> - * constants) has the given access permission (one of the - * <code> _PERMISSION </code> constants) to the file. + /** + * Sets the type of the file (<code>DIRECTORY_TYPE</code>, <code>FILE_TYPE</code>, etc.). * - * @param access The access group (one of the <code> _ACCESS </code> - * constants) - * @param permission The access permission (one of the - * <code> _PERMISSION </code> constants) - * @throws ArrayIndexOutOfBoundsException if either of the parameters is out of range - * @return true if {@link #isValid()} is {@code true &&} the associated permission is set; - * {@code false} otherwise. - ***/ - public boolean hasPermission(int access, int permission) - { - if (_permissions == null) { - return false; - } - return _permissions[access][permission]; + * @param type The integer code representing the type of the file. + */ + public void setType(final int type) { + this.type = type; } - /*** - * Returns a string representation of the FTPFile information. + /** + * Sets the name of the user owning the file. This may be a string representation of the user number; * - * @return A string representation of the FTPFile information. + * @param user The name of the user owning the file. */ - @Override - public String toString() - { - return getRawListing(); + public void setUser(final String user) { + this.user = user; } - /*** - * Returns a string representation of the FTPFile information. - * This currently mimics the Unix listing format. - * This method uses the timezone of the Calendar entry, which is - * the server time zone (if one was provided) otherwise it is - * the local time zone. + /** + * Gets a string representation of the FTPFile information. This currently mimics the Unix listing format. This method uses the time zone of the Calendar + * entry, which is the server time zone (if one was provided) otherwise it is the local time zone. * <p> - * Note: if the instance is not valid {@link #isValid()}, no useful - * information can be returned. In this case, use {@link #getRawListing()} - * instead. + * Note: if the instance is not valid {@link #isValid()}, no useful information can be returned. In this case, use {@link #getRawListing()} instead. + * </p> * * @return A string representation of the FTPFile information. * @since 3.0 */ - public String toFormattedString() - { + public String toFormattedString() { return toFormattedString(null); } /** - * Returns a string representation of the FTPFile information. - * This currently mimics the Unix listing format. - * This method allows the Calendar time zone to be overridden. + * Gets a string representation of the FTPFile information. This currently mimics the Unix listing format. This method allows the Calendar time zone to be + * overridden. * <p> - * Note: if the instance is not valid {@link #isValid()}, no useful - * information can be returned. In this case, use {@link #getRawListing()} - * instead. - * @param timezone the timezone to use for displaying the time stamp - * If {@code null}, then use the Calendar entry timezone + * Note: if the instance is not valid {@link #isValid()}, no useful information can be returned. In this case, use {@link #getRawListing()} instead. + * </p> + * + * @param timezone the time zone to use for displaying the time stamp If {@code null}, then use the Calendar entry + * * @return A string representation of the FTPFile information. * @since 3.4 */ - public String toFormattedString(final String timezone) - { + public String toFormattedString(final String timezone) { if (!isValid()) { return "[Invalid: could not parse file entry]"; } - StringBuilder sb = new StringBuilder(); - Formatter fmt = new Formatter(sb); - sb.append(formatType()); - sb.append(permissionToString(USER_ACCESS)); - sb.append(permissionToString(GROUP_ACCESS)); - sb.append(permissionToString(WORLD_ACCESS)); - fmt.format(" %4d", Integer.valueOf(getHardLinkCount())); - fmt.format(" %-8s %-8s", getUser(), getGroup()); - fmt.format(" %8d", Long.valueOf(getSize())); - Calendar timestamp = getTimestamp(); - if (timestamp != null) { - if (timezone != null) { - TimeZone newZone = TimeZone.getTimeZone(timezone); - if (!newZone.equals(timestamp.getTimeZone())){ - Date original = timestamp.getTime(); - Calendar newStamp = Calendar.getInstance(newZone); - newStamp.setTime(original); - timestamp = newStamp; + final StringBuilder sb = new StringBuilder(); + try (final Formatter fmt = new Formatter(sb)) { + sb.append(formatType()); + sb.append(permissionToString(USER_ACCESS)); + sb.append(permissionToString(GROUP_ACCESS)); + sb.append(permissionToString(WORLD_ACCESS)); + fmt.format(" %4d", Integer.valueOf(getHardLinkCount())); + fmt.format(" %-8s %-8s", getUser(), getGroup()); + fmt.format(" %8d", Long.valueOf(getSize())); + Calendar timestamp = getTimestamp(); + if (timestamp != null) { + if (timezone != null) { + final TimeZone newZone = TimeZone.getTimeZone(timezone); + if (!newZone.equals(timestamp.getTimeZone())) { + final Date original = timestamp.getTime(); + final Calendar newStamp = Calendar.getInstance(newZone); + newStamp.setTime(original); + timestamp = newStamp; + } } - } - fmt.format(" %1$tY-%1$tm-%1$td", timestamp); - // Only display time units if they are present - if (timestamp.isSet(Calendar.HOUR_OF_DAY)) { - fmt.format(" %1$tH", timestamp); - if (timestamp.isSet(Calendar.MINUTE)) { - fmt.format(":%1$tM", timestamp); - if (timestamp.isSet(Calendar.SECOND)) { - fmt.format(":%1$tS", timestamp); - if (timestamp.isSet(Calendar.MILLISECOND)) { - fmt.format(".%1$tL", timestamp); + fmt.format(" %1$tY-%1$tm-%1$td", timestamp); + // Only display time units if they are present + if (timestamp.isSet(Calendar.HOUR_OF_DAY)) { + fmt.format(" %1$tH", timestamp); + if (timestamp.isSet(Calendar.MINUTE)) { + fmt.format(":%1$tM", timestamp); + if (timestamp.isSet(Calendar.SECOND)) { + fmt.format(":%1$tS", timestamp); + if (timestamp.isSet(Calendar.MILLISECOND)) { + fmt.format(".%1$tL", timestamp); + } } } + fmt.format(" %1$tZ", timestamp); } - fmt.format(" %1$tZ", timestamp); } + sb.append(' '); + sb.append(getName()); } - sb.append(' '); - sb.append(getName()); - fmt.close(); return sb.toString(); } - private char formatType(){ - switch(_type) { - case FILE_TYPE: - return '-'; - case DIRECTORY_TYPE: - return 'd'; - case SYMBOLIC_LINK_TYPE: - return 'l'; - default: - return '?'; - } + /* + * Serialization is unnecessary for this class. Reject attempts to do so until such time as the Serializable attribute can be dropped. + */ + + /** + * Gets a string representation of the FTPFile information. + * + * @return A string representation of the FTPFile information. + */ + @Override + public String toString() { + return getRawListing(); } - private String permissionToString(int access ){ - StringBuilder sb = new StringBuilder(); - if (hasPermission(access, READ_PERMISSION)) { - sb.append('r'); - } else { - sb.append('-'); - } - if (hasPermission(access, WRITE_PERMISSION)) { - sb.append('w'); - } else { - sb.append('-'); - } - if (hasPermission(access, EXECUTE_PERMISSION)) { - sb.append('x'); - } else { - sb.append('-'); - } - return sb.toString(); + private void writeObject(final java.io.ObjectOutputStream out) { + throw new UnsupportedOperationException("Serialization is not supported"); } + } diff --git a/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParser.java b/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParser.java index 0c71a0a..cefb4d3 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParser.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParser.java @@ -16,76 +16,59 @@ */ package org.apache.commons.net.ftp; + import java.io.BufferedReader; import java.io.IOException; import java.util.List; /** - * FTPFileEntryParser defines the interface for parsing a single FTP file - * listing and converting that information into an - * {@link org.apache.commons.net.ftp.FTPFile} instance. - * Sometimes you will want to parse unusual listing formats, in which - * case you would create your own implementation of FTPFileEntryParser and - * if necessary, subclass FTPFile. + * FTPFileEntryParser defines the interface for parsing a single FTP file listing and converting that information into an + * {@link org.apache.commons.net.ftp.FTPFile} instance. Sometimes you will want to parse unusual listing formats, in which case you would create your own + * implementation of FTPFileEntryParser and if necessary, subclass FTPFile. * <p> - * Here are some examples showing how to use one of the classes that - * implement this interface. + * Here are some examples showing how to use one of the classes that implement this interface. * <p> * - * The first example uses the <code>FTPClient.listFiles()</code> - * API to pull the whole list from the subfolder <code>subfolder</code> in - * one call, attempting to automatically detect the parser type. This - * method, without a parserKey parameter, indicates that autodection should - * be used. + * The first example uses the <code>FTPClient.listFiles()</code> API to pull the whole list from the subfolder <code>subfolder</code> in one call, attempting to + * automatically detect the parser type. This method, without a parserKey parameter, indicates that autodection should be used. * * <pre> - * FTPClient f=FTPClient(); - * f.connect(server); - * f.login(username, password); - * FTPFile[] files = f.listFiles("subfolder"); + * FTPClient f = FTPClient(); + * f.connect(server); + * f.login(username, password); + * FTPFile[] files = f.listFiles("subfolder"); * </pre> * - * The second example uses the <code>FTPClient.listFiles()</code> - * API to pull the whole list from the current working directory in one call, - * but specifying by classname the parser to be used. For this particular - * parser class, this approach is necessary since there is no way to - * autodetect this server type. + * The second example uses the <code>FTPClient.listFiles()</code> API to pull the whole list from the current working directory in one call, but specifying by + * classname the parser to be used. For this particular parser class, this approach is necessary since there is no way to autodetect this server type. * * <pre> - * FTPClient f=FTPClient(); - * f.connect(server); - * f.login(username, password); - * FTPFile[] files = f.listFiles( - * "org.apache.commons.net.ftp.parser.EnterpriseUnixFTPFileEntryParser", - * "."); + * FTPClient f = FTPClient(); + * f.connect(server); + * f.login(username, password); + * FTPFile[] files = f.listFiles("org.apache.commons.net.ftp.parser.EnterpriseUnixFTPFileEntryParser", "."); * </pre> * - * The third example uses the <code>FTPClient.listFiles()</code> - * API to pull a single file listing in an arbitrary directory in one call, - * specifying by KEY the parser to be used, in this case, VMS. + * The third example uses the <code>FTPClient.listFiles()</code> API to pull a single file listing in an arbitrary directory in one call, specifying by KEY the + * parser to be used, in this case, VMS. * * <pre> - * FTPClient f=FTPClient(); - * f.connect(server); - * f.login(username, password); - * FTPFile[] files = f.listFiles("VMS", "subfolder/foo.java"); + * FTPClient f = FTPClient(); + * f.connect(server); + * f.login(username, password); + * FTPFile[] files = f.listFiles("VMS", "subfolder/foo.java"); * </pre> * - * For an alternative approach, see the {@link FTPListParseEngine} class - * which provides iterative access. + * For an alternative approach, see the {@link FTPListParseEngine} class which provides iterative access. * - * @version $Id: FTPFileEntryParser.java 1747119 2016-06-07 02:22:24Z ggregory $ * @see org.apache.commons.net.ftp.FTPFile * @see org.apache.commons.net.ftp.FTPClient#listFiles() */ -public interface FTPFileEntryParser -{ +public interface FTPFileEntryParser { /** - * Parses a line of an FTP server file listing and converts it into a usable - * format in the form of an <code> FTPFile </code> instance. If the - * file listing line doesn't describe a file, <code> null </code> should be - * returned, otherwise a <code> FTPFile </code> instance representing the - * files in the directory is returned. + * Parses a line of an FTP server file listing and converts it into a usable format in the form of an <code> FTPFile </code> instance. If the file listing + * line doesn't describe a file, <code> null </code> should be returned, otherwise a <code> FTPFile </code> instance representing the files in the directory + * is returned. * * @param listEntry A line of text from the file listing * @return An FTPFile instance corresponding to the supplied entry @@ -93,25 +76,8 @@ public interface FTPFileEntryParser FTPFile parseFTPEntry(String listEntry); /** - * Reads the next entry using the supplied BufferedReader object up to - * whatever delemits one entry from the next. Implementors must define - * this for the particular ftp system being parsed. In many but not all - * cases, this can be defined simply by calling BufferedReader.readLine(). - * - * @param reader The BufferedReader object from which entries are to be - * read. - * - * @return A string representing the next ftp entry or null if none found. - * @throws IOException thrown on any IO Error reading from the reader. - */ - String readNextEntry(BufferedReader reader) throws IOException; - - - /** - * This method is a hook for those implementors (such as - * VMSVersioningFTPEntryParser, and possibly others) which need to - * perform some action upon the FTPFileList after it has been created - * from the server stream, but before any clients see the list. + * This method is a hook for those implementors (such as VMSVersioningFTPEntryParser, and possibly others) which need to perform some action upon the + * FTPFileList after it has been created from the server stream, but before any clients see the list. * * The default implementation can be a no-op. * @@ -121,14 +87,15 @@ public interface FTPFileEntryParser */ List<String> preParse(List<String> original); + /** + * Reads the next entry using the supplied BufferedReader object up to whatever delimits one entry from the next. Implementors must define this for the + * particular ftp system being parsed. In many but not all cases, this can be defined simply by calling BufferedReader.readLine(). + * + * @param reader The BufferedReader object from which entries are to be read. + * + * @return A string representing the next ftp entry or null if none found. + * @throws IOException thrown on any IO Error reading from the reader. + */ + String readNextEntry(BufferedReader reader) throws IOException; } - - -/* Emacs configuration - * Local variables: ** - * mode: java ** - * c-basic-offset: 4 ** - * indent-tabs-mode: nil ** - * End: ** - */ diff --git a/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParserImpl.java b/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParserImpl.java index 7f29224..e2e9d45 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParserImpl.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPFileEntryParserImpl.java @@ -16,64 +16,49 @@ */ package org.apache.commons.net.ftp; + import java.io.BufferedReader; import java.io.IOException; import java.util.List; /** - * This abstract class implements both the older FTPFileListParser and - * newer FTPFileEntryParser interfaces with default functionality. - * All the classes in the parser subpackage inherit from this. + * This abstract class implements both the older FTPFileListParser and newer FTPFileEntryParser interfaces with default functionality. All the classes in the + * parser subpackage inherit from this. * */ -public abstract class FTPFileEntryParserImpl - implements FTPFileEntryParser -{ +public abstract class FTPFileEntryParserImpl implements FTPFileEntryParser { /** * The constructor for a FTPFileEntryParserImpl object. */ - public FTPFileEntryParserImpl() - { + public FTPFileEntryParserImpl() { } /** - * Reads the next entry using the supplied BufferedReader object up to - * whatever delimits one entry from the next. This default implementation - * simply calls BufferedReader.readLine(). + * This method is a hook for those implementors (such as VMSVersioningFTPEntryParser, and possibly others) which need to perform some action upon the + * FTPFileList after it has been created from the server stream, but before any clients see the list. * - * @param reader The BufferedReader object from which entries are to be - * read. + * This default implementation does nothing. * - * @return A string representing the next ftp entry or null if none found. - * @throws java.io.IOException thrown on any IO Error reading from the reader. + * @param original Original list after it has been created from the server stream + * + * @return <code>original</code> unmodified. */ @Override - public String readNextEntry(BufferedReader reader) throws IOException - { - return reader.readLine(); + public List<String> preParse(final List<String> original) { + return original; } + /** - * This method is a hook for those implementors (such as - * VMSVersioningFTPEntryParser, and possibly others) which need to - * perform some action upon the FTPFileList after it has been created - * from the server stream, but before any clients see the list. + * Reads the next entry using the supplied BufferedReader object up to whatever delimits one entry from the next. This default implementation simply calls + * BufferedReader.readLine(). * - * This default implementation does nothing. + * @param reader The BufferedReader object from which entries are to be read. * - * @param original Original list after it has been created from the server stream - * - * @return <code>original</code> unmodified. + * @return A string representing the next ftp entry or null if none found. + * @throws IOException thrown on any IO Error reading from the reader. */ @Override - public List<String> preParse(List<String> original) { - return original; - } + public String readNextEntry(final BufferedReader reader) throws IOException { + return reader.readLine(); + } } - -/* Emacs configuration - * Local variables: ** - * mode: java ** - * c-basic-offset: 4 ** - * indent-tabs-mode: nil ** - * End: ** - */ diff --git a/src/main/java/org/apache/commons/net/ftp/FTPFileFilter.java b/src/main/java/org/apache/commons/net/ftp/FTPFileFilter.java index 16f9c13..252c11b 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPFileFilter.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPFileFilter.java @@ -19,15 +19,17 @@ package org.apache.commons.net.ftp; /** - * Perform filtering on FTPFile entries. + * Performs filtering on {@link FTPFile} instances. + * * @since 2.2 */ public interface FTPFileFilter { + /** * Checks if an FTPFile entry should be included or not. * - * @param file entry to be checked for inclusion. May be <code>null</code>. - * @return <code>true</code> if the file is to be included, <code>false</code> otherwise + * @param file entry to be checked for inclusion. May be {@code null}. + * @return {@code true} if the file is to be included, {@code false} otherwise. */ - public boolean accept(FTPFile file); + boolean accept(FTPFile file); } diff --git a/src/main/java/org/apache/commons/net/ftp/FTPFileFilters.java b/src/main/java/org/apache/commons/net/ftp/FTPFileFilters.java index 29eba60..b1c3537 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPFileFilters.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPFileFilters.java @@ -18,8 +18,11 @@ package org.apache.commons.net.ftp; +import java.util.Objects; + /** * Implements some simple FTPFileFilter classes. + * * @since 2.2 */ public class FTPFileFilters { @@ -27,31 +30,16 @@ public class FTPFileFilters { /** * Accepts all FTPFile entries, including null. */ - public static final FTPFileFilter ALL = new FTPFileFilter() { - @Override - public boolean accept(FTPFile file) { - return true; - } - }; + public static final FTPFileFilter ALL = file -> true; /** * Accepts all non-null FTPFile entries. */ - public static final FTPFileFilter NON_NULL = new FTPFileFilter() { - @Override - public boolean accept(FTPFile file) { - return file != null; - } - }; + public static final FTPFileFilter NON_NULL = Objects::nonNull; /** * Accepts all (non-null) FTPFile directory entries. */ - public static final FTPFileFilter DIRECTORIES = new FTPFileFilter() { - @Override - public boolean accept(FTPFile file) { - return file != null && file.isDirectory(); - } - }; + public static final FTPFileFilter DIRECTORIES = file -> file != null && file.isDirectory(); } diff --git a/src/main/java/org/apache/commons/net/ftp/FTPHTTPClient.java b/src/main/java/org/apache/commons/net/ftp/FTPHTTPClient.java index 52df2c8..3ee696a 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPHTTPClient.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPHTTPClient.java @@ -27,6 +27,8 @@ import java.io.UnsupportedEncodingException; import java.net.Inet6Address; import java.net.Socket; import java.net.SocketException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -38,29 +40,68 @@ import org.apache.commons.net.util.Base64; * @since 2.2 */ public class FTPHTTPClient extends FTPClient { + private static final byte[] CRLF = { '\r', '\n' }; private final String proxyHost; private final int proxyPort; private final String proxyUsername; private final String proxyPassword; - private static final byte[] CRLF={'\r', '\n'}; + private final Charset charset; private final Base64 base64 = new Base64(); private String tunnelHost; // Save the host when setting up a tunnel (needed for EPSV) - public FTPHTTPClient(String proxyHost, int proxyPort, String proxyUser, String proxyPass) { + /** + * Create an instance using the UTF-8 encoding, with no proxy credentials. + * + * @param proxyHost the hostname to use + * @param proxyPort the port to use + */ + public FTPHTTPClient(final String proxyHost, final int proxyPort) { + this(proxyHost, proxyPort, null, null); + } + + /** + * Create an instance using the specified encoding, with no proxy credentials. + * + * @param proxyHost the hostname to use + * @param proxyPort the port to use + * @param encoding the encoding to use + */ + public FTPHTTPClient(final String proxyHost, final int proxyPort, final Charset encoding) { + this(proxyHost, proxyPort, null, null, encoding); + } + + /** + * Create an instance using the UTF-8 encoding + * + * @param proxyHost the hostname to use + * @param proxyPort the port to use + * @param proxyUser the user name for the proxy + * @param proxyPass the password for the proxy + */ + public FTPHTTPClient(final String proxyHost, final int proxyPort, final String proxyUser, final String proxyPass) { + this(proxyHost, proxyPort, proxyUser, proxyPass, StandardCharsets.UTF_8); + } + + /** + * Create an instance with the specified encoding + * + * @param proxyHost the hostname to use + * @param proxyPort the port to use + * @param proxyUser the user name for the proxy + * @param proxyPass the password for the proxy + * @param encoding the encoding to use + */ + public FTPHTTPClient(final String proxyHost, final int proxyPort, final String proxyUser, final String proxyPass, final Charset encoding) { this.proxyHost = proxyHost; this.proxyPort = proxyPort; this.proxyUsername = proxyUser; this.proxyPassword = proxyPass; this.tunnelHost = null; + this.charset = encoding; } - public FTPHTTPClient(String proxyHost, int proxyPort) { - this(proxyHost, proxyPort, null, null); - } - - /** * {@inheritDoc} * @@ -71,8 +112,7 @@ public class FTPHTTPClient extends FTPClient { // Not strictly necessary, but Clirr complains even though there is a super-impl @Override @Deprecated - protected Socket _openDataConnection_(int command, String arg) - throws IOException { + protected Socket _openDataConnection_(final int command, final String arg) throws IOException { return super._openDataConnection_(command, arg); } @@ -83,9 +123,8 @@ public class FTPHTTPClient extends FTPClient { * @since 3.1 */ @Override - protected Socket _openDataConnection_(String command, String arg) - throws IOException { - //Force local passive mode, active mode not supported by through proxy + protected Socket _openDataConnection_(final String command, final String arg) throws IOException { + // Force local passive mode, active mode not supported by through proxy if (getDataConnectionMode() != PASSIVE_LOCAL_DATA_CONNECTION_MODE) { throw new IllegalStateException("Only passive connection mode supported"); } @@ -93,7 +132,7 @@ public class FTPHTTPClient extends FTPClient { final boolean isInet6Address = getRemoteAddress() instanceof Inet6Address; String passiveHost = null; - boolean attemptEPSV = isUseEPSVwithIPv4() || isInet6Address; + final boolean attemptEPSV = isUseEPSVwithIPv4() || isInet6Address; if (attemptEPSV && epsv() == FTPReply.ENTERING_EPSV_MODE) { _parseExtendedPassiveModeReply(_replyLines.get(0)); passiveHost = this.tunnelHost; @@ -109,11 +148,11 @@ public class FTPHTTPClient extends FTPClient { passiveHost = this.getPassiveHost(); } - Socket socket = _socketFactory_.createSocket(proxyHost, proxyPort); - InputStream is = socket.getInputStream(); - OutputStream os = socket.getOutputStream(); + final Socket socket = _socketFactory_.createSocket(proxyHost, proxyPort); + final InputStream is = socket.getInputStream(); + final OutputStream os = socket.getOutputStream(); tunnelHandshake(passiveHost, this.getPassivePort(), is, os); - if ((getRestartOffset() > 0) && !restart(getRestartOffset())) { + if (getRestartOffset() > 0 && !restart(getRestartOffset())) { socket.close(); return null; } @@ -127,69 +166,64 @@ public class FTPHTTPClient extends FTPClient { } @Override - public void connect(String host, int port) throws SocketException, IOException { + public void connect(final String host, final int port) throws SocketException, IOException { _socket_ = _socketFactory_.createSocket(proxyHost, proxyPort); _input_ = _socket_.getInputStream(); _output_ = _socket_.getOutputStream(); - Reader socketIsReader; + final Reader socketIsReader; try { socketIsReader = tunnelHandshake(host, port, _input_, _output_); - } - catch (Exception e) { - IOException ioe = new IOException("Could not connect to " + host+ " using port " + port); + } catch (final Exception e) { + final IOException ioe = new IOException("Could not connect to " + host + " using port " + port); ioe.initCause(e); throw ioe; } super._connectAction_(socketIsReader); } - private BufferedReader tunnelHandshake(String host, int port, InputStream input, OutputStream output) throws IOException, - UnsupportedEncodingException { - final String connectString = "CONNECT " + host + ":" + port + " HTTP/1.1"; + private BufferedReader tunnelHandshake(final String host, final int port, final InputStream input, final OutputStream output) + throws IOException, UnsupportedEncodingException { + final String connectString = "CONNECT " + host + ":" + port + " HTTP/1.1"; final String hostString = "Host: " + host + ":" + port; this.tunnelHost = host; - output.write(connectString.getBytes("UTF-8")); // TODO what is the correct encoding? + output.write(connectString.getBytes(charset)); output.write(CRLF); - output.write(hostString.getBytes("UTF-8")); + output.write(hostString.getBytes(charset)); output.write(CRLF); if (proxyUsername != null && proxyPassword != null) { final String auth = proxyUsername + ":" + proxyPassword; - final String header = "Proxy-Authorization: Basic " - + base64.encodeToString(auth.getBytes("UTF-8")); - output.write(header.getBytes("UTF-8")); + final String header = "Proxy-Authorization: Basic " + base64.encodeToString(auth.getBytes(charset)); + output.write(header.getBytes(charset)); } output.write(CRLF); - List<String> response = new ArrayList<String>(); - BufferedReader reader = new BufferedReader( - new InputStreamReader(input, getCharset())); + final List<String> response = new ArrayList<>(); + final BufferedReader reader = new BufferedReader(new InputStreamReader(input, getCharset())); - for (String line = reader.readLine(); line != null - && line.length() > 0; line = reader.readLine()) { + for (String line = reader.readLine(); line != null && !line.isEmpty(); line = reader.readLine()) { response.add(line); } - int size = response.size(); + final int size = response.size(); if (size == 0) { throw new IOException("No response from proxy"); } String code = null; - String resp = response.get(0); - if (resp.startsWith("HTTP/") && resp.length() >= 12) { - code = resp.substring(9, 12); - } else { + final String resp = response.get(0); + if (!resp.startsWith("HTTP/") || (resp.length() < 12)) { throw new IOException("Invalid response from proxy: " + resp); } + code = resp.substring(9, 12); if (!"200".equals(code)) { - StringBuilder msg = new StringBuilder(); + final StringBuilder msg = new StringBuilder(); msg.append("HTTPTunnelConnector: connection failed\r\n"); msg.append("Response received from the proxy:\r\n"); - for (String line : response) { + for (final String line : response) { msg.append(line); msg.append("\r\n"); } @@ -198,5 +232,3 @@ public class FTPHTTPClient extends FTPClient { return reader; } } - - diff --git a/src/main/java/org/apache/commons/net/ftp/FTPListParseEngine.java b/src/main/java/org/apache/commons/net/ftp/FTPListParseEngine.java index 29e1bae..14e5988 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPListParseEngine.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPListParseEngine.java @@ -21,77 +21,70 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; +import java.util.stream.Collectors; import org.apache.commons.net.util.Charsets; - /** - * This class handles the entire process of parsing a listing of - * file entries from the server. + * This class handles the entire process of parsing a listing of file entries from the server. * <p> * This object defines a two-part parsing mechanism. * <p> - * The first part is comprised of reading the raw input into an internal - * list of strings. Every item in this list corresponds to an actual - * file. All extraneous matter emitted by the server will have been - * removed by the end of this phase. This is accomplished in conjunction - * with the FTPFileEntryParser associated with this engine, by calling - * its methods <code>readNextEntry()</code> - which handles the issue of - * what delimits one entry from another, usually but not always a line - * feed and <code>preParse()</code> - which handles removal of - * extraneous matter such as the preliminary lines of a listing, removal - * of duplicates on versioning systems, etc. + * The first part is comprised of reading the raw input into an internal list of strings. Every item in this list corresponds to an actual file. All extraneous + * matter emitted by the server will have been removed by the end of this phase. This is accomplished in conjunction with the FTPFileEntryParser associated with + * this engine, by calling its methods <code>readNextEntry()</code> - which handles the issue of what delimits one entry from another, usually but not always a + * line feed and <code>preParse()</code> - which handles removal of extraneous matter such as the preliminary lines of a listing, removal of duplicates on + * versioning systems, etc. * <p> - * The second part is composed of the actual parsing, again in conjunction - * with the particular parser used by this engine. This is controlled - * by an iterator over the internal list of strings. This may be done - * either in block mode, by calling the <code>getNext()</code> and - * <code>getPrevious()</code> methods to provide "paged" output of less - * than the whole list at one time, or by calling the - * <code>getFiles()</code> method to return the entire list. + * The second part is composed of the actual parsing, again in conjunction with the particular parser used by this engine. This is controlled by an iterator + * over the internal list of strings. This may be done either in block mode, by calling the <code>getNext()</code> and <code>getPrevious()</code> methods to + * provide "paged" output of less than the whole list at one time, or by calling the <code>getFiles()</code> method to return the entire list. * <p> * Examples: * <p> * Paged access: + * * <pre> - * FTPClient f=FTPClient(); - * f.connect(server); - * f.login(username, password); - * FTPListParseEngine engine = f.initiateListParsing(directory); + * FTPClient f = FTPClient(); + * f.connect(server); + * f.login(username, password); + * FTPListParseEngine engine = f.initiateListParsing(directory); * - * while (engine.hasNext()) { - * FTPFile[] files = engine.getNext(25); // "page size" you want - * //do whatever you want with these files, display them, etc. - * //expensive FTPFile objects not created until needed. - * } + * while (engine.hasNext()) { + * FTPFile[] files = engine.getNext(25); // "page size" you want + * // do whatever you want with these files, display them, etc. + * // expensive FTPFile objects not created until needed. + * } * </pre> * <p> - * For unpaged access, simply use FTPClient.listFiles(). That method - * uses this class transparently. - * @version $Id: FTPListParseEngine.java 1747119 2016-06-07 02:22:24Z ggregory $ + * For unpaged access, simply use FTPClient.listFiles(). That method uses this class transparently. */ public class FTPListParseEngine { - private List<String> entries = new LinkedList<String>(); - private ListIterator<String> _internalIterator = entries.listIterator(); + /** + * An empty immutable {@code FTPFile} array. + */ + private static final FTPFile[] EMPTY_FTP_FILE_ARRAY = {}; + private List<String> entries = new LinkedList<>(); + private ListIterator<String> internalIterator = entries.listIterator(); private final FTPFileEntryParser parser; + // Should invalid files (parse failures) be allowed? private final boolean saveUnparseableEntries; - public FTPListParseEngine(FTPFileEntryParser parser) { + public FTPListParseEngine(final FTPFileEntryParser parser) { this(parser, null); } /** * Intended for use by FTPClient only + * * @since 3.4 */ - FTPListParseEngine(FTPFileEntryParser parser, FTPClientConfig configuration) { + FTPListParseEngine(final FTPFileEntryParser parser, final FTPClientConfig configuration) { this.parser = parser; if (configuration != null) { this.saveUnparseableEntries = configuration.getUnparseableEntries(); @@ -101,83 +94,74 @@ public class FTPListParseEngine { } /** - * handle the initial reading and preparsing of the list returned by - * the server. After this method has completed, this object will contain - * a list of unparsed entries (Strings) each referring to a unique file - * on the server. + * Returns a list of FTPFile objects containing the whole list of files returned by the server as read by this object's parser. The files are filtered + * before being added to the array. + * + * @param filter FTPFileFilter, must not be <code>null</code>. * - * @param stream input stream provided by the server socket. - * @param encoding the encoding to be used for reading the stream + * @return a list of FTPFile objects containing the whole list of files returned by the server as read by this object's parser. + * <p> + * <b> NOTE:</b> This array may contain null members if any of the individual file listings failed to parse. The caller should check each entry for + * null before referencing it, or use the a filter such as {@link FTPFileFilters#NON_NULL} which does not allow null entries. + * @since 3.9.0 + */ + public List<FTPFile> getFileList(final FTPFileFilter filter) { + return entries.stream().map(e -> { + final FTPFile file = parser.parseFTPEntry(e); + return file == null && saveUnparseableEntries ? new FTPFile(e) : file; + }).filter(file -> filter.accept(file)).collect(Collectors.toList()); + } + + /** + * Returns an array of FTPFile objects containing the whole list of files returned by the server as read by this object's parser. * - * @throws IOException - * thrown on any failure to read from the sever. + * @return an array of FTPFile objects containing the whole list of files returned by the server as read by this object's parser. None of the entries will + * be null + * @throws IOException - not ever thrown, may be removed in a later release */ - public void readServerList(InputStream stream, String encoding) - throws IOException + public FTPFile[] getFiles() throws IOException // TODO remove; not actually thrown { - this.entries = new LinkedList<String>(); - readStream(stream, encoding); - this.parser.preParse(this.entries); - resetIterator(); + return getFiles(FTPFileFilters.NON_NULL); } /** - * Internal method for reading the input into the <code>entries</code> list. - * After this method has completed, <code>entries</code> will contain a - * collection of entries (as defined by - * <code>FTPFileEntryParser.readNextEntry()</code>), but this may contain - * various non-entry preliminary lines from the server output, duplicates, - * and other data that will not be part of the final listing. + * Returns an array of FTPFile objects containing the whole list of files returned by the server as read by this object's parser. The files are filtered + * before being added to the array. * - * @param stream The socket stream on which the input will be read. - * @param encoding The encoding to use. + * @param filter FTPFileFilter, must not be <code>null</code>. * - * @throws IOException - * thrown on any failure to read the stream + * @return an array of FTPFile objects containing the whole list of files returned by the server as read by this object's parser. + * <p> + * <b> NOTE:</b> This array may contain null members if any of the individual file listings failed to parse. The caller should check each entry for + * null before referencing it, or use the a filter such as {@link FTPFileFilters#NON_NULL} which does not allow null entries. + * @since 2.2 + * @throws IOException - not ever thrown, may be removed in a later release */ - private void readStream(InputStream stream, String encoding) throws IOException + public FTPFile[] getFiles(final FTPFileFilter filter) throws IOException // TODO remove; not actually thrown { - BufferedReader reader = new BufferedReader( - new InputStreamReader(stream, Charsets.toCharset(encoding))); - - String line = this.parser.readNextEntry(reader); - - while (line != null) - { - this.entries.add(line); - line = this.parser.readNextEntry(reader); - } - reader.close(); + return getFileList(filter).toArray(EMPTY_FTP_FILE_ARRAY); } /** - * Returns an array of at most <code>quantityRequested</code> FTPFile - * objects starting at this object's internal iterator's current position. - * If fewer than <code>quantityRequested</code> such - * elements are available, the returned array will have a length equal - * to the number of entries at and after after the current position. - * If no such entries are found, this array will have a length of 0. + * Returns an array of at most <code>quantityRequested</code> FTPFile objects starting at this object's internal iterator's current position. If fewer than + * <code>quantityRequested</code> such elements are available, the returned array will have a length equal to the number of entries at and after after the + * current position. If no such entries are found, this array will have a length of 0. * - * After this method is called this object's internal iterator is advanced - * by a number of positions equal to the size of the array returned. + * After this method is called this object's internal iterator is advanced by a number of positions equal to the size of the array returned. * - * @param quantityRequested - * the maximum number of entries we want to get. + * @param quantityRequested the maximum number of entries we want to get. * - * @return an array of at most <code>quantityRequested</code> FTPFile - * objects starting at the current position of this iterator within its - * list and at least the number of elements which exist in the list at - * and after its current position. - * <p><b> - * NOTE:</b> This array may contain null members if any of the - * individual file listings failed to parse. The caller should - * check each entry for null before referencing it. + * @return an array of at most <code>quantityRequested</code> FTPFile objects starting at the current position of this iterator within its list and at least + * the number of elements which exist in the list at and after its current position. + * <p> + * <b> NOTE:</b> This array may contain null members if any of the individual file listings failed to parse. The caller should check each entry for + * null before referencing it. */ - public FTPFile[] getNext(int quantityRequested) { - List<FTPFile> tmpResults = new LinkedList<FTPFile>(); + public FTPFile[] getNext(final int quantityRequested) { + final List<FTPFile> tmpResults = new LinkedList<>(); int count = quantityRequested; - while (count > 0 && this._internalIterator.hasNext()) { - String entry = this._internalIterator.next(); + while (count > 0 && this.internalIterator.hasNext()) { + final String entry = this.internalIterator.next(); FTPFile temp = this.parser.parseFTPEntry(entry); if (temp == null && saveUnparseableEntries) { temp = new FTPFile(entry); @@ -185,145 +169,118 @@ public class FTPListParseEngine { tmpResults.add(temp); count--; } - return tmpResults.toArray(new FTPFile[tmpResults.size()]); + return tmpResults.toArray(EMPTY_FTP_FILE_ARRAY); } /** - * Returns an array of at most <code>quantityRequested</code> FTPFile - * objects starting at this object's internal iterator's current position, - * and working back toward the beginning. + * Returns an array of at most <code>quantityRequested</code> FTPFile objects starting at this object's internal iterator's current position, and working + * back toward the beginning. * - * If fewer than <code>quantityRequested</code> such - * elements are available, the returned array will have a length equal - * to the number of entries at and after after the current position. - * If no such entries are found, this array will have a length of 0. + * If fewer than <code>quantityRequested</code> such elements are available, the returned array will have a length equal to the number of entries at and + * after after the current position. If no such entries are found, this array will have a length of 0. * - * After this method is called this object's internal iterator is moved - * back by a number of positions equal to the size of the array returned. + * After this method is called this object's internal iterator is moved back by a number of positions equal to the size of the array returned. * - * @param quantityRequested - * the maximum number of entries we want to get. + * @param quantityRequested the maximum number of entries we want to get. * - * @return an array of at most <code>quantityRequested</code> FTPFile - * objects starting at the current position of this iterator within its - * list and at least the number of elements which exist in the list at - * and after its current position. This array will be in the same order - * as the underlying list (not reversed). - * <p><b> - * NOTE:</b> This array may contain null members if any of the - * individual file listings failed to parse. The caller should - * check each entry for null before referencing it. + * @return an array of at most <code>quantityRequested</code> FTPFile objects starting at the current position of this iterator within its list and at least + * the number of elements which exist in the list at and after its current position. This array will be in the same order as the underlying list + * (not reversed). + * <p> + * <b> NOTE:</b> This array may contain null members if any of the individual file listings failed to parse. The caller should check each entry for + * null before referencing it. */ - public FTPFile[] getPrevious(int quantityRequested) { - List<FTPFile> tmpResults = new LinkedList<FTPFile>(); + public FTPFile[] getPrevious(final int quantityRequested) { + final List<FTPFile> tmpResults = new LinkedList<>(); int count = quantityRequested; - while (count > 0 && this._internalIterator.hasPrevious()) { - String entry = this._internalIterator.previous(); + while (count > 0 && this.internalIterator.hasPrevious()) { + final String entry = this.internalIterator.previous(); FTPFile temp = this.parser.parseFTPEntry(entry); if (temp == null && saveUnparseableEntries) { temp = new FTPFile(entry); } - tmpResults.add(0,temp); + tmpResults.add(0, temp); count--; } - return tmpResults.toArray(new FTPFile[tmpResults.size()]); + return tmpResults.toArray(EMPTY_FTP_FILE_ARRAY); } /** - * Returns an array of FTPFile objects containing the whole list of - * files returned by the server as read by this object's parser. + * convenience method to allow clients to know whether this object's internal iterator's current position is at the end of the list. * - * @return an array of FTPFile objects containing the whole list of - * files returned by the server as read by this object's parser. - * None of the entries will be null - * @throws IOException - not ever thrown, may be removed in a later release + * @return true if internal iterator is not at end of list, false otherwise. */ - public FTPFile[] getFiles() - throws IOException // TODO remove; not actually thrown - { - return getFiles(FTPFileFilters.NON_NULL); + public boolean hasNext() { + return internalIterator.hasNext(); } /** - * Returns an array of FTPFile objects containing the whole list of - * files returned by the server as read by this object's parser. - * The files are filtered before being added to the array. - * - * @param filter FTPFileFilter, must not be <code>null</code>. + * convenience method to allow clients to know whether this object's internal iterator's current position is at the beginning of the list. * - * @return an array of FTPFile objects containing the whole list of - * files returned by the server as read by this object's parser. - * <p><b> - * NOTE:</b> This array may contain null members if any of the - * individual file listings failed to parse. The caller should - * check each entry for null before referencing it, or use the - * a filter such as {@link FTPFileFilters#NON_NULL} which does not - * allow null entries. - * @since 2.2 - * @throws IOException - not ever thrown, may be removed in a later release + * @return true if internal iterator is not at beginning of list, false otherwise. */ - public FTPFile[] getFiles(FTPFileFilter filter) - throws IOException // TODO remove; not actually thrown - { - List<FTPFile> tmpResults = new ArrayList<FTPFile>(); - Iterator<String> iter = this.entries.iterator(); - while (iter.hasNext()) { - String entry = iter.next(); - FTPFile temp = this.parser.parseFTPEntry(entry); - if (temp == null && saveUnparseableEntries) { - temp = new FTPFile(entry); - } - if (filter.accept(temp)){ - tmpResults.add(temp); - } - } - return tmpResults.toArray(new FTPFile[tmpResults.size()]); - + public boolean hasPrevious() { + return internalIterator.hasPrevious(); } /** - * convenience method to allow clients to know whether this object's - * internal iterator's current position is at the end of the list. + * Internal method for reading (and closing) the input into the <code>entries</code> list. After this method has completed, <code>entries</code> will + * contain a collection of entries (as defined by <code>FTPFileEntryParser.readNextEntry()</code>), but this may contain various non-entry preliminary lines + * from the server output, duplicates, and other data that will not be part of the final listing. + * + * @param inputStream The socket stream on which the input will be read. + * @param charsetName The encoding to use. * - * @return true if internal iterator is not at end of list, false - * otherwise. + * @throws IOException thrown on any failure to read the stream */ - public boolean hasNext() { - return _internalIterator.hasNext(); + private void read(final InputStream inputStream, final String charsetName) throws IOException { + try (final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charsets.toCharset(charsetName)))) { + + String line = this.parser.readNextEntry(reader); + + while (line != null) { + this.entries.add(line); + line = this.parser.readNextEntry(reader); + } + } } /** - * convenience method to allow clients to know whether this object's - * internal iterator's current position is at the beginning of the list. + * Do not use. * - * @return true if internal iterator is not at beginning of list, false - * otherwise. + * @param inputStream the stream from which to read + * @throws IOException on error + * @deprecated use {@link #readServerList(InputStream, String)} instead */ - public boolean hasPrevious() { - return _internalIterator.hasPrevious(); + @Deprecated + public void readServerList(final InputStream inputStream) throws IOException { + readServerList(inputStream, null); } /** - * resets this object's internal iterator to the beginning of the list. + * Reads (and closes) the initial reading and preparsing of the list returned by the server. After this method has completed, this object will contain a + * list of unparsed entries (Strings) each referring to a unique file on the server. + * + * @param inputStream input stream provided by the server socket. + * @param charsetName the encoding to be used for reading the stream + * + * @throws IOException thrown on any failure to read from the sever. */ - public void resetIterator() { - this._internalIterator = this.entries.listIterator(); + public void readServerList(final InputStream inputStream, final String charsetName) throws IOException { + this.entries = new LinkedList<>(); + read(inputStream, charsetName); + this.parser.preParse(this.entries); + resetIterator(); } // DEPRECATED METHODS - for API compatibility only - DO NOT USE /** - * Do not use. - * @param stream the stream from which to read - * @throws IOException on error - * @deprecated use {@link #readServerList(InputStream, String)} instead - */ - @Deprecated - public void readServerList(InputStream stream) - throws IOException - { - readServerList(stream, null); + * resets this object's internal iterator to the beginning of the list. + */ + public void resetIterator() { + this.internalIterator = this.entries.listIterator(); } } diff --git a/src/main/java/org/apache/commons/net/ftp/FTPReply.java b/src/main/java/org/apache/commons/net/ftp/FTPReply.java index 37d5df5..3cf1b76 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPReply.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPReply.java @@ -17,17 +17,14 @@ package org.apache.commons.net.ftp; -/*** - * FTPReply stores a set of constants for FTP reply codes. To interpret - * the meaning of the codes, familiarity with RFC 959 is assumed. - * The mnemonic constant names are transcriptions from the code descriptions - * of RFC 959. +/** + * FTPReply stores a set of constants for FTP reply codes. To interpret the meaning of the codes, familiarity with RFC 959 is assumed. The mnemonic constant + * names are transcriptions from the code descriptions of RFC 959. * <p> * TODO replace with an enum - ***/ + */ -public final class FTPReply -{ +public final class FTPReply { public static final int RESTART_MARKER = 110; public static final int SERVICE_NOT_READY = 120; @@ -99,103 +96,78 @@ public final class FTPReply /** @since 2.2 */ public static final int EXTENDED_PORT_FAILURE = 522; - // Cannot be instantiated - private FTPReply() - {} - - /*** - * Determine if a reply code is a positive preliminary response. All - * codes beginning with a 1 are positive preliminary responses. - * Postitive preliminary responses are used to indicate tentative success. - * No further commands can be issued to the FTP server after a positive - * preliminary response until a follow up response is received from the - * server. + /** + * Determine if a reply code is a negative permanent response. All codes beginning with a 5 are negative permanent responses. The FTP server will send a + * negative permanent response on the failure of a command that cannot be reattempted with success. * - * @param reply The reply code to test. - * @return True if a reply code is a postive preliminary response, false - * if not. - ***/ - public static boolean isPositivePreliminary(int reply) - { - return (reply >= 100 && reply < 200); + * @param reply The reply code to test. + * @return True if a reply code is a negative permanent response, false if not. + */ + public static boolean isNegativePermanent(final int reply) { + return reply >= 500 && reply < 600; } - /*** - * Determine if a reply code is a positive completion response. All - * codes beginning with a 2 are positive completion responses. - * The FTP server will send a positive completion response on the final - * successful completion of a command. + /** + * Determine if a reply code is a negative transient response. All codes beginning with a 4 are negative transient responses. The FTP server will send a + * negative transient response on the failure of a command that can be reattempted with success. * - * @param reply The reply code to test. - * @return True if a reply code is a postive completion response, false - * if not. - ***/ - public static boolean isPositiveCompletion(int reply) - { - return (reply >= 200 && reply < 300); + * @param reply The reply code to test. + * @return True if a reply code is a negative transient response, false if not. + */ + public static boolean isNegativeTransient(final int reply) { + return reply >= 400 && reply < 500; } - /*** - * Determine if a reply code is a positive intermediate response. All - * codes beginning with a 3 are positive intermediate responses. - * The FTP server will send a positive intermediate response on the - * successful completion of one part of a multi-part sequence of - * commands. For example, after a successful USER command, a positive - * intermediate response will be sent to indicate that the server is - * ready for the PASS command. + /** + * Determine if a reply code is a positive completion response. All codes beginning with a 2 are positive completion responses. The FTP server will send a + * positive completion response on the final successful completion of a command. * - * @param reply The reply code to test. - * @return True if a reply code is a postive intermediate response, false - * if not. - ***/ - public static boolean isPositiveIntermediate(int reply) - { - return (reply >= 300 && reply < 400); + * @param reply The reply code to test. + * @return True if a reply code is a positive completion response, false if not. + */ + public static boolean isPositiveCompletion(final int reply) { + return reply >= 200 && reply < 300; } - /*** - * Determine if a reply code is a negative transient response. All - * codes beginning with a 4 are negative transient responses. - * The FTP server will send a negative transient response on the - * failure of a command that can be reattempted with success. + /** + * Determine if a reply code is a positive intermediate response. All codes beginning with a 3 are positive intermediate responses. The FTP server will send + * a positive intermediate response on the successful completion of one part of a multi-part sequence of commands. For example, after a successful USER + * command, a positive intermediate response will be sent to indicate that the server is ready for the PASS command. * - * @param reply The reply code to test. - * @return True if a reply code is a negative transient response, false - * if not. - ***/ - public static boolean isNegativeTransient(int reply) - { - return (reply >= 400 && reply < 500); + * @param reply The reply code to test. + * @return True if a reply code is a positive intermediate response, false if not. + */ + public static boolean isPositiveIntermediate(final int reply) { + return reply >= 300 && reply < 400; } - /*** - * Determine if a reply code is a negative permanent response. All - * codes beginning with a 5 are negative permanent responses. - * The FTP server will send a negative permanent response on the - * failure of a command that cannot be reattempted with success. + /** + * Determine if a reply code is a positive preliminary response. All codes beginning with a 1 are positive preliminary responses. Postitive preliminary + * responses are used to indicate tentative success. No further commands can be issued to the FTP server after a positive preliminary response until a + * follow up response is received from the server. * - * @param reply The reply code to test. - * @return True if a reply code is a negative permanent response, false - * if not. - ***/ - public static boolean isNegativePermanent(int reply) - { - return (reply >= 500 && reply < 600); + * @param reply The reply code to test. + * @return True if a reply code is a positive preliminary response, false if not. + */ + public static boolean isPositivePreliminary(final int reply) { + return reply >= 100 && reply < 200; } /** * Determine if a reply code is a protected response. - * @param reply The reply code to test. - * @return True if a reply code is a protected response, false - * if not. + * + * @param reply The reply code to test. + * @return True if a reply code is a protected response, false if not. * @since 3.0 */ - public static boolean isProtectedReplyCode(int reply) - { + public static boolean isProtectedReplyCode(final int reply) { // actually, only 3 protected reply codes are // defined in RFC 2228: 631, 632 and 633. - return (reply >= 600 && reply < 700); + return reply >= 600 && reply < 700; } + // Cannot be instantiated + private FTPReply() { + } } diff --git a/src/main/java/org/apache/commons/net/ftp/FTPSClient.java b/src/main/java/org/apache/commons/net/ftp/FTPSClient.java index f9b06ab..74cad43 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPSClient.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPSClient.java @@ -22,6 +22,10 @@ import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; import java.net.Socket; import javax.net.ssl.HostnameVerifier; @@ -39,13 +43,11 @@ import org.apache.commons.net.util.SSLSocketUtils; import org.apache.commons.net.util.TrustManagerUtils; /** - * FTP over SSL processing. If desired, the JVM property -Djavax.net.debug=all can be used to - * see wire-level SSL details. + * FTP over SSL processing. If desired, the JVM property -Djavax.net.debug=all can be used to see wire-level SSL details. + * + * Warning: the hostname is not verified against the certificate by default, use {@link #setHostnameVerifier(HostnameVerifier)} or + * {@link #setEndpointCheckingEnabled(boolean)} (on Java 1.7+) to enable verification. Verification is only performed on client mode connections. * - * Warning: the hostname is not verified against the certificate by default, use - * {@link #setHostnameVerifier(HostnameVerifier)} or {@link #setEndpointCheckingEnabled(boolean)} - * (on Java 1.7+) to enable verification. Verification is only performed on client mode connections. - * @version $Id: FTPSClient.java 1747829 2016-06-11 00:57:57Z sebb $ * @since 2.0 */ public class FTPSClient extends FTPClient { @@ -60,8 +62,8 @@ public class FTPSClient extends FTPClient { public static final int DEFAULT_FTPS_DATA_PORT = 989; public static final int DEFAULT_FTPS_PORT = 990; - /** The value that I can set in PROT command (C = Clear, P = Protected) */ - private static final String[] PROT_COMMAND_VALUE = {"C","E","S","P"}; + /** The value that I can set in PROT command (C = Clear, P = Protected) */ + private static final String[] PROT_COMMAND_VALUE = { "C", "E", "S", "P" }; /** Default PROT Command */ private static final String DEFAULT_PROT = "C"; /** Default secure socket protocol name, i.e. TLS */ @@ -69,21 +71,33 @@ public class FTPSClient extends FTPClient { /** The AUTH (Authentication/Security Mechanism) command. */ private static final String CMD_AUTH = "AUTH"; - /** The ADAT (Authentication/Security Data) command. */ + /** The ADAT (Authentication/Security Data) command. */ private static final String CMD_ADAT = "ADAT"; - /** The PROT (Data Channel Protection Level) command. */ + /** The PROT (Data Channel Protection Level) command. */ private static final String CMD_PROT = "PROT"; - /** The PBSZ (Protection Buffer Size) command. */ + /** The PBSZ (Protection Buffer Size) command. */ private static final String CMD_PBSZ = "PBSZ"; - /** The MIC (Integrity Protected Command) command. */ + /** The MIC (Integrity Protected Command) command. */ private static final String CMD_MIC = "MIC"; - /** The CONF (Confidentiality Protected Command) command. */ + /** The CONF (Confidentiality Protected Command) command. */ private static final String CMD_CONF = "CONF"; - /** The ENC (Privacy Protected Command) command. */ + /** The ENC (Privacy Protected Command) command. */ private static final String CMD_ENC = "ENC"; - /** The CCC (Clear Command Channel) command. */ + /** The CCC (Clear Command Channel) command. */ private static final String CMD_CCC = "CCC"; + /** @deprecated - not used - may be removed in a future release */ + @Deprecated + public static String KEYSTORE_ALGORITHM; + /** @deprecated - not used - may be removed in a future release */ + @Deprecated + public static String TRUSTSTORE_ALGORITHM; + /** @deprecated - not used - may be removed in a future release */ + @Deprecated + public static String PROVIDER; + /** @deprecated - not used - may be removed in a future release */ + @Deprecated + public static String STORE_TYPE; /** The security mode. (True - Implicit Mode / False - Explicit Mode) */ private final boolean isImplicit; /** The secure socket protocol to be used, e.g. SSL/TLS. */ @@ -98,27 +112,31 @@ public class FTPSClient extends FTPClient { private boolean isCreation = true; /** The use client mode flag. */ private boolean isClientMode = true; + /** The need client auth flag. */ - private boolean isNeedClientAuth = false; + private boolean isNeedClientAuth; + /** The want client auth flag. */ - private boolean isWantClientAuth = false; + private boolean isWantClientAuth; + /** The cipher suites */ - private String[] suites = null; + private String[] suites; + /** The protocol versions */ - private String[] protocols = null; + private String[] protocols; - /** The FTPS {@link TrustManager} implementation, default validate only - * {@link TrustManagerUtils#getValidateServerCertificateTrustManager()}. + /** + * The FTPS {@link TrustManager} implementation, default validate only {@link TrustManagerUtils#getValidateServerCertificateTrustManager()}. */ private TrustManager trustManager = TrustManagerUtils.getValidateServerCertificateTrustManager(); /** The {@link KeyManager}, default null (i.e. use system default). */ - private KeyManager keyManager = null; + private KeyManager keyManager; /** The {@link HostnameVerifier} to use post-TLS, default null (i.e. no verification). */ - private HostnameVerifier hostnameVerifier = null; + private HostnameVerifier hostnameVerifier; - /** Use Java 1.7+ HTTPS Endpoint Identification Algorithim. */ + /** Use Java 1.7+ HTTPS Endpoint Identification Algorithm. */ private boolean tlsEndpointChecking; /** @@ -131,85 +149,63 @@ public class FTPSClient extends FTPClient { } /** - * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS - * Calls {@link #FTPSClient(String, boolean)} + * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS Calls {@link #FTPSClient(String, boolean)} + * * @param isImplicit The security mode (Implicit/Explicit). */ - public FTPSClient(boolean isImplicit) { + public FTPSClient(final boolean isImplicit) { this(DEFAULT_PROTOCOL, isImplicit); } /** - * Constructor for FTPSClient, using explict mode, calls {@link #FTPSClient(String, boolean)}. + * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS The default TrustManager is set from + * {@link TrustManagerUtils#getValidateServerCertificateTrustManager()} * - * @param protocol the protocol to use - */ - public FTPSClient(String protocol) { - this(protocol, false); - } - - /** - * Constructor for FTPSClient allowing specification of protocol - * and security mode. If isImplicit is true, the port is set to - * {@link #DEFAULT_FTPS_PORT} i.e. 990. - * The default TrustManager is set from {@link TrustManagerUtils#getValidateServerCertificateTrustManager()} - * @param protocol the protocol - * @param isImplicit The security mode(Implicit/Explicit). - */ - public FTPSClient(String protocol, boolean isImplicit) { - super(); - this.protocol = protocol; - this.isImplicit = isImplicit; - if (isImplicit) { - setDefaultPort(DEFAULT_FTPS_PORT); - } - } - - /** - * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS - * The default TrustManager is set from {@link TrustManagerUtils#getValidateServerCertificateTrustManager()} * @param isImplicit The security mode(Implicit/Explicit). - * @param context A pre-configured SSL Context + * @param context A pre-configured SSL Context */ - public FTPSClient(boolean isImplicit, SSLContext context) { + public FTPSClient(final boolean isImplicit, final SSLContext context) { this(DEFAULT_PROTOCOL, isImplicit); this.context = context; } /** - * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS - * and isImplicit {@code false} - * Calls {@link #FTPSClient(boolean, SSLContext)} + * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS and isImplicit {@code false} Calls {@link #FTPSClient(boolean, SSLContext)} + * * @param context A pre-configured SSL Context */ - public FTPSClient(SSLContext context) { + public FTPSClient(final SSLContext context) { this(false, context); } - /** - * Set AUTH command use value. - * This processing is done before connected processing. - * @param auth AUTH command use value. + * Constructor for FTPSClient, using explict mode, calls {@link #FTPSClient(String, boolean)}. + * + * @param protocol the protocol to use */ - public void setAuthValue(String auth) { - this.auth = auth; + public FTPSClient(final String protocol) { + this(protocol, false); } /** - * Return AUTH command use value. - * @return AUTH command use value. + * Constructor for FTPSClient allowing specification of protocol and security mode. If isImplicit is true, the port is set to {@link #DEFAULT_FTPS_PORT} + * i.e. 990. The default TrustManager is set from {@link TrustManagerUtils#getValidateServerCertificateTrustManager()} + * + * @param protocol the protocol + * @param isImplicit The security mode(Implicit/Explicit). */ - public String getAuthValue() { - return this.auth; + public FTPSClient(final String protocol, final boolean isImplicit) { + this.protocol = protocol; + this.isImplicit = isImplicit; + if (isImplicit) { + setDefaultPort(DEFAULT_FTPS_PORT); + } } - /** - * Because there are so many connect() methods, - * the _connectAction_() method is provided as a means of performing - * some action immediately after establishing a connection, - * rather than reimplementing all of the connect() methods. + * Because there are so many connect() methods, the _connectAction_() method is provided as a means of performing some action immediately after establishing + * a connection, rather than reimplementing all of the connect() methods. + * * @throws IOException If it throw by _connectAction_. * @see org.apache.commons.net.SocketClient#_connectAction_() */ @@ -217,6 +213,7 @@ public class FTPSClient extends FTPClient { protected void _connectAction_() throws IOException { // Implicit mode. if (isImplicit) { + applySocketAttributes(); sslNegotiation(); } super._connectAction_(); @@ -228,278 +225,268 @@ public class FTPSClient extends FTPClient { } /** - * AUTH command. - * @throws SSLException If it server reply code not equal "234" and "334". - * @throws IOException If an I/O error occurs while either sending - * the command. - */ - protected void execAUTH() throws SSLException, IOException { - int replyCode = sendCommand(CMD_AUTH, auth); - if (FTPReply.SECURITY_MECHANISM_IS_OK == replyCode) { - // replyCode = 334 - // I carry out an ADAT command. - } else if (FTPReply.SECURITY_DATA_EXCHANGE_COMPLETE != replyCode) { - throw new SSLException(getReplyString()); - } - } - - /** - * Performs a lazy init of the SSL context - * @throws IOException + * Returns a socket of the data connection. Wrapped as an {@link SSLSocket}, which carries out handshake processing. + * + * @param command The int representation of the FTP command to send. + * @param arg The arguments to the FTP command. If this parameter is set to null, then the command is sent with no arguments. + * @return corresponding to the established data connection. Null is returned if an FTP protocol error is reported at any point during the establishment and + * initialization of the connection. + * @throws IOException If there is any problem with the connection. + * @see FTPClient#_openDataConnection_(int, String) + * @deprecated (3.3) Use {@link FTPClient#_openDataConnection_(FTPCmd, String)} instead */ - private void initSslContext() throws IOException { - if (context == null) { - context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager()); - } + @Override + // Strictly speaking this is not needed, but it works round a Clirr bug + // So rather than invoke the parent code, we do it here + @Deprecated + protected Socket _openDataConnection_(final int command, final String arg) throws IOException { + return _openDataConnection_(FTPCommand.getCommand(command), arg); } /** - * SSL/TLS negotiation. Acquires an SSL socket of a control - * connection and carries out handshake processing. - * @throws IOException If server negotiation fails + * Returns a socket of the data connection. Wrapped as an {@link SSLSocket}, which carries out handshake processing. + * + * @param command The textual representation of the FTP command to send. + * @param arg The arguments to the FTP command. If this parameter is set to null, then the command is sent with no arguments. + * @return corresponding to the established data connection. Null is returned if an FTP protocol error is reported at any point during the establishment and + * initialization of the connection. + * @throws IOException If there is any problem with the connection. + * @see FTPClient#_openDataConnection_(int, String) + * @since 3.2 */ - protected void sslNegotiation() throws IOException { - plainSocket = _socket_; - initSslContext(); + @Override + protected Socket _openDataConnection_(final String command, final String arg) throws IOException { + final Socket socket = openDataSecureConnection(command, arg); + _prepareDataSocket_(socket); + if (socket instanceof SSLSocket) { + final SSLSocket sslSocket = (SSLSocket) socket; - SSLSocketFactory ssf = context.getSocketFactory(); - String host = (_hostname_ != null) ? _hostname_ : getRemoteAddress().getHostAddress(); - int port = _socket_.getPort(); - SSLSocket socket = - (SSLSocket) ssf.createSocket(_socket_, host, port, false); - socket.setEnableSessionCreation(isCreation); - socket.setUseClientMode(isClientMode); + sslSocket.setUseClientMode(isClientMode); + sslSocket.setEnableSessionCreation(isCreation); - // client mode - if (isClientMode) { - if (tlsEndpointChecking) { - SSLSocketUtils.enableEndpointNameVerification(socket); + // server mode + if (!isClientMode) { + sslSocket.setNeedClientAuth(isNeedClientAuth); + sslSocket.setWantClientAuth(isWantClientAuth); } - } else { // server mode - socket.setNeedClientAuth(isNeedClientAuth); - socket.setWantClientAuth(isWantClientAuth); - } - - if (protocols != null) { - socket.setEnabledProtocols(protocols); - } - if (suites != null) { - socket.setEnabledCipherSuites(suites); - } - socket.startHandshake(); - - // TODO the following setup appears to duplicate that in the super class methods - _socket_ = socket; - _controlInput_ = new BufferedReader(new InputStreamReader( - socket .getInputStream(), getControlEncoding())); - _controlOutput_ = new BufferedWriter(new OutputStreamWriter( - socket.getOutputStream(), getControlEncoding())); - - if (isClientMode) { - if (hostnameVerifier != null && !hostnameVerifier.verify(host, socket.getSession())) { - throw new SSLHandshakeException("Hostname doesn't match certificate"); + if (suites != null) { + sslSocket.setEnabledCipherSuites(suites); + } + if (protocols != null) { + sslSocket.setEnabledProtocols(protocols); } + sslSocket.startHandshake(); } - } - - /** - * Get the {@link KeyManager} instance. - * @return The {@link KeyManager} instance - */ - private KeyManager getKeyManager() { - return keyManager; - } - /** - * Set a {@link KeyManager} to use - * - * @param keyManager The KeyManager implementation to set. - * @see org.apache.commons.net.util.KeyManagerUtils - */ - public void setKeyManager(KeyManager keyManager) { - this.keyManager = keyManager; + return socket; } /** - * Controls whether a new SSL session may be established by this socket. - * @param isCreation The established socket flag. + * Performs any custom initialization for a newly created SSLSocket (before the SSL handshake happens). Called by {@link #_openDataConnection_(int, String)} + * immediately after creating the socket. The default implementation is a no-op + * + * @param socket the socket to set up + * @throws IOException on error + * @since 3.1 */ - public void setEnabledSessionCreation(boolean isCreation) { - this.isCreation = isCreation; + protected void _prepareDataSocket_(final Socket socket) throws IOException { } /** - * Returns true if new SSL sessions may be established by this socket. - * When the underlying {@link Socket} instance is not SSL-enabled (i.e. an - * instance of {@link SSLSocket} with {@link SSLSocket}{@link #getEnableSessionCreation()}) enabled, - * this returns False. - * @return true - Indicates that sessions may be created; - * this is the default. - * false - indicates that an existing session must be resumed. + * Check the value that can be set in PROT Command value. + * + * @param prot Data Channel Protection Level. + * @return True - A set point is right / False - A set point is not right */ - public boolean getEnableSessionCreation() { - if (_socket_ instanceof SSLSocket) { - return ((SSLSocket)_socket_).getEnableSessionCreation(); + private boolean checkPROTValue(final String prot) { + for (final String element : PROT_COMMAND_VALUE) { + if (element.equals(prot)) { + return true; + } } return false; } /** - * Configures the socket to require client authentication. - * @param isNeedClientAuth The need client auth flag. + * Close open sockets. + * + * @param socket main socket for proxy if enabled + * @param sslSocket ssl socket + * @throws IOException closing sockets is not successful */ - public void setNeedClientAuth(boolean isNeedClientAuth) { - this.isNeedClientAuth = isNeedClientAuth; + private void closeSockets(final Socket socket, final Socket sslSocket) throws IOException { + if (socket != null) { + socket.close(); + } + if (sslSocket != null) { + sslSocket.close(); + } } /** - * Returns true if the socket will require client authentication. - * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false. - * @return true - If the server mode socket should request - * that the client authenticate itself. + * Create SSL socket from plain socket. + * + * @param socket + * @return SSL Socket + * @throws IOException */ - public boolean getNeedClientAuth() { - if (_socket_ instanceof SSLSocket) { - return ((SSLSocket)_socket_).getNeedClientAuth(); + private SSLSocket createSSLSocket(final Socket socket) throws IOException { + if (socket != null) { + final SSLSocketFactory f = context.getSocketFactory(); + return (SSLSocket) f.createSocket(socket, _hostname_, socket.getPort(), false); } - return false; + return null; } /** - * Configures the socket to request client authentication, - * but only if such a request is appropriate to the cipher - * suite negotiated. - * @param isWantClientAuth The want client auth flag. + * Closes the connection to the FTP server and restores connection parameters to the default values. + * <p> + * Calls {@code setSocketFactory(null)} and {@code setServerSocketFactory(null)} to reset the factories that may have been changed during the session, e.g. + * by {@link #execPROT(String)} + * + * @throws IOException If an error occurs while disconnecting. + * @since 3.0 */ - public void setWantClientAuth(boolean isWantClientAuth) { - this.isWantClientAuth = isWantClientAuth; + @Override + public void disconnect() throws IOException { + super.disconnect(); + if (plainSocket != null) { + plainSocket.close(); + } + setSocketFactory(null); + setServerSocketFactory(null); } /** - * Returns true if the socket will request client authentication. - * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false. - * @return true - If the server mode socket should request - * that the client authenticate itself. + * Send the ADAT command with the specified authentication data. + * + * @param data The data to send with the command. + * @return server reply. + * @throws IOException If an I/O error occurs while sending the command. + * @since 3.0 */ - public boolean getWantClientAuth() { - if (_socket_ instanceof SSLSocket) { - return ((SSLSocket)_socket_).getWantClientAuth(); + public int execADAT(final byte[] data) throws IOException { + if (data != null) { + return sendCommand(CMD_ADAT, Base64.encodeBase64StringUnChunked(data)); } - return false; + return sendCommand(CMD_ADAT); } /** - * Configures the socket to use client (or server) mode in its first - * handshake. - * @param isClientMode The use client mode flag. + * AUTH command. + * + * @throws SSLException If it server reply code not equal "234" and "334". + * @throws IOException If an I/O error occurs while either sending the command. */ - public void setUseClientMode(boolean isClientMode) { - this.isClientMode = isClientMode; + protected void execAUTH() throws SSLException, IOException { + final int replyCode = sendCommand(CMD_AUTH, auth); + if (FTPReply.SECURITY_MECHANISM_IS_OK == replyCode) { + // replyCode = 334 + // I carry out an ADAT command. + } else if (FTPReply.SECURITY_DATA_EXCHANGE_COMPLETE != replyCode) { + throw new SSLException(getReplyString()); + } } /** - * Returns true if the socket is set to use client mode - * in its first handshake. - * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false. - * @return true - If the socket should start its first handshake - * in "client" mode. + * Send the AUTH command with the specified mechanism. + * + * @param mechanism The mechanism name to send with the command. + * @return server reply. + * @throws IOException If an I/O error occurs while sending the command. + * @since 3.0 */ - public boolean getUseClientMode() { - if (_socket_ instanceof SSLSocket) { - return ((SSLSocket)_socket_).getUseClientMode(); - } - return false; + public int execAUTH(final String mechanism) throws IOException { + return sendCommand(CMD_AUTH, mechanism); } /** - * Controls which particular cipher suites are enabled for use on this - * connection. Called before server negotiation. - * @param cipherSuites The cipher suites. + * Send the CCC command to the server. The CCC (Clear Command Channel) command causes the underlying {@link SSLSocket} instance to be assigned to a plain + * {@link Socket} instances + * + * @return server reply. + * @throws IOException If an I/O error occurs while sending the command. + * @since 3.0 */ - public void setEnabledCipherSuites(String[] cipherSuites) { - suites = new String[cipherSuites.length]; - System.arraycopy(cipherSuites, 0, suites, 0, cipherSuites.length); + public int execCCC() throws IOException { + final int repCode = sendCommand(CMD_CCC); +// This will be performed by sendCommand(String, String) +// if (FTPReply.isPositiveCompletion(repCode)) { +// _socket_.close(); +// _socket_ = plainSocket; +// _controlInput_ = new BufferedReader( +// new InputStreamReader( +// _socket_.getInputStream(), getControlEncoding())); +// _controlOutput_ = new BufferedWriter( +// new OutputStreamWriter( +// _socket_.getOutputStream(), getControlEncoding())); +// } + return repCode; } /** - * Returns the names of the cipher suites which could be enabled - * for use on this connection. - * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns null. - * @return An array of cipher suite names, or <code>null</code> + * Send the CONF command with the specified data. + * + * @param data The data to send with the command. + * @return server reply. + * @throws IOException If an I/O error occurs while sending the command. + * @since 3.0 */ - public String[] getEnabledCipherSuites() { - if (_socket_ instanceof SSLSocket) { - return ((SSLSocket)_socket_).getEnabledCipherSuites(); + public int execCONF(final byte[] data) throws IOException { + if (data != null) { + return sendCommand(CMD_CONF, Base64.encodeBase64StringUnChunked(data)); } - return null; + return sendCommand(CMD_CONF, ""); // perhaps "=" or just sendCommand(String)? } /** - * Controls which particular protocol versions are enabled for use on this - * connection. I perform setting before a server negotiation. - * @param protocolVersions The protocol versions. + * Send the ENC command with the specified data. + * + * @param data The data to send with the command. + * @return server reply. + * @throws IOException If an I/O error occurs while sending the command. + * @since 3.0 */ - public void setEnabledProtocols(String[] protocolVersions) { - protocols = new String[protocolVersions.length]; - System.arraycopy(protocolVersions, 0, protocols, 0, protocolVersions.length); + public int execENC(final byte[] data) throws IOException { + if (data != null) { + return sendCommand(CMD_ENC, Base64.encodeBase64StringUnChunked(data)); + } + return sendCommand(CMD_ENC, ""); // perhaps "=" or just sendCommand(String)? } /** - * Returns the names of the protocol versions which are currently - * enabled for use on this connection. - * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns null. - * @return An array of protocols, or <code>null</code> - */ - public String[] getEnabledProtocols() { - if (_socket_ instanceof SSLSocket) { - return ((SSLSocket)_socket_).getEnabledProtocols(); + * Send the MIC command with the specified data. + * + * @param data The data to send with the command. + * @return server reply. + * @throws IOException If an I/O error occurs while sending the command. + * @since 3.0 + */ + public int execMIC(final byte[] data) throws IOException { + if (data != null) { + return sendCommand(CMD_MIC, Base64.encodeBase64StringUnChunked(data)); } - return null; + return sendCommand(CMD_MIC, ""); // perhaps "=" or just sendCommand(String)? } /** * PBSZ command. pbsz value: 0 to (2^32)-1 decimal integer. + * * @param pbsz Protection Buffer Size. * @throws SSLException If the server reply code does not equal "200". - * @throws IOException If an I/O error occurs while sending - * the command. + * @throws IOException If an I/O error occurs while sending the command. * @see #parsePBSZ(long) */ - public void execPBSZ(long pbsz) throws SSLException, IOException { + public void execPBSZ(final long pbsz) throws SSLException, IOException { if (pbsz < 0 || 4294967295L < pbsz) { // 32-bit unsigned number throw new IllegalArgumentException(); } - int status = sendCommand(CMD_PBSZ, String.valueOf(pbsz)); + final int status = sendCommand(CMD_PBSZ, String.valueOf(pbsz)); if (FTPReply.COMMAND_OK != status) { throw new SSLException(getReplyString()); } } - /** - * PBSZ command. pbsz value: 0 to (2^32)-1 decimal integer. - * Issues the command and parses the response to return the negotiated value. - * - * @param pbsz Protection Buffer Size. - * @throws SSLException If the server reply code does not equal "200". - * @throws IOException If an I/O error occurs while sending - * the command. - * @return the negotiated value. - * @see #execPBSZ(long) - * @since 3.0 - */ - public long parsePBSZ(long pbsz) throws SSLException, IOException { - execPBSZ(pbsz); - long minvalue = pbsz; - String remainder = extractPrefixedData("PBSZ=", getReplyString()); - if (remainder != null) { - long replysz = Long.parseLong(remainder); - if (replysz < minvalue) { - minvalue = replysz; - } - } - return minvalue; - } - /** * PROT command. * <ul> @@ -508,14 +495,11 @@ public class FTPSClient extends FTPClient { * <li>E - Confidential(SSL protocol only)</li> * <li>P - Private</li> * </ul> - * <b>N.B.</b> the method calls - * {@link #setSocketFactory(javax.net.SocketFactory)} and - * {@link #setServerSocketFactory(javax.net.ServerSocketFactory)} + * <b>N.B.</b> the method calls {@link #setSocketFactory(javax.net.SocketFactory)} and {@link #setServerSocketFactory(javax.net.ServerSocketFactory)} * * @param prot Data Channel Protection Level, if {@code null}, use {@link #DEFAULT_PROT}. - * @throws SSLException If the server reply code does not equal {@code 200}. - * @throws IOException If an I/O error occurs while sending - * the command. + * @throws SSLException If the server reply code does not equal {@code 200}. + * @throws IOException If an I/O error occurs while sending the command. */ public void execPROT(String prot) throws SSLException, IOException { if (prot == null) { @@ -538,129 +522,98 @@ public class FTPSClient extends FTPClient { } /** - * Check the value that can be set in PROT Command value. - * @param prot Data Channel Protection Level. - * @return True - A set point is right / False - A set point is not right + * Extract the data from a reply with a prefix, e.g. PBSZ=1234 => 1234 + * + * @param prefix the prefix to find + * @param reply where to find the prefix + * @return the remainder of the string after the prefix, or null if the prefix was not present. */ - private boolean checkPROTValue(String prot) { - for (String element : PROT_COMMAND_VALUE) - { - if (element.equals(prot)) { - return true; - } + private String extractPrefixedData(final String prefix, final String reply) { + final int idx = reply.indexOf(prefix); + if (idx == -1) { + return null; } - return false; + // N.B. Cannot use trim before substring as leading space would affect the offset. + return reply.substring(idx + prefix.length()).trim(); } /** - * Send an FTP command. - * A successful CCC (Clear Command Channel) command causes the underlying {@link SSLSocket} - * instance to be assigned to a plain {@link Socket} - * @param command The FTP command. - * @return server reply. - * @throws IOException If an I/O error occurs while sending the command. - * @throws SSLException if a CCC command fails - * @see org.apache.commons.net.ftp.FTP#sendCommand(java.lang.String) + * Return AUTH command use value. + * + * @return AUTH command use value. */ - // Would like to remove this method, but that will break any existing clients that are using CCC - @Override - public int sendCommand(String command, String args) throws IOException { - int repCode = super.sendCommand(command, args); - /* If CCC is issued, restore socket i/o streams to unsecured versions */ - if (CMD_CCC.equals(command)) { - if (FTPReply.COMMAND_OK == repCode) { - _socket_.close(); - _socket_ = plainSocket; - _controlInput_ = new BufferedReader( - new InputStreamReader( - _socket_ .getInputStream(), getControlEncoding())); - _controlOutput_ = new BufferedWriter( - new OutputStreamWriter( - _socket_.getOutputStream(), getControlEncoding())); - } else { - throw new SSLException(getReplyString()); - } - } - return repCode; + public String getAuthValue() { + return this.auth; } /** - * Returns a socket of the data connection. - * Wrapped as an {@link SSLSocket}, which carries out handshake processing. - * @param command The int representation of the FTP command to send. - * @param arg The arguments to the FTP command. - * If this parameter is set to null, then the command is sent with - * no arguments. - * @return corresponding to the established data connection. - * Null is returned if an FTP protocol error is reported at any point - * during the establishment and initialization of the connection. - * @throws IOException If there is any problem with the connection. - * @see FTPClient#_openDataConnection_(int, String) - * @deprecated (3.3) Use {@link FTPClient#_openDataConnection_(FTPCmd, String)} instead + * Returns the names of the cipher suites which could be enabled for use on this connection. When the underlying {@link Socket} is not an {@link SSLSocket} + * instance, returns null. + * + * @return An array of cipher suite names, or <code>null</code> */ - @Override - // Strictly speaking this is not needed, but it works round a Clirr bug - // So rather than invoke the parent code, we do it here - @Deprecated - protected Socket _openDataConnection_(int command, String arg) - throws IOException { - return _openDataConnection_(FTPCommand.getCommand(command), arg); + public String[] getEnabledCipherSuites() { + if (_socket_ instanceof SSLSocket) { + return ((SSLSocket) _socket_).getEnabledCipherSuites(); + } + return null; } - /** - * Returns a socket of the data connection. - * Wrapped as an {@link SSLSocket}, which carries out handshake processing. - * @param command The textual representation of the FTP command to send. - * @param arg The arguments to the FTP command. - * If this parameter is set to null, then the command is sent with - * no arguments. - * @return corresponding to the established data connection. - * Null is returned if an FTP protocol error is reported at any point - * during the establishment and initialization of the connection. - * @throws IOException If there is any problem with the connection. - * @see FTPClient#_openDataConnection_(int, String) - * @since 3.2 + /** + * Returns the names of the protocol versions which are currently enabled for use on this connection. When the underlying {@link Socket} is not an + * {@link SSLSocket} instance, returns null. + * + * @return An array of protocols, or <code>null</code> */ - @Override - protected Socket _openDataConnection_(String command, String arg) - throws IOException { - Socket socket = super._openDataConnection_(command, arg); - _prepareDataSocket_(socket); - if (socket instanceof SSLSocket) { - SSLSocket sslSocket = (SSLSocket)socket; - - sslSocket.setUseClientMode(isClientMode); - sslSocket.setEnableSessionCreation(isCreation); + public String[] getEnabledProtocols() { + if (_socket_ instanceof SSLSocket) { + return ((SSLSocket) _socket_).getEnabledProtocols(); + } + return null; + } - // server mode - if (!isClientMode) { - sslSocket.setNeedClientAuth(isNeedClientAuth); - sslSocket.setWantClientAuth(isWantClientAuth); - } - if (suites != null) { - sslSocket.setEnabledCipherSuites(suites); - } - if (protocols != null) { - sslSocket.setEnabledProtocols(protocols); - } - sslSocket.startHandshake(); + /** + * Returns true if new SSL sessions may be established by this socket. When the underlying {@link Socket} instance is not SSL-enabled (i.e. an instance of + * {@link SSLSocket} with {@link SSLSocket}{@link #getEnableSessionCreation()}) enabled, this returns False. + * + * @return true - Indicates that sessions may be created; this is the default. false - indicates that an existing session must be resumed. + */ + public boolean getEnableSessionCreation() { + if (_socket_ instanceof SSLSocket) { + return ((SSLSocket) _socket_).getEnableSessionCreation(); } + return false; + } - return socket; + /** + * Get the currently configured {@link HostnameVerifier}. The verifier is only used on client mode connections. + * + * @return A HostnameVerifier instance. + * @since 3.4 + */ + public HostnameVerifier getHostnameVerifier() { + return hostnameVerifier; } /** - * Performs any custom initialization for a newly created SSLSocket (before - * the SSL handshake happens). - * Called by {@link #_openDataConnection_(int, String)} immediately - * after creating the socket. - * The default implementation is a no-op - * @param socket the socket to set up - * @throws IOException on error - * @since 3.1 - */ - protected void _prepareDataSocket_(Socket socket) - throws IOException { + * Gets the {@link KeyManager} instance. + * + * @return The {@link KeyManager} instance + */ + private KeyManager getKeyManager() { + return keyManager; + } + + /** + * Returns true if the socket will require client authentication. When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false. + * + * @return true - If the server mode socket should request that the client authenticate itself. + */ + public boolean getNeedClientAuth() { + if (_socket_ instanceof SSLSocket) { + return ((SSLSocket) _socket_).getNeedClientAuth(); + } + return false; } /** @@ -673,253 +626,409 @@ public class FTPSClient extends FTPClient { } /** - * Override the default {@link TrustManager} to use; if set to {@code null}, - * the default TrustManager from the JVM will be used. + * Returns true if the socket is set to use client mode in its first handshake. When the underlying {@link Socket} is not an {@link SSLSocket} instance, + * returns false. * - * @param trustManager The TrustManager implementation to set, may be {@code null} - * @see org.apache.commons.net.util.TrustManagerUtils + * @return true - If the socket should start its first handshake in "client" mode. */ - public void setTrustManager(TrustManager trustManager) { - this.trustManager = trustManager; + public boolean getUseClientMode() { + if (_socket_ instanceof SSLSocket) { + return ((SSLSocket) _socket_).getUseClientMode(); + } + return false; } /** - * Get the currently configured {@link HostnameVerifier}. - * The verifier is only used on client mode connections. - * @return A HostnameVerifier instance. - * @since 3.4 + * Returns true if the socket will request client authentication. When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false. + * + * @return true - If the server mode socket should request that the client authenticate itself. */ - public HostnameVerifier getHostnameVerifier() - { - return hostnameVerifier; + public boolean getWantClientAuth() { + if (_socket_ instanceof SSLSocket) { + return ((SSLSocket) _socket_).getWantClientAuth(); + } + return false; } /** - * Override the default {@link HostnameVerifier} to use. - * The verifier is only used on client mode connections. - * @param newHostnameVerifier The HostnameVerifier implementation to set or <code>null</code> to disable. - * @since 3.4 + * Performs a lazy init of the SSL context + * + * @throws IOException */ - public void setHostnameVerifier(HostnameVerifier newHostnameVerifier) - { - hostnameVerifier = newHostnameVerifier; + private void initSslContext() throws IOException { + if (context == null) { + context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager()); + } } /** - * Return whether or not endpoint identification using the HTTPS algorithm - * on Java 1.7+ is enabled. The default behaviour is for this to be disabled. + * Return whether or not endpoint identification using the HTTPS algorithm on Java 1.7+ is enabled. The default behavior is for this to be disabled. * * This check is only performed on client mode connections. * * @return True if enabled, false if not. * @since 3.4 */ - public boolean isEndpointCheckingEnabled() - { + public boolean isEndpointCheckingEnabled() { return tlsEndpointChecking; } /** - * Automatic endpoint identification checking using the HTTPS algorithm - * is supported on Java 1.7+. The default behaviour is for this to be disabled. + * Establishes a data connection with the FTP server, returning a Socket for the connection if successful. If a restart offset has been set with + * {@link #setRestartOffset(long)}, a REST command is issued to the server with the offset as an argument before establishing the data connection. Active + * mode connections also cause a local PORT command to be issued. * - * This check is only performed on client mode connections. - * - * @param enable Enable automatic endpoint identification checking using the HTTPS algorithm on Java 1.7+. - * @since 3.4 + * @param command The text representation of the FTP command to send. + * @param arg The arguments to the FTP command. If this parameter is set to null, then the command is sent with no argument. + * @return A Socket corresponding to the established data connection. Null is returned if an FTP protocol error is reported at any point during the + * establishment and initialization of the connection. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @since 3.1 */ - public void setEndpointCheckingEnabled(boolean enable) - { - tlsEndpointChecking = enable; + private Socket openDataSecureConnection(final String command, final String arg) throws IOException { + if (getDataConnectionMode() != ACTIVE_LOCAL_DATA_CONNECTION_MODE && getDataConnectionMode() != PASSIVE_LOCAL_DATA_CONNECTION_MODE) { + return null; + } + + final boolean isInet6Address = getRemoteAddress() instanceof Inet6Address; + + final Socket socket; + Socket sslSocket = null; + final int soTimeoutMillis = DurationUtils.toMillisInt(getDataTimeout()); + if (getDataConnectionMode() == ACTIVE_LOCAL_DATA_CONNECTION_MODE) { + // if no activePortRange was set (correctly) -> getActivePort() = 0 + // -> new ServerSocket(0) -> bind to any free local port + try (final ServerSocket server = _serverSocketFactory_.createServerSocket(getActivePort(), 1, getHostAddress())) { + // Try EPRT only if remote server is over IPv6, if not use PORT, + // because EPRT has no advantage over PORT on IPv4. + // It could even have the disadvantage, + // that EPRT will make the data connection fail, because + // today's intelligent NAT Firewalls are able to + // substitute IP addresses in the PORT command, + // but might not be able to recognize the EPRT command. + if (isInet6Address) { + if (!FTPReply.isPositiveCompletion(eprt(getReportHostAddress(), server.getLocalPort()))) { + return null; + } + } else if (!FTPReply.isPositiveCompletion(port(getReportHostAddress(), server.getLocalPort()))) { + return null; + } + + if ((getRestartOffset() > 0) && !restart(getRestartOffset())) { + return null; + } + + if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) { + return null; + } + + // For now, let's just use the data timeout value for waiting for + // the data connection. It may be desirable to let this be a + // separately configurable value. In any case, we really want + // to allow preventing the accept from blocking indefinitely. + if (soTimeoutMillis >= 0) { + server.setSoTimeout(soTimeoutMillis); + } + socket = server.accept(); + + // Ensure the timeout is set before any commands are issued on the new socket + if (soTimeoutMillis >= 0) { + socket.setSoTimeout(soTimeoutMillis); + } + if (getReceiveDataSocketBufferSize() > 0) { + socket.setReceiveBufferSize(getReceiveDataSocketBufferSize()); + } + if (getSendDataSocketBufferSize() > 0) { + socket.setSendBufferSize(getSendDataSocketBufferSize()); + } + } + } else { // We must be in PASSIVE_LOCAL_DATA_CONNECTION_MODE + + // Try EPSV command first on IPv6 - and IPv4 if enabled. + // When using IPv4 with NAT it has the advantage + // to work with more rare configurations. + // E.g. if FTP server has a static PASV address (external network) + // and the client is coming from another internal network. + // In that case the data connection after PASV command would fail, + // while EPSV would make the client succeed by taking just the port. + final boolean attemptEPSV = isUseEPSVwithIPv4() || isInet6Address; + if (attemptEPSV && epsv() == FTPReply.ENTERING_EPSV_MODE) { + _parseExtendedPassiveModeReply(_replyLines.get(0)); + } else { + if (isInet6Address) { + return null; // Must use EPSV for IPV6 + } + // If EPSV failed on IPV4, revert to PASV + if (pasv() != FTPReply.ENTERING_PASSIVE_MODE) { + return null; + } + _parsePassiveModeReply(_replyLines.get(0)); + } + + if (getProxy() != null) { + socket = new Socket(getProxy()); + } else { + socket = _socketFactory_.createSocket(); + } + + if (getReceiveDataSocketBufferSize() > 0) { + socket.setReceiveBufferSize(getReceiveDataSocketBufferSize()); + } + if (getSendDataSocketBufferSize() > 0) { + socket.setSendBufferSize(getSendDataSocketBufferSize()); + } + if (getPassiveLocalIPAddress() != null) { + socket.bind(new InetSocketAddress(getPassiveLocalIPAddress(), 0)); + } + + // For now, let's just use the data timeout value for waiting for + // the data connection. It may be desirable to let this be a + // separately configurable value. In any case, we really want + // to allow preventing the accept from blocking indefinitely. + if (soTimeoutMillis >= 0) { + socket.setSoTimeout(soTimeoutMillis); + } + + socket.connect(new InetSocketAddress(getPassiveHost(), getPassivePort()), connectTimeout); + + if (getProxy() != null) { + sslSocket = context.getSocketFactory().createSocket(socket, getPassiveHost(), getPassivePort(), true); + } + + if ((getRestartOffset() > 0) && !restart(getRestartOffset())) { + closeSockets(socket, sslSocket); + return null; + } + + if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) { + closeSockets(socket, sslSocket); + return null; + } + } + + if (isRemoteVerificationEnabled() && !verifyRemote(socket)) { + // Grab the host before we close the socket to avoid NET-663 + final InetAddress socketHost = socket.getInetAddress(); + + closeSockets(socket, sslSocket); + + throw new IOException( + "Host attempting data connection " + socketHost.getHostAddress() + " is not same as server " + getRemoteAddress().getHostAddress()); + } + + return getProxy() != null ? sslSocket : socket; } /** - * Closes the connection to the FTP server and restores - * connection parameters to the default values. - * <p> - * Calls {@code setSocketFactory(null)} and {@code setServerSocketFactory(null)} - * to reset the factories that may have been changed during the session, - * e.g. by {@link #execPROT(String)} - * @throws IOException If an error occurs while disconnecting. + * Parses the given ADAT response line and base64-decodes the data. + * + * @param reply The ADAT reply to parse. + * @return the data in the reply, base64-decoded. * @since 3.0 */ - @Override - public void disconnect() throws IOException - { - super.disconnect(); - if (plainSocket != null) { - plainSocket.close(); + public byte[] parseADATReply(final String reply) { + if (reply == null) { + return null; } - setSocketFactory(null); - setServerSocketFactory(null); + return Base64.decodeBase64(extractPrefixedData("ADAT=", reply)); } /** - * Send the AUTH command with the specified mechanism. - * @param mechanism The mechanism name to send with the command. - * @return server reply. - * @throws IOException If an I/O error occurs while sending - * the command. + * PBSZ command. pbsz value: 0 to (2^32)-1 decimal integer. Issues the command and parses the response to return the negotiated value. + * + * @param pbsz Protection Buffer Size. + * @throws SSLException If the server reply code does not equal "200". + * @throws IOException If an I/O error occurs while sending the command. + * @return the negotiated value. + * @see #execPBSZ(long) * @since 3.0 */ - public int execAUTH(String mechanism) throws IOException - { - return sendCommand(CMD_AUTH, mechanism); + public long parsePBSZ(final long pbsz) throws SSLException, IOException { + execPBSZ(pbsz); + long minvalue = pbsz; + final String remainder = extractPrefixedData("PBSZ=", getReplyString()); + if (remainder != null) { + final long replysz = Long.parseLong(remainder); + if (replysz < minvalue) { + minvalue = replysz; + } + } + return minvalue; } /** - * Send the ADAT command with the specified authentication data. - * @param data The data to send with the command. + * Send an FTP command. A successful CCC (Clear Command Channel) command causes the underlying {@link SSLSocket} instance to be assigned to a plain + * {@link Socket} + * + * @param command The FTP command. * @return server reply. - * @throws IOException If an I/O error occurs while sending - * the command. - * @since 3.0 + * @throws IOException If an I/O error occurs while sending the command. + * @throws SSLException if a CCC command fails + * @see org.apache.commons.net.ftp.FTP#sendCommand(String) */ - public int execADAT(byte[] data) throws IOException - { - if (data != null) - { - return sendCommand(CMD_ADAT, Base64.encodeBase64StringUnChunked(data)); - } - else - { - return sendCommand(CMD_ADAT); + // Would like to remove this method, but that will break any existing clients that are using CCC + @Override + public int sendCommand(final String command, final String args) throws IOException { + final int repCode = super.sendCommand(command, args); + /* If CCC is issued, restore socket i/o streams to unsecured versions */ + if (CMD_CCC.equals(command)) { + if (FTPReply.COMMAND_OK != repCode) { + throw new SSLException(getReplyString()); + } + _socket_.close(); + _socket_ = plainSocket; + _controlInput_ = new BufferedReader(new InputStreamReader(_socket_.getInputStream(), getControlEncoding())); + _controlOutput_ = new BufferedWriter(new OutputStreamWriter(_socket_.getOutputStream(), getControlEncoding())); } + return repCode; } /** - * Send the CCC command to the server. - * The CCC (Clear Command Channel) command causes the underlying {@link SSLSocket} instance to be assigned - * to a plain {@link Socket} instances - * @return server reply. - * @throws IOException If an I/O error occurs while sending - * the command. - * @since 3.0 + * Set AUTH command use value. This processing is done before connected processing. + * + * @param auth AUTH command use value. */ - public int execCCC() throws IOException - { - int repCode = sendCommand(CMD_CCC); -// This will be performed by sendCommand(String, String) -// if (FTPReply.isPositiveCompletion(repCode)) { -// _socket_.close(); -// _socket_ = plainSocket; -// _controlInput_ = new BufferedReader( -// new InputStreamReader( -// _socket_.getInputStream(), getControlEncoding())); -// _controlOutput_ = new BufferedWriter( -// new OutputStreamWriter( -// _socket_.getOutputStream(), getControlEncoding())); -// } - return repCode; + public void setAuthValue(final String auth) { + this.auth = auth; } /** - * Send the MIC command with the specified data. - * @param data The data to send with the command. - * @return server reply. - * @throws IOException If an I/O error occurs while sending - * the command. - * @since 3.0 + * Controls which particular cipher suites are enabled for use on this connection. Called before server negotiation. + * + * @param cipherSuites The cipher suites. */ - public int execMIC(byte[] data) throws IOException - { - if (data != null) - { - return sendCommand(CMD_MIC, Base64.encodeBase64StringUnChunked(data)); - } - else - { - return sendCommand(CMD_MIC, ""); // perhaps "=" or just sendCommand(String)? - } + public void setEnabledCipherSuites(final String[] cipherSuites) { + suites = cipherSuites.clone(); } /** - * Send the CONF command with the specified data. - * @param data The data to send with the command. - * @return server reply. - * @throws IOException If an I/O error occurs while sending - * the command. - * @since 3.0 + * Controls which particular protocol versions are enabled for use on this connection. I perform setting before a server negotiation. + * + * @param protocolVersions The protocol versions. */ - public int execCONF(byte[] data) throws IOException - { - if (data != null) - { - return sendCommand(CMD_CONF, Base64.encodeBase64StringUnChunked(data)); - } - else - { - return sendCommand(CMD_CONF, ""); // perhaps "=" or just sendCommand(String)? - } + public void setEnabledProtocols(final String[] protocolVersions) { + protocols = protocolVersions.clone(); } /** - * Send the ENC command with the specified data. - * @param data The data to send with the command. - * @return server reply. - * @throws IOException If an I/O error occurs while sending - * the command. - * @since 3.0 + * Controls whether a new SSL session may be established by this socket. + * + * @param isCreation The established socket flag. */ - public int execENC(byte[] data) throws IOException - { - if (data != null) - { - return sendCommand(CMD_ENC, Base64.encodeBase64StringUnChunked(data)); - } - else - { - return sendCommand(CMD_ENC, ""); // perhaps "=" or just sendCommand(String)? - } + public void setEnabledSessionCreation(final boolean isCreation) { + this.isCreation = isCreation; } /** - * Parses the given ADAT response line and base64-decodes the data. - * @param reply The ADAT reply to parse. - * @return the data in the reply, base64-decoded. - * @since 3.0 + * Automatic endpoint identification checking using the HTTPS algorithm is supported on Java 1.7+. The default behavior is for this to be disabled. + * + * This check is only performed on client mode connections. + * + * @param enable Enable automatic endpoint identification checking using the HTTPS algorithm on Java 1.7+. + * @since 3.4 */ - public byte[] parseADATReply(String reply) - { - if (reply == null) { - return null; - } else { - return Base64.decodeBase64(extractPrefixedData("ADAT=", reply)); - } + public void setEndpointCheckingEnabled(final boolean enable) { + tlsEndpointChecking = enable; } /** - * Extract the data from a reply with a prefix, e.g. PBSZ=1234 => 1234 - * @param prefix the prefix to find - * @param reply where to find the prefix - * @return the remainder of the string after the prefix, or null if the prefix was not present. + * Override the default {@link HostnameVerifier} to use. The verifier is only used on client mode connections. + * + * @param newHostnameVerifier The HostnameVerifier implementation to set or <code>null</code> to disable. + * @since 3.4 */ - private String extractPrefixedData(String prefix, String reply) { - int idx = reply.indexOf(prefix); - if (idx == -1) { - return null; - } - // N.B. Cannot use trim before substring as leading space would affect the offset. - return reply.substring(idx+prefix.length()).trim(); + public void setHostnameVerifier(final HostnameVerifier newHostnameVerifier) { + hostnameVerifier = newHostnameVerifier; + } + + /** + * Set a {@link KeyManager} to use + * + * @param keyManager The KeyManager implementation to set. + * @see org.apache.commons.net.util.KeyManagerUtils + */ + public void setKeyManager(final KeyManager keyManager) { + this.keyManager = keyManager; } // DEPRECATED - for API compatibility only - DO NOT USE - /** @deprecated - not used - may be removed in a future release */ - @Deprecated - public static String KEYSTORE_ALGORITHM; + /** + * Configures the socket to require client authentication. + * + * @param isNeedClientAuth The need client auth flag. + */ + public void setNeedClientAuth(final boolean isNeedClientAuth) { + this.isNeedClientAuth = isNeedClientAuth; + } - /** @deprecated - not used - may be removed in a future release */ - @Deprecated - public static String TRUSTSTORE_ALGORITHM; + /** + * Override the default {@link TrustManager} to use; if set to {@code null}, the default TrustManager from the JVM will be used. + * + * @param trustManager The TrustManager implementation to set, may be {@code null} + * @see org.apache.commons.net.util.TrustManagerUtils + */ + public void setTrustManager(final TrustManager trustManager) { + this.trustManager = trustManager; + } - /** @deprecated - not used - may be removed in a future release */ - @Deprecated - public static String PROVIDER; + /** + * Configures the socket to use client (or server) mode in its first handshake. + * + * @param isClientMode The use client mode flag. + */ + public void setUseClientMode(final boolean isClientMode) { + this.isClientMode = isClientMode; + } - /** @deprecated - not used - may be removed in a future release */ - @Deprecated - public static String STORE_TYPE; + /** + * Configures the socket to request client authentication, but only if such a request is appropriate to the cipher suite negotiated. + * + * @param isWantClientAuth The want client auth flag. + */ + public void setWantClientAuth(final boolean isWantClientAuth) { + this.isWantClientAuth = isWantClientAuth; + } + + /** + * SSL/TLS negotiation. Acquires an SSL socket of a control connection and carries out handshake processing. + * + * @throws IOException If server negotiation fails + */ + protected void sslNegotiation() throws IOException { + plainSocket = _socket_; + initSslContext(); + final SSLSocket socket = createSSLSocket(_socket_); + socket.setEnableSessionCreation(isCreation); + socket.setUseClientMode(isClientMode); + + // client mode + if (isClientMode) { + if (tlsEndpointChecking) { + SSLSocketUtils.enableEndpointNameVerification(socket); + } + } else { // server mode + socket.setNeedClientAuth(isNeedClientAuth); + socket.setWantClientAuth(isWantClientAuth); + } + + if (protocols != null) { + socket.setEnabledProtocols(protocols); + } + if (suites != null) { + socket.setEnabledCipherSuites(suites); + } + socket.startHandshake(); + + // TODO the following setup appears to duplicate that in the super class methods + _socket_ = socket; + _controlInput_ = new BufferedReader(new InputStreamReader(socket.getInputStream(), getControlEncoding())); + _controlOutput_ = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), getControlEncoding())); + + if (isClientMode && (hostnameVerifier != null && !hostnameVerifier.verify(_hostname_, socket.getSession()))) { + throw new SSLHandshakeException("Hostname doesn't match certificate"); + } + } } /* kate: indent-width 4; replace-tabs on; */ diff --git a/src/main/java/org/apache/commons/net/ftp/FTPSCommand.java b/src/main/java/org/apache/commons/net/ftp/FTPSCommand.java index c6bade3..dc9c3ae 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPSCommand.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPSCommand.java @@ -19,6 +19,7 @@ package org.apache.commons.net.ftp; /** * FTPS-specific commands. + * * @since 2.0 * @deprecated 3.0 DO NOT USE */ @@ -36,17 +37,15 @@ public final class FTPSCommand { public static final int DATA_CHANNEL_PROTECTION_LEVEL = PROT; public static final int CLEAR_COMMAND_CHANNEL = CCC; - private static final String[] _commands = {"AUTH","ADAT","PBSZ","PROT","CCC"}; + private static final String[] commands = { "AUTH", "ADAT", "PBSZ", "PROT", "CCC" }; /** - * Retrieve the FTPS command string corresponding to a specified - * command code. + * Retrieve the FTPS command string corresponding to a specified command code. * * @param command The command code. - * @return The FTPS command string corresponding to a specified - * command code. + * @return The FTPS command string corresponding to a specified command code. */ - public static final String getCommand(int command) { - return _commands[command]; + public static String getCommand(final int command) { + return commands[command]; } } diff --git a/src/main/java/org/apache/commons/net/ftp/FTPSServerSocketFactory.java b/src/main/java/org/apache/commons/net/ftp/FTPSServerSocketFactory.java index abd382d..58beda7 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPSServerSocketFactory.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPSServerSocketFactory.java @@ -24,51 +24,64 @@ import java.net.ServerSocket; import javax.net.ServerSocketFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; /** * Server socket factory for FTPS connections. + * * @since 2.2 */ public class FTPSServerSocketFactory extends ServerSocketFactory { /** Factory for secure socket factories */ - private final SSLContext context; + private final SSLContext sslContext; - public FTPSServerSocketFactory(SSLContext context) { - this.context = context; + /** + * Constructs a new instance for the given SSL context. + * + * @param sslContext The SSL context. + */ + public FTPSServerSocketFactory(final SSLContext sslContext) { + this.sslContext = sslContext; } - // Override the default superclass method + @SuppressWarnings("resource") // Factory method. @Override public ServerSocket createServerSocket() throws IOException { - return init(this.context.getServerSocketFactory().createServerSocket()); + return init(getServerSocketFactory().createServerSocket()); } + @SuppressWarnings("resource") // Factory method. @Override - public ServerSocket createServerSocket(int port) throws IOException { - return init(this.context.getServerSocketFactory().createServerSocket(port)); + public ServerSocket createServerSocket(final int port) throws IOException { + return init(getServerSocketFactory().createServerSocket(port)); } + @SuppressWarnings("resource") // Factory method. @Override - public ServerSocket createServerSocket(int port, int backlog) throws IOException { - return init(this.context.getServerSocketFactory().createServerSocket(port, backlog)); + public ServerSocket createServerSocket(final int port, final int backlog) throws IOException { + return init(getServerSocketFactory().createServerSocket(port, backlog)); } + @SuppressWarnings("resource") // Factory method. @Override - public ServerSocket createServerSocket(int port, int backlog, InetAddress ifAddress) throws IOException { - return init(this.context.getServerSocketFactory().createServerSocket(port, backlog, ifAddress)); + public ServerSocket createServerSocket(final int port, final int backlog, final InetAddress ifAddress) throws IOException { + return init(getServerSocketFactory().createServerSocket(port, backlog, ifAddress)); + } + + private SSLServerSocketFactory getServerSocketFactory() { + return this.sslContext.getServerSocketFactory(); } /** * Sets the socket so newly accepted connections will use SSL client mode. * - * @param socket the SSLServerSocket to initialise + * @param socket the SSLServerSocket to initialize * @return the socket * @throws ClassCastException if socket is not an instance of SSLServerSocket */ - public ServerSocket init(ServerSocket socket) { + public ServerSocket init(final ServerSocket socket) { ((SSLServerSocket) socket).setUseClientMode(true); return socket; } } - diff --git a/src/main/java/org/apache/commons/net/ftp/FTPSSocketFactory.java b/src/main/java/org/apache/commons/net/ftp/FTPSSocketFactory.java index b148180..ef29131 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPSSocketFactory.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPSSocketFactory.java @@ -27,7 +27,7 @@ import javax.net.ssl.SSLContext; /** * - * Implementation of org.apache.commons.net.SocketFactory + * Socket factory for FTPS connections. * * @since 2.0 */ @@ -35,76 +35,83 @@ public class FTPSSocketFactory extends SocketFactory { private final SSLContext context; - public FTPSSocketFactory(SSLContext context) { + public FTPSSocketFactory(final SSLContext context) { this.context = context; } - // Override the default implementation - @Override - public Socket createSocket() throws IOException{ - return this.context.getSocketFactory().createSocket(); + /** + * @param port the port + * @return the socket + * @throws IOException on error + * @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int) instead} + */ + @Deprecated + public java.net.ServerSocket createServerSocket(final int port) throws IOException { + return this.init(this.context.getServerSocketFactory().createServerSocket(port)); } - @Override - public Socket createSocket(String address, int port) throws UnknownHostException, IOException { - return this.context.getSocketFactory().createSocket(address, port); + /** + * @param port the port + * @param backlog the backlog + * @return the socket + * @throws IOException on error + * @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int, int) instead} + */ + @Deprecated + public java.net.ServerSocket createServerSocket(final int port, final int backlog) throws IOException { + return this.init(this.context.getServerSocketFactory().createServerSocket(port, backlog)); } - @Override - public Socket createSocket(InetAddress address, int port) throws IOException { - return this.context.getSocketFactory().createSocket(address, port); + /** + * @param port the port + * @param backlog the backlog + * @param ifAddress the interface + * @return the socket + * @throws IOException on error + * @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int, int, InetAddress) instead} + */ + @Deprecated + public java.net.ServerSocket createServerSocket(final int port, final int backlog, final InetAddress ifAddress) throws IOException { + return this.init(this.context.getServerSocketFactory().createServerSocket(port, backlog, ifAddress)); } + // Override the default implementation @Override - public Socket createSocket(String address, int port, InetAddress localAddress, int localPort) - throws UnknownHostException, IOException { - return this.context.getSocketFactory().createSocket(address, port, localAddress, localPort); + public Socket createSocket() throws IOException { + return this.context.getSocketFactory().createSocket(); } @Override - public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { - return this.context.getSocketFactory().createSocket(address, port, localAddress, localPort); + public Socket createSocket(final InetAddress address, final int port) throws IOException { + return this.context.getSocketFactory().createSocket(address, port); } - // DEPRECATED METHODS - for API compatibility only - DO NOT USE - /** @param port the port - * @return the socket - * @throws IOException on error - * @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int) instead} */ - @Deprecated - public java.net.ServerSocket createServerSocket(int port) throws IOException { - return this.init(this.context.getServerSocketFactory().createServerSocket(port)); + @Override + public Socket createSocket(final InetAddress address, final int port, final InetAddress localAddress, final int localPort) throws IOException { + return this.context.getSocketFactory().createSocket(address, port, localAddress, localPort); } - /** @param port the port - * @param backlog the backlog - * @return the socket - * @throws IOException on error - * @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int, int) instead} */ - @Deprecated - public java.net.ServerSocket createServerSocket(int port, int backlog) throws IOException { - return this.init(this.context.getServerSocketFactory().createServerSocket(port, backlog)); + @Override + public Socket createSocket(final String address, final int port) throws UnknownHostException, IOException { + return this.context.getSocketFactory().createSocket(address, port); } - /** @param port the port - * @param backlog the backlog - * @param ifAddress the interface - * @return the socket - * @throws IOException on error - * @deprecated (2.2) use {@link FTPSServerSocketFactory#createServerSocket(int, int, InetAddress) instead} */ - @Deprecated - public java.net.ServerSocket createServerSocket(int port, int backlog, InetAddress ifAddress) throws IOException { - return this.init(this.context.getServerSocketFactory().createServerSocket(port, backlog, ifAddress)); + @Override + public Socket createSocket(final String address, final int port, final InetAddress localAddress, final int localPort) + throws UnknownHostException, IOException { + return this.context.getSocketFactory().createSocket(address, port, localAddress, localPort); } - /** @param socket the socket + /** + * @param socket the socket * @return the socket - * @throws IOException on error - * @deprecated (2.2) use {@link FTPSServerSocketFactory#init(java.net.ServerSocket)} */ + * @throws IOException on error + * @deprecated (2.2) use {@link FTPSServerSocketFactory#init(java.net.ServerSocket)} + */ @Deprecated - public java.net.ServerSocket init(java.net.ServerSocket socket) throws IOException { + public java.net.ServerSocket init(final java.net.ServerSocket socket) throws IOException { ((javax.net.ssl.SSLServerSocket) socket).setUseClientMode(true); return socket; } diff --git a/src/main/java/org/apache/commons/net/ftp/FTPSTrustManager.java b/src/main/java/org/apache/commons/net/ftp/FTPSTrustManager.java index 56fbea2..d9797c9 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPSTrustManager.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPSTrustManager.java @@ -22,39 +22,33 @@ import java.security.cert.X509Certificate; import javax.net.ssl.X509TrustManager; +import org.apache.commons.net.util.NetConstants; + /** * Do not use. + * * @since 2.0 - * @deprecated 3.0 use - * {@link org.apache.commons.net.util.TrustManagerUtils#getValidateServerCertificateTrustManager() - * TrustManagerUtils#getValidateServerCertificateTrustManager()} instead + * @deprecated 3.0 use {@link org.apache.commons.net.util.TrustManagerUtils#getValidateServerCertificateTrustManager() + * TrustManagerUtils#getValidateServerCertificateTrustManager()} instead */ @Deprecated -public class FTPSTrustManager implements X509TrustManager -{ - private static final X509Certificate[] EMPTY_X509CERTIFICATE_ARRAY = new X509Certificate[]{}; - +public class FTPSTrustManager implements X509TrustManager { /** * No-op */ @Override - public void checkClientTrusted(X509Certificate[] certificates, String authType) - { - return; + public void checkClientTrusted(final X509Certificate[] certificates, final String authType) { } @Override - public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException - { - for (X509Certificate certificate : certificates) - { + public void checkServerTrusted(final X509Certificate[] certificates, final String authType) throws CertificateException { + for (final X509Certificate certificate : certificates) { certificate.checkValidity(); } } @Override - public X509Certificate[] getAcceptedIssuers() - { - return EMPTY_X509CERTIFICATE_ARRAY; + public X509Certificate[] getAcceptedIssuers() { + return NetConstants.EMPTY_X509_CERTIFICATE_ARRAY; } } diff --git a/src/main/java/org/apache/commons/net/ftp/parser/CompositeFileEntryParser.java b/src/main/java/org/apache/commons/net/ftp/parser/CompositeFileEntryParser.java index 7c26643..3d642eb 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/CompositeFileEntryParser.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/CompositeFileEntryParser.java @@ -22,46 +22,28 @@ import org.apache.commons.net.ftp.FTPFileEntryParser; import org.apache.commons.net.ftp.FTPFileEntryParserImpl; /** - * This implementation allows to pack some FileEntryParsers together - * and handle the case where to returned dirstyle isnt clearly defined. - * The matching parser will be cached. - * If the cached parser wont match due to the server changed the dirstyle, - * a new matching parser will be searched. + * This implementation allows to pack some FileEntryParsers together and handle the case where to returned dir style isn't clearly defined. The matching parser + * will be cached. If the cached parser wont match due to the server changed the dir style, a new matching parser will be searched. */ -public class CompositeFileEntryParser extends FTPFileEntryParserImpl -{ +public class CompositeFileEntryParser extends FTPFileEntryParserImpl { private final FTPFileEntryParser[] ftpFileEntryParsers; private FTPFileEntryParser cachedFtpFileEntryParser; - public CompositeFileEntryParser(FTPFileEntryParser[] ftpFileEntryParsers) - { - super(); - + public CompositeFileEntryParser(final FTPFileEntryParser[] ftpFileEntryParsers) { this.cachedFtpFileEntryParser = null; this.ftpFileEntryParsers = ftpFileEntryParsers; } @Override - public FTPFile parseFTPEntry(String listEntry) - { - if (cachedFtpFileEntryParser != null) - { - FTPFile matched = cachedFtpFileEntryParser.parseFTPEntry(listEntry); - if (matched != null) - { - return matched; - } + public FTPFile parseFTPEntry(final String listEntry) { + if (cachedFtpFileEntryParser != null) { + return cachedFtpFileEntryParser.parseFTPEntry(listEntry); } - else - { - for (FTPFileEntryParser ftpFileEntryParser : ftpFileEntryParsers) - { - FTPFile matched = ftpFileEntryParser.parseFTPEntry(listEntry); - if (matched != null) - { - cachedFtpFileEntryParser = ftpFileEntryParser; - return matched; - } + for (final FTPFileEntryParser ftpFileEntryParser : ftpFileEntryParsers) { + final FTPFile matched = ftpFileEntryParser.parseFTPEntry(listEntry); + if (matched != null) { + cachedFtpFileEntryParser = ftpFileEntryParser; + return matched; } } return null; diff --git a/src/main/java/org/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java b/src/main/java/org/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java index d61693e..3ff88f0 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/ConfigurableFTPFileEntryParserImpl.java @@ -23,86 +23,54 @@ import java.util.Calendar; import org.apache.commons.net.ftp.Configurable; import org.apache.commons.net.ftp.FTPClientConfig; - /** * <p> - * This abstract class implements the common timestamp parsing - * algorithm for all the concrete parsers. Classes derived from - * this one will parse file listings via a supplied regular expression - * that pulls out the date portion as a separate string which is - * passed to the underlying {@link FTPTimestampParser delegate} to - * handle parsing of the file timestamp. + * This abstract class implements the common timestamp parsing algorithm for all the concrete parsers. Classes derived from this one will parse file listings + * via a supplied regular expression that pulls out the date portion as a separate string which is passed to the underlying {@link FTPTimestampParser delegate} + * to handle parsing of the file timestamp. * <p> - * This class also implements the {@link Configurable Configurable} - * interface to allow the parser to be configured from the outside. + * This class also implements the {@link Configurable Configurable} interface to allow the parser to be configured from the outside. * * @since 1.4 */ -public abstract class ConfigurableFTPFileEntryParserImpl -extends RegexFTPFileEntryParserImpl -implements Configurable -{ +public abstract class ConfigurableFTPFileEntryParserImpl extends RegexFTPFileEntryParserImpl implements Configurable { private final FTPTimestampParser timestampParser; /** * constructor for this abstract class. - * @param regex Regular expression used main parsing of the - * file listing. + * + * @param regex Regular expression used main parsing of the file listing. */ - public ConfigurableFTPFileEntryParserImpl(String regex) - { + public ConfigurableFTPFileEntryParserImpl(final String regex) { super(regex); this.timestampParser = new FTPTimestampParserImpl(); } /** * constructor for this abstract class. - * @param regex Regular expression used main parsing of the - * file listing. - * @param flags the flags to apply, see - * {@link java.util.regex.Pattern#compile(String, int) Pattern#compile(String, int)}. Use 0 for none. + * + * @param regex Regular expression used main parsing of the file listing. + * @param flags the flags to apply, see {@link java.util.regex.Pattern#compile(String, int) Pattern#compile(String, int)}. Use 0 for none. * @since 3.4 */ - public ConfigurableFTPFileEntryParserImpl(String regex, int flags) - { + public ConfigurableFTPFileEntryParserImpl(final String regex, final int flags) { super(regex, flags); this.timestampParser = new FTPTimestampParserImpl(); } /** - * This method is called by the concrete parsers to delegate - * timestamp parsing to the timestamp parser. + * Implementation of the {@link Configurable Configurable} interface. Configures this parser by delegating to the underlying Configurable FTPTimestampParser + * implementation, ' passing it the supplied {@link FTPClientConfig FTPClientConfig} if that is non-null or a default configuration defined by each concrete + * subclass. * - * @param timestampStr the timestamp string pulled from the - * file listing by the regular expression parser, to be submitted - * to the <code>timestampParser</code> for extracting the timestamp. - * @return a <code>java.util.Calendar</code> containing results of the - * timestamp parse. - * @throws ParseException on parse error - */ - public Calendar parseTimestamp(String timestampStr) throws ParseException { - return this.timestampParser.parseTimestamp(timestampStr); - } - - - /** - * Implementation of the {@link Configurable Configurable} - * interface. Configures this parser by delegating to the - * underlying Configurable FTPTimestampParser implementation, ' - * passing it the supplied {@link FTPClientConfig FTPClientConfig} - * if that is non-null or a default configuration defined by - * each concrete subclass. - * - * @param config the configuration to be used to configure this parser. - * If it is null, a default configuration defined by - * each concrete subclass is used instead. + * @param config the configuration to be used to configure this parser. If it is null, a default configuration defined by each concrete subclass is used + * instead. */ @Override - public void configure(FTPClientConfig config) - { + public void configure(final FTPClientConfig config) { if (this.timestampParser instanceof Configurable) { - FTPClientConfig defaultCfg = getDefaultConfiguration(); + final FTPClientConfig defaultCfg = getDefaultConfiguration(); if (config != null) { if (null == config.getDefaultDateFormatStr()) { config.setDefaultDateFormatStr(defaultCfg.getDefaultDateFormatStr()); @@ -110,19 +78,30 @@ implements Configurable if (null == config.getRecentDateFormatStr()) { config.setRecentDateFormatStr(defaultCfg.getRecentDateFormatStr()); } - ((Configurable)this.timestampParser).configure(config); + ((Configurable) this.timestampParser).configure(config); } else { - ((Configurable)this.timestampParser).configure(defaultCfg); + ((Configurable) this.timestampParser).configure(defaultCfg); } } } /** - * Each concrete subclass must define this member to create - * a default configuration to be used when that subclass is - * instantiated without a {@link FTPClientConfig FTPClientConfig} - * parameter being specified. + * Each concrete subclass must define this member to create a default configuration to be used when that subclass is instantiated without a + * {@link FTPClientConfig FTPClientConfig} parameter being specified. + * * @return the default configuration for the subclass. */ protected abstract FTPClientConfig getDefaultConfiguration(); + + /** + * This method is called by the concrete parsers to delegate timestamp parsing to the timestamp parser. + * + * @param timestampStr the timestamp string pulled from the file listing by the regular expression parser, to be submitted to the + * <code>timestampParser</code> for extracting the timestamp. + * @return a <code>java.util.Calendar</code> containing results of the timestamp parse. + * @throws ParseException on parse error + */ + public Calendar parseTimestamp(final String timestampStr) throws ParseException { + return this.timestampParser.parseTimestamp(timestampStr); + } } diff --git a/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java b/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java index 19e4c8e..7ea258e 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactory.java @@ -23,71 +23,76 @@ import org.apache.commons.net.ftp.Configurable; import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPFileEntryParser; - /** - * This is the default implementation of the - * FTPFileEntryParserFactory interface. This is the - * implementation that will be used by - * org.apache.commons.net.ftp.FTPClient.listFiles() - * if no other implementation has been specified. + * This is the default implementation of the FTPFileEntryParserFactory interface. This is the implementation that will be used by + * org.apache.commons.net.ftp.FTPClient.listFiles() if no other implementation has been specified. * * @see org.apache.commons.net.ftp.FTPClient#listFiles * @see org.apache.commons.net.ftp.FTPClient#setParserFactory */ -public class DefaultFTPFileEntryParserFactory - implements FTPFileEntryParserFactory -{ +public class DefaultFTPFileEntryParserFactory implements FTPFileEntryParserFactory { // Match a plain Java Identifier private static final String JAVA_IDENTIFIER = "\\p{javaJavaIdentifierStart}(\\p{javaJavaIdentifierPart})*"; // Match a qualified name, e.g. a.b.c.Name - but don't allow the default package as that would allow "VMS"/"UNIX" etc. - private static final String JAVA_QUALIFIED_NAME = "("+JAVA_IDENTIFIER+"\\.)+"+JAVA_IDENTIFIER; + private static final String JAVA_QUALIFIED_NAME = "(" + JAVA_IDENTIFIER + "\\.)+" + JAVA_IDENTIFIER; // Create the pattern, as it will be reused many times private static final Pattern JAVA_QUALIFIED_NAME_PATTERN = Pattern.compile(JAVA_QUALIFIED_NAME); /** - * This default implementation of the FTPFileEntryParserFactory - * interface works according to the following logic: - * First it attempts to interpret the supplied key as a fully - * qualified classname (default package is not allowed) of a class implementing the - * FTPFileEntryParser interface. If that succeeds, a parser - * object of this class is instantiated and is returned; - * otherwise it attempts to interpret the key as an identirier - * commonly used by the FTP SYST command to identify systems. * <p> - * If <code>key</code> is not recognized as a fully qualified - * classname known to the system, this method will then attempt - * to see whether it <b>contains</b> a string identifying one of - * the known parsers. This comparison is <b>case-insensitive</b>. - * The intent here is where possible, to select as keys strings - * which are returned by the SYST command on the systems which - * the corresponding parser successfully parses. This enables - * this factory to be used in the auto-detection system. + * Implementation extracts a key from the supplied {@link FTPClientConfig FTPClientConfig} parameter and creates an object implementing the interface + * FTPFileEntryParser and uses the supplied configuration to configure it. + * </p> + * <p> + * Note that this method will generally not be called in scenarios that call for autodetection of parser type but rather, for situations where the user + * knows that the server uses a non-default configuration and knows what that configuration is. + * </p> + * + * @param config A {@link FTPClientConfig FTPClientConfig} used to configure the parser created + * + * @return the @link FTPFileEntryParser FTPFileEntryParser} so created. + * @throws ParserInitializationException Thrown on any exception in instantiation + * @throws NullPointerException if {@code config} is {@code null} + * @since 1.4 + */ + @Override + public FTPFileEntryParser createFileEntryParser(final FTPClientConfig config) throws ParserInitializationException { + final String key = config.getServerSystemKey(); + return createFileEntryParser(key, config); + } + + /** + * This default implementation of the FTPFileEntryParserFactory interface works according to the following logic: First it attempts to interpret the + * supplied key as a fully qualified classname (default package is not allowed) of a class implementing the FTPFileEntryParser interface. If that succeeds, + * a parser object of this class is instantiated and is returned; otherwise it attempts to interpret the key as an identirier commonly used by the FTP SYST + * command to identify systems. + * <p> + * If <code>key</code> is not recognized as a fully qualified classname known to the system, this method will then attempt to see whether it <b>contains</b> + * a string identifying one of the known parsers. This comparison is <b>case-insensitive</b>. The intent here is where possible, to select as keys strings + * which are returned by the SYST command on the systems which the corresponding parser successfully parses. This enables this factory to be used in the + * auto-detection system. * - * @param key should be a fully qualified classname corresponding to - * a class implementing the FTPFileEntryParser interface<br> - * OR<br> - * a string containing (case-insensitively) one of the - * following keywords: - * <ul> - * <li>{@link FTPClientConfig#SYST_UNIX UNIX}</li> - * <li>{@link FTPClientConfig#SYST_NT WINDOWS}</li> - * <li>{@link FTPClientConfig#SYST_OS2 OS/2}</li> - * <li>{@link FTPClientConfig#SYST_OS400 OS/400}</li> - * <li>{@link FTPClientConfig#SYST_AS400 AS/400}</li> - * <li>{@link FTPClientConfig#SYST_VMS VMS}</li> - * <li>{@link FTPClientConfig#SYST_MVS MVS}</li> - * <li>{@link FTPClientConfig#SYST_NETWARE NETWARE}</li> - * <li>{@link FTPClientConfig#SYST_L8 TYPE:L8}</li> - * </ul> + * @param key should be a fully qualified classname corresponding to a class implementing the FTPFileEntryParser interface<br> + * OR<br> + * a string containing (case-insensitively) one of the following keywords: + * <ul> + * <li>{@link FTPClientConfig#SYST_UNIX UNIX}</li> + * <li>{@link FTPClientConfig#SYST_NT WINDOWS}</li> + * <li>{@link FTPClientConfig#SYST_OS2 OS/2}</li> + * <li>{@link FTPClientConfig#SYST_OS400 OS/400}</li> + * <li>{@link FTPClientConfig#SYST_AS400 AS/400}</li> + * <li>{@link FTPClientConfig#SYST_VMS VMS}</li> + * <li>{@link FTPClientConfig#SYST_MVS MVS}</li> + * <li>{@link FTPClientConfig#SYST_NETWARE NETWARE}</li> + * <li>{@link FTPClientConfig#SYST_L8 TYPE:L8}</li> + * </ul> * @return the FTPFileEntryParser corresponding to the supplied key. - * @throws ParserInitializationException thrown if for any reason the factory cannot resolve - * the supplied key into an FTPFileEntryParser. + * @throws ParserInitializationException thrown if for any reason the factory cannot resolve the supplied key into an FTPFileEntryParser. * @see FTPFileEntryParser */ @Override - public FTPFileEntryParser createFileEntryParser(String key) - { + public FTPFileEntryParser createFileEntryParser(final String key) { if (key == null) { throw new ParserInitializationException("Parser key cannot be null"); } @@ -95,201 +100,123 @@ public class DefaultFTPFileEntryParserFactory } // Common method to process both key and config parameters. - private FTPFileEntryParser createFileEntryParser(String key, FTPClientConfig config) { + private FTPFileEntryParser createFileEntryParser(final String key, final FTPClientConfig config) { FTPFileEntryParser parser = null; // Is the key a possible class name? if (JAVA_QUALIFIED_NAME_PATTERN.matcher(key).matches()) { - try - { - Class<?> parserClass = Class.forName(key); + try { + final Class<?> parserClass = Class.forName(key); try { parser = (FTPFileEntryParser) parserClass.newInstance(); - } catch (ClassCastException e) { - throw new ParserInitializationException(parserClass.getName() - + " does not implement the interface " - + "org.apache.commons.net.ftp.FTPFileEntryParser.", e); - } catch (Exception e) { - throw new ParserInitializationException("Error initializing parser", e); - } catch (ExceptionInInitializerError e) { + } catch (final ClassCastException e) { + throw new ParserInitializationException( + parserClass.getName() + " does not implement the interface " + "org.apache.commons.net.ftp.FTPFileEntryParser.", e); + } catch (final Exception | ExceptionInInitializerError e) { throw new ParserInitializationException("Error initializing parser", e); } - } catch (ClassNotFoundException e) { + } catch (final ClassNotFoundException e) { // OK, assume it is an alias } } if (parser == null) { // Now try for aliases - String ukey = key.toUpperCase(java.util.Locale.ENGLISH); - if (ukey.indexOf(FTPClientConfig.SYST_UNIX_TRIM_LEADING) >= 0) - { + final String ukey = key.toUpperCase(java.util.Locale.ENGLISH); + if (ukey.contains(FTPClientConfig.SYST_UNIX_TRIM_LEADING)) { parser = new UnixFTPEntryParser(config, true); } // must check this after SYST_UNIX_TRIM_LEADING as it is a substring of it - else if (ukey.indexOf(FTPClientConfig.SYST_UNIX) >= 0) - { + else if (ukey.contains(FTPClientConfig.SYST_UNIX)) { parser = new UnixFTPEntryParser(config, false); - } - else if (ukey.indexOf(FTPClientConfig.SYST_VMS) >= 0) - { + } else if (ukey.contains(FTPClientConfig.SYST_VMS)) { parser = new VMSVersioningFTPEntryParser(config); - } - else if (ukey.indexOf(FTPClientConfig.SYST_NT) >= 0) - { + } else if (ukey.contains(FTPClientConfig.SYST_NT)) { parser = createNTFTPEntryParser(config); - } - else if (ukey.indexOf(FTPClientConfig.SYST_OS2) >= 0) - { + } else if (ukey.contains(FTPClientConfig.SYST_OS2)) { parser = new OS2FTPEntryParser(config); - } - else if (ukey.indexOf(FTPClientConfig.SYST_OS400) >= 0 || - ukey.indexOf(FTPClientConfig.SYST_AS400) >= 0) - { + } else if (ukey.contains(FTPClientConfig.SYST_OS400) || ukey.contains(FTPClientConfig.SYST_AS400)) { parser = createOS400FTPEntryParser(config); - } - else if (ukey.indexOf(FTPClientConfig.SYST_MVS) >= 0) - { + } else if (ukey.contains(FTPClientConfig.SYST_MVS)) { parser = new MVSFTPEntryParser(); // Does not currently support config parameter - } - else if (ukey.indexOf(FTPClientConfig.SYST_NETWARE) >= 0) - { + } else if (ukey.contains(FTPClientConfig.SYST_NETWARE)) { parser = new NetwareFTPEntryParser(config); - } - else if (ukey.indexOf(FTPClientConfig.SYST_MACOS_PETER) >= 0) - { + } else if (ukey.contains(FTPClientConfig.SYST_MACOS_PETER)) { parser = new MacOsPeterFTPEntryParser(config); - } - else if (ukey.indexOf(FTPClientConfig.SYST_L8) >= 0) - { + } else if (ukey.contains(FTPClientConfig.SYST_L8)) { // L8 normally means Unix, but move it to the end for some L8 systems that aren't. // This check should be last! parser = new UnixFTPEntryParser(config); - } - else - { + } else { throw new ParserInitializationException("Unknown parser type: " + key); } } if (parser instanceof Configurable) { - ((Configurable)parser).configure(config); + ((Configurable) parser).configure(config); } return parser; } - /** - * <p>Implementation extracts a key from the supplied - * {@link FTPClientConfig FTPClientConfig} - * parameter and creates an object implementing the - * interface FTPFileEntryParser and uses the supplied configuration - * to configure it. - * </p><p> - * Note that this method will generally not be called in scenarios - * that call for autodetection of parser type but rather, for situations - * where the user knows that the server uses a non-default configuration - * and knows what that configuration is. - * </p> - * @param config A {@link FTPClientConfig FTPClientConfig} - * used to configure the parser created - * - * @return the @link FTPFileEntryParser FTPFileEntryParser} so created. - * @throws ParserInitializationException - * Thrown on any exception in instantiation - * @throws NullPointerException if {@code config} is {@code null} - * @since 1.4 - */ - @Override - public FTPFileEntryParser createFileEntryParser(FTPClientConfig config) - throws ParserInitializationException - { - String key = config.getServerSystemKey(); - return createFileEntryParser(key, config); - } - - - public FTPFileEntryParser createUnixFTPEntryParser() - { - return new UnixFTPEntryParser(); - } - - public FTPFileEntryParser createVMSVersioningFTPEntryParser() - { - return new VMSVersioningFTPEntryParser(); + public FTPFileEntryParser createMVSEntryParser() { + return new MVSFTPEntryParser(); } public FTPFileEntryParser createNetwareFTPEntryParser() { return new NetwareFTPEntryParser(); } - public FTPFileEntryParser createNTFTPEntryParser() - { + public FTPFileEntryParser createNTFTPEntryParser() { return createNTFTPEntryParser(null); } /** - * Creates an NT FTP parser: if the config exists, and the system key equals - * {@link FTPClientConfig.SYST_NT} then a plain {@link NTFTPEntryParser} is used, + * Creates an NT FTP parser: if the config exists, and the system key equals {@link FTPClientConfig#SYST_NT} then a plain {@link NTFTPEntryParser} is used, * otherwise a composite of {@link NTFTPEntryParser} and {@link UnixFTPEntryParser} is used. + * * @param config the config to use, may be {@code null} * @return the parser */ - private FTPFileEntryParser createNTFTPEntryParser(FTPClientConfig config) - { - if (config != null && FTPClientConfig.SYST_NT.equals( - config.getServerSystemKey())) - { + private FTPFileEntryParser createNTFTPEntryParser(final FTPClientConfig config) { + if (config != null && FTPClientConfig.SYST_NT.equals(config.getServerSystemKey())) { return new NTFTPEntryParser(config); - } else { - // clone the config as it may be changed by the parsers (NET-602) - final FTPClientConfig config2 = (config != null) ? new FTPClientConfig(config) : null; - return new CompositeFileEntryParser(new FTPFileEntryParser[] - { - new NTFTPEntryParser(config), - new UnixFTPEntryParser(config2, - config2 != null && FTPClientConfig.SYST_UNIX_TRIM_LEADING.equals(config2.getServerSystemKey())) - }); } + // clone the config as it may be changed by the parsers (NET-602) + final FTPClientConfig config2 = config != null ? new FTPClientConfig(config) : null; + return new CompositeFileEntryParser(new FTPFileEntryParser[] { new NTFTPEntryParser(config), + new UnixFTPEntryParser(config2, config2 != null && FTPClientConfig.SYST_UNIX_TRIM_LEADING.equals(config2.getServerSystemKey())) }); } - public FTPFileEntryParser createOS2FTPEntryParser() - { + public FTPFileEntryParser createOS2FTPEntryParser() { return new OS2FTPEntryParser(); } - public FTPFileEntryParser createOS400FTPEntryParser() - { + public FTPFileEntryParser createOS400FTPEntryParser() { return createOS400FTPEntryParser(null); } /** - * Creates an OS400 FTP parser: if the config exists, and the system key equals - * {@link FTPClientConfig.SYST_OS400} then a plain {@link OS400FTPEntryParser} is used, - * otherwise a composite of {@link OS400FTPEntryParser} and {@link UnixFTPEntryParser} is used. + * Creates an OS400 FTP parser: if the config exists, and the system key equals {@link FTPClientConfig#SYST_OS400} then a plain {@link OS400FTPEntryParser} + * is used, otherwise a composite of {@link OS400FTPEntryParser} and {@link UnixFTPEntryParser} is used. + * * @param config the config to use, may be {@code null} * @return the parser */ - private FTPFileEntryParser createOS400FTPEntryParser(FTPClientConfig config) - { - if (config != null && - FTPClientConfig.SYST_OS400.equals(config.getServerSystemKey())) - { + private FTPFileEntryParser createOS400FTPEntryParser(final FTPClientConfig config) { + if (config != null && FTPClientConfig.SYST_OS400.equals(config.getServerSystemKey())) { return new OS400FTPEntryParser(config); - } else { - // clone the config as it may be changed by the parsers (NET-602) - final FTPClientConfig config2 = (config != null) ? new FTPClientConfig(config) : null; - return new CompositeFileEntryParser(new FTPFileEntryParser[] - { - new OS400FTPEntryParser(config), - new UnixFTPEntryParser(config2, - config2 != null && FTPClientConfig.SYST_UNIX_TRIM_LEADING.equals(config2.getServerSystemKey())) - }); } + // clone the config as it may be changed by the parsers (NET-602) + final FTPClientConfig config2 = config != null ? new FTPClientConfig(config) : null; + return new CompositeFileEntryParser(new FTPFileEntryParser[] { new OS400FTPEntryParser(config), + new UnixFTPEntryParser(config2, config2 != null && FTPClientConfig.SYST_UNIX_TRIM_LEADING.equals(config2.getServerSystemKey())) }); } - public FTPFileEntryParser createMVSEntryParser() - { - return new MVSFTPEntryParser(); + public FTPFileEntryParser createUnixFTPEntryParser() { + return new UnixFTPEntryParser(); } -} + public FTPFileEntryParser createVMSVersioningFTPEntryParser() { + return new VMSVersioningFTPEntryParser(); + } +} diff --git a/src/main/java/org/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParser.java b/src/main/java/org/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParser.java index 142cbf2..363b139 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParser.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParser.java @@ -16,132 +16,116 @@ */ package org.apache.commons.net.ftp.parser; + import java.util.Calendar; import org.apache.commons.net.ftp.FTPFile; /** - * Parser for the Connect Enterprise Unix FTP Server From Sterling Commerce. - * Here is a sample of the sort of output line this parser processes: - * "-C--E-----FTP B QUA1I1 18128 41 Aug 12 13:56 QUADTEST" - * <P><B> - * Note: EnterpriseUnixFTPEntryParser can only be instantiated through the - * DefaultFTPParserFactory by classname. It will not be chosen - * by the autodetection scheme. - * </B> - * @version $Id: EnterpriseUnixFTPEntryParser.java 1741829 2016-05-01 00:24:44Z sebb $ + * Parser for the Connect Enterprise Unix FTP Server From Sterling Commerce. Here is a sample of the sort of output line this parser processes: + * + * <pre> + * "-C--E-----FTP B QUA1I1 18128 41 Aug 12 13:56 QUADTEST" + * </pre> + * <p> + * Note: EnterpriseUnixFTPEntryParser can only be instantiated through the DefaultFTPParserFactory by classname. It will not be chosen by the autodetection + * scheme. + * </p> + * * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory */ -public class EnterpriseUnixFTPEntryParser extends RegexFTPFileEntryParserImpl -{ +public class EnterpriseUnixFTPEntryParser extends RegexFTPFileEntryParserImpl { /** - * months abbreviations looked for by this parser. Also used - * to determine <b>which</b> month has been matched by the parser. + * months abbreviations looked for by this parser. Also used to determine <b>which</b> month has been matched by the parser. */ - private static final String MONTHS = - "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)"; + private static final String MONTHS = "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)"; /** * this is the regular expression used by this parser. */ - private static final String REGEX = - "(([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])" - + "([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z]))" - + "(\\S*)\\s*" // 12 - + "(\\S+)\\s*" // 13 - + "(\\S*)\\s*" // 14 user - + "(\\d*)\\s*" // 15 group - + "(\\d*)\\s*" // 16 filesize - + MONTHS // 17 month - + "\\s*" // TODO should the space be optional? - // TODO \\d* should be \\d? surely ? Otherwise 01111 is allowed - + "((?:[012]\\d*)|(?:3[01]))\\s*" // 18 date [012]\d* or 3[01] - + "((\\d\\d\\d\\d)|((?:[01]\\d)|(?:2[0123])):([012345]\\d))\\s" - // 20 \d\d\d\d = year OR - // 21 [01]\d or 2[0123] hour + ':' - // 22 [012345]\d = minute - + "(\\S*)(\\s*.*)"; // 23 name + private static final String REGEX = "(([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])" + + "([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z])([\\-]|[A-Z]))" + "(\\S*)\\s*" // 12 + + "(\\S+)\\s*" // 13 + + "(\\S*)\\s*" // 14 user + + "(\\d*)\\s*" // 15 group + + "(\\d*)\\s*" // 16 filesize + + MONTHS // 17 month + + "\\s*" // TODO should the space be optional? + // TODO \\d* should be \\d? surely ? Otherwise 01111 is allowed + + "((?:[012]\\d*)|(?:3[01]))\\s*" // 18 date [012]\d* or 3[01] + + "((\\d\\d\\d\\d)|((?:[01]\\d)|(?:2[0123])):([012345]\\d))\\s" + // 20 \d\d\d\d = year OR + // 21 [01]\d or 2[0123] hour + ':' + // 22 [012345]\d = minute + + "(\\S*)(\\s*.*)"; // 23 name /** * The sole constructor for a EnterpriseUnixFTPEntryParser object. * */ - public EnterpriseUnixFTPEntryParser() - { + public EnterpriseUnixFTPEntryParser() { super(REGEX); } /** - * Parses a line of a unix FTP server file listing and converts it into a - * usable format in the form of an <code> FTPFile </code> instance. If - * the file listing line doesn't describe a file, <code> null </code> is - * returned, otherwise a <code> FTPFile </code> instance representing the - * files in the directory is returned. + * Parses a line of a unix FTP server file listing and converts it into a usable format in the form of an <code> FTPFile </code> instance. If the file + * listing line doesn't describe a file, <code> null </code> is returned, otherwise a <code> FTPFile </code> instance representing the files in the + * directory is returned. * * @param entry A line of text from the file listing * @return An FTPFile instance corresponding to the supplied entry */ @Override - public FTPFile parseFTPEntry(String entry) - { + public FTPFile parseFTPEntry(final String entry) { - FTPFile file = new FTPFile(); + final FTPFile file = new FTPFile(); file.setRawListing(entry); - if (matches(entry)) - { - String usr = group(14); - String grp = group(15); - String filesize = group(16); - String mo = group(17); - String da = group(18); - String yr = group(20); - String hr = group(21); - String min = group(22); - String name = group(23); + if (matches(entry)) { + final String usr = group(14); + final String grp = group(15); + final String filesize = group(16); + final String mo = group(17); + final String da = group(18); + final String yr = group(20); + final String hr = group(21); + final String min = group(22); + final String name = group(23); file.setType(FTPFile.FILE_TYPE); file.setUser(usr); file.setGroup(grp); - try - { + try { file.setSize(Long.parseLong(filesize)); - } - catch (NumberFormatException e) - { + } catch (final NumberFormatException e) { // intentionally do nothing } - Calendar cal = Calendar.getInstance(); + final Calendar cal = Calendar.getInstance(); cal.set(Calendar.MILLISECOND, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.HOUR_OF_DAY, 0); - int pos = MONTHS.indexOf(mo); - int month = pos / 4; + final int pos = MONTHS.indexOf(mo); + final int month = pos / 4; final int missingUnit; // the first missing unit - try - { + try { - if (yr != null) - { + if (yr != null) { // it's a year; there are no hours and minutes cal.set(Calendar.YEAR, Integer.parseInt(yr)); missingUnit = Calendar.HOUR_OF_DAY; - } - else - { - // it must be hour/minute or we wouldn't have matched + } else { + // it must be hour/minute or we wouldn't have matched missingUnit = Calendar.SECOND; int year = cal.get(Calendar.YEAR); // if the month we're reading is greater than now, it must // be last year - if (cal.get(Calendar.MONTH) < month) - { + if (cal.get(Calendar.MONTH) < month) { year--; } cal.set(Calendar.YEAR, year); @@ -152,9 +136,7 @@ public class EnterpriseUnixFTPEntryParser extends RegexFTPFileEntryParserImpl cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(da)); cal.clear(missingUnit); file.setTimestamp(cal); - } - catch (NumberFormatException e) - { + } catch (final NumberFormatException e) { // do nothing, date will be uninitialized } file.setName(name); diff --git a/src/main/java/org/apache/commons/net/ftp/parser/FTPFileEntryParserFactory.java b/src/main/java/org/apache/commons/net/ftp/parser/FTPFileEntryParserFactory.java index 9901a97..e287b6c 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/FTPFileEntryParserFactory.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/FTPFileEntryParserFactory.java @@ -16,53 +16,42 @@ */ package org.apache.commons.net.ftp.parser; + import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPFileEntryParser; /** * The interface describes a factory for creating FTPFileEntryParsers. + * * @since 1.2 */ -public interface FTPFileEntryParserFactory -{ +public interface FTPFileEntryParserFactory { /** - * Implementation should be a method that decodes the - * supplied key and creates an object implementing the - * interface FTPFileEntryParser. + * <p> + * Implementation should be a method that extracts a key from the supplied {@link FTPClientConfig FTPClientConfig} parameter and creates an object + * implementing the interface FTPFileEntryParser and uses the supplied configuration to configure it. + * </p> + * <p> + * Note that this method will generally not be called in scenarios that call for autodetection of parser type but rather, for situations where the user + * knows that the server uses a non-default configuration and knows what that configuration is. + * </p> * - * @param key A string that somehow identifies an - * FTPFileEntryParser to be created. + * @param config A {@link FTPClientConfig FTPClientConfig} used to configure the parser created * - * @return the FTPFileEntryParser created. - * @throws ParserInitializationException - * Thrown on any exception in instantiation + * @return the @link FTPFileEntryParser FTPFileEntryParser} so created. + * @throws ParserInitializationException Thrown on any exception in instantiation + * @since 1.4 */ - public FTPFileEntryParser createFileEntryParser(String key) - throws ParserInitializationException; + FTPFileEntryParser createFileEntryParser(FTPClientConfig config) throws ParserInitializationException; /** - *<p> - * Implementation should be a method that extracts - * a key from the supplied {@link FTPClientConfig FTPClientConfig} - * parameter and creates an object implementing the - * interface FTPFileEntryParser and uses the supplied configuration - * to configure it. - * </p><p> - * Note that this method will generally not be called in scenarios - * that call for autodetection of parser type but rather, for situations - * where the user knows that the server uses a non-default configuration - * and knows what that configuration is. - * </p> + * Implementation should be a method that decodes the supplied key and creates an object implementing the interface FTPFileEntryParser. * - * @param config A {@link FTPClientConfig FTPClientConfig} - * used to configure the parser created + * @param key A string that somehow identifies an FTPFileEntryParser to be created. * - * @return the @link FTPFileEntryParser FTPFileEntryParser} so created. - * @throws ParserInitializationException - * Thrown on any exception in instantiation - * @since 1.4 + * @return the FTPFileEntryParser created. + * @throws ParserInitializationException Thrown on any exception in instantiation */ - public FTPFileEntryParser createFileEntryParser(FTPClientConfig config) - throws ParserInitializationException; + FTPFileEntryParser createFileEntryParser(String key) throws ParserInitializationException; } diff --git a/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParser.java b/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParser.java index 0f74437..04f561a 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParser.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParser.java @@ -21,8 +21,8 @@ import java.text.ParseException; import java.util.Calendar; /** - * This interface specifies the concept of parsing an FTP server's - * timestamp. + * This interface specifies the concept of parsing an FTP server's timestamp. + * * @since 1.4 */ public interface FTPTimestampParser { @@ -30,23 +30,19 @@ public interface FTPTimestampParser { /** * the default default date format. */ - public static final String DEFAULT_SDF = UnixFTPEntryParser.DEFAULT_DATE_FORMAT; + String DEFAULT_SDF = UnixFTPEntryParser.DEFAULT_DATE_FORMAT; /** * the default recent date format. */ - public static final String DEFAULT_RECENT_SDF = UnixFTPEntryParser.DEFAULT_RECENT_DATE_FORMAT; + String DEFAULT_RECENT_SDF = UnixFTPEntryParser.DEFAULT_RECENT_DATE_FORMAT; /** - * Parses the supplied datestamp parameter. This parameter typically would - * have been pulled from a longer FTP listing via the regular expression - * mechanism - * @param timestampStr - the timestamp portion of the FTP directory listing - * to be parsed - * @return a <code>java.util.Calendar</code> object initialized to the date - * parsed by the parser - * @throws ParseException if none of the parser mechanisms belonging to - * the implementor can parse the input. + * Parses the supplied datestamp parameter. This parameter typically would have been pulled from a longer FTP listing via the regular expression mechanism + * + * @param timestampStr - the timestamp portion of the FTP directory listing to be parsed + * @return a <code>java.util.Calendar</code> object initialized to the date parsed by the parser + * @throws ParseException if none of the parser mechanisms belonging to the implementor can parse the input. */ - public Calendar parseTimestamp(String timestampStr) throws ParseException; + Calendar parseTimestamp(String timestampStr) throws ParseException; } diff --git a/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java b/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java index 44cfec7..fdc532b 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImpl.java @@ -29,65 +29,35 @@ import org.apache.commons.net.ftp.Configurable; import org.apache.commons.net.ftp.FTPClientConfig; /** - * Default implementation of the {@link FTPTimestampParser FTPTimestampParser} - * interface also implements the {@link org.apache.commons.net.ftp.Configurable Configurable} - * interface to allow the parsing to be configured from the outside. + * Default implementation of the {@link FTPTimestampParser FTPTimestampParser} interface also implements the {@link org.apache.commons.net.ftp.Configurable + * Configurable} interface to allow the parsing to be configured from the outside. * * @see ConfigurableFTPFileEntryParserImpl * @since 1.4 */ -public class FTPTimestampParserImpl implements - FTPTimestampParser, Configurable -{ - - - /** The date format for all dates, except possibly recent dates. Assumed to include the year. */ - private SimpleDateFormat defaultDateFormat; - /* The index in CALENDAR_UNITS of the smallest time unit in defaultDateFormat */ - private int defaultDateSmallestUnitIndex; - - /** The format used for recent dates (which don't have the year). May be null. */ - private SimpleDateFormat recentDateFormat; - /* The index in CALENDAR_UNITS of the smallest time unit in recentDateFormat */ - private int recentDateSmallestUnitIndex; - - private boolean lenientFutureDates = false; +public class FTPTimestampParserImpl implements FTPTimestampParser, Configurable { /* - * List of units in order of increasing significance. - * This allows the code to clear all units in the Calendar until it - * reaches the least significant unit in the parse string. - * The date formats are analysed to find the least significant - * unit (e.g. Minutes or Milliseconds) and the appropriate index to - * the array is saved. - * This is done by searching the array for the unit specifier, - * and returning the index. When clearing the Calendar units, - * the code loops through the array until the previous entry. - * e.g. for MINUTE it would clear MILLISECOND and SECOND + * List of units in order of increasing significance. This allows the code to clear all units in the Calendar until it reaches the least significant unit in + * the parse string. The date formats are analysed to find the least significant unit (e.g. Minutes or Milliseconds) and the appropriate index to the array + * is saved. This is done by searching the array for the unit specifier, and returning the index. When clearing the Calendar units, the code loops through + * the array until the previous entry. e.g. for MINUTE it would clear MILLISECOND and SECOND */ - private static final int[] CALENDAR_UNITS = { - Calendar.MILLISECOND, - Calendar.SECOND, - Calendar.MINUTE, - Calendar.HOUR_OF_DAY, - Calendar.DAY_OF_MONTH, - Calendar.MONTH, - Calendar.YEAR}; + private static final int[] CALENDAR_UNITS = { Calendar.MILLISECOND, Calendar.SECOND, Calendar.MINUTE, Calendar.HOUR_OF_DAY, Calendar.DAY_OF_MONTH, + Calendar.MONTH, Calendar.YEAR }; /* - * Return the index to the array representing the least significant - * unit found in the date format. - * Default is 0 (to avoid dropping precision) + * Return the index to the array representing the least significant unit found in the date format. Default is 0 (to avoid dropping precision) */ - private static int getEntry(SimpleDateFormat dateFormat) { + private static int getEntry(final SimpleDateFormat dateFormat) { if (dateFormat == null) { return 0; } - final String FORMAT_CHARS="SsmHdM"; + final String FORMAT_CHARS = "SsmHdM"; final String pattern = dateFormat.toPattern(); - for(char ch : FORMAT_CHARS.toCharArray()) { - if (pattern.indexOf(ch) != -1){ // found the character - switch(ch) { + for (final char ch : FORMAT_CHARS.toCharArray()) { + if (pattern.indexOf(ch) != -1) { // found the character + switch (ch) { case 'S': return indexOf(Calendar.MILLISECOND); case 's': @@ -109,9 +79,9 @@ public class FTPTimestampParserImpl implements /* * Find the entry in the CALENDAR_UNITS array. */ - private static int indexOf(int calendarUnit) { + private static int indexOf(final int calendarUnit) { int i; - for(i = 0; i <CALENDAR_UNITS.length; i++) { + for (i = 0; i < CALENDAR_UNITS.length; i++) { if (calendarUnit == CALENDAR_UNITS[i]) { return i; } @@ -120,15 +90,14 @@ public class FTPTimestampParserImpl implements } /* - * Sets the Calendar precision (used by FTPFile#toFormattedDate) by clearing - * the immediately preceeding unit (if any). - * Unfortunately the clear(int) method results in setting all other units. + * Sets the Calendar precision (used by FTPFile#toFormattedDate) by clearing the immediately preceeding unit (if any). Unfortunately the clear(int) method + * results in setting all other units. */ - private static void setPrecision(int index, Calendar working) { + private static void setPrecision(final int index, final Calendar working) { if (index <= 0) { // e.g. MILLISECONDS return; } - final int field = CALENDAR_UNITS[index-1]; + final int field = CALENDAR_UNITS[index - 1]; // Just in case the analysis is wrong, stop clearing if // field value is not the default. final int value = working.get(field); @@ -139,6 +108,20 @@ public class FTPTimestampParserImpl implements } } + /** The date format for all dates, except possibly recent dates. Assumed to include the year. */ + private SimpleDateFormat defaultDateFormat; + + /* The index in CALENDAR_UNITS of the smallest time unit in defaultDateFormat */ + private int defaultDateSmallestUnitIndex; + + /** The format used for recent dates (which don't have the year). May be null. */ + private SimpleDateFormat recentDateFormat; + + /* The index in CALENDAR_UNITS of the smallest time unit in recentDateFormat */ + private int recentDateSmallestUnitIndex; + + private boolean lenientFutureDates; + /** * The only constructor for this class. */ @@ -148,14 +131,106 @@ public class FTPTimestampParserImpl implements } /** - * Implements the one {@link FTPTimestampParser#parseTimestamp(String) method} - * in the {@link FTPTimestampParser FTPTimestampParser} interface - * according to this algorithm: + * Implementation of the {@link Configurable Configurable} interface. Configures this <code>FTPTimestampParser</code> according to the following logic: + * <p> + * Set up the {@link FTPClientConfig#setDefaultDateFormatStr(java.lang.String) defaultDateFormat} and optionally the + * {@link FTPClientConfig#setRecentDateFormatStr(String) recentDateFormat} to values supplied in the config based on month names configured as follows: + * </p> + * <ul> + * <li>If a {@link FTPClientConfig#setShortMonthNames(String) shortMonthString} has been supplied in the <code>config</code>, use that to parse parse + * timestamps.</li> + * <li>Otherwise, if a {@link FTPClientConfig#setServerLanguageCode(String) serverLanguageCode} has been supplied in the <code>config</code>, use the month + * names represented by that {@link FTPClientConfig#lookupDateFormatSymbols(String) language} to parse timestamps.</li> + * <li>otherwise use default English month names</li> + * </ul> + * <p> + * Finally if a {@link org.apache.commons.net.ftp.FTPClientConfig#setServerTimeZoneId(String) serverTimeZoneId} has been supplied via the config, set that + * into all date formats that have been configured. + * </p> + */ + @Override + public void configure(final FTPClientConfig config) { + DateFormatSymbols dfs = null; + + final String languageCode = config.getServerLanguageCode(); + final String shortmonths = config.getShortMonthNames(); + if (shortmonths != null) { + dfs = FTPClientConfig.getDateFormatSymbols(shortmonths); + } else if (languageCode != null) { + dfs = FTPClientConfig.lookupDateFormatSymbols(languageCode); + } else { + dfs = FTPClientConfig.lookupDateFormatSymbols("en"); + } + + final String recentFormatString = config.getRecentDateFormatStr(); + setRecentDateFormat(recentFormatString, dfs); + + final String defaultFormatString = config.getDefaultDateFormatStr(); + if (defaultFormatString == null) { + throw new IllegalArgumentException("defaultFormatString cannot be null"); + } + setDefaultDateFormat(defaultFormatString, dfs); + + setServerTimeZone(config.getServerTimeZoneId()); + + this.lenientFutureDates = config.isLenientFutureDates(); + } + + /** + * @return Returns the defaultDateFormat. + */ + public SimpleDateFormat getDefaultDateFormat() { + return defaultDateFormat; + } + + /** + * @return Returns the defaultDateFormat pattern string. + */ + public String getDefaultDateFormatString() { + return defaultDateFormat.toPattern(); + } + + /** + * @return Returns the recentDateFormat. + */ + public SimpleDateFormat getRecentDateFormat() { + return recentDateFormat; + } + + /** + * @return Returns the recentDateFormat. + */ + public String getRecentDateFormatString() { + return recentDateFormat.toPattern(); + } + + /** + * @return Returns the serverTimeZone used by this parser. + */ + public TimeZone getServerTimeZone() { + return this.defaultDateFormat.getTimeZone(); + } + + /** + * @return returns an array of 12 strings representing the short month names used by this parse. + */ + public String[] getShortMonths() { + return defaultDateFormat.getDateFormatSymbols().getShortMonths(); + } + + /** + * @return Returns the lenientFutureDates. + */ + boolean isLenientFutureDates() { + return lenientFutureDates; + } + + /** + * Implements the one {@link FTPTimestampParser#parseTimestamp(String) method} in the {@link FTPTimestampParser FTPTimestampParser} interface according to + * this algorithm: * - * If the recentDateFormat member has been defined, try to parse the - * supplied string with that. If that parse fails, or if the recentDateFormat - * member has not been defined, attempt to parse with the defaultDateFormat - * member. If that fails, throw a ParseException. + * If the recentDateFormat member has been defined, try to parse the supplied string with that. If that parse fails, or if the recentDateFormat member has + * not been defined, attempt to parse with the defaultDateFormat member. If that fails, throw a ParseException. * * This method assumes that the server time is the same as the local time. * @@ -165,39 +240,36 @@ public class FTPTimestampParserImpl implements * @return a Calendar with the parsed timestamp */ @Override - public Calendar parseTimestamp(String timestampStr) throws ParseException { - Calendar now = Calendar.getInstance(); + public Calendar parseTimestamp(final String timestampStr) throws ParseException { + final Calendar now = Calendar.getInstance(); return parseTimestamp(timestampStr, now); } /** - * If the recentDateFormat member has been defined, try to parse the - * supplied string with that. If that parse fails, or if the recentDateFormat - * member has not been defined, attempt to parse with the defaultDateFormat - * member. If that fails, throw a ParseException. + * If the recentDateFormat member has been defined, try to parse the supplied string with that. If that parse fails, or if the recentDateFormat member has + * not been defined, attempt to parse with the defaultDateFormat member. If that fails, throw a ParseException. * - * This method allows a {@link Calendar} instance to be passed in which represents the - * current (system) time. + * This method allows a {@link Calendar} instance to be passed in which represents the current (system) time. * * @see FTPTimestampParser#parseTimestamp(String) * @param timestampStr The timestamp to be parsed - * @param serverTime The current time for the server + * @param serverTime The current time for the server * @return the calendar * @throws ParseException if timestamp cannot be parsed * @since 1.5 */ - public Calendar parseTimestamp(String timestampStr, Calendar serverTime) throws ParseException { - Calendar working = (Calendar) serverTime.clone(); + public Calendar parseTimestamp(final String timestampStr, final Calendar serverTime) throws ParseException { + final Calendar working = (Calendar) serverTime.clone(); working.setTimeZone(getServerTimeZone()); // is this needed? Date parsed = null; if (recentDateFormat != null) { - Calendar now = (Calendar) serverTime.clone();// Copy this, because we may change it + final Calendar now = (Calendar) serverTime.clone();// Copy this, because we may change it now.setTimeZone(this.getServerTimeZone()); if (lenientFutureDates) { // add a day to "now" so that "slop" doesn't cause a date - // slightly in the future to roll back a full year. (Bug 35181 => NET-83) + // slightly in the future to roll back a full year. (Bug 35181 => NET-83) now.add(Calendar.DAY_OF_MONTH, 1); } // The Java SimpleDateFormat class uses the epoch year 1970 if not present in the input @@ -209,13 +281,12 @@ public class FTPTimestampParserImpl implements // all instances of short dates which are +- 6 months from current date. // TODO this won't always work for systems that use short dates +0/-12months // e.g. if today is Jan 1 2001 and the short date is Feb 29 - String year = Integer.toString(now.get(Calendar.YEAR)); - String timeStampStrPlusYear = timestampStr + " " + year; - SimpleDateFormat hackFormatter = new SimpleDateFormat(recentDateFormat.toPattern() + " yyyy", - recentDateFormat.getDateFormatSymbols()); + final String year = Integer.toString(now.get(Calendar.YEAR)); + final String timeStampStrPlusYear = timestampStr + " " + year; + final SimpleDateFormat hackFormatter = new SimpleDateFormat(recentDateFormat.toPattern() + " yyyy", recentDateFormat.getDateFormatSymbols()); hackFormatter.setLenient(false); hackFormatter.setTimeZone(recentDateFormat.getTimeZone()); - ParsePosition pp = new ParsePosition(0); + final ParsePosition pp = new ParsePosition(0); parsed = hackFormatter.parse(timeStampStrPlusYear, pp); // Check if we parsed the full string, if so it must have been a short date originally if (parsed != null && pp.getIndex() == timeStampStrPlusYear.length()) { @@ -228,44 +299,29 @@ public class FTPTimestampParserImpl implements } } - ParsePosition pp = new ParsePosition(0); + final ParsePosition pp = new ParsePosition(0); parsed = defaultDateFormat.parse(timestampStr, pp); // note, length checks are mandatory for us since // SimpleDateFormat methods will succeed if less than - // full string is matched. They will also accept, + // full string is matched. They will also accept, // despite "leniency" setting, a two-digit number as // a valid year (e.g. 22:04 will parse as 22 A.D.) // so could mistakenly confuse an hour with a year, // if we don't insist on full length parsing. - if (parsed != null && pp.getIndex() == timestampStr.length()) { - working.setTime(parsed); - } else { - throw new ParseException( - "Timestamp '"+timestampStr+"' could not be parsed using a server time of " - +serverTime.getTime().toString(), + if ((parsed == null) || (pp.getIndex() != timestampStr.length())) { + throw new ParseException("Timestamp '" + timestampStr + "' could not be parsed using a server time of " + serverTime.getTime().toString(), pp.getErrorIndex()); } + working.setTime(parsed); setPrecision(defaultDateSmallestUnitIndex, working); return working; } - /** - * @return Returns the defaultDateFormat. - */ - public SimpleDateFormat getDefaultDateFormat() { - return defaultDateFormat; - } - /** - * @return Returns the defaultDateFormat pattern string. - */ - public String getDefaultDateFormatString() { - return defaultDateFormat.toPattern(); - } /** * @param format The defaultDateFormat to be set. - * @param dfs the symbols to use (may be null) + * @param dfs the symbols to use (may be null) */ - private void setDefaultDateFormat(String format, DateFormatSymbols dfs) { + private void setDefaultDateFormat(final String format, final DateFormatSymbols dfs) { if (format != null) { if (dfs != null) { this.defaultDateFormat = new SimpleDateFormat(format, dfs); @@ -278,23 +334,19 @@ public class FTPTimestampParserImpl implements } this.defaultDateSmallestUnitIndex = getEntry(this.defaultDateFormat); } + /** - * @return Returns the recentDateFormat. - */ - public SimpleDateFormat getRecentDateFormat() { - return recentDateFormat; - } - /** - * @return Returns the recentDateFormat. + * @param lenientFutureDates The lenientFutureDates to set. */ - public String getRecentDateFormatString() { - return recentDateFormat.toPattern(); + void setLenientFutureDates(final boolean lenientFutureDates) { + this.lenientFutureDates = lenientFutureDates; } + /** * @param format The recentDateFormat to set. - * @param dfs the symbols to use (may be null) + * @param dfs the symbols to use (may be null) */ - private void setRecentDateFormat(String format, DateFormatSymbols dfs) { + private void setRecentDateFormat(final String format, final DateFormatSymbols dfs) { if (format != null) { if (dfs != null) { this.recentDateFormat = new SimpleDateFormat(format, dfs); @@ -309,27 +361,11 @@ public class FTPTimestampParserImpl implements } /** - * @return returns an array of 12 strings representing the short - * month names used by this parse. - */ - public String[] getShortMonths() { - return defaultDateFormat.getDateFormatSymbols().getShortMonths(); - } - - - /** - * @return Returns the serverTimeZone used by this parser. - */ - public TimeZone getServerTimeZone() { - return this.defaultDateFormat.getTimeZone(); - } - /** - * sets a TimeZone represented by the supplied ID string into all - * of the parsers used by this server. - * @param serverTimeZone Time Id java.util.TimeZone id used by - * the ftp server. If null the client's local time zone is assumed. + * sets a TimeZone represented by the supplied ID string into all of the parsers used by this server. + * + * @param serverTimeZoneId Time Id java.util.TimeZone id used by the ftp server. If null the client's local time zone is assumed. */ - private void setServerTimeZone(String serverTimeZoneId) { + private void setServerTimeZone(final String serverTimeZoneId) { TimeZone serverTimeZone = TimeZone.getDefault(); if (serverTimeZoneId != null) { serverTimeZone = TimeZone.getTimeZone(serverTimeZoneId); @@ -339,68 +375,4 @@ public class FTPTimestampParserImpl implements this.recentDateFormat.setTimeZone(serverTimeZone); } } - - /** - * Implementation of the {@link Configurable Configurable} - * interface. Configures this <code>FTPTimestampParser</code> according - * to the following logic: - * <p> - * Set up the {@link FTPClientConfig#setDefaultDateFormatStr(java.lang.String) defaultDateFormat} - * and optionally the {@link FTPClientConfig#setRecentDateFormatStr(String) recentDateFormat} - * to values supplied in the config based on month names configured as follows: - * </p> - * <ul> - * <li>If a {@link FTPClientConfig#setShortMonthNames(String) shortMonthString} - * has been supplied in the <code>config</code>, use that to parse parse timestamps.</li> - * <li>Otherwise, if a {@link FTPClientConfig#setServerLanguageCode(String) serverLanguageCode} - * has been supplied in the <code>config</code>, use the month names represented - * by that {@link FTPClientConfig#lookupDateFormatSymbols(String) language} - * to parse timestamps.</li> - * <li>otherwise use default English month names</li> - * </ul><p> - * Finally if a {@link org.apache.commons.net.ftp.FTPClientConfig#setServerTimeZoneId(String) serverTimeZoneId} - * has been supplied via the config, set that into all date formats that have - * been configured. - * </p> - */ - @Override - public void configure(FTPClientConfig config) { - DateFormatSymbols dfs = null; - - String languageCode = config.getServerLanguageCode(); - String shortmonths = config.getShortMonthNames(); - if (shortmonths != null) { - dfs = FTPClientConfig.getDateFormatSymbols(shortmonths); - } else if (languageCode != null) { - dfs = FTPClientConfig.lookupDateFormatSymbols(languageCode); - } else { - dfs = FTPClientConfig.lookupDateFormatSymbols("en"); - } - - - String recentFormatString = config.getRecentDateFormatStr(); - setRecentDateFormat(recentFormatString, dfs); - - String defaultFormatString = config.getDefaultDateFormatStr(); - if (defaultFormatString == null) { - throw new IllegalArgumentException("defaultFormatString cannot be null"); - } - setDefaultDateFormat(defaultFormatString, dfs); - - setServerTimeZone(config.getServerTimeZoneId()); - - this.lenientFutureDates = config.isLenientFutureDates(); - } - /** - * @return Returns the lenientFutureDates. - */ - boolean isLenientFutureDates() { - return lenientFutureDates; - } - /** - * @param lenientFutureDates The lenientFutureDates to set. - */ - void setLenientFutureDates(boolean lenientFutureDates) { - this.lenientFutureDates = lenientFutureDates; - } } diff --git a/src/main/java/org/apache/commons/net/ftp/parser/MLSxEntryParser.java b/src/main/java/org/apache/commons/net/ftp/parser/MLSxEntryParser.java index 6cdf9ea..6364780 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/MLSxEntryParser.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/MLSxEntryParser.java @@ -16,8 +16,10 @@ */ package org.apache.commons.net.ftp.parser; + import java.text.ParsePosition; import java.text.SimpleDateFormat; +import java.time.Instant; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; @@ -32,6 +34,8 @@ import org.apache.commons.net.ftp.FTPFileEntryParserImpl; * Parser class for MSLT and MLSD replies. See RFC 3659. * <p> * Format is as follows: + * </p> + * * <pre> * entry = [ facts ] SP pathname * facts = 1*( fact ";" ) @@ -47,19 +51,18 @@ import org.apache.commons.net.ftp.FTPFileEntryParserImpl; * Sample os-depend-fact: * UNIX.group=0;UNIX.mode=0755;UNIX.owner=0; * </pre> - * A single control response entry (MLST) is returned with a leading space; - * multiple (data) entries are returned without any leading spaces. - * The parser requires that the leading space from the MLST entry is removed. - * MLSD entries can begin with a single space if there are no facts. + * <p> + * A single control response entry (MLST) is returned with a leading space; multiple (data) entries are returned without any leading spaces. The parser requires + * that the leading space from the MLST entry is removed. MLSD entries can begin with a single space if there are no facts. + * </p> * * @since 3.0 */ -public class MLSxEntryParser extends FTPFileEntryParserImpl -{ +public class MLSxEntryParser extends FTPFileEntryParserImpl { // This class is immutable, so a single instance can be shared. - private static final MLSxEntryParser PARSER = new MLSxEntryParser(); + private static final MLSxEntryParser INSTANCE = new MLSxEntryParser(); - private static final HashMap<String, Integer> TYPE_TO_INT = new HashMap<String, Integer>(); + private static final HashMap<String, Integer> TYPE_TO_INT = new HashMap<>(); static { TYPE_TO_INT.put("file", Integer.valueOf(FTPFile.FILE_TYPE)); TYPE_TO_INT.put("cdir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // listed directory @@ -67,105 +70,184 @@ public class MLSxEntryParser extends FTPFileEntryParserImpl TYPE_TO_INT.put("dir", Integer.valueOf(FTPFile.DIRECTORY_TYPE)); // dir or sub-dir } - private static int UNIX_GROUPS[] = { // Groups in order of mode digits - FTPFile.USER_ACCESS, - FTPFile.GROUP_ACCESS, - FTPFile.WORLD_ACCESS, - }; + private static final int[] UNIX_GROUPS = { // Groups in order of mode digits + FTPFile.USER_ACCESS, FTPFile.GROUP_ACCESS, FTPFile.WORLD_ACCESS, }; - private static int UNIX_PERMS[][] = { // perm bits, broken down by octal int value -/* 0 */ {}, -/* 1 */ {FTPFile.EXECUTE_PERMISSION}, -/* 2 */ {FTPFile.WRITE_PERMISSION}, -/* 3 */ {FTPFile.EXECUTE_PERMISSION, FTPFile.WRITE_PERMISSION}, -/* 4 */ {FTPFile.READ_PERMISSION}, -/* 5 */ {FTPFile.READ_PERMISSION, FTPFile.EXECUTE_PERMISSION}, -/* 6 */ {FTPFile.READ_PERMISSION, FTPFile.WRITE_PERMISSION}, -/* 7 */ {FTPFile.READ_PERMISSION, FTPFile.WRITE_PERMISSION, FTPFile.EXECUTE_PERMISSION}, - }; + private static final int[][] UNIX_PERMS = { // perm bits, broken down by octal int value + /* 0 */ {}, /* 1 */ { FTPFile.EXECUTE_PERMISSION }, /* 2 */ { FTPFile.WRITE_PERMISSION }, + /* 3 */ { FTPFile.EXECUTE_PERMISSION, FTPFile.WRITE_PERMISSION }, /* 4 */ { FTPFile.READ_PERMISSION }, + /* 5 */ { FTPFile.READ_PERMISSION, FTPFile.EXECUTE_PERMISSION }, /* 6 */ { FTPFile.READ_PERMISSION, FTPFile.WRITE_PERMISSION }, + /* 7 */ { FTPFile.READ_PERMISSION, FTPFile.WRITE_PERMISSION, FTPFile.EXECUTE_PERMISSION }, }; + + public static MLSxEntryParser getInstance() { + return INSTANCE; + } + + public static FTPFile parseEntry(final String entry) { + return INSTANCE.parseFTPEntry(entry); + } /** - * Create the parser for MSLT and MSLD listing entries - * This class is immutable, so one can use {@link #getInstance()} instead. + * Parse a GMT time stamp of the form yyyyMMDDHHMMSS[.sss] + * + * @param timestamp the date-time to parse + * @return a Calendar entry, may be {@code null} + * @since 3.4 */ - public MLSxEntryParser() - { - super(); + public static Calendar parseGMTdateTime(final String timestamp) { + final SimpleDateFormat dateFormat; + final boolean hasMillis; + if (timestamp.contains(".")) { + dateFormat = new SimpleDateFormat("yyyyMMddHHmmss.SSS"); + hasMillis = true; + } else { + dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); + hasMillis = false; + } + final TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT"); + // both time zones need to be set for the parse to work OK + dateFormat.setTimeZone(gmtTimeZone); + final GregorianCalendar gCalendar = new GregorianCalendar(gmtTimeZone); + final ParsePosition pos = new ParsePosition(0); + dateFormat.setLenient(false); // We want to parse the whole string + final Date parsed = dateFormat.parse(timestamp, pos); + if (pos.getIndex() != timestamp.length()) { + return null; // did not fully parse the input + } + gCalendar.setTime(parsed); + if (!hasMillis) { + gCalendar.clear(Calendar.MILLISECOND); // flag up missing ms units + } + return gCalendar; + } + + /** + * Parse a GMT time stamp of the form yyyyMMDDHHMMSS[.sss] + * + * @param timestamp the date-time to parse + * @return a Calendar entry, may be {@code null} + * @since 3.9.0 + */ + public static Instant parseGmtInstant(final String timestamp) { + return parseGMTdateTime(timestamp).toInstant(); + } + + /** + * Create the parser for MSLT and MSLD listing entries This class is immutable, so one can use {@link #getInstance()} instead. + */ + public MLSxEntryParser() { + } + + // perm-fact = "Perm" "=" *pvals + // pvals = "a" / "c" / "d" / "e" / "f" / + // "l" / "m" / "p" / "r" / "w" + private void doUnixPerms(final FTPFile file, final String valueLowerCase) { + for (final char c : valueLowerCase.toCharArray()) { + // TODO these are mostly just guesses at present + switch (c) { + case 'a': // (file) may APPEnd + file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + case 'c': // (dir) files may be created in the dir + file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + case 'd': // deletable + file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + case 'e': // (dir) can change to this dir + file.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, true); + break; + case 'f': // (file) renamable + // ?? file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + case 'l': // (dir) can be listed + file.setPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION, true); + break; + case 'm': // (dir) can create directory here + file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + case 'p': // (dir) entries may be deleted + file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + case 'r': // (files) file may be RETRieved + file.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, true); + break; + case 'w': // (files) file may be STORed + file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); + break; + default: + break; + // ignore unexpected flag for now. + } // switch + } // each char } @Override - public FTPFile parseFTPEntry(String entry) { + public FTPFile parseFTPEntry(final String entry) { if (entry.startsWith(" ")) {// leading space means no facts are present if (entry.length() > 1) { // is there a path name? - FTPFile file = new FTPFile(); + final FTPFile file = new FTPFile(); file.setRawListing(entry); file.setName(entry.substring(1)); return file; - } else { - return null; // Invalid - no pathname } + return null; // Invalid - no pathname } - String parts[] = entry.split(" ",2); // Path may contain space - if (parts.length != 2 || parts[1].length() == 0) { + final String parts[] = entry.split(" ", 2); // Path may contain space + if (parts.length != 2 || parts[1].isEmpty()) { return null; // no space found or no file name } final String factList = parts[0]; if (!factList.endsWith(";")) { return null; } - FTPFile file = new FTPFile(); + final FTPFile file = new FTPFile(); file.setRawListing(entry); file.setName(parts[1]); - String[] facts = factList.split(";"); - boolean hasUnixMode = parts[0].toLowerCase(Locale.ENGLISH).contains("unix.mode="); - for(String fact : facts) { - String []factparts = fact.split("=", -1); // Don't drop empty values + final String[] facts = factList.split(";"); + final boolean hasUnixMode = parts[0].toLowerCase(Locale.ENGLISH).contains("unix.mode="); + for (final String fact : facts) { + final String[] factparts = fact.split("=", -1); // Don't drop empty values // Sample missing permission // drwx------ 2 mirror mirror 4096 Mar 13 2010 subversion // modify=20100313224553;perm=;type=dir;unique=811U282598;UNIX.group=500;UNIX.mode=0700;UNIX.owner=500; subversion if (factparts.length != 2) { return null; // invalid - there was no "=" sign } - String factname = factparts[0].toLowerCase(Locale.ENGLISH); - String factvalue = factparts[1]; - if (factvalue.length() == 0) { + final String factname = factparts[0].toLowerCase(Locale.ENGLISH); + final String factvalue = factparts[1]; + if (factvalue.isEmpty()) { continue; // nothing to see here } - String valueLowerCase = factvalue.toLowerCase(Locale.ENGLISH); - if ("size".equals(factname)) { + final String valueLowerCase = factvalue.toLowerCase(Locale.ENGLISH); + if ("size".equals(factname) || "sizd".equals(factname)) { file.setSize(Long.parseLong(factvalue)); - } - else if ("sizd".equals(factname)) { // Directory size - file.setSize(Long.parseLong(factvalue)); - } - else if ("modify".equals(factname)) { + } else if ("modify".equals(factname)) { final Calendar parsed = parseGMTdateTime(factvalue); if (parsed == null) { return null; } file.setTimestamp(parsed); - } - else if ("type".equals(factname)) { - Integer intType = TYPE_TO_INT.get(valueLowerCase); - if (intType == null) { - file.setType(FTPFile.UNKNOWN_TYPE); - } else { - file.setType(intType.intValue()); - } - } - else if (factname.startsWith("unix.")) { - String unixfact = factname.substring("unix.".length()).toLowerCase(Locale.ENGLISH); - if ("group".equals(unixfact)){ + } else if ("type".equals(factname)) { + final Integer intType = TYPE_TO_INT.get(valueLowerCase); + if (intType == null) { + file.setType(FTPFile.UNKNOWN_TYPE); + } else { + file.setType(intType.intValue()); + } + } else if (factname.startsWith("unix.")) { + final String unixfact = factname.substring("unix.".length()).toLowerCase(Locale.ENGLISH); + if ("group".equals(unixfact)) { file.setGroup(factvalue); - } else if ("owner".equals(unixfact)){ + } else if ("owner".equals(unixfact)) { file.setUser(factvalue); - } else if ("mode".equals(unixfact)){ // e.g. 0[1]755 - int off = factvalue.length()-3; // only parse last 3 digits - for(int i=0; i < 3; i++){ - int ch = factvalue.charAt(off+i)-'0'; + } else if ("mode".equals(unixfact)) { // e.g. 0[1]755 + final int off = factvalue.length() - 3; // only parse last 3 digits + for (int i = 0; i < 3; i++) { + final int ch = factvalue.charAt(off + i) - '0'; if (ch >= 0 && ch <= 7) { // Check it's valid octal - for(int p : UNIX_PERMS[ch]) { + for (final int p : UNIX_PERMS[ch]) { file.setPermission(UNIX_GROUPS[i], p, true); } } else { @@ -180,90 +262,4 @@ public class MLSxEntryParser extends FTPFileEntryParserImpl } // each fact return file; } - - /** - * Parse a GMT time stamp of the form YYYYMMDDHHMMSS[.sss] - * - * @param timestamp the date-time to parse - * @return a Calendar entry, may be {@code null} - * @since 3.4 - */ - public static Calendar parseGMTdateTime(String timestamp) { - final SimpleDateFormat sdf; - final boolean hasMillis; - if (timestamp.contains(".")){ - sdf = new SimpleDateFormat("yyyyMMddHHmmss.SSS"); - hasMillis = true; - } else { - sdf = new SimpleDateFormat("yyyyMMddHHmmss"); - hasMillis = false; - } - TimeZone GMT = TimeZone.getTimeZone("GMT"); - // both timezones need to be set for the parse to work OK - sdf.setTimeZone(GMT); - GregorianCalendar gc = new GregorianCalendar(GMT); - ParsePosition pos = new ParsePosition(0); - sdf.setLenient(false); // We want to parse the whole string - final Date parsed = sdf.parse(timestamp, pos); - if (pos.getIndex() != timestamp.length()) { - return null; // did not fully parse the input - } - gc.setTime(parsed); - if (!hasMillis) { - gc.clear(Calendar.MILLISECOND); // flag up missing ms units - } - return gc; - } - - // perm-fact = "Perm" "=" *pvals - // pvals = "a" / "c" / "d" / "e" / "f" / - // "l" / "m" / "p" / "r" / "w" - private void doUnixPerms(FTPFile file, String valueLowerCase) { - for(char c : valueLowerCase.toCharArray()) { - // TODO these are mostly just guesses at present - switch (c) { - case 'a': // (file) may APPEnd - file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); - break; - case 'c': // (dir) files may be created in the dir - file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); - break; - case 'd': // deletable - file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); - break; - case 'e': // (dir) can change to this dir - file.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, true); - break; - case 'f': // (file) renamable - // ?? file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); - break; - case 'l': // (dir) can be listed - file.setPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION, true); - break; - case 'm': // (dir) can create directory here - file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); - break; - case 'p': // (dir) entries may be deleted - file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); - break; - case 'r': // (files) file may be RETRieved - file.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, true); - break; - case 'w': // (files) file may be STORed - file.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); - break; - default: - break; - // ignore unexpected flag for now. - } // switch - } // each char - } - - public static FTPFile parseEntry(String entry) { - return PARSER.parseFTPEntry(entry); - } - - public static MLSxEntryParser getInstance() { - return PARSER; - } } diff --git a/src/main/java/org/apache/commons/net/ftp/parser/MVSFTPEntryParser.java b/src/main/java/org/apache/commons/net/ftp/parser/MVSFTPEntryParser.java index 28a45dc..4b97494 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/MVSFTPEntryParser.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/MVSFTPEntryParser.java @@ -24,12 +24,9 @@ import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPFile; /** - * Implementation of FTPFileEntryParser and FTPFileListParser for IBM zOS/MVS - * Systems. + * Implementation of FTPFileEntryParser and FTPFileListParser for IBM zOS/MVS Systems. * - * @version $Id: MVSFTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $ - * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for - * usage instructions) + * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) */ public class MVSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { @@ -40,34 +37,30 @@ public class MVSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { static final int JES_LEVEL_1_LIST_TYPE = 3; static final int JES_LEVEL_2_LIST_TYPE = 4; - private int isType = UNKNOWN_LIST_TYPE; - /** - * Fallback parser for Unix-style listings - */ - private UnixFTPEntryParser unixFTPEntryParser; - - /** - * Dates are ignored for file lists, but are used for member lists where - * possible + * Dates are ignored for file lists, but are used for member lists where possible */ static final String DEFAULT_DATE_FORMAT = "yyyy/MM/dd HH:mm"; // 2001/09/18 - // 13:52 + // 13:52 /** * Matches these entries: + * * <pre> * Volume Unit Referred Ext Used Recfm Lrecl BlkSz Dsorg Dsname * B10142 3390 2006/03/20 2 31 F 80 80 PS MDI.OKL.WORK * </pre> + * + * @see <a href= "https://www.ibm.com/support/knowledgecenter/zosbasics/com.ibm.zos.zconcepts/zconcepts_159.htm">Data set record formats</a> */ static final String FILE_LIST_REGEX = "\\S+\\s+" + // volume - // ignored + // ignored "\\S+\\s+" + // unit - ignored "\\S+\\s+" + // access date - ignored "\\S+\\s+" + // extents -ignored - "\\S+\\s+" + // used - ignored - "[FV]\\S*\\s+" + // recfm - must start with F or V + // If the values are too large, the fields may be merged (NET-639) + "(?:\\S+\\s+)?" + // used - ignored + "(?:F|FB|V|VB|U)\\s+" + // recfm - F[B], V[B], U "\\S+\\s+" + // logical record length -ignored "\\S+\\s+" + // block size - ignored "(PS|PO|PO-E)\\s+" + // Dataset organisation. Many exist @@ -76,6 +69,7 @@ public class MVSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { /** * Matches these entries: + * * <pre> * Name VV.MM Created Changed Size Init Mod Id * TBSHELF 01.03 2002/09/12 2002/10/11 09:37 11 11 0 KIL001 @@ -93,14 +87,14 @@ public class MVSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { /** * Matches these entries, note: no header: + * * <pre> * IBMUSER1 JOB01906 OUTPUT 3 Spool Files * 012345678901234567890123456789012345678901234 * 1 2 3 4 * </pre> */ - static final String JES_LEVEL_1_LIST_REGEX = - "(\\S+)\\s+" + // job name ignored + static final String JES_LEVEL_1_LIST_REGEX = "(\\S+)\\s+" + // job name ignored "(\\S+)\\s+" + // job number "(\\S+)\\s+" + // job status (OUTPUT,INPUT,ACTIVE) "(\\S+)\\s+" + // number of spool files @@ -109,14 +103,16 @@ public class MVSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { ; /** - * JES INTERFACE LEVEL 2 parser - * Matches these entries: + * JES INTERFACE LEVEL 2 parser Matches these entries: + * * <pre> * JOBNAME JOBID OWNER STATUS CLASS * IBMUSER1 JOB01906 IBMUSER OUTPUT A RC=0000 3 spool files * IBMUSER TSU01830 IBMUSER OUTPUT TSU ABEND=522 3 spool files * </pre> + * * Sample output from FTP session: + * * <pre> * ftp> quote site filetype=jes * 200 SITE command was accepted @@ -142,8 +138,7 @@ public class MVSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { * </pre> */ - static final String JES_LEVEL_2_LIST_REGEX = - "(\\S+)\\s+" + // job name ignored + static final String JES_LEVEL_2_LIST_REGEX = "(\\S+)\\s+" + // job name ignored "(\\S+)\\s+" + // job number "(\\S+)\\s+" + // owner ignored "(\\S+)\\s+" + // job status (OUTPUT,INPUT,ACTIVE) ignored @@ -151,83 +146,71 @@ public class MVSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { "(\\S+).*" // rest ignored ; + private int isType = UNKNOWN_LIST_TYPE; + + /** + * Fallback parser for Unix-style listings + */ + private UnixFTPEntryParser unixFTPEntryParser; + /* - * --------------------------------------------------------------------- - * Very brief and incomplete description of the zOS/MVS-filesystem. (Note: - * "zOS" is the operating system on the mainframe, and is the new name for - * MVS) + * --------------------------------------------------------------------- Very brief and incomplete description of the zOS/MVS-file system. (Note: "zOS" is + * the operating system on the mainframe, and is the new name for MVS) * - * The filesystem on the mainframe does not have hierarchal structure as for - * example the unix filesystem. For a more comprehensive description, please + * The file system on the mainframe does not have hierarchal structure as for example the unix file system. For a more comprehensive description, please * refer to the IBM manuals * - * @LINK: - * http://publibfp.boulder.ibm.com/cgi-bin/bookmgr/BOOKS/dgt2d440/CONTENTS + * @LINK: http://publibfp.boulder.ibm.com/cgi-bin/bookmgr/BOOKS/dgt2d440/CONTENTS * * * Dataset names ============= * - * A dataset name consist of a number of qualifiers separated by '.', each - * qualifier can be at most 8 characters, and the total length of a dataset - * can be max 44 characters including the dots. + * A dataset name consist of a number of qualifiers separated by '.', each qualifier can be at most 8 characters, and the total length of a dataset can be + * max 44 characters including the dots. * * * Dataset organisation ==================== * - * A dataset represents a piece of storage allocated on one or more disks. - * The structure of the storage is described with the field dataset - * organinsation (DSORG). There are a number of dataset organisations, but - * only two are usable for FTP transfer. + * A dataset represents a piece of storage allocated on one or more disks. The structure of the storage is described with the field dataset organinsation + * (DSORG). There are a number of dataset organisations, but only two are usable for FTP transfer. * - * DSORG: PS: sequential, or flat file PO: partitioned dataset PO-E: - * extended partitioned dataset + * DSORG: PS: sequential, or flat file PO: partitioned dataset PO-E: extended partitioned dataset * - * The PS file is just a flat file, as you would find it on the unix file - * system. + * The PS file is just a flat file, as you would find it on the unix file system. * - * The PO and PO-E files, can be compared to a single level directory - * structure. A PO file consist of a number of dataset members, or files if - * you will. It is possible to CD into the file, and to retrieve the - * individual members. + * The PO and PO-E files, can be compared to a single level directory structure. A PO file consist of a number of dataset members, or files if you will. It + * is possible to CD into the file, and to retrieve the individual members. * * * Dataset record format ===================== * - * The physical layout of the dataset is described on the dataset itself. - * There are a number of record formats (RECFM), but just a few is relavant - * for the FTP transfer. + * The physical layout of the dataset is described on the dataset itself. There are a number of record formats (RECFM), but just a few is relavant for the + * FTP transfer. * - * Any one beginning with either F or V can safely used by FTP transfer. All - * others should only be used with great care, so this version will just - * ignore the other record formats. F means a fixed number of records per - * allocated storage, and V means a variable number of records. + * Any one beginning with either F or V can safely used by FTP transfer. All others should only be used with great care. F means a fixed number of records + * per allocated storage, and V means a variable number of records. * * * Other notes =========== * - * The file system supports automatically backup and retrieval of datasets. - * If a file is backed up, the ftp LIST command will return: ARCIVE Not - * Direct Access Device KJ.IOP998.ERROR.PL.UNITTEST + * The file system supports automatically backup and retrieval of datasets. If a file is backed up, the ftp LIST command will return: ARCIVE Not Direct + * Access Device KJ.IOP998.ERROR.PL.UNITTEST * * * Implementation notes ==================== * - * Only datasets that have dsorg PS, PO or PO-E and have recfm beginning - * with F or V, is fully parsed. + * Only datasets that have dsorg PS, PO or PO-E and have recfm beginning with F or V or U, is fully parsed. * - * The following fields in FTPFile is used: FTPFile.Rawlisting: Always set. - * FTPFile.Type: DIRECTORY_TYPE or FILE_TYPE or UNKNOWN FTPFile.Name: name + * The following fields in FTPFile is used: FTPFile.Rawlisting: Always set. FTPFile.Type: DIRECTORY_TYPE or FILE_TYPE or UNKNOWN FTPFile.Name: name * FTPFile.Timestamp: change time or null * * * * Additional information ====================== * - * The MVS ftp server supports a number of features via the FTP interface. - * The features are controlled with the FTP command quote site filetype=<SEQ|JES|DB2> - * SEQ is the default and used for normal file transfer JES is used to - * interact with the Job Entry Subsystem (JES) similar to a job scheduler - * DB2 is used to interact with a DB2 subsystem + * The MVS ftp server supports a number of features via the FTP interface. The features are controlled with the FTP command quote site + * filetype=<SEQ|JES|DB2> SEQ is the default and used for normal file transfer JES is used to interact with the Job Entry Subsystem (JES) similar to a job + * scheduler DB2 is used to interact with a DB2 subsystem * * This parser supports SEQ and JES. * @@ -247,178 +230,85 @@ public class MVSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { super.configure(null); // configure parser with default configurations } - /** - * Parses a line of an z/OS - MVS FTP server file listing and converts it - * into a usable format in the form of an <code> FTPFile </code> instance. - * If the file listing line doesn't describe a file, then - * <code> null </code> is returned. Otherwise a <code> FTPFile </code> - * instance representing the file is returned. - * - * @param entry - * A line of text from the file listing - * @return An FTPFile instance corresponding to the supplied entry + /* + * @return */ @Override - public FTPFile parseFTPEntry(String entry) { - boolean isParsed = false; - FTPFile f = new FTPFile(); - - if (isType == FILE_LIST_TYPE) { - isParsed = parseFileList(f, entry); - } else if (isType == MEMBER_LIST_TYPE) { - isParsed = parseMemberList(f, entry); - if (!isParsed) { - isParsed = parseSimpleEntry(f, entry); - } - } else if (isType == UNIX_LIST_TYPE) { - isParsed = parseUnixList(f, entry); - } else if (isType == JES_LEVEL_1_LIST_TYPE) { - isParsed = parseJeslevel1List(f, entry); - } else if (isType == JES_LEVEL_2_LIST_TYPE) { - isParsed = parseJeslevel2List(f, entry); - } - - if (!isParsed) { - f = null; - } - - return f; + protected FTPClientConfig getDefaultConfiguration() { + return new FTPClientConfig(FTPClientConfig.SYST_MVS, DEFAULT_DATE_FORMAT, null); } /** - * Parse entries representing a dataset list. Only datasets with DSORG PS or - * PO or PO-E and with RECFM F* or V* will be parsed. - * - * Format of ZOS/MVS file list: 1 2 3 4 5 6 7 8 9 10 Volume Unit Referred - * Ext Used Recfm Lrecl BlkSz Dsorg Dsname B10142 3390 2006/03/20 2 31 F 80 - * 80 PS MDI.OKL.WORK ARCIVE Not Direct Access Device - * KJ.IOP998.ERROR.PL.UNITTEST B1N231 3390 2006/03/20 1 15 VB 256 27998 PO - * PLU B1N231 3390 2006/03/20 1 15 VB 256 27998 PO-E PLB - * - * ----------------------------------- Group within Regex [1] Volume [2] - * Unit [3] Referred [4] Ext: number of extents [5] Used [6] Recfm: Record - * format [7] Lrecl: Logical record length [8] BlkSz: Block size [9] Dsorg: - * Dataset organisation. Many exists but only support: PS, PO, PO-E [10] - * Dsname: Dataset name - * - * Note: When volume is ARCIVE, it means the dataset is stored somewhere in - * a tape archive. These entries is currently not supported by this parser. - * A null value is returned. - * - * @param file - * will be updated with Name, Type, Timestamp if parsed. + * Parse entries representing a dataset list. Only datasets with DSORG PS or PO or PO-E and with RECFM F[B], V[B], U will be parsed. + * + * Format of ZOS/MVS file list: 1 2 3 4 5 6 7 8 9 10 Volume Unit Referred Ext Used Recfm Lrecl BlkSz Dsorg Dsname B10142 3390 2006/03/20 2 31 F 80 80 PS + * MDI.OKL.WORK ARCIVE Not Direct Access Device KJ.IOP998.ERROR.PL.UNITTEST B1N231 3390 2006/03/20 1 15 VB 256 27998 PO PLU B1N231 3390 2006/03/20 1 15 VB + * 256 27998 PO-E PLB + * + * ----------------------------------- Group within Regex [1] Volume [2] Unit [3] Referred [4] Ext: number of extents [5] Used [6] Recfm: Record format [7] + * Lrecl: Logical record length [8] BlkSz: Block size [9] Dsorg: Dataset organisation. Many exists but only support: PS, PO, PO-E [10] Dsname: Dataset name + * + * Note: When volume is ARCIVE, it means the dataset is stored somewhere in a tape archive. These entries is currently not supported by this parser. A null + * value is returned. + * * @param entry zosDirectoryEntry - * @return true: entry was parsed, false: entry was not parsed. + * @return null: entry was not parsed. */ - private boolean parseFileList(FTPFile file, String entry) { + private FTPFile parseFileList(final String entry) { if (matches(entry)) { + final FTPFile file = new FTPFile(); file.setRawListing(entry); - String name = group(2); - String dsorg = group(1); + final String name = group(2); + final String dsorg = group(1); file.setName(name); // DSORG if ("PS".equals(dsorg)) { file.setType(FTPFile.FILE_TYPE); - } - else if ("PO".equals(dsorg) || "PO-E".equals(dsorg)) { + } else if ("PO".equals(dsorg) || "PO-E".equals(dsorg)) { // regex already ruled out anything other than PO or PO-E file.setType(FTPFile.DIRECTORY_TYPE); - } - else { - return false; + } else { + return null; } - return true; - } - - return false; - } - - /** - * Parse entries within a partitioned dataset. - * - * Format of a memberlist within a PDS: - * <pre> - * 0 1 2 3 4 5 6 7 8 - * Name VV.MM Created Changed Size Init Mod Id - * TBSHELF 01.03 2002/09/12 2002/10/11 09:37 11 11 0 KIL001 - * TBTOOL 01.12 2002/09/12 2004/11/26 19:54 51 28 0 KIL001 - * - * ------------------------------------------- - * [1] Name - * [2] VV.MM: Version . modification - * [3] Created: yyyy / MM / dd - * [4,5] Changed: yyyy / MM / dd HH:mm - * [6] Size: number of lines - * [7] Init: number of lines when first created - * [8] Mod: number of modified lines a last save - * [9] Id: User id for last update - * </pre> - * - * @param file - * will be updated with Name, Type and Timestamp if parsed. - * @param entry zosDirectoryEntry - * @return true: entry was parsed, false: entry was not parsed. - */ - private boolean parseMemberList(FTPFile file, String entry) { - if (matches(entry)) { - file.setRawListing(entry); - String name = group(1); - String datestr = group(2) + " " + group(3); - file.setName(name); - file.setType(FTPFile.FILE_TYPE); - try { - file.setTimestamp(super.parseTimestamp(datestr)); - } catch (ParseException e) { - e.printStackTrace(); - // just ignore parsing errors. - // TODO check this is ok - return false; // this is a parsing failure too. - } - return true; + return file; } - return false; + return null; } /** - * Assigns the name to the first word of the entry. Only to be used from a - * safe context, for example from a memberlist, where the regex for some - * reason fails. Then just assign the name field of FTPFile. + * Parses a line of an z/OS - MVS FTP server file listing and converts it into a usable format in the form of an <code> FTPFile </code> instance. If the + * file listing line doesn't describe a file, then <code> null </code> is returned. Otherwise a <code> FTPFile </code> instance representing the file is + * returned. * - * @param file - * @param entry - * @return true if the entry string is non-null and non-empty + * @param entry A line of text from the file listing + * @return An FTPFile instance corresponding to the supplied entry */ - private boolean parseSimpleEntry(FTPFile file, String entry) { - if (entry != null && entry.trim().length() > 0) { - file.setRawListing(entry); - String name = entry.split(" ")[0]; - file.setName(name); - file.setType(FTPFile.FILE_TYPE); - return true; + @Override + public FTPFile parseFTPEntry(final String entry) { + switch (isType) { + case FILE_LIST_TYPE: + return parseFileList(entry); + case MEMBER_LIST_TYPE: + return parseMemberList(entry); + case UNIX_LIST_TYPE: + return unixFTPEntryParser.parseFTPEntry(entry); + case JES_LEVEL_1_LIST_TYPE: + return parseJeslevel1List(entry); + case JES_LEVEL_2_LIST_TYPE: + return parseJeslevel2List(entry); + default: + break; } - return false; - } - /** - * Parse the entry as a standard unix file. Using the UnixFTPEntryParser. - * - * @param file - * @param entry - * @return true: entry is parsed, false: entry could not be parsed. - */ - private boolean parseUnixList(FTPFile file, String entry) { - file = unixFTPEntryParser.parseFTPEntry(entry); - if (file == null) { - return false; - } - return true; + return null; } /** * Matches these entries, note: no header: + * * <pre> * [1] [2] [3] [4] [5] * IBMUSER1 JOB01906 OUTPUT 3 Spool Files @@ -431,29 +321,29 @@ public class MVSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { * [3] Job status (INPUT,ACTIVE,OUTPUT) * [4] Number of sysout files * [5] The string "Spool Files" - *</pre> + * </pre> * - * @param file - * will be updated with Name, Type and Timestamp if parsed. * @param entry zosDirectoryEntry - * @return true: entry was parsed, false: entry was not parsed. + * @return null: entry was not parsed. */ - private boolean parseJeslevel1List(FTPFile file, String entry) { + private FTPFile parseJeslevel1List(final String entry) { if (matches(entry)) { + final FTPFile file = new FTPFile(); if (group(3).equalsIgnoreCase("OUTPUT")) { file.setRawListing(entry); - String name = group(2); /* Job Number, used by GET */ + final String name = group(2); /* Job Number, used by GET */ file.setName(name); file.setType(FTPFile.FILE_TYPE); - return true; + return file; } } - return false; + return null; } /** * Matches these entries: + * * <pre> * [1] [2] [3] [4] [5] * JOBNAME JOBID OWNER STATUS CLASS @@ -471,45 +361,98 @@ public class MVSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { * [6] The rest * </pre> * - * @param file - * will be updated with Name, Type and Timestamp if parsed. * @param entry zosDirectoryEntry - * @return true: entry was parsed, false: entry was not parsed. + * @return null: entry was not parsed. */ - private boolean parseJeslevel2List(FTPFile file, String entry) { + private FTPFile parseJeslevel2List(final String entry) { if (matches(entry)) { + final FTPFile file = new FTPFile(); if (group(4).equalsIgnoreCase("OUTPUT")) { file.setRawListing(entry); - String name = group(2); /* Job Number, used by GET */ + final String name = group(2); /* Job Number, used by GET */ file.setName(name); file.setType(FTPFile.FILE_TYPE); - return true; + return file; } } - return false; + return null; } /** - * preParse is called as part of the interface. Per definition is is called - * before the parsing takes place. - * Three kind of lists is recognize: - * z/OS-MVS File lists - * z/OS-MVS Member lists - * unix file lists + * Parse entries within a partitioned dataset. + * + * Format of a memberlist within a PDS: + * + * <pre> + * 0 1 2 3 4 5 6 7 8 + * Name VV.MM Created Changed Size Init Mod Id + * TBSHELF 01.03 2002/09/12 2002/10/11 09:37 11 11 0 KIL001 + * TBTOOL 01.12 2002/09/12 2004/11/26 19:54 51 28 0 KIL001 + * + * ------------------------------------------- + * [1] Name + * [2] VV.MM: Version . modification + * [3] Created: yyyy / MM / dd + * [4,5] Changed: yyyy / MM / dd HH:mm + * [6] Size: number of lines + * [7] Init: number of lines when first created + * [8] Mod: number of modified lines a last save + * [9] Id: User id for last update + * </pre> + * + * @param entry zosDirectoryEntry + * @return null: entry was not parsed. + */ + private FTPFile parseMemberList(final String entry) { + final FTPFile file = new FTPFile(); + if (matches(entry)) { + file.setRawListing(entry); + final String name = group(1); + final String datestr = group(2) + " " + group(3); + file.setName(name); + file.setType(FTPFile.FILE_TYPE); + try { + file.setTimestamp(super.parseTimestamp(datestr)); + } catch (final ParseException e) { + // just ignore parsing errors. + // TODO check this is ok + // Drop thru to try simple parser + } + return file; + } + + /* + * Assigns the name to the first word of the entry. Only to be used from a safe context, for example from a memberlist, where the regex for some reason + * fails. Then just assign the name field of FTPFile. + */ + if (entry != null && !entry.trim().isEmpty()) { + file.setRawListing(entry); + final String name = entry.split(" ")[0]; + file.setName(name); + file.setType(FTPFile.FILE_TYPE); + return file; + } + return null; + } + + /** + * preParse is called as part of the interface. Per definition is is called before the parsing takes place. Three kind of lists is recognize: z/OS-MVS File + * lists z/OS-MVS Member lists unix file lists + * * @since 2.0 */ @Override - public List<String> preParse(List<String> orig) { + public List<String> preParse(final List<String> orig) { // simply remove the header line. Composite logic will take care of the // two different types of // list in short order. - if (orig != null && orig.size() > 0) { - String header = orig.get(0); - if (header.indexOf("Volume") >= 0 && header.indexOf("Dsname") >= 0) { + if (orig != null && !orig.isEmpty()) { + final String header = orig.get(0); + if (header.contains("Volume") && header.contains("Dsname")) { setType(FILE_LIST_TYPE); super.setRegex(FILE_LIST_REGEX); - } else if (header.indexOf("Name") >= 0 && header.indexOf("Id") >= 0) { + } else if (header.contains("Name") && header.contains("Id")) { setType(MEMBER_LIST_TYPE); super.setRegex(MEMBER_LIST_REGEX); } else if (header.indexOf("total") == 0) { @@ -518,8 +461,7 @@ public class MVSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { } else if (header.indexOf("Spool Files") >= 30) { setType(JES_LEVEL_1_LIST_TYPE); super.setRegex(JES_LEVEL_1_LIST_REGEX); - } else if (header.indexOf("JOBNAME") == 0 - && header.indexOf("JOBID") > 8) {// header contains JOBNAME JOBID OWNER // STATUS CLASS + } else if (header.indexOf("JOBNAME") == 0 && header.indexOf("JOBID") > 8) {// header contains JOBNAME JOBID OWNER // STATUS CLASS setType(JES_LEVEL_2_LIST_TYPE); super.setRegex(JES_LEVEL_2_LIST_REGEX); } else { @@ -536,19 +478,11 @@ public class MVSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { /** * Explicitly set the type of listing being processed. + * * @param type The listing type. */ - void setType(int type) { + void setType(final int type) { isType = type; } - /* - * @return - */ - @Override - protected FTPClientConfig getDefaultConfiguration() { - return new FTPClientConfig(FTPClientConfig.SYST_MVS, - DEFAULT_DATE_FORMAT, null); - } - } diff --git a/src/main/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java b/src/main/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java index ac88f3b..18bc491 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParser.java @@ -16,6 +16,7 @@ */ package org.apache.commons.net.ftp.parser; + import java.text.ParseException; import org.apache.commons.net.ftp.FTPClientConfig; @@ -24,140 +25,106 @@ import org.apache.commons.net.ftp.FTPFile; /** * Implementation FTPFileEntryParser and FTPFileListParser for pre MacOS-X Systems. * - * @version $Id: MacOsPeterFTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $ * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) * @since 3.1 */ -public class MacOsPeterFTPEntryParser extends ConfigurableFTPFileEntryParserImpl -{ +public class MacOsPeterFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { - static final String DEFAULT_DATE_FORMAT - = "MMM d yyyy"; //Nov 9 2001 + static final String DEFAULT_DATE_FORMAT = "MMM d yyyy"; // Nov 9 2001 - static final String DEFAULT_RECENT_DATE_FORMAT - = "MMM d HH:mm"; //Nov 9 20:06 + static final String DEFAULT_RECENT_DATE_FORMAT = "MMM d HH:mm"; // Nov 9 20:06 /** * this is the regular expression used by this parser. * - * Permissions: - * r the file is readable - * w the file is writable - * x the file is executable - * - the indicated permission is not granted - * L mandatory locking occurs during access (the set-group-ID bit is - * on and the group execution bit is off) - * s the set-user-ID or set-group-ID bit is on, and the corresponding - * user or group execution bit is also on - * S undefined bit-state (the set-user-ID bit is on and the user - * execution bit is off) - * t the 1000 (octal) bit, or sticky bit, is on [see chmod(1)], and - * execution is on - * T the 1000 bit is turned on, and execution is off (undefined bit- - * state) - * e z/OS external link bit + * Permissions: r the file is readable w the file is writable x the file is executable - the indicated permission is not granted L mandatory locking occurs + * during access (the set-group-ID bit is on and the group execution bit is off) s the set-user-ID or set-group-ID bit is on, and the corresponding user or + * group execution bit is also on S undefined bit-state (the set-user-ID bit is on and the user execution bit is off) t the 1000 (octal) bit, or sticky bit, + * is on [see chmod(1)], and execution is on T the 1000 bit is turned on, and execution is off (undefined bit- state) e z/OS external link bit */ - private static final String REGEX = - "([bcdelfmpSs-])" // type (1) - + "(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?\\s+" // permission - + "(" - + "(folder\\s+)" - + "|" - + "((\\d+)\\s+(\\d+)\\s+)" // resource size & data size - + ")" - + "(\\d+)\\s+" // size - /* - * numeric or standard format date: - * yyyy-mm-dd (expecting hh:mm to follow) - * MMM [d]d - * [d]d MMM - * N.B. use non-space for MMM to allow for languages such as German which use - * diacritics (e.g. umlaut) in some abbreviations. - */ - + "((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S{3}\\s+\\d{1,2})|(?:\\d{1,2}\\s+\\S{3}))\\s+" - /* - year (for non-recent standard format) - yyyy - or time (for numeric or recent standard format) [h]h:mm - */ - + "(\\d+(?::\\d+)?)\\s+" - - + "(\\S*)(\\s*.*)"; // the rest - + private static final String REGEX = "([bcdelfmpSs-])" // type (1) + + "(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?\\s+" // permission + + "(" + "(folder\\s+)" + "|" + "((\\d+)\\s+(\\d+)\\s+)" // resource size & data size + + ")" + "(\\d+)\\s+" // size + /* + * numeric or standard format date: yyyy-mm-dd (expecting hh:mm to follow) MMM [d]d [d]d MMM N.B. use non-space for MMM to allow for languages such + * as German which use diacritics (e.g. umlaut) in some abbreviations. + */ + + "((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S{3}\\s+\\d{1,2})|(?:\\d{1,2}\\s+\\S{3}))\\s+" + /* + * year (for non-recent standard format) - yyyy or time (for numeric or recent standard format) [h]h:mm + */ + + "(\\d+(?::\\d+)?)\\s+" + + + "(\\S*)(\\s*.*)"; // the rest /** * The default constructor for a UnixFTPEntryParser object. * - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen - * under normal conditions. It it is seen, this is a sign that - * <code>REGEX</code> is not a valid regular expression. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. It it is seen, this is a + * sign that <code>REGEX</code> is not a valid regular expression. */ - public MacOsPeterFTPEntryParser() - { + public MacOsPeterFTPEntryParser() { this(null); } /** - * This constructor allows the creation of a UnixFTPEntryParser object with - * something other than the default configuration. + * This constructor allows the creation of a UnixFTPEntryParser object with something other than the default configuration. * - * @param config The {@link FTPClientConfig configuration} object used to - * configure this parser. - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen - * under normal conditions. It it is seen, this is a sign that - * <code>REGEX</code> is not a valid regular expression. + * @param config The {@link FTPClientConfig configuration} object used to configure this parser. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. It it is seen, this is a + * sign that <code>REGEX</code> is not a valid regular expression. * @since 1.4 */ - public MacOsPeterFTPEntryParser(FTPClientConfig config) - { + public MacOsPeterFTPEntryParser(final FTPClientConfig config) { super(REGEX); configure(config); } /** - * Parses a line of a unix (standard) FTP server file listing and converts - * it into a usable format in the form of an <code> FTPFile </code> - * instance. If the file listing line doesn't describe a file, - * <code> null </code> is returned, otherwise a <code> FTPFile </code> - * instance representing the files in the directory is returned. + * Defines a default configuration to be used when this class is instantiated without a {@link FTPClientConfig FTPClientConfig} parameter being specified. + * + * @return the default configuration for this parser. + */ + @Override + protected FTPClientConfig getDefaultConfiguration() { + return new FTPClientConfig(FTPClientConfig.SYST_UNIX, DEFAULT_DATE_FORMAT, DEFAULT_RECENT_DATE_FORMAT); + } + + /** + * Parses a line of a unix (standard) FTP server file listing and converts it into a usable format in the form of an <code> FTPFile </code> instance. If the + * file listing line doesn't describe a file, <code> null </code> is returned, otherwise a <code> FTPFile </code> instance representing the files in the + * directory is returned. * * @param entry A line of text from the file listing * @return An FTPFile instance corresponding to the supplied entry */ @Override - public FTPFile parseFTPEntry(String entry) { - FTPFile file = new FTPFile(); + public FTPFile parseFTPEntry(final String entry) { + final FTPFile file = new FTPFile(); file.setRawListing(entry); - int type; + final int type; boolean isDevice = false; - if (matches(entry)) - { - String typeStr = group(1); - String hardLinkCount = "0"; - String usr = null; - String grp = null; - String filesize = group(20); - String datestr = group(21) + " " + group(22); + if (matches(entry)) { + final String typeStr = group(1); + final String hardLinkCount = "0"; + final String filesize = group(20); + final String datestr = group(21) + " " + group(22); String name = group(23); - String endtoken = group(24); + final String endtoken = group(24); - try - { + try { file.setTimestamp(super.parseTimestamp(datestr)); - } - catch (ParseException e) - { - // intentionally do nothing + } catch (final ParseException e) { + // intentionally do nothing } // A 'whiteout' file is an ARTIFICIAL entry in any of several types of // 'translucent' filesystems, of which a 'union' filesystem is one. // bcdelfmpSs- - switch (typeStr.charAt(0)) - { + switch (typeStr.charAt(0)) { case 'd': type = FTPFile.DIRECTORY_TYPE; break; @@ -183,76 +150,50 @@ public class MacOsPeterFTPEntryParser extends ConfigurableFTPFileEntryParserImpl file.setType(type); int g = 4; - for (int access = 0; access < 3; access++, g += 4) - { + for (int access = 0; access < 3; access++, g += 4) { // Use != '-' to avoid having to check for suid and sticky bits - file.setPermission(access, FTPFile.READ_PERMISSION, - (!group(g).equals("-"))); - file.setPermission(access, FTPFile.WRITE_PERMISSION, - (!group(g + 1).equals("-"))); + file.setPermission(access, FTPFile.READ_PERMISSION, (!group(g).equals("-"))); + file.setPermission(access, FTPFile.WRITE_PERMISSION, (!group(g + 1).equals("-"))); - String execPerm = group(g + 2); - if (!execPerm.equals("-") && !Character.isUpperCase(execPerm.charAt(0))) - { - file.setPermission(access, FTPFile.EXECUTE_PERMISSION, true); - } - else - { - file.setPermission(access, FTPFile.EXECUTE_PERMISSION, false); - } + final String execPerm = group(g + 2); + file.setPermission(access, FTPFile.EXECUTE_PERMISSION, !execPerm.equals("-") && !Character.isUpperCase(execPerm.charAt(0))); } - if (!isDevice) - { - try - { + if (!isDevice) { + try { file.setHardLinkCount(Integer.parseInt(hardLinkCount)); - } - catch (NumberFormatException e) - { + } catch (final NumberFormatException e) { // intentionally do nothing } } - file.setUser(usr); - file.setGroup(grp); + file.setUser(null); + file.setGroup(null); - try - { + try { file.setSize(Long.parseLong(filesize)); - } - catch (NumberFormatException e) - { + } catch (final NumberFormatException e) { // intentionally do nothing } - if (null == endtoken) - { + if (null == endtoken) { file.setName(name); - } - else - { + } else { // oddball cases like symbolic links, file names // with spaces in them. name += endtoken; - if (type == FTPFile.SYMBOLIC_LINK_TYPE) - { + if (type == FTPFile.SYMBOLIC_LINK_TYPE) { - int end = name.indexOf(" -> "); + final int end = name.indexOf(" -> "); // Give up if no link indicator is present - if (end == -1) - { + if (end == -1) { file.setName(name); - } - else - { + } else { file.setName(name.substring(0, end)); file.setLink(name.substring(end + 4)); } - } - else - { + } else { file.setName(name); } } @@ -261,18 +202,4 @@ public class MacOsPeterFTPEntryParser extends ConfigurableFTPFileEntryParserImpl return null; } - /** - * Defines a default configuration to be used when this class is - * instantiated without a {@link FTPClientConfig FTPClientConfig} - * parameter being specified. - * @return the default configuration for this parser. - */ - @Override - protected FTPClientConfig getDefaultConfiguration() { - return new FTPClientConfig( - FTPClientConfig.SYST_UNIX, - DEFAULT_DATE_FORMAT, - DEFAULT_RECENT_DATE_FORMAT); - } - } diff --git a/src/main/java/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java b/src/main/java/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java index 1c78aad..a7de045 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/NTFTPEntryParser.java @@ -16,6 +16,7 @@ */ package org.apache.commons.net.ftp.parser; + import java.text.ParseException; import java.util.regex.Pattern; @@ -26,142 +27,106 @@ import org.apache.commons.net.ftp.FTPFile; /** * Implementation of FTPFileEntryParser and FTPFileListParser for NT Systems. * - * @version $Id: NTFTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $ * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) */ -public class NTFTPEntryParser extends ConfigurableFTPFileEntryParserImpl -{ - - private static final String DEFAULT_DATE_FORMAT - = "MM-dd-yy hh:mma"; //11-09-01 12:30PM +public class NTFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { - private static final String DEFAULT_DATE_FORMAT2 - = "MM-dd-yy kk:mm"; //11-09-01 18:30 + private static final String DEFAULT_DATE_FORMAT = "MM-dd-yy hh:mma"; // 11-09-01 12:30PM - private final FTPTimestampParser timestampParser; + private static final String DEFAULT_DATE_FORMAT2 = "MM-dd-yy kk:mm"; // 11-09-01 18:30 /** * this is the regular expression used by this parser. */ - private static final String REGEX = - "(\\S+)\\s+(\\S+)\\s+" // MM-dd-yy whitespace hh:mma|kk:mm; swallow trailing spaces - + "(?:(<DIR>)|([0-9]+))\\s+" // <DIR> or ddddd; swallow trailing spaces - + "(\\S.*)"; // First non-space followed by rest of line (name) + private static final String REGEX = "(\\S+)\\s+(\\S+)\\s+" // MM-dd-yy whitespace hh:mma|kk:mm; swallow trailing spaces + + "(?:(<DIR>)|([0-9]+))\\s+" // <DIR> or ddddd; swallow trailing spaces + + "(\\S.*)"; // First non-space followed by rest of line (name) + + private final FTPTimestampParser timestampParser; /** * The sole constructor for an NTFTPEntryParser object. * - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen - * under normal conditions. It it is seen, this is a sign that - * <code>REGEX</code> is not a valid regular expression. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. It it is seen, this is a + * sign that <code>REGEX</code> is not a valid regular expression. */ - public NTFTPEntryParser() - { + public NTFTPEntryParser() { this(null); } /** - * This constructor allows the creation of an NTFTPEntryParser object - * with something other than the default configuration. + * This constructor allows the creation of an NTFTPEntryParser object with something other than the default configuration. * - * @param config The {@link FTPClientConfig configuration} object used to - * configure this parser. - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen - * under normal conditions. It it is seen, this is a sign that - * <code>REGEX</code> is not a valid regular expression. + * @param config The {@link FTPClientConfig configuration} object used to configure this parser. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. It it is seen, this is a + * sign that <code>REGEX</code> is not a valid regular expression. * @since 1.4 */ - public NTFTPEntryParser(FTPClientConfig config) - { + public NTFTPEntryParser(final FTPClientConfig config) { super(REGEX, Pattern.DOTALL); configure(config); - FTPClientConfig config2 = new FTPClientConfig( - FTPClientConfig.SYST_NT, - DEFAULT_DATE_FORMAT2, - null); + final FTPClientConfig config2 = new FTPClientConfig(FTPClientConfig.SYST_NT, DEFAULT_DATE_FORMAT2, null); config2.setDefaultDateFormatStr(DEFAULT_DATE_FORMAT2); this.timestampParser = new FTPTimestampParserImpl(); - ((Configurable)this.timestampParser).configure(config2); + ((Configurable) this.timestampParser).configure(config2); + } + + /** + * Defines a default configuration to be used when this class is instantiated without a {@link FTPClientConfig FTPClientConfig} parameter being specified. + * + * @return the default configuration for this parser. + */ + @Override + public FTPClientConfig getDefaultConfiguration() { + return new FTPClientConfig(FTPClientConfig.SYST_NT, DEFAULT_DATE_FORMAT, null); } /** - * Parses a line of an NT FTP server file listing and converts it into a - * usable format in the form of an <code> FTPFile </code> instance. If the - * file listing line doesn't describe a file, <code> null </code> is - * returned, otherwise a <code> FTPFile </code> instance representing the - * files in the directory is returned. + * Parses a line of an NT FTP server file listing and converts it into a usable format in the form of an <code> FTPFile </code> instance. If the file + * listing line doesn't describe a file, <code> null </code> is returned, otherwise a <code> FTPFile </code> instance representing the files in the + * directory is returned. * * @param entry A line of text from the file listing * @return An FTPFile instance corresponding to the supplied entry */ @Override - public FTPFile parseFTPEntry(String entry) - { - FTPFile f = new FTPFile(); + public FTPFile parseFTPEntry(final String entry) { + final FTPFile f = new FTPFile(); f.setRawListing(entry); - if (matches(entry)) - { - String datestr = group(1)+" "+group(2); - String dirString = group(3); - String size = group(4); - String name = group(5); - try - { + if (matches(entry)) { + final String datestr = group(1) + " " + group(2); + final String dirString = group(3); + final String size = group(4); + final String name = group(5); + try { f.setTimestamp(super.parseTimestamp(datestr)); - } - catch (ParseException e) - { + } catch (final ParseException e) { // parsing fails, try the other date format - try - { + try { f.setTimestamp(timestampParser.parseTimestamp(datestr)); - } - catch (ParseException e2) - { + } catch (final ParseException e2) { // intentionally do nothing } } - if (null == name || name.equals(".") || name.equals("..")) - { - return (null); + if (null == name || name.equals(".") || name.equals("..")) { + return null; } f.setName(name); - - if ("<DIR>".equals(dirString)) - { + if ("<DIR>".equals(dirString)) { f.setType(FTPFile.DIRECTORY_TYPE); f.setSize(0); - } - else - { + } else { f.setType(FTPFile.FILE_TYPE); - if (null != size) - { - f.setSize(Long.parseLong(size)); + if (null != size) { + f.setSize(Long.parseLong(size)); } } - return (f); + return f; } return null; } - /** - * Defines a default configuration to be used when this class is - * instantiated without a {@link FTPClientConfig FTPClientConfig} - * parameter being specified. - * @return the default configuration for this parser. - */ - @Override - public FTPClientConfig getDefaultConfiguration() { - return new FTPClientConfig( - FTPClientConfig.SYST_NT, - DEFAULT_DATE_FORMAT, - null); - } - } diff --git a/src/main/java/org/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java b/src/main/java/org/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java index 2d1b340..9d0c90d 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/NetwareFTPEntryParser.java @@ -23,14 +23,11 @@ import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPFile; /** - * Implementation of FTPFileEntryParser and FTPFileListParser for Netware Systems. Note that some of the proprietary - * extensions for Novell-specific operations are not supported. See - * <a href="http://www.novell.com/documentation/nw65/index.html?page=/documentation/nw65/ftp_enu/data/fbhbgcfa.html"> - * http://www.novell.com/documentation/nw65/index.html?page=/documentation/nw65/ftp_enu/data/fbhbgcfa.html</a> - * for more details. + * Implementation of FTPFileEntryParser and FTPFileListParser for Netware Systems. Note that some of the proprietary extensions for Novell-specific operations + * are not supported. See <a href="http://www.novell.com/documentation/nw65/index.html?page=/documentation/nw65/ftp_enu/data/fbhbgcfa.html"> + * http://www.novell.com/documentation/nw65/index.html?page=/documentation/nw65/ftp_enu/data/fbhbgcfa.html</a> for more details. * * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) - * @version $Id: NetwareFTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $ * @since 1.5 */ public class NetwareFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { @@ -46,52 +43,53 @@ public class NetwareFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { private static final String DEFAULT_RECENT_DATE_FORMAT = "MMM dd HH:mm"; /** - * this is the regular expression used by this parser. - * Example: d [-W---F--] SCION_VOL2 512 Apr 13 23:12 VOL2 + * this is the regular expression used by this parser. Example: d [-W---F--] SCION_VOL2 512 Apr 13 23:12 VOL2 */ - private static final String REGEX = "(d|-){1}\\s+" // Directory/file flag - + "\\[([-A-Z]+)\\]\\s+" // Attributes RWCEAFMS or - - + "(\\S+)\\s+" + "(\\d+)\\s+" // Owner and size - + "(\\S+\\s+\\S+\\s+((\\d+:\\d+)|(\\d{4})))" // Long/short date format - + "\\s+(.*)"; // Filename (incl. spaces) + private static final String REGEX = "(d|-){1}\\s+" // Directory/file flag + + "\\[([-A-Z]+)\\]\\s+" // Attributes RWCEAFMS or - + + "(\\S+)\\s+" + "(\\d+)\\s+" // Owner and size + + "(\\S+\\s+\\S+\\s+((\\d+:\\d+)|(\\d{4})))" // Long/short date format + + "\\s+(.*)"; // Filename (incl. spaces) /** * The default constructor for a NetwareFTPEntryParser object. * - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen - * under normal conditions. It it is seen, this is a sign that - * <code>REGEX</code> is not a valid regular expression. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. It it is seen, this is a + * sign that <code>REGEX</code> is not a valid regular expression. */ public NetwareFTPEntryParser() { this(null); } /** - * This constructor allows the creation of an NetwareFTPEntryParser object - * with something other than the default configuration. + * This constructor allows the creation of an NetwareFTPEntryParser object with something other than the default configuration. * - * @param config The {@link FTPClientConfig configuration} object used to - * configure this parser. - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen - * under normal conditions. It it is seen, this is a sign that - * <code>REGEX</code> is not a valid regular expression. + * @param config The {@link FTPClientConfig configuration} object used to configure this parser. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. It it is seen, this is a + * sign that <code>REGEX</code> is not a valid regular expression. * @since 1.4 */ - public NetwareFTPEntryParser(FTPClientConfig config) { + public NetwareFTPEntryParser(final FTPClientConfig config) { super(REGEX); configure(config); } /** - * Parses a line of an NetwareFTP server file listing and converts it into a - * usable format in the form of an <code> FTPFile </code> instance. If the - * file listing line doesn't describe a file, <code> null </code> is - * returned, otherwise a <code> FTPFile </code> instance representing the - * files in the directory is returned. + * Defines a default configuration to be used when this class is instantiated without a {@link FTPClientConfig FTPClientConfig} parameter being specified. + * + * @return the default configuration for this parser. + */ + @Override + protected FTPClientConfig getDefaultConfiguration() { + return new FTPClientConfig(FTPClientConfig.SYST_NETWARE, DEFAULT_DATE_FORMAT, DEFAULT_RECENT_DATE_FORMAT); + } + + /** + * Parses a line of an NetwareFTP server file listing and converts it into a usable format in the form of an <code> FTPFile </code> instance. If the file + * listing line doesn't describe a file, <code> null </code> is returned, otherwise a <code> FTPFile </code> instance representing the files in the + * directory is returned. * <p> - * Netware file permissions are in the following format: RWCEAFMS, and are explained as follows: + * Netware file permissions are in the following format: RWCEAFMS, and are explained as follows: * <ul> * <li><b>S</b> - Supervisor; All rights. * <li><b>R</b> - Read; Right to open and read or execute. @@ -103,33 +101,30 @@ public class NetwareFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { * <li><b>A</b> - Access Control; Right to modify trustee assignments and the Inherited Rights Mask. * </ul> * - * See - * <a href="http://www.novell.com/documentation/nfap10/index.html?page=/documentation/nfap10/nfaubook/data/abxraws.html"> - * here</a> - * for more details + * See <a href="http://www.novell.com/documentation/nfap10/index.html?page=/documentation/nfap10/nfaubook/data/abxraws.html"> here</a> for more details * * @param entry A line of text from the file listing * @return An FTPFile instance corresponding to the supplied entry */ @Override - public FTPFile parseFTPEntry(String entry) { + public FTPFile parseFTPEntry(final String entry) { - FTPFile f = new FTPFile(); + final FTPFile f = new FTPFile(); if (matches(entry)) { - String dirString = group(1); - String attrib = group(2); - String user = group(3); - String size = group(4); - String datestr = group(5); - String name = group(9); + final String dirString = group(1); + final String attrib = group(2); + final String user = group(3); + final String size = group(4); + final String datestr = group(5); + final String name = group(9); try { f.setTimestamp(super.parseTimestamp(datestr)); - } catch (ParseException e) { - // intentionally do nothing + } catch (final ParseException e) { + // intentionally do nothing } - //is it a DIR or a file + // is it a DIR or a file if (dirString.trim().equals("d")) { f.setType(FTPFile.DIRECTORY_TYPE); } else // Should be "-" @@ -139,39 +134,25 @@ public class NetwareFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { f.setUser(user); - //set the name + // set the name f.setName(name.trim()); - //set the size + // set the size f.setSize(Long.parseLong(size.trim())); // Now set the permissions (or at least a subset thereof - full permissions would probably require // subclassing FTPFile and adding extra metainformation there) - if (attrib.indexOf("R") != -1) { - f.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, - true); + if (attrib.indexOf('R') != -1) { + f.setPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION, true); } - if (attrib.indexOf("W") != -1) { - f.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, - true); + if (attrib.indexOf('W') != -1) { + f.setPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION, true); } - return (f); + return f; } return null; } - /** - * Defines a default configuration to be used when this class is - * instantiated without a {@link FTPClientConfig FTPClientConfig} - * parameter being specified. - * @return the default configuration for this parser. - */ - @Override - protected FTPClientConfig getDefaultConfiguration() { - return new FTPClientConfig(FTPClientConfig.SYST_NETWARE, - DEFAULT_DATE_FORMAT, DEFAULT_RECENT_DATE_FORMAT); - } - } diff --git a/src/main/java/org/apache/commons/net/ftp/parser/OS2FTPEntryParser.java b/src/main/java/org/apache/commons/net/ftp/parser/OS2FTPEntryParser.java index fbd7828..2b49f09 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/OS2FTPEntryParser.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/OS2FTPEntryParser.java @@ -16,6 +16,7 @@ */ package org.apache.commons.net.ftp.parser; + import java.text.ParseException; import org.apache.commons.net.ftp.FTPClientConfig; @@ -24,123 +25,93 @@ import org.apache.commons.net.ftp.FTPFile; /** * Implementation of FTPFileEntryParser and FTPFileListParser for OS2 Systems. * - * @version $Id: OS2FTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $ * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) */ public class OS2FTPEntryParser extends ConfigurableFTPFileEntryParserImpl { - private static final String DEFAULT_DATE_FORMAT - = "MM-dd-yy HH:mm"; //11-09-01 12:30 + private static final String DEFAULT_DATE_FORMAT = "MM-dd-yy HH:mm"; // 11-09-01 12:30 /** * this is the regular expression used by this parser. */ - private static final String REGEX = - "\\s*([0-9]+)\\s*" - + "(\\s+|[A-Z]+)\\s*" - + "(DIR|\\s+)\\s*" - + "(\\S+)\\s+(\\S+)\\s+" /* date stuff */ - + "(\\S.*)"; + private static final String REGEX = "\\s*([0-9]+)\\s*" + "(\\s+|[A-Z]+)\\s*" + "(DIR|\\s+)\\s*" + "(\\S+)\\s+(\\S+)\\s+" /* date stuff */ + + "(\\S.*)"; /** * The default constructor for a OS2FTPEntryParser object. * - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen - * under normal conditions. It it is seen, this is a sign that - * <code>REGEX</code> is not a valid regular expression. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. It it is seen, this is a + * sign that <code>REGEX</code> is not a valid regular expression. */ - public OS2FTPEntryParser() - { + public OS2FTPEntryParser() { this(null); } /** - * This constructor allows the creation of an OS2FTPEntryParser object - * with something other than the default configuration. + * This constructor allows the creation of an OS2FTPEntryParser object with something other than the default configuration. * - * @param config The {@link FTPClientConfig configuration} object used to - * configure this parser. - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen - * under normal conditions. It it is seen, this is a sign that - * <code>REGEX</code> is not a valid regular expression. + * @param config The {@link FTPClientConfig configuration} object used to configure this parser. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. It it is seen, this is a + * sign that <code>REGEX</code> is not a valid regular expression. * @since 1.4 */ - public OS2FTPEntryParser(FTPClientConfig config) - { + public OS2FTPEntryParser(final FTPClientConfig config) { super(REGEX); configure(config); } /** - * Parses a line of an OS2 FTP server file listing and converts it into a - * usable format in the form of an <code> FTPFile </code> instance. If the - * file listing line doesn't describe a file, <code> null </code> is - * returned, otherwise a <code> FTPFile </code> instance representing the - * files in the directory is returned. + * Defines a default configuration to be used when this class is instantiated without a {@link FTPClientConfig FTPClientConfig} parameter being specified. + * + * @return the default configuration for this parser. + */ + @Override + protected FTPClientConfig getDefaultConfiguration() { + return new FTPClientConfig(FTPClientConfig.SYST_OS2, DEFAULT_DATE_FORMAT, null); + } + + /** + * Parses a line of an OS2 FTP server file listing and converts it into a usable format in the form of an <code> FTPFile </code> instance. If the file + * listing line doesn't describe a file, <code> null </code> is returned, otherwise a <code> FTPFile </code> instance representing the files in the + * directory is returned. * * @param entry A line of text from the file listing * @return An FTPFile instance corresponding to the supplied entry */ @Override - public FTPFile parseFTPEntry(String entry) - { - - FTPFile f = new FTPFile(); - if (matches(entry)) - { - String size = group(1); - String attrib = group(2); - String dirString = group(3); - String datestr = group(4)+" "+group(5); - String name = group(6); - try - { + public FTPFile parseFTPEntry(final String entry) { + + final FTPFile f = new FTPFile(); + if (matches(entry)) { + final String size = group(1); + final String attrib = group(2); + final String dirString = group(3); + final String datestr = group(4) + " " + group(5); + final String name = group(6); + try { f.setTimestamp(super.parseTimestamp(datestr)); - } - catch (ParseException e) - { + } catch (final ParseException e) { // intentionally do nothing } - - //is it a DIR or a file - if (dirString.trim().equals("DIR") || attrib.trim().equals("DIR")) - { + // is it a DIR or a file + if (dirString.trim().equals("DIR") || attrib.trim().equals("DIR")) { f.setType(FTPFile.DIRECTORY_TYPE); - } - else - { + } else { f.setType(FTPFile.FILE_TYPE); } - - //set the name + // set the name f.setName(name.trim()); - //set the size + // set the size f.setSize(Long.parseLong(size.trim())); - return (f); + return f; } return null; } - /** - * Defines a default configuration to be used when this class is - * instantiated without a {@link FTPClientConfig FTPClientConfig} - * parameter being specified. - * @return the default configuration for this parser. - */ - @Override - protected FTPClientConfig getDefaultConfiguration() { - return new FTPClientConfig( - FTPClientConfig.SYST_OS2, - DEFAULT_DATE_FORMAT, - null); - } - } diff --git a/src/main/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParser.java b/src/main/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParser.java index 5248a81..d50c6da 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParser.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParser.java @@ -19,12 +19,12 @@ package org.apache.commons.net.ftp.parser; import java.io.File; import java.text.ParseException; +import java.util.Locale; import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPFile; /** - * @version $Id: OS400FTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $ * <pre> * Example *FILE/*MEM FTP entries, when the current * working directory is a file of file system QSYS: @@ -231,103 +231,93 @@ import org.apache.commons.net.ftp.FTPFile; * * </pre> */ -public class OS400FTPEntryParser extends ConfigurableFTPFileEntryParserImpl -{ - private static final String DEFAULT_DATE_FORMAT - = "yy/MM/dd HH:mm:ss"; //01/11/09 12:30:24 - - - - private static final String REGEX = - "(\\S+)\\s+" // user - + "(?:(\\d+)\\s+)?" // size, empty for members - + "(?:(\\S+)\\s+(\\S+)\\s+)?" // date stuff, empty for members - + "(\\*STMF|\\*DIR|\\*FILE|\\*MEM)\\s+" // *STMF/*DIR/*FILE/*MEM - + "(?:(\\S+)\\s*)?"; // filename, missing, when CWD is a *FILE +public class OS400FTPEntryParser extends ConfigurableFTPFileEntryParserImpl { + private static final String DEFAULT_DATE_FORMAT = "yy/MM/dd HH:mm:ss"; // 01/11/09 12:30:24 + private static final String REGEX = "(\\S+)\\s+" // user + + "(?:(\\d+)\\s+)?" // size, empty for members + + "(?:(\\S+)\\s+(\\S+)\\s+)?" // date stuff, empty for members + + "(\\*STMF|\\*DIR|\\*FILE|\\*MEM)\\s+" // *STMF/*DIR/*FILE/*MEM + + "((\\S+\\s*)+)?"; // file name, missing, when CWD is a *FILE /** * The default constructor for a OS400FTPEntryParser object. * - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen - * under normal conditions. It it is seen, this is a sign that - * <code>REGEX</code> is not a valid regular expression. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. It it is seen, this is a + * sign that <code>REGEX</code> is not a valid regular expression. */ - public OS400FTPEntryParser() - { + public OS400FTPEntryParser() { this(null); } /** - * This constructor allows the creation of an OS400FTPEntryParser object - * with something other than the default configuration. + * This constructor allows the creation of an OS400FTPEntryParser object with something other than the default configuration. * - * @param config The {@link FTPClientConfig configuration} object used to - * configure this parser. - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen - * under normal conditions. It it is seen, this is a sign that - * <code>REGEX</code> is not a valid regular expression. + * @param config The {@link FTPClientConfig configuration} object used to configure this parser. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. It it is seen, this is a + * sign that <code>REGEX</code> is not a valid regular expression. * @since 1.4 */ - public OS400FTPEntryParser(FTPClientConfig config) - { + public OS400FTPEntryParser(final FTPClientConfig config) { super(REGEX); configure(config); } + /** + * Defines a default configuration to be used when this class is instantiated without a {@link FTPClientConfig FTPClientConfig} parameter being specified. + * + * @return the default configuration for this parser. + */ + @Override + protected FTPClientConfig getDefaultConfiguration() { + return new FTPClientConfig(FTPClientConfig.SYST_OS400, DEFAULT_DATE_FORMAT, null); + } + + /** + * + * @param string String value that is checked for <code>null</code> or empty. + * @return <code>true</code> for <code>null</code> or empty values, else <code>false</code>. + */ + private boolean isNullOrEmpty(final String string) { + return string == null || string.isEmpty(); + } @Override - public FTPFile parseFTPEntry(String entry) - { + public FTPFile parseFTPEntry(final String entry) { - FTPFile file = new FTPFile(); + final FTPFile file = new FTPFile(); file.setRawListing(entry); - int type; + final int type; - if (matches(entry)) - { - String usr = group(1); - String filesize = group(2); + if (matches(entry)) { + final String usr = group(1); + final String filesize = group(2); String datestr = ""; - if (!isNullOrEmpty(group(3)) || !isNullOrEmpty(group(4))) - { - datestr = group(3)+" "+group(4); + if (!isNullOrEmpty(group(3)) || !isNullOrEmpty(group(4))) { + datestr = group(3) + " " + group(4); } - String typeStr = group(5); + final String typeStr = group(5); String name = group(6); boolean mustScanForPathSeparator = true; - try - { + try { file.setTimestamp(super.parseTimestamp(datestr)); - } - catch (ParseException e) - { + } catch (final ParseException e) { // intentionally do nothing } - - if (typeStr.equalsIgnoreCase("*STMF")) - { + if (typeStr.equalsIgnoreCase("*STMF")) { type = FTPFile.FILE_TYPE; - if (isNullOrEmpty(filesize) || isNullOrEmpty(name)) - { + if (isNullOrEmpty(filesize) || isNullOrEmpty(name)) { return null; } - } - else if (typeStr.equalsIgnoreCase("*DIR")) - { + } else if (typeStr.equalsIgnoreCase("*DIR")) { type = FTPFile.DIRECTORY_TYPE; - if (isNullOrEmpty(filesize) || isNullOrEmpty(name)) - { + if (isNullOrEmpty(filesize) || isNullOrEmpty(name)) { return null; } - } - else if (typeStr.equalsIgnoreCase("*FILE")) - { + } else if (typeStr.equalsIgnoreCase("*FILE")) { // File, defines the structure of the data (columns of a row) // but the data is stored in one or more members. Typically a // source file contains multiple members whereas it is @@ -335,27 +325,19 @@ public class OS400FTPEntryParser extends ConfigurableFTPFileEntryParserImpl // file. // Save files are a special type of files which are used // to save objects, e.g. for backups. - if (name != null && name.toUpperCase().endsWith(".SAVF")) - { - mustScanForPathSeparator = false; - type = FTPFile.FILE_TYPE; - } - else - { + if ((name == null) || !name.toUpperCase(Locale.ROOT).endsWith(".SAVF")) { return null; } - } - else if (typeStr.equalsIgnoreCase("*MEM")) - { + mustScanForPathSeparator = false; + type = FTPFile.FILE_TYPE; + } else if (typeStr.equalsIgnoreCase("*MEM")) { mustScanForPathSeparator = false; type = FTPFile.FILE_TYPE; - if (isNullOrEmpty(name)) - { + if (isNullOrEmpty(name)) { return null; } - if (!(isNullOrEmpty(filesize) && isNullOrEmpty(datestr))) - { + if (!(isNullOrEmpty(filesize) && isNullOrEmpty(datestr))) { return null; } @@ -365,9 +347,7 @@ public class OS400FTPEntryParser extends ConfigurableFTPFileEntryParserImpl // use the separator of the FTP server, which is a forward // slash in case of an AS/400. name = name.replace('/', File.separatorChar); - } - else - { + } else { type = FTPFile.UNKNOWN_TYPE; } @@ -375,24 +355,18 @@ public class OS400FTPEntryParser extends ConfigurableFTPFileEntryParserImpl file.setUser(usr); - try - { + try { file.setSize(Long.parseLong(filesize)); - } - catch (NumberFormatException e) - { + } catch (final NumberFormatException e) { // intentionally do nothing } - if (name.endsWith("/")) - { + if (name.endsWith("/")) { name = name.substring(0, name.length() - 1); } - if (mustScanForPathSeparator) - { - int pos = name.lastIndexOf('/'); - if (pos > -1) - { + if (mustScanForPathSeparator) { + final int pos = name.lastIndexOf('/'); + if (pos > -1) { name = name.substring(pos + 1); } } @@ -404,32 +378,4 @@ public class OS400FTPEntryParser extends ConfigurableFTPFileEntryParserImpl return null; } - /** - * - * @param string String value that is checked for <code>null</code> - * or empty. - * @return <code>true</code> for <code>null</code> or empty values, - * else <code>false</code>. - */ - private boolean isNullOrEmpty(String string) { - if (string == null || string.length() == 0) { - return true; - } - return false; - } - - /** - * Defines a default configuration to be used when this class is - * instantiated without a {@link FTPClientConfig FTPClientConfig} - * parameter being specified. - * @return the default configuration for this parser. - */ - @Override - protected FTPClientConfig getDefaultConfiguration() { - return new FTPClientConfig( - FTPClientConfig.SYST_OS400, - DEFAULT_DATE_FORMAT, - null); - } - } diff --git a/src/main/java/org/apache/commons/net/ftp/parser/ParserInitializationException.java b/src/main/java/org/apache/commons/net/ftp/parser/ParserInitializationException.java index 080f809..3ae1bdb 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/ParserInitializationException.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/ParserInitializationException.java @@ -18,9 +18,7 @@ package org.apache.commons.net.ftp.parser; /** - * This class encapsulates all errors that may be thrown by - * the process of an FTPFileEntryParserFactory creating and - * instantiating an FTPFileEntryParser. + * This class encapsulates all errors that may be thrown by the process of an FTPFileEntryParserFactory creating and instantiating an FTPFileEntryParser. */ public class ParserInitializationException extends RuntimeException { @@ -31,25 +29,22 @@ public class ParserInitializationException extends RuntimeException { * * @param message Exception message */ - public ParserInitializationException(String message) { + public ParserInitializationException(final String message) { super(message); } /** - * Constucts a ParserInitializationException with a message - * and a root cause. + * Constucts a ParserInitializationException with a message and a root cause. * * @param message Exception message - * @param rootCause root cause throwable that caused - * this to be thrown + * @param rootCause root cause throwable that caused this to be thrown */ - public ParserInitializationException(String message, Throwable rootCause) { + public ParserInitializationException(final String message, final Throwable rootCause) { super(message, rootCause); } /** - * returns the root cause of this exception or null - * if no root cause was specified. + * returns the root cause of this exception or null if no root cause was specified. * * @return the root cause of this exception being thrown * @deprecated use {@link #getCause()} instead diff --git a/src/main/java/org/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java b/src/main/java/org/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java index 50ef365..d33eb95 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/RegexFTPFileEntryParserImpl.java @@ -15,7 +15,6 @@ * limitations under the License. */ - package org.apache.commons.net.ftp.parser; import java.util.regex.MatchResult; @@ -26,86 +25,71 @@ import java.util.regex.PatternSyntaxException; import org.apache.commons.net.ftp.FTPFileEntryParserImpl; /** - * This abstract class implements both the older FTPFileListParser and - * newer FTPFileEntryParser interfaces with default functionality. - * All the classes in the parser subpackage inherit from this. + * This abstract class implements both the older FTPFileListParser and newer FTPFileEntryParser interfaces with default functionality. All the classes in the + * parser subpackage inherit from this. * * This is the base class for all regular expression based FTPFileEntryParser classes */ -public abstract class RegexFTPFileEntryParserImpl extends - FTPFileEntryParserImpl { +public abstract class RegexFTPFileEntryParserImpl extends FTPFileEntryParserImpl { /** - * internal pattern the matcher tries to match, representing a file - * entry + * internal pattern the matcher tries to match, representing a file entry */ - private Pattern pattern = null; + private Pattern pattern; /** * internal match result used by the parser */ - private MatchResult result = null; + private MatchResult result; /** - * Internal PatternMatcher object used by the parser. It has protected - * scope in case subclasses want to make use of it for their own purposes. + * Internal PatternMatcher object used by the parser. It has protected scope in case subclasses want to make use of it for their own purposes. */ - protected Matcher _matcher_ = null; + protected Matcher _matcher_; /** - * The constructor for a RegexFTPFileEntryParserImpl object. - * The expression is compiled with flags = 0. + * The constructor for a RegexFTPFileEntryParserImpl object. The expression is compiled with flags = 0. * - * @param regex The regular expression with which this object is - * initialized. + * @param regex The regular expression with which this object is initialized. * - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen in - * normal conditions. It it is seen, this is a sign that a subclass has - * been created with a bad regular expression. Since the parser must be - * created before use, this means that any bad parser subclasses created - * from this will bomb very quickly, leading to easy detection. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen in normal conditions. It it is seen, this is a sign + * that a subclass has been created with a bad regular expression. Since the parser must be created before use, this means + * that any bad parser subclasses created from this will bomb very quickly, leading to easy detection. */ - public RegexFTPFileEntryParserImpl(String regex) { - super(); + public RegexFTPFileEntryParserImpl(final String regex) { compileRegex(regex, 0); } /** * The constructor for a RegexFTPFileEntryParserImpl object. * - * @param regex The regular expression with which this object is - * initialized. + * @param regex The regular expression with which this object is initialized. * @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none. * - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen in - * normal conditions. It it is seen, this is a sign that a subclass has - * been created with a bad regular expression. Since the parser must be - * created before use, this means that any bad parser subclasses created - * from this will bomb very quickly, leading to easy detection. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen in normal conditions. It it is seen, this is a sign + * that a subclass has been created with a bad regular expression. Since the parser must be created before use, this means + * that any bad parser subclasses created from this will bomb very quickly, leading to easy detection. * @since 3.4 */ - public RegexFTPFileEntryParserImpl(String regex, final int flags) { - super(); + public RegexFTPFileEntryParserImpl(final String regex, final int flags) { compileRegex(regex, flags); } /** - * Convenience method delegates to the internal MatchResult's matches() - * method. + * Compile the regex and store the {@link Pattern}. * - * @param s the String to be matched - * @return true if s matches this object's regular expression. + * This is an internal method to do the work so the constructor does not have to call an overrideable method. + * + * @param regex the expression to compile + * @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none. + * @throws IllegalArgumentException if the regex cannot be compiled */ - - public boolean matches(String s) { - this.result = null; - _matcher_ = pattern.matcher(s); - if (_matcher_.matches()) { - this.result = _matcher_.toMatchResult(); + private void compileRegex(final String regex, final int flags) { + try { + pattern = Pattern.compile(regex, flags); + } catch (final PatternSyntaxException pse) { + throw new IllegalArgumentException("Unparseable regex supplied: " + regex); } - return null != this.result; } /** @@ -122,16 +106,27 @@ public abstract class RegexFTPFileEntryParserImpl extends } /** - * Convenience method delegates to the internal MatchResult's group() - * method. + * For debugging purposes - returns a string shows each match group by number. + * + * @return a string shows each match group by number. + */ + + public String getGroupsAsString() { + final StringBuilder b = new StringBuilder(); + for (int i = 1; i <= this.result.groupCount(); i++) { + b.append(i).append(") ").append(this.result.group(i)).append(System.lineSeparator()); + } + return b.toString(); + } + + /** + * Convenience method delegates to the internal MatchResult's group() method. * * @param matchnum match group number to be retrieved * - * @return the content of the <code>matchnum'th</code> group of the internal - * match or null if this method is called without a match having - * been made. + * @return the content of the <code>matchnum'th</code> group of the internal match or null if this method is called without a match having been made. */ - public String group(int matchnum) { + public String group(final int matchnum) { if (this.result == null) { return null; } @@ -139,26 +134,26 @@ public abstract class RegexFTPFileEntryParserImpl extends } /** - * For debugging purposes - returns a string shows each match group by - * number. + * Convenience method delegates to the internal MatchResult's matches() method. * - * @return a string shows each match group by number. + * @param s the String to be matched + * @return true if s matches this object's regular expression. */ - public String getGroupsAsString() { - StringBuilder b = new StringBuilder(); - for (int i = 1; i <= this.result.groupCount(); i++) { - b.append(i).append(") ").append(this.result.group(i)).append( - System.getProperty("line.separator")); + public boolean matches(final String s) { + this.result = null; + _matcher_ = pattern.matcher(s); + if (_matcher_.matches()) { + this.result = _matcher_.toMatchResult(); } - return b.toString(); + return null != this.result; } /** - * Alter the current regular expression being utilised for entry parsing - * and create a new {@link Pattern} instance. + * Alter the current regular expression being utilised for entry parsing and create a new {@link Pattern} instance. + * * @param regex The new regular expression - * @return true + * @return true * @since 2.0 * @throws IllegalArgumentException if the regex cannot be compiled */ @@ -167,13 +162,12 @@ public abstract class RegexFTPFileEntryParserImpl extends return true; } - /** - * Alter the current regular expression being utilised for entry parsing - * and create a new {@link Pattern} instance. + * Alter the current regular expression being utilised for entry parsing and create a new {@link Pattern} instance. + * * @param regex The new regular expression * @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none. - * @return true + * @return true * @since 3.4 * @throws IllegalArgumentException if the regex cannot be compiled */ @@ -181,22 +175,4 @@ public abstract class RegexFTPFileEntryParserImpl extends compileRegex(regex, flags); return true; } - - /** - * Compile the regex and store the {@link Pattern}. - * - * This is an internal method to do the work so the constructor does not - * have to call an overrideable method. - * - * @param regex the expression to compile - * @param flags the flags to apply, see {@link Pattern#compile(String, int)}. Use 0 for none. - * @throws IllegalArgumentException if the regex cannot be compiled - */ - private void compileRegex(final String regex, final int flags) { - try { - pattern = Pattern.compile(regex, flags); - } catch (PatternSyntaxException pse) { - throw new IllegalArgumentException("Unparseable regex supplied: " + regex); - } - } } diff --git a/src/main/java/org/apache/commons/net/ftp/parser/UnixFTPEntryParser.java b/src/main/java/org/apache/commons/net/ftp/parser/UnixFTPEntryParser.java index f48cf72..185e1f0 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/UnixFTPEntryParser.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/UnixFTPEntryParser.java @@ -16,135 +16,92 @@ */ package org.apache.commons.net.ftp.parser; + import java.text.ParseException; import java.util.List; -import java.util.ListIterator; import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPFile; /** - * Implementation FTPFileEntryParser and FTPFileListParser for standard - * Unix Systems. + * Implementation FTPFileEntryParser and FTPFileListParser for standard Unix Systems. + * + * This class is based on the logic of Daniel Savarese's DefaultFTPListParser, but adapted to use regular expressions and to fit the new FTPFileEntryParser + * interface. * - * This class is based on the logic of Daniel Savarese's - * DefaultFTPListParser, but adapted to use regular expressions and to fit the - * new FTPFileEntryParser interface. - * @version $Id: UnixFTPEntryParser.java 1781925 2017-02-06 16:43:40Z sebb $ * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) */ -public class UnixFTPEntryParser extends ConfigurableFTPFileEntryParserImpl -{ +public class UnixFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { - static final String DEFAULT_DATE_FORMAT - = "MMM d yyyy"; //Nov 9 2001 + static final String DEFAULT_DATE_FORMAT = "MMM d yyyy"; // Nov 9 2001 - static final String DEFAULT_RECENT_DATE_FORMAT - = "MMM d HH:mm"; //Nov 9 20:06 + static final String DEFAULT_RECENT_DATE_FORMAT = "MMM d HH:mm"; // Nov 9 20:06 - static final String NUMERIC_DATE_FORMAT - = "yyyy-MM-dd HH:mm"; //2001-11-09 20:06 + static final String NUMERIC_DATE_FORMAT = "yyyy-MM-dd HH:mm"; // 2001-11-09 20:06 // Suffixes used in Japanese listings after the numeric values private static final String JA_MONTH = "\u6708"; - private static final String JA_DAY = "\u65e5"; - private static final String JA_YEAR = "\u5e74"; + private static final String JA_DAY = "\u65e5"; + private static final String JA_YEAR = "\u5e74"; - private static final String DEFAULT_DATE_FORMAT_JA - = "M'" + JA_MONTH + "' d'" + JA_DAY + "' yyyy'" + JA_YEAR + "'"; //6月 3日 2003年 + private static final String DEFAULT_DATE_FORMAT_JA = "M'" + JA_MONTH + "' d'" + JA_DAY + "' yyyy'" + JA_YEAR + "'"; // 6月 3日 2003年 - private static final String DEFAULT_RECENT_DATE_FORMAT_JA - = "M'" + JA_MONTH + "' d'" + JA_DAY + "' HH:mm"; //8月 17日 20:10 + private static final String DEFAULT_RECENT_DATE_FORMAT_JA = "M'" + JA_MONTH + "' d'" + JA_DAY + "' HH:mm"; // 8月 17日 20:10 /** - * Some Linux distributions are now shipping an FTP server which formats - * file listing dates in an all-numeric format: - * <code>"yyyy-MM-dd HH:mm</code>. - * This is a very welcome development, and hopefully it will soon become - * the standard. However, since it is so new, for now, and possibly - * forever, we merely accomodate it, but do not make it the default. + * Some Linux distributions are now shipping an FTP server which formats file listing dates in an all-numeric format: <code>"yyyy-MM-dd HH:mm</code>. This + * is a very welcome development, and hopefully it will soon become the standard. However, since it is so new, for now, and possibly forever, we merely + * accomodate it, but do not make it the default. * <p> - * For now end users may specify this format only via - * <code>UnixFTPEntryParser(FTPClientConfig)</code>. - * Steve Cohen - 2005-04-17 + * For now end users may specify this format only via <code>UnixFTPEntryParser(FTPClientConfig)</code>. Steve Cohen - 2005-04-17 */ - public static final FTPClientConfig NUMERIC_DATE_CONFIG = - new FTPClientConfig( - FTPClientConfig.SYST_UNIX, - NUMERIC_DATE_FORMAT, - null); + public static final FTPClientConfig NUMERIC_DATE_CONFIG = new FTPClientConfig(FTPClientConfig.SYST_UNIX, NUMERIC_DATE_FORMAT, null); /** * this is the regular expression used by this parser. * - * Permissions: - * r the file is readable - * w the file is writable - * x the file is executable - * - the indicated permission is not granted - * L mandatory locking occurs during access (the set-group-ID bit is - * on and the group execution bit is off) - * s the set-user-ID or set-group-ID bit is on, and the corresponding - * user or group execution bit is also on - * S undefined bit-state (the set-user-ID bit is on and the user - * execution bit is off) - * t the 1000 (octal) bit, or sticky bit, is on [see chmod(1)], and - * execution is on - * T the 1000 bit is turned on, and execution is off (undefined bit- - * state) - * e z/OS external link bit - * Final letter may be appended: - * + file has extended security attributes (e.g. ACL) - * Note: local listings on MacOSX also use '@'; - * this is not allowed for here as does not appear to be shown by FTP servers - * {@code @} file has extended attributes + * Permissions: r the file is readable w the file is writable x the file is executable - the indicated permission is not granted L mandatory locking occurs + * during access (the set-group-ID bit is on and the group execution bit is off) s the set-user-ID or set-group-ID bit is on, and the corresponding user or + * group execution bit is also on S undefined bit-state (the set-user-ID bit is on and the user execution bit is off) t the 1000 (octal) bit, or sticky bit, + * is on [see chmod(1)], and execution is on T the 1000 bit is turned on, and execution is off (undefined bit- state) e z/OS external link bit Final letter + * may be appended: + file has extended security attributes (e.g. ACL) Note: local listings on MacOSX also use '@'; this is not allowed for here as does not + * appear to be shown by FTP servers {@code @} file has extended attributes */ - private static final String REGEX = - "([bcdelfmpSs-])" // file type - +"(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?" // permissions - - + "\\s*" // separator TODO why allow it to be omitted?? - - + "(\\d+)" // link count - - + "\\s+" // separator - - + "(?:(\\S+(?:\\s\\S+)*?)\\s+)?" // owner name (optional spaces) - + "(?:(\\S+(?:\\s\\S+)*)\\s+)?" // group name (optional spaces) - + "(\\d+(?:,\\s*\\d+)?)" // size or n,m - - + "\\s+" // separator - - /* - * numeric or standard format date: - * yyyy-mm-dd (expecting hh:mm to follow) - * MMM [d]d - * [d]d MMM - * N.B. use non-space for MMM to allow for languages such as German which use - * diacritics (e.g. umlaut) in some abbreviations. - * Japanese uses numeric day and month with suffixes to distinguish them - * [d]dXX [d]dZZ - */ - + "("+ - "(?:\\d+[-/]\\d+[-/]\\d+)" + // yyyy-mm-dd - "|(?:\\S{3}\\s+\\d{1,2})" + // MMM [d]d - "|(?:\\d{1,2}\\s+\\S{3})" + // [d]d MMM - "|(?:\\d{1,2}" + JA_MONTH + "\\s+\\d{1,2}" + JA_DAY + ")"+ - ")" + private static final String REGEX = "([bcdelfmpSs-])" // file type + + "(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?" // permissions + + + "\\s*" // separator TODO why allow it to be omitted?? - + "\\s+" // separator + + "(\\d+)" // link count - /* - year (for non-recent standard format) - yyyy - or time (for numeric or recent standard format) [h]h:mm - or Japanese year - yyyyXX - */ - + "((?:\\d+(?::\\d+)?)|(?:\\d{4}" + JA_YEAR + "))" // (20) + + "\\s+" // separator + + + "(?:(\\S+(?:\\s\\S+)*?)\\s+)?" // owner name (optional spaces) + + "(?:(\\S+(?:\\s\\S+)*)\\s+)?" // group name (optional spaces) + + "(\\d+(?:,\\s*\\d+)?)" // size or n,m + + + "\\s+" // separator + + /* + * numeric or standard format date: yyyy-mm-dd (expecting hh:mm to follow) MMM [d]d [d]d MMM N.B. use non-space for MMM to allow for languages such + * as German which use diacritics (e.g. umlaut) in some abbreviations. Japanese uses numeric day and month with suffixes to distinguish them [d]dXX + * [d]dZZ + */ + + "(" + "(?:\\d+[-/]\\d+[-/]\\d+)" + // yyyy-mm-dd + "|(?:\\S{3}\\s+\\d{1,2})" + // MMM [d]d + "|(?:\\d{1,2}\\s+\\S{3})" + // [d]d MMM + "|(?:\\d{1,2}" + JA_MONTH + "\\s+\\d{1,2}" + JA_DAY + ")" + ")" - + "\\s" // separator + + "\\s+" // separator - + "(.*)"; // the rest (21) + /* + * year (for non-recent standard format) - yyyy or time (for numeric or recent standard format) [h]h:mm or Japanese year - yyyyXX + */ + + "((?:\\d+(?::\\d+)?)|(?:\\d{4}" + JA_YEAR + "))" // (20) + + "\\s" // separator + + + "(.*)"; // the rest (21) // if true, leading spaces are trimmed from file names // this was the case for the original implementation @@ -153,120 +110,94 @@ public class UnixFTPEntryParser extends ConfigurableFTPFileEntryParserImpl /** * The default constructor for a UnixFTPEntryParser object. * - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen - * under normal conditions. It it is seen, this is a sign that - * <code>REGEX</code> is not a valid regular expression. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. It it is seen, this is a + * sign that <code>REGEX</code> is not a valid regular expression. */ - public UnixFTPEntryParser() - { + public UnixFTPEntryParser() { this(null); } /** - * This constructor allows the creation of a UnixFTPEntryParser object with - * something other than the default configuration. + * This constructor allows the creation of a UnixFTPEntryParser object with something other than the default configuration. * - * @param config The {@link FTPClientConfig configuration} object used to - * configure this parser. - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen - * under normal conditions. It it is seen, this is a sign that - * <code>REGEX</code> is not a valid regular expression. + * @param config The {@link FTPClientConfig configuration} object used to configure this parser. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. It it is seen, this is a + * sign that <code>REGEX</code> is not a valid regular expression. * @since 1.4 */ - public UnixFTPEntryParser(FTPClientConfig config) - { + public UnixFTPEntryParser(final FTPClientConfig config) { this(config, false); } /** - * This constructor allows the creation of a UnixFTPEntryParser object with - * something other than the default configuration. + * This constructor allows the creation of a UnixFTPEntryParser object with something other than the default configuration. * - * @param config The {@link FTPClientConfig configuration} object used to - * configure this parser. + * @param config The {@link FTPClientConfig configuration} object used to configure this parser. * @param trimLeadingSpaces if {@code true}, trim leading spaces from file names - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen - * under normal conditions. It it is seen, this is a sign that - * <code>REGEX</code> is not a valid regular expression. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. It it is seen, this is a + * sign that <code>REGEX</code> is not a valid regular expression. * @since 3.4 */ - public UnixFTPEntryParser(FTPClientConfig config, boolean trimLeadingSpaces) - { + public UnixFTPEntryParser(final FTPClientConfig config, final boolean trimLeadingSpaces) { super(REGEX); configure(config); this.trimLeadingSpaces = trimLeadingSpaces; } /** - * Preparse the list to discard "total nnn" lines + * Defines a default configuration to be used when this class is instantiated without a {@link FTPClientConfig FTPClientConfig} parameter being specified. + * + * @return the default configuration for this parser. */ @Override - public List<String> preParse(List<String> original) { - ListIterator<String> iter = original.listIterator(); - while (iter.hasNext()) { - String entry = iter.next(); - if (entry.matches("^total \\d+$")) { // NET-389 - iter.remove(); - } - } - return original; + protected FTPClientConfig getDefaultConfiguration() { + return new FTPClientConfig(FTPClientConfig.SYST_UNIX, DEFAULT_DATE_FORMAT, DEFAULT_RECENT_DATE_FORMAT); } /** - * Parses a line of a unix (standard) FTP server file listing and converts - * it into a usable format in the form of an <code> FTPFile </code> - * instance. If the file listing line doesn't describe a file, - * <code> null </code> is returned, otherwise a <code> FTPFile </code> - * instance representing the files in the directory is returned. + * Parses a line of a unix (standard) FTP server file listing and converts it into a usable format in the form of an <code> FTPFile </code> instance. If the + * file listing line doesn't describe a file, <code> null </code> is returned, otherwise a <code> FTPFile </code> instance representing the files in the + * directory is returned. * * @param entry A line of text from the file listing * @return An FTPFile instance corresponding to the supplied entry */ @Override - public FTPFile parseFTPEntry(String entry) { - FTPFile file = new FTPFile(); + public FTPFile parseFTPEntry(final String entry) { + final FTPFile file = new FTPFile(); file.setRawListing(entry); - int type; + final int type; boolean isDevice = false; - if (matches(entry)) - { - String typeStr = group(1); - String hardLinkCount = group(15); - String usr = group(16); - String grp = group(17); - String filesize = group(18); - String datestr = group(19) + " " + group(20); + if (matches(entry)) { + final String typeStr = group(1); + final String hardLinkCount = group(15); + final String usr = group(16); + final String grp = group(17); + final String filesize = group(18); + final String datestr = group(19) + " " + group(20); String name = group(21); if (trimLeadingSpaces) { name = name.replaceFirst("^\\s+", ""); } - try - { + try { if (group(19).contains(JA_MONTH)) { // special processing for Japanese format - FTPTimestampParserImpl jaParser = new FTPTimestampParserImpl(); - jaParser.configure(new FTPClientConfig( - FTPClientConfig.SYST_UNIX, DEFAULT_DATE_FORMAT_JA, DEFAULT_RECENT_DATE_FORMAT_JA)); + final FTPTimestampParserImpl jaParser = new FTPTimestampParserImpl(); + jaParser.configure(new FTPClientConfig(FTPClientConfig.SYST_UNIX, DEFAULT_DATE_FORMAT_JA, DEFAULT_RECENT_DATE_FORMAT_JA)); file.setTimestamp(jaParser.parseTimestamp(datestr)); } else { file.setTimestamp(super.parseTimestamp(datestr)); } - } - catch (ParseException e) - { - // intentionally do nothing + } catch (final ParseException e) { + // intentionally do nothing } // A 'whiteout' file is an ARTIFICIAL entry in any of several types of // 'translucent' filesystems, of which a 'union' filesystem is one. // bcdelfmpSs- - switch (typeStr.charAt(0)) - { + switch (typeStr.charAt(0)) { case 'd': type = FTPFile.DIRECTORY_TYPE; break; @@ -292,33 +223,19 @@ public class UnixFTPEntryParser extends ConfigurableFTPFileEntryParserImpl file.setType(type); int g = 4; - for (int access = 0; access < 3; access++, g += 4) - { + for (int access = 0; access < 3; access++, g += 4) { // Use != '-' to avoid having to check for suid and sticky bits - file.setPermission(access, FTPFile.READ_PERMISSION, - (!group(g).equals("-"))); - file.setPermission(access, FTPFile.WRITE_PERMISSION, - (!group(g + 1).equals("-"))); - - String execPerm = group(g + 2); - if (!execPerm.equals("-") && !Character.isUpperCase(execPerm.charAt(0))) - { - file.setPermission(access, FTPFile.EXECUTE_PERMISSION, true); - } - else - { - file.setPermission(access, FTPFile.EXECUTE_PERMISSION, false); - } + file.setPermission(access, FTPFile.READ_PERMISSION, !group(g).equals("-")); + file.setPermission(access, FTPFile.WRITE_PERMISSION, !group(g + 1).equals("-")); + + final String execPerm = group(g + 2); + file.setPermission(access, FTPFile.EXECUTE_PERMISSION, !execPerm.equals("-") && !Character.isUpperCase(execPerm.charAt(0))); } - if (!isDevice) - { - try - { + if (!isDevice) { + try { file.setHardLinkCount(Integer.parseInt(hardLinkCount)); - } - catch (NumberFormatException e) - { + } catch (final NumberFormatException e) { // intentionally do nothing } } @@ -326,35 +243,26 @@ public class UnixFTPEntryParser extends ConfigurableFTPFileEntryParserImpl file.setUser(usr); file.setGroup(grp); - try - { + try { file.setSize(Long.parseLong(filesize)); - } - catch (NumberFormatException e) - { + } catch (final NumberFormatException e) { // intentionally do nothing } // oddball cases like symbolic links, file names // with spaces in them. - if (type == FTPFile.SYMBOLIC_LINK_TYPE) - { + if (type == FTPFile.SYMBOLIC_LINK_TYPE) { - int end = name.indexOf(" -> "); + final int end = name.indexOf(" -> "); // Give up if no link indicator is present - if (end == -1) - { + if (end == -1) { file.setName(name); - } - else - { + } else { file.setName(name.substring(0, end)); file.setLink(name.substring(end + 4)); } - } - else - { + } else { file.setName(name); } return file; @@ -363,17 +271,13 @@ public class UnixFTPEntryParser extends ConfigurableFTPFileEntryParserImpl } /** - * Defines a default configuration to be used when this class is - * instantiated without a {@link FTPClientConfig FTPClientConfig} - * parameter being specified. - * @return the default configuration for this parser. + * Preparse the list to discard "total nnn" lines */ @Override - protected FTPClientConfig getDefaultConfiguration() { - return new FTPClientConfig( - FTPClientConfig.SYST_UNIX, - DEFAULT_DATE_FORMAT, - DEFAULT_RECENT_DATE_FORMAT); + public List<String> preParse(final List<String> original) { + // NET-389 + original.removeIf(entry -> entry.matches("^total \\d+$")); + return original; } } diff --git a/src/main/java/org/apache/commons/net/ftp/parser/VMSFTPEntryParser.java b/src/main/java/org/apache/commons/net/ftp/parser/VMSFTPEntryParser.java index 73b3840..e7d5601 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/VMSFTPEntryParser.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/VMSFTPEntryParser.java @@ -16,6 +16,7 @@ */ package org.apache.commons.net.ftp.parser; + import java.io.BufferedReader; import java.io.IOException; import java.text.ParseException; @@ -25,169 +26,164 @@ import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPFile; /** - * Implementation FTPFileEntryParser and FTPFileListParser for VMS Systems. - * This is a sample of VMS LIST output + * Implementation FTPFileEntryParser and FTPFileListParser for VMS Systems. This is a sample of VMS LIST output * + * <pre> * "1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", * "1-JUN.LIS;2 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", * "DATA.DIR;1 1/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", - * <P><B> - * Note: VMSFTPEntryParser can only be instantiated through the - * DefaultFTPParserFactory by classname. It will not be chosen - * by the autodetection scheme. - * </B> - * <P> - * - * @version $Id: VMSFTPEntryParser.java 1752660 2016-07-14 13:25:39Z sebb $ + * </pre> + * <p> + * Note: VMSFTPEntryParser can only be instantiated through the DefaultFTPParserFactory by classname. It will not be chosen by the autodetection scheme. + * </p> * * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) * @see org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory */ -public class VMSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl -{ +public class VMSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl { - private static final String DEFAULT_DATE_FORMAT - = "d-MMM-yyyy HH:mm:ss"; //9-NOV-2001 12:30:24 + private static final String DEFAULT_DATE_FORMAT = "d-MMM-yyyy HH:mm:ss"; // 9-NOV-2001 12:30:24 /** * this is the regular expression used by this parser. */ - private static final String REGEX = - "(.*?;[0-9]+)\\s*" //1 file and version - + "(\\d+)/\\d+\\s*" //2 size/allocated - +"(\\S+)\\s+(\\S+)\\s+" //3+4 date and time - + "\\[(([0-9$A-Za-z_]+)|([0-9$A-Za-z_]+),([0-9$a-zA-Z_]+))\\]?\\s*" //5(6,7,8) owner - + "\\([a-zA-Z]*,([a-zA-Z]*),([a-zA-Z]*),([a-zA-Z]*)\\)"; //9,10,11 Permissions (O,G,W) + private static final String REGEX = "(.*?;[0-9]+)\\s*" // 1 file and version + + "(\\d+)(?:/\\d+)?\\s*" // 2 size/allocated + + "(\\S+)\\s+(\\S+)\\s+" // 3+4 date and time + + "\\[(([0-9$A-Za-z_]+)|([0-9$A-Za-z_]+),([0-9$a-zA-Z_]+))\\]?\\s*" // 5(6,7,8) owner + + "\\([a-zA-Z]*,([a-zA-Z]*),([a-zA-Z]*),([a-zA-Z]*)\\)"; // 9,10,11 Permissions (O,G,W) // TODO - perhaps restrict permissions to [RWED]* ? - - /** * Constructor for a VMSFTPEntryParser object. * - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen - * under normal conditions. It it is seen, this is a sign that - * <code>REGEX</code> is not a valid regular expression. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. It it is seen, this is a + * sign that <code>REGEX</code> is not a valid regular expression. */ - public VMSFTPEntryParser() - { + public VMSFTPEntryParser() { this(null); } /** - * This constructor allows the creation of a VMSFTPEntryParser object with - * something other than the default configuration. + * This constructor allows the creation of a VMSFTPEntryParser object with something other than the default configuration. * - * @param config The {@link FTPClientConfig configuration} object used to - * configure this parser. - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen - * under normal conditions. It it is seen, this is a sign that - * <code>REGEX</code> is not a valid regular expression. + * @param config The {@link FTPClientConfig configuration} object used to configure this parser. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. It it is seen, this is a + * sign that <code>REGEX</code> is not a valid regular expression. * @since 1.4 */ - public VMSFTPEntryParser(FTPClientConfig config) - { + public VMSFTPEntryParser(final FTPClientConfig config) { super(REGEX); configure(config); } /** - * Parses a line of a VMS FTP server file listing and converts it into a - * usable format in the form of an <code> FTPFile </code> instance. If the - * file listing line doesn't describe a file, <code> null </code> is - * returned, otherwise a <code> FTPFile </code> instance representing the - * files in the directory is returned. + * Defines a default configuration to be used when this class is instantiated without a {@link FTPClientConfig FTPClientConfig} parameter being specified. + * + * @return the default configuration for this parser. + */ + @Override + protected FTPClientConfig getDefaultConfiguration() { + return new FTPClientConfig(FTPClientConfig.SYST_VMS, DEFAULT_DATE_FORMAT, null); + } + + protected boolean isVersioning() { + return false; + } + + /** + * DO NOT USE + * + * @param listStream the stream + * @return the array of files + * @throws IOException on error + * @deprecated (2.2) No other FTPFileEntryParser implementations have this method. + */ + @Deprecated + public FTPFile[] parseFileList(final java.io.InputStream listStream) throws IOException { + final org.apache.commons.net.ftp.FTPListParseEngine engine = new org.apache.commons.net.ftp.FTPListParseEngine(this); + engine.readServerList(listStream, null); + return engine.getFiles(); + } + + /** + * Parses a line of a VMS FTP server file listing and converts it into a usable format in the form of an <code> FTPFile </code> instance. If the file + * listing line doesn't describe a file, <code> null </code> is returned, otherwise a <code> FTPFile </code> instance representing the files in the + * directory is returned. * * @param entry A line of text from the file listing * @return An FTPFile instance corresponding to the supplied entry */ @Override - public FTPFile parseFTPEntry(String entry) - { - //one block in VMS equals 512 bytes - long longBlock = 512; + public FTPFile parseFTPEntry(final String entry) { + // one block in VMS equals 512 bytes + final long longBlock = 512; - if (matches(entry)) - { - FTPFile f = new FTPFile(); + if (matches(entry)) { + final FTPFile f = new FTPFile(); f.setRawListing(entry); String name = group(1); - String size = group(2); - String datestr = group(3)+" "+group(4); - String owner = group(5); - String permissions[] = new String[3]; - permissions[0]= group(9); - permissions[1]= group(10); - permissions[2]= group(11); - try - { + final String size = group(2); + final String datestr = group(3) + " " + group(4); + final String owner = group(5); + final String permissions[] = new String[3]; + permissions[0] = group(9); + permissions[1] = group(10); + permissions[2] = group(11); + try { f.setTimestamp(super.parseTimestamp(datestr)); - } - catch (ParseException e) - { - // intentionally do nothing + } catch (final ParseException e) { + // intentionally do nothing } - - String grp; - String user; - StringTokenizer t = new StringTokenizer(owner, ","); + final String grp; + final String user; + final StringTokenizer t = new StringTokenizer(owner, ","); switch (t.countTokens()) { - case 1: - grp = null; - user = t.nextToken(); - break; - case 2: - grp = t.nextToken(); - user = t.nextToken(); - break; - default: - grp = null; - user = null; + case 1: + grp = null; + user = t.nextToken(); + break; + case 2: + grp = t.nextToken(); + user = t.nextToken(); + break; + default: + grp = null; + user = null; } - if (name.lastIndexOf(".DIR") != -1) - { + if (name.lastIndexOf(".DIR") != -1) { f.setType(FTPFile.DIRECTORY_TYPE); - } - else - { + } else { f.setType(FTPFile.FILE_TYPE); } - //set FTPFile name - //Check also for versions to be returned or not - if (isVersioning()) - { - f.setName(name); + // set FTPFile name + // Check also for versions to be returned or not + if (!isVersioning()) { + name = name.substring(0, name.lastIndexOf(';')); } - else - { - name = name.substring(0, name.lastIndexOf(";")); - f.setName(name); - } - //size is retreived in blocks and needs to be put in bytes - //for us humans and added to the FTPFile array - long sizeInBytes = Long.parseLong(size) * longBlock; + f.setName(name); + // size is retreived in blocks and needs to be put in bytes + // for us humans and added to the FTPFile array + final long sizeInBytes = Long.parseLong(size) * longBlock; f.setSize(sizeInBytes); f.setGroup(grp); f.setUser(user); - //set group and owner + // set group and owner - //Set file permission. - //VMS has (SYSTEM,OWNER,GROUP,WORLD) users that can contain - //R (read) W (write) E (execute) D (delete) + // Set file permission. + // VMS has (SYSTEM,OWNER,GROUP,WORLD) users that can contain + // R (read) W (write) E (execute) D (delete) - //iterate for OWNER GROUP WORLD permissions - for (int access = 0; access < 3; access++) - { - String permission = permissions[access]; + // iterate for OWNER GROUP WORLD permissions + for (int access = 0; access < 3; access++) { + final String permission = permissions[access]; - f.setPermission(access, FTPFile.READ_PERMISSION, permission.indexOf('R')>=0); - f.setPermission(access, FTPFile.WRITE_PERMISSION, permission.indexOf('W')>=0); - f.setPermission(access, FTPFile.EXECUTE_PERMISSION, permission.indexOf('E')>=0); + f.setPermission(access, FTPFile.READ_PERMISSION, permission.indexOf('R') >= 0); + f.setPermission(access, FTPFile.WRITE_PERMISSION, permission.indexOf('W') >= 0); + f.setPermission(access, FTPFile.EXECUTE_PERMISSION, permission.indexOf('E') >= 0); } return f; @@ -195,81 +191,34 @@ public class VMSFTPEntryParser extends ConfigurableFTPFileEntryParserImpl return null; } + // DEPRECATED METHODS - for API compatibility only - DO NOT USE /** - * Reads the next entry using the supplied BufferedReader object up to - * whatever delemits one entry from the next. This parser cannot use - * the default implementation of simply calling BufferedReader.readLine(), - * because one entry may span multiple lines. + * Reads the next entry using the supplied BufferedReader object up to whatever delimits one entry from the next. This parser cannot use the default + * implementation of simply calling BufferedReader.readLine(), because one entry may span multiple lines. * - * @param reader The BufferedReader object from which entries are to be - * read. + * @param reader The BufferedReader object from which entries are to be read. * * @return A string representing the next ftp entry or null if none found. * @throws IOException thrown on any IO Error reading from the reader. */ @Override - public String readNextEntry(BufferedReader reader) throws IOException - { + public String readNextEntry(final BufferedReader reader) throws IOException { String line = reader.readLine(); - StringBuilder entry = new StringBuilder(); - while (line != null) - { + final StringBuilder entry = new StringBuilder(); + while (line != null) { if (line.startsWith("Directory") || line.startsWith("Total")) { line = reader.readLine(); continue; } entry.append(line); - if (line.trim().endsWith(")")) - { + if (line.trim().endsWith(")")) { break; } line = reader.readLine(); } - return (entry.length() == 0 ? null : entry.toString()); - } - - protected boolean isVersioning() { - return false; - } - - /** - * Defines a default configuration to be used when this class is - * instantiated without a {@link FTPClientConfig FTPClientConfig} - * parameter being specified. - * @return the default configuration for this parser. - */ - @Override - protected FTPClientConfig getDefaultConfiguration() { - return new FTPClientConfig( - FTPClientConfig.SYST_VMS, - DEFAULT_DATE_FORMAT, - null); - } - - // DEPRECATED METHODS - for API compatibility only - DO NOT USE - - /** - * DO NOT USE - * @param listStream the stream - * @return the array of files - * @throws IOException on error - * @deprecated (2.2) No other FTPFileEntryParser implementations have this method. - */ - @Deprecated - public FTPFile[] parseFileList(java.io.InputStream listStream) throws IOException { - org.apache.commons.net.ftp.FTPListParseEngine engine = new org.apache.commons.net.ftp.FTPListParseEngine(this); - engine.readServerList(listStream, null); - return engine.getFiles(); + return entry.length() == 0 ? null : entry.toString(); } } - -/* Emacs configuration - * Local variables: ** - * mode: java ** - * c-basic-offset: 4 ** - * indent-tabs-mode: nil ** - * End: ** - */ diff --git a/src/main/java/org/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java b/src/main/java/org/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java index 554f11e..b92f5fd 100644 --- a/src/main/java/org/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java +++ b/src/main/java/org/apache/commons/net/ftp/parser/VMSVersioningFTPEntryParser.java @@ -28,98 +28,84 @@ import java.util.regex.PatternSyntaxException; import org.apache.commons.net.ftp.FTPClientConfig; /** - * Special implementation VMSFTPEntryParser with versioning turned on. - * This parser removes all duplicates and only leaves the version with the highest - * version number for each filename. - * + * Special implementation VMSFTPEntryParser with versioning turned on. This parser removes all duplicates and only leaves the version with the highest version + * number for each file name. + * <p> * This is a sample of VMS LIST output + * </p> * + * <pre> * "1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", * "1-JUN.LIS;2 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", * "DATA.DIR;1 1/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", - * <P> - * - * @version $Id: VMSVersioningFTPEntryParser.java 1747119 2016-06-07 02:22:24Z ggregory $ + * </pre> * * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions) */ -public class VMSVersioningFTPEntryParser extends VMSFTPEntryParser -{ +public class VMSVersioningFTPEntryParser extends VMSFTPEntryParser { - private final Pattern _preparse_pattern_; - private static final String PRE_PARSE_REGEX = - "(.*?);([0-9]+)\\s*.*"; + private static final String PRE_PARSE_REGEX = "(.*?);([0-9]+)\\s*.*"; + private final Pattern preparsePattern; /** * Constructor for a VMSFTPEntryParser object. * - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen - * under normal conditions. It it is seen, this is a sign that - * <code>REGEX</code> is not a valid regular expression. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. It it is seen, this is a + * sign that <code>REGEX</code> is not a valid regular expression. */ - public VMSVersioningFTPEntryParser() - { + public VMSVersioningFTPEntryParser() { this(null); } /** - * This constructor allows the creation of a VMSVersioningFTPEntryParser - * object with something other than the default configuration. + * This constructor allows the creation of a VMSVersioningFTPEntryParser object with something other than the default configuration. * - * @param config The {@link FTPClientConfig configuration} object used to - * configure this parser. - * @throws IllegalArgumentException - * Thrown if the regular expression is unparseable. Should not be seen - * under normal conditions. It it is seen, this is a sign that - * <code>REGEX</code> is not a valid regular expression. + * @param config The {@link FTPClientConfig configuration} object used to configure this parser. + * @throws IllegalArgumentException Thrown if the regular expression is unparseable. Should not be seen under normal conditions. It it is seen, this is a + * sign that <code>REGEX</code> is not a valid regular expression. * @since 1.4 */ - public VMSVersioningFTPEntryParser(FTPClientConfig config) - { - super(); + public VMSVersioningFTPEntryParser(final FTPClientConfig config) { configure(config); - try - { - //_preparse_matcher_ = new Perl5Matcher(); - _preparse_pattern_ = Pattern.compile(PRE_PARSE_REGEX); - } - catch (PatternSyntaxException pse) - { - throw new IllegalArgumentException ( - "Unparseable regex supplied: " + PRE_PARSE_REGEX); + try { + // _preparse_matcher_ = new Perl5Matcher(); + preparsePattern = Pattern.compile(PRE_PARSE_REGEX); + } catch (final PatternSyntaxException pse) { + throw new IllegalArgumentException("Unparseable regex supplied: " + PRE_PARSE_REGEX); } - } + } + + @Override + protected boolean isVersioning() { + return true; + } /** - * Implement hook provided for those implementers (such as - * VMSVersioningFTPEntryParser, and possibly others) which return - * multiple files with the same name to remove the duplicates .. + * Implement hook provided for those implementers (such as VMSVersioningFTPEntryParser, and possibly others) which return multiple files with the same name + * to remove the duplicates .. * * @param original Original list * * @return Original list purged of duplicates */ @Override - public List<String> preParse(List<String> original) { - HashMap<String, Integer> existingEntries = new HashMap<String, Integer>(); - ListIterator<String> iter = original.listIterator(); + public List<String> preParse(final List<String> original) { + final HashMap<String, Integer> existingEntries = new HashMap<>(); + final ListIterator<String> iter = original.listIterator(); while (iter.hasNext()) { - String entry = iter.next().trim(); + final String entry = iter.next().trim(); MatchResult result = null; - Matcher _preparse_matcher_ = _preparse_pattern_.matcher(entry); + final Matcher _preparse_matcher_ = preparsePattern.matcher(entry); if (_preparse_matcher_.matches()) { result = _preparse_matcher_.toMatchResult(); - String name = result.group(1); - String version = result.group(2); - Integer nv = Integer.valueOf(version); - Integer existing = existingEntries.get(name); - if (null != existing) { - if (nv.intValue() < existing.intValue()) { - iter.remove(); // removes older version from original list. - continue; - } + final String name = result.group(1); + final String version = result.group(2); + final Integer nv = Integer.valueOf(version); + final Integer existing = existingEntries.get(name); + if ((null != existing) && (nv.intValue() < existing.intValue())) { + iter.remove(); // removes older version from original list. + continue; } existingEntries.put(name, nv); } @@ -130,19 +116,17 @@ public class VMSVersioningFTPEntryParser extends VMSFTPEntryParser // we now must remove those with smaller than the largest version number // for each name that were found before the largest while (iter.hasPrevious()) { - String entry = iter.previous().trim(); + final String entry = iter.previous().trim(); MatchResult result = null; - Matcher _preparse_matcher_ = _preparse_pattern_.matcher(entry); + final Matcher _preparse_matcher_ = preparsePattern.matcher(entry); if (_preparse_matcher_.matches()) { result = _preparse_matcher_.toMatchResult(); - String name = result.group(1); - String version = result.group(2); - Integer nv = Integer.valueOf(version); - Integer existing = existingEntries.get(name); - if (null != existing) { - if (nv.intValue() < existing.intValue()) { - iter.remove(); // removes older version from original list. - } + final String name = result.group(1); + final String version = result.group(2); + final int nv = Integer.parseInt(version); + final Integer existing = existingEntries.get(name); + if ((null != existing) && (nv < existing.intValue())) { + iter.remove(); // removes older version from original list. } } @@ -150,18 +134,4 @@ public class VMSVersioningFTPEntryParser extends VMSFTPEntryParser return original; } - - @Override - protected boolean isVersioning() { - return true; - } - } - -/* Emacs configuration - * Local variables: ** - * mode: java ** - * c-basic-offset: 4 ** - * indent-tabs-mode: nil ** - * End: ** - */ diff --git a/src/main/java/org/apache/commons/net/imap/AuthenticatingIMAPClient.java b/src/main/java/org/apache/commons/net/imap/AuthenticatingIMAPClient.java index 30f5cee..20d5420 100644 --- a/src/main/java/org/apache/commons/net/imap/AuthenticatingIMAPClient.java +++ b/src/main/java/org/apache/commons/net/imap/AuthenticatingIMAPClient.java @@ -25,244 +25,216 @@ import java.security.spec.InvalidKeySpecException; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.SSLContext; + import org.apache.commons.net.util.Base64; /** * An IMAP Client class with authentication support. + * * @see IMAPSClient */ -public class AuthenticatingIMAPClient extends IMAPSClient -{ +public class AuthenticatingIMAPClient extends IMAPSClient { /** - * Constructor for AuthenticatingIMAPClient that delegates to IMAPSClient. - * Sets security mode to explicit (isImplicit = false). + * The enumeration of currently-supported authentication methods. */ - public AuthenticatingIMAPClient() - { + public enum AUTH_METHOD { + /** The standarised (RFC4616) PLAIN method, which sends the password unencrypted (insecure). */ + PLAIN("PLAIN"), + /** The standarised (RFC2195) CRAM-MD5 method, which doesn't send the password (secure). */ + CRAM_MD5("CRAM-MD5"), + /** The unstandarised Microsoft LOGIN method, which sends the password unencrypted (insecure). */ + LOGIN("LOGIN"), + /** XOAUTH */ + XOAUTH("XOAUTH"), + /** XOAUTH 2 */ + XOAUTH2("XOAUTH2"); + + private final String authName; + + AUTH_METHOD(final String name) { + this.authName = name; + } + + /** + * Gets the name of the given authentication method suitable for the server. + * + * @return The name of the given authentication method suitable for the server. + */ + public final String getAuthName() { + return authName; + } + } + + /** + * Constructor for AuthenticatingIMAPClient that delegates to IMAPSClient. Sets security mode to explicit (isImplicit = false). + */ + public AuthenticatingIMAPClient() { this(DEFAULT_PROTOCOL, false); } /** * Constructor for AuthenticatingIMAPClient that delegates to IMAPSClient. + * * @param implicit The security mode (Implicit/Explicit). */ - public AuthenticatingIMAPClient(boolean implicit) - { + public AuthenticatingIMAPClient(final boolean implicit) { this(DEFAULT_PROTOCOL, implicit); } /** * Constructor for AuthenticatingIMAPClient that delegates to IMAPSClient. - * @param proto the protocol. + * + * @param implicit The security mode(Implicit/Explicit). + * @param ctx A pre-configured SSL Context. */ - public AuthenticatingIMAPClient(String proto) - { - this(proto, false); + public AuthenticatingIMAPClient(final boolean implicit, final SSLContext ctx) { + this(DEFAULT_PROTOCOL, implicit, ctx); } /** * Constructor for AuthenticatingIMAPClient that delegates to IMAPSClient. - * @param proto the protocol. - * @param implicit The security mode(Implicit/Explicit). + * + * @param context A pre-configured SSL Context. */ - public AuthenticatingIMAPClient(String proto, boolean implicit) - { - this(proto, implicit, null); + public AuthenticatingIMAPClient(final SSLContext context) { + this(false, context); } /** * Constructor for AuthenticatingIMAPClient that delegates to IMAPSClient. + * * @param proto the protocol. - * @param implicit The security mode(Implicit/Explicit). - * @param ctx the context */ - public AuthenticatingIMAPClient(String proto, boolean implicit, SSLContext ctx) - { - super(proto, implicit, ctx); + public AuthenticatingIMAPClient(final String proto) { + this(proto, false); } /** * Constructor for AuthenticatingIMAPClient that delegates to IMAPSClient. + * + * @param proto the protocol. * @param implicit The security mode(Implicit/Explicit). - * @param ctx A pre-configured SSL Context. */ - public AuthenticatingIMAPClient(boolean implicit, SSLContext ctx) - { - this(DEFAULT_PROTOCOL, implicit, ctx); + public AuthenticatingIMAPClient(final String proto, final boolean implicit) { + this(proto, implicit, null); } /** * Constructor for AuthenticatingIMAPClient that delegates to IMAPSClient. - * @param context A pre-configured SSL Context. - */ - public AuthenticatingIMAPClient(SSLContext context) - { - this(false, context); - } - - /** - * Authenticate to the IMAP server by sending the AUTHENTICATE command with the - * selected mechanism, using the given username and the given password. * - * @param method the method name - * @param username user - * @param password password - * @return True if successfully completed, false if not. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @throws NoSuchAlgorithmException If the CRAM hash algorithm - * cannot be instantiated by the Java runtime system. - * @throws InvalidKeyException If the CRAM hash algorithm - * failed to use the given password. - * @throws InvalidKeySpecException If the CRAM hash algorithm - * failed to use the given password. + * @param proto the protocol. + * @param implicit The security mode(Implicit/Explicit). + * @param ctx the context */ - public boolean authenticate(AuthenticatingIMAPClient.AUTH_METHOD method, - String username, String password) - throws IOException, NoSuchAlgorithmException, - InvalidKeyException, InvalidKeySpecException - { - return auth(method, username, password); + public AuthenticatingIMAPClient(final String proto, final boolean implicit, final SSLContext ctx) { + super(proto, implicit, ctx); } /** - * Authenticate to the IMAP server by sending the AUTHENTICATE command with the - * selected mechanism, using the given username and the given password. + * Authenticate to the IMAP server by sending the AUTHENTICATE command with the selected mechanism, using the given username and the given password. * - * @param method the method name + * @param method the method name * @param username user * @param password password * @return True if successfully completed, false if not. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @throws NoSuchAlgorithmException If the CRAM hash algorithm - * cannot be instantiated by the Java runtime system. - * @throws InvalidKeyException If the CRAM hash algorithm - * failed to use the given password. - * @throws InvalidKeySpecException If the CRAM hash algorithm - * failed to use the given password. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @throws NoSuchAlgorithmException If the CRAM hash algorithm cannot be instantiated by the Java runtime system. + * @throws InvalidKeyException If the CRAM hash algorithm failed to use the given password. + * @throws InvalidKeySpecException If the CRAM hash algorithm failed to use the given password. */ - public boolean auth(AuthenticatingIMAPClient.AUTH_METHOD method, - String username, String password) - throws IOException, NoSuchAlgorithmException, - InvalidKeyException, InvalidKeySpecException - { - if (!IMAPReply.isContinuation(sendCommand(IMAPCommand.AUTHENTICATE, method.getAuthName()))) - { + public boolean auth(final AuthenticatingIMAPClient.AUTH_METHOD method, final String username, final String password) + throws IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException { + if (!IMAPReply.isContinuation(sendCommand(IMAPCommand.AUTHENTICATE, method.getAuthName()))) { return false; } switch (method) { - case PLAIN: - { - // the server sends an empty response ("+ "), so we don't have to read it. - int result = sendData( - Base64.encodeBase64StringUnChunked(("\000" + username + "\000" + password) - .getBytes(getCharset()))); - if (result == IMAPReply.OK) - { - setState(IMAP.IMAPState.AUTH_STATE); - } - return result == IMAPReply.OK; + case PLAIN: { + // the server sends an empty response ("+ "), so we don't have to read it. + final int result = sendData(Base64.encodeBase64StringUnChunked(("\000" + username + "\000" + password).getBytes(getCharset()))); + if (result == IMAPReply.OK) { + setState(IMAP.IMAPState.AUTH_STATE); + } + return result == IMAPReply.OK; + } + case CRAM_MD5: { + // get the CRAM challenge (after "+ ") + final byte[] serverChallenge = Base64.decodeBase64(getReplyString().substring(2).trim()); + // get the Mac instance + final Mac hmac_md5 = Mac.getInstance("HmacMD5"); + hmac_md5.init(new SecretKeySpec(password.getBytes(getCharset()), "HmacMD5")); + // compute the result: + final byte[] hmacResult = convertToHexString(hmac_md5.doFinal(serverChallenge)).getBytes(getCharset()); + // join the byte arrays to form the reply + final byte[] usernameBytes = username.getBytes(getCharset()); + final byte[] toEncode = new byte[usernameBytes.length + 1 /* the space */ + hmacResult.length]; + System.arraycopy(usernameBytes, 0, toEncode, 0, usernameBytes.length); + toEncode[usernameBytes.length] = ' '; + System.arraycopy(hmacResult, 0, toEncode, usernameBytes.length + 1, hmacResult.length); + // send the reply and read the server code: + final int result = sendData(Base64.encodeBase64StringUnChunked(toEncode)); + if (result == IMAPReply.OK) { + setState(IMAP.IMAPState.AUTH_STATE); } - case CRAM_MD5: - { - // get the CRAM challenge (after "+ ") - byte[] serverChallenge = Base64.decodeBase64(getReplyString().substring(2).trim()); - // get the Mac instance - Mac hmac_md5 = Mac.getInstance("HmacMD5"); - hmac_md5.init(new SecretKeySpec(password.getBytes(getCharset()), "HmacMD5")); - // compute the result: - byte[] hmacResult = _convertToHexString(hmac_md5.doFinal(serverChallenge)).getBytes(getCharset()); - // join the byte arrays to form the reply - byte[] usernameBytes = username.getBytes(getCharset()); - byte[] toEncode = new byte[usernameBytes.length + 1 /* the space */ + hmacResult.length]; - System.arraycopy(usernameBytes, 0, toEncode, 0, usernameBytes.length); - toEncode[usernameBytes.length] = ' '; - System.arraycopy(hmacResult, 0, toEncode, usernameBytes.length + 1, hmacResult.length); - // send the reply and read the server code: - int result = sendData(Base64.encodeBase64StringUnChunked(toEncode)); - if (result == IMAPReply.OK) - { - setState(IMAP.IMAPState.AUTH_STATE); - } - return result == IMAPReply.OK; + return result == IMAPReply.OK; + } + case LOGIN: { + // the server sends fixed responses (base64("Username") and + // base64("Password")), so we don't have to read them. + if (sendData(Base64.encodeBase64StringUnChunked(username.getBytes(getCharset()))) != IMAPReply.CONT) { + return false; } - case LOGIN: - { - // the server sends fixed responses (base64("Username") and - // base64("Password")), so we don't have to read them. - if (sendData(Base64.encodeBase64StringUnChunked(username.getBytes(getCharset()))) != IMAPReply.CONT) - { - return false; - } - int result = sendData(Base64.encodeBase64StringUnChunked(password.getBytes(getCharset()))); - if (result == IMAPReply.OK) - { - setState(IMAP.IMAPState.AUTH_STATE); - } - return result == IMAPReply.OK; + final int result = sendData(Base64.encodeBase64StringUnChunked(password.getBytes(getCharset()))); + if (result == IMAPReply.OK) { + setState(IMAP.IMAPState.AUTH_STATE); } - case XOAUTH: - { - int result = sendData(username); - if (result == IMAPReply.OK) - { - setState(IMAP.IMAPState.AUTH_STATE); - } - return result == IMAPReply.OK; + return result == IMAPReply.OK; + } + case XOAUTH: + case XOAUTH2: { + final int result = sendData(username); + if (result == IMAPReply.OK) { + setState(IMAP.IMAPState.AUTH_STATE); } + return result == IMAPReply.OK; + } } return false; // safety check } /** - * Converts the given byte array to a String containing the hex values of the bytes. - * For example, the byte 'A' will be converted to '41', because this is the ASCII code - * (and the byte value) of the capital letter 'A'. + * Authenticate to the IMAP server by sending the AUTHENTICATE command with the selected mechanism, using the given username and the given password. + * + * @param method the method name + * @param username user + * @param password password + * @return True if successfully completed, false if not. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @throws NoSuchAlgorithmException If the CRAM hash algorithm cannot be instantiated by the Java runtime system. + * @throws InvalidKeyException If the CRAM hash algorithm failed to use the given password. + * @throws InvalidKeySpecException If the CRAM hash algorithm failed to use the given password. + */ + public boolean authenticate(final AuthenticatingIMAPClient.AUTH_METHOD method, final String username, final String password) + throws IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException { + return auth(method, username, password); + } + + /** + * Converts the given byte array to a String containing the hex values of the bytes. For example, the byte 'A' will be converted to '41', because this is + * the ASCII code (and the byte value) of the capital letter 'A'. + * * @param a The byte array to convert. * @return The resulting String of hex codes. */ - private String _convertToHexString(byte[] a) - { - StringBuilder result = new StringBuilder(a.length*2); - for (byte element : a) - { - if ( (element & 0x0FF) <= 15 ) { + private String convertToHexString(final byte[] a) { + final StringBuilder result = new StringBuilder(a.length * 2); + for (final byte element : a) { + if ((element & 0x0FF) <= 15) { result.append("0"); } result.append(Integer.toHexString(element & 0x0FF)); } return result.toString(); } - - /** - * The enumeration of currently-supported authentication methods. - */ - public static enum AUTH_METHOD - { - /** The standarised (RFC4616) PLAIN method, which sends the password unencrypted (insecure). */ - PLAIN("PLAIN"), - /** The standarised (RFC2195) CRAM-MD5 method, which doesn't send the password (secure). */ - CRAM_MD5("CRAM-MD5"), - /** The unstandarised Microsoft LOGIN method, which sends the password unencrypted (insecure). */ - LOGIN("LOGIN"), - /** XOAUTH */ - XOAUTH("XOAUTH"); - - private final String authName; - - private AUTH_METHOD(String name){ - this.authName=name; - } - /** - * Gets the name of the given authentication method suitable for the server. - * @return The name of the given authentication method suitable for the server. - */ - public final String getAuthName() - { - return authName; - } - } } /* kate: indent-width 4; replace-tabs on; */ diff --git a/src/main/java/org/apache/commons/net/imap/IMAP.java b/src/main/java/org/apache/commons/net/imap/IMAP.java index 5469116..13471b5 100644 --- a/src/main/java/org/apache/commons/net/imap/IMAP.java +++ b/src/main/java/org/apache/commons/net/imap/IMAP.java @@ -20,158 +20,257 @@ package org.apache.commons.net.imap; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.EOFException; -import java.io.InputStreamReader; import java.io.IOException; +import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.List; import org.apache.commons.net.SocketClient; import org.apache.commons.net.io.CRLFLineReader; - +import org.apache.commons.net.util.NetConstants; /** - * The IMAP class provides the basic the functionality necessary to implement your - * own IMAP client. + * The IMAP class provides the basic the functionality necessary to implement your own IMAP client. */ -public class IMAP extends SocketClient -{ - /** The default IMAP port (RFC 3501). */ - public static final int DEFAULT_PORT = 143; - - public enum IMAPState - { - /** A constant representing the state where the client is not yet connected to a server. */ - DISCONNECTED_STATE, - /** A constant representing the "not authenticated" state. */ - NOT_AUTH_STATE, - /** A constant representing the "authenticated" state. */ - AUTH_STATE, - /** A constant representing the "logout" state. */ - LOGOUT_STATE; - } - - // RFC 3501, section 5.1.3. It should be "modified UTF-7". - /** - * The default control socket ecoding. - */ - protected static final String __DEFAULT_ENCODING = "ISO-8859-1"; - - private IMAPState __state; - protected BufferedWriter __writer; - - protected BufferedReader _reader; - private int _replyCode; - private final List<String> _replyLines; - +public class IMAP extends SocketClient { /** - * Implement this interface and register it via {@link #setChunkListener(IMAPChunkListener)} - * in order to get access to multi-line partial command responses. + * Implement this interface and register it via {@link #setChunkListener(IMAPChunkListener)} in order to get access to multi-line partial command responses. * Useful when processing large FETCH responses. */ public interface IMAPChunkListener { /** * Called when a multi-line partial response has been received. - * @param imap the instance, get the response - * by calling {@link #getReplyString()} or {@link #getReplyStrings()} + * + * @param imap the instance, get the response by calling {@link #getReplyString()} or {@link #getReplyStrings()} * @return {@code true} if the reply buffer is to be cleared on return */ boolean chunkReceived(IMAP imap); } + public enum IMAPState { + /** A constant representing the state where the client is not yet connected to a server. */ + DISCONNECTED_STATE, + /** A constant representing the "not authenticated" state. */ + NOT_AUTH_STATE, + /** A constant representing the "authenticated" state. */ + AUTH_STATE, + /** A constant representing the "logout" state. */ + LOGOUT_STATE + } + + /** The default IMAP port (RFC 3501). */ + public static final int DEFAULT_PORT = 143; + + // RFC 3501, section 5.1.3. It should be "modified UTF-7". + /** + * The default control socket encoding. + */ + protected static final String __DEFAULT_ENCODING = "ISO-8859-1"; /** * <p> - * Implementation of IMAPChunkListener that returns {@code true} - * but otherwise does nothing. + * Implementation of IMAPChunkListener that returns {@code true} but otherwise does nothing. * </p> * <p> - * This is intended for use with a suitable ProtocolCommandListener. - * If the IMAP response contains multiple-line data, the protocol listener - * will be called for each multi-line chunk. - * The accumulated reply data will be cleared after calling the listener. - * If the response is very long, this can significantly reduce memory requirements. - * The listener will also start receiving response data earlier, as it does not have - * to wait for the entire response to be read. + * This is intended for use with a suitable ProtocolCommandListener. If the IMAP response contains multiple-line data, the protocol listener will be called + * for each multi-line chunk. The accumulated reply data will be cleared after calling the listener. If the response is very long, this can significantly + * reduce memory requirements. The listener will also start receiving response data earlier, as it does not have to wait for the entire response to be read. * </p> * <p> - * The ProtocolCommandListener must be prepared to accept partial responses. - * This should not be a problem for listeners that just log the input. + * The ProtocolCommandListener must be prepared to accept partial responses. This should not be a problem for listeners that just log the input. * </p> + * * @see #setChunkListener(IMAPChunkListener) * @since 3.4 */ - public static final IMAPChunkListener TRUE_CHUNK_LISTENER = new IMAPChunkListener(){ - @Override - public boolean chunkReceived(IMAP imap) { - return true; + public static final IMAPChunkListener TRUE_CHUNK_LISTENER = imap -> true; + + /** + * Quote an input string if necessary. If the string is enclosed in double-quotes it is assumed to be quoted already and is returned unchanged. If it is the + * empty string, "" is returned. If it contains a space then it is enclosed in double quotes, escaping the characters backslash and double-quote. + * + * @param input the value to be quoted, may be null + * @return the quoted value + */ + static String quoteMailboxName(final String input) { + if (input == null) { // Don't throw NPE here + return null; + } + if (input.isEmpty()) { + return "\"\""; // return the string "" + } + // Length check is necessary to ensure a lone double-quote is quoted + if (input.length() > 1 && input.startsWith("\"") && input.endsWith("\"")) { + return input; // Assume already quoted + } + if (input.contains(" ")) { + // quoted strings must escape \ and " + return "\"" + input.replaceAll("([\\\\\"])", "\\\\$1") + "\""; } + return input; - }; - private volatile IMAPChunkListener __chunkListener; + } - private final char[] _initialID = { 'A', 'A', 'A', 'A' }; + private IMAPState state; + protected BufferedWriter __writer; + + protected BufferedReader _reader; + + private int replyCode; + private final List<String> replyLines; + + private volatile IMAPChunkListener chunkListener; + + private final char[] initialID = { 'A', 'A', 'A', 'A' }; /** - * The default IMAPClient constructor. Initializes the state - * to <code>DISCONNECTED_STATE</code>. + * The default IMAPClient constructor. Initializes the state to <code>DISCONNECTED_STATE</code>. */ - public IMAP() - { + public IMAP() { setDefaultPort(DEFAULT_PORT); - __state = IMAPState.DISCONNECTED_STATE; + state = IMAPState.DISCONNECTED_STATE; _reader = null; __writer = null; - _replyLines = new ArrayList<String>(); + replyLines = new ArrayList<>(); createCommandSupport(); } + /** + * Performs connection initialization and sets state to {@link IMAPState#NOT_AUTH_STATE}. + */ + @Override + protected void _connectAction_() throws IOException { + super._connectAction_(); + _reader = new CRLFLineReader(new InputStreamReader(_input_, __DEFAULT_ENCODING)); + __writer = new BufferedWriter(new OutputStreamWriter(_output_, __DEFAULT_ENCODING)); + final int tmo = getSoTimeout(); + if (tmo <= 0) { // none set currently + setSoTimeout(connectTimeout); // use connect timeout to ensure we don't block forever + } + getReply(false); // untagged response + if (tmo <= 0) { + setSoTimeout(tmo); // restore the original value + } + setState(IMAPState.NOT_AUTH_STATE); + } + + /** + * Disconnects the client from the server, and sets the state to <code> DISCONNECTED_STATE </code>. The reply text information from the last issued command + * is voided to allow garbage collection of the memory used to store that information. + * + * @throws IOException If there is an error in disconnecting. + */ + @Override + public void disconnect() throws IOException { + super.disconnect(); + _reader = null; + __writer = null; + replyLines.clear(); + setState(IMAPState.DISCONNECTED_STATE); + } + + /** + * Sends a command to the server and return whether successful. + * + * @param command The IMAP command to send (one of the IMAPCommand constants). + * @return {@code true} if the command was successful + * @throws IOException on error + */ + public boolean doCommand(final IMAPCommand command) throws IOException { + return IMAPReply.isSuccess(sendCommand(command)); + } + + /** + * Sends a command and arguments to the server and return whether successful. + * + * @param command The IMAP command to send (one of the IMAPCommand constants). + * @param args The command arguments. + * @return {@code true} if the command was successful + * @throws IOException on error + */ + public boolean doCommand(final IMAPCommand command, final String args) throws IOException { + return IMAPReply.isSuccess(sendCommand(command, args)); + } + + /** + * Overrides {@link SocketClient#fireReplyReceived(int, String)} so as to avoid creating the reply string if there are no listeners to invoke. + * + * @param replyCode passed to the listeners + * @param ignored the string is only created if there are listeners defined. + * @see #getReplyString() + * @since 3.4 + */ + @Override + protected void fireReplyReceived(final int replyCode, final String ignored) { + if (getCommandSupport().getListenerCount() > 0) { + getCommandSupport().fireReplyReceived(replyCode, getReplyString()); + } + } + + /** + * Generates a new command ID (tag) for a command. + * + * @return a new command ID (tag) for an IMAP command. + */ + protected String generateCommandID() { + final String res = new String(initialID); + // "increase" the ID for the next call + boolean carry = true; // want to increment initially + for (int i = initialID.length - 1; carry && i >= 0; i--) { + if (initialID[i] == 'Z') { + initialID[i] = 'A'; + } else { + initialID[i]++; + carry = false; // did not wrap round + } + } + return res; + } + /** * Get the reply for a command that expects a tagged response. * * @throws IOException */ - private void __getReply() throws IOException - { - __getReply(true); // tagged response + private void getReply() throws IOException { + getReply(true); // tagged response } /** - * Get the reply for a command, reading the response until the - * reply is found. + * Get the reply for a command, reading the response until the reply is found. * * @param wantTag {@code true} if the command expects a tagged response. * @throws IOException */ - private void __getReply(boolean wantTag) throws IOException - { - _replyLines.clear(); + private void getReply(final boolean wantTag) throws IOException { + replyLines.clear(); String line = _reader.readLine(); if (line == null) { throw new EOFException("Connection closed without indication."); } - _replyLines.add(line); + replyLines.add(line); if (wantTag) { - while(IMAPReply.isUntagged(line)) { + while (IMAPReply.isUntagged(line)) { int literalCount = IMAPReply.literalCount(line); final boolean isMultiLine = literalCount >= 0; while (literalCount >= 0) { - line=_reader.readLine(); + line = _reader.readLine(); if (line == null) { throw new EOFException("Connection closed without indication."); } - _replyLines.add(line); - literalCount -= (line.length() + 2); // Allow for CRLF + replyLines.add(line); + literalCount -= line.length() + 2; // Allow for CRLF } if (isMultiLine) { - IMAPChunkListener il = __chunkListener; + final IMAPChunkListener il = chunkListener; if (il != null) { - boolean clear = il.chunkReceived(this); + final boolean clear = il.chunkReceived(this); if (clear) { fireReplyReceived(IMAPReply.PARTIAL, getReplyString()); - _replyLines.clear(); + replyLines.clear(); } } } @@ -179,293 +278,159 @@ public class IMAP extends SocketClient if (line == null) { throw new EOFException("Connection closed without indication."); } - _replyLines.add(line); + replyLines.add(line); } // check the response code on the last line - _replyCode = IMAPReply.getReplyCode(line); + replyCode = IMAPReply.getReplyCode(line); } else { - _replyCode = IMAPReply.getUntaggedReplyCode(line); + replyCode = IMAPReply.getUntaggedReplyCode(line); } - fireReplyReceived(_replyCode, getReplyString()); + fireReplyReceived(replyCode, getReplyString()); } /** - * Overrides {@link SocketClient#fireReplyReceived(int, String)} so as to - * avoid creating the reply string if there are no listeners to invoke. + * Returns the reply to the last command sent to the server. The value is a single string containing all the reply lines including newlines. * - * @param replyCode passed to the listeners - * @param ignored the string is only created if there are listeners defined. - * @see #getReplyString() - * @since 3.4 + * @return The last server response. */ - @Override - protected void fireReplyReceived(int replyCode, String ignored) { - if (getCommandSupport().getListenerCount() > 0) { - getCommandSupport().fireReplyReceived(replyCode, getReplyString()); + public String getReplyString() { + final StringBuilder buffer = new StringBuilder(256); + for (final String s : replyLines) { + buffer.append(s); + buffer.append(SocketClient.NETASCII_EOL); } - } - /** - * Performs connection initialization and sets state to - * {@link IMAPState#NOT_AUTH_STATE}. - */ - @Override - protected void _connectAction_() throws IOException - { - super._connectAction_(); - _reader = - new CRLFLineReader(new InputStreamReader(_input_, - __DEFAULT_ENCODING)); - __writer = - new BufferedWriter(new OutputStreamWriter(_output_, - __DEFAULT_ENCODING)); - int tmo = getSoTimeout(); - if (tmo <= 0) { // none set currently - setSoTimeout(connectTimeout); // use connect timeout to ensure we don't block forever - } - __getReply(false); // untagged response - if (tmo <= 0) { - setSoTimeout(tmo); // restore the original value - } - setState(IMAPState.NOT_AUTH_STATE); + return buffer.toString(); } /** - * Sets IMAP client state. This must be one of the - * <code>_STATE</code> constants. + * Returns an array of lines received as a reply to the last command sent to the server. The lines have end of lines truncated. * - * @param state The new state. + * @return The last server response. */ - protected void setState(IMAP.IMAPState state) - { - __state = state; + public String[] getReplyStrings() { + return replyLines.toArray(NetConstants.EMPTY_STRING_ARRAY); } - /** * Returns the current IMAP client state. * * @return The current IMAP client state. */ - public IMAP.IMAPState getState() - { - return __state; - } - - /** - * Disconnects the client from the server, and sets the state to - * <code> DISCONNECTED_STATE </code>. The reply text information - * from the last issued command is voided to allow garbage collection - * of the memory used to store that information. - * - * @throws IOException If there is an error in disconnecting. - */ - @Override - public void disconnect() throws IOException - { - super.disconnect(); - _reader = null; - __writer = null; - _replyLines.clear(); - setState(IMAPState.DISCONNECTED_STATE); - } - - - /** - * Sends a command an arguments to the server and returns the reply code. - * - * @param commandID The ID (tag) of the command. - * @param command The IMAP command to send. - * @param args The command arguments. - * @return The server reply code (either IMAPReply.OK, IMAPReply.NO or IMAPReply.BAD). - */ - private int sendCommandWithID(String commandID, String command, String args) throws IOException - { - StringBuilder __commandBuffer = new StringBuilder(); - if (commandID != null) - { - __commandBuffer.append(commandID); - __commandBuffer.append(' '); - } - __commandBuffer.append(command); - - if (args != null) - { - __commandBuffer.append(' '); - __commandBuffer.append(args); - } - __commandBuffer.append(SocketClient.NETASCII_EOL); - - String message = __commandBuffer.toString(); - __writer.write(message); - __writer.flush(); - - fireCommandSent(command, message); - - __getReply(); - return _replyCode; - } - - /** - * Sends a command an arguments to the server and returns the reply code. - * - * @param command The IMAP command to send. - * @param args The command arguments. - * @return The server reply code (see IMAPReply). - * @throws IOException on error - */ - public int sendCommand(String command, String args) throws IOException - { - return sendCommandWithID(generateCommandID(), command, args); + public IMAP.IMAPState getState() { + return state; } /** - * Sends a command with no arguments to the server and returns the - * reply code. + * Sends a command with no arguments to the server and returns the reply code. * - * @param command The IMAP command to send. - * @return The server reply code (see IMAPReply). + * @param command The IMAP command to send (one of the IMAPCommand constants). + * @return The server reply code (see IMAPReply). * @throws IOException on error - */ - public int sendCommand(String command) throws IOException - { + **/ + public int sendCommand(final IMAPCommand command) throws IOException { return sendCommand(command, null); } /** * Sends a command and arguments to the server and returns the reply code. * - * @param command The IMAP command to send - * (one of the IMAPCommand constants). - * @param args The command arguments. - * @return The server reply code (see IMAPReply). + * @param command The IMAP command to send (one of the IMAPCommand constants). + * @param args The command arguments. + * @return The server reply code (see IMAPReply). * @throws IOException on error */ - public int sendCommand(IMAPCommand command, String args) throws IOException - { + public int sendCommand(final IMAPCommand command, final String args) throws IOException { return sendCommand(command.getIMAPCommand(), args); } /** - * Sends a command and arguments to the server and return whether successful. + * Sends a command with no arguments to the server and returns the reply code. * - * @param command The IMAP command to send - * (one of the IMAPCommand constants). - * @param args The command arguments. - * @return {@code true} if the command was successful + * @param command The IMAP command to send. + * @return The server reply code (see IMAPReply). * @throws IOException on error */ - public boolean doCommand(IMAPCommand command, String args) throws IOException - { - return IMAPReply.isSuccess(sendCommand(command, args)); - } - - /** - * Sends a command with no arguments to the server and returns the - * reply code. - * - * @param command The IMAP command to send - * (one of the IMAPCommand constants). - * @return The server reply code (see IMAPReply). - * @throws IOException on error - **/ - public int sendCommand(IMAPCommand command) throws IOException - { + public int sendCommand(final String command) throws IOException { return sendCommand(command, null); } /** - * Sends a command to the server and return whether successful. + * Sends a command an arguments to the server and returns the reply code. * - * @param command The IMAP command to send - * (one of the IMAPCommand constants). - * @return {@code true} if the command was successful + * @param command The IMAP command to send. + * @param args The command arguments. + * @return The server reply code (see IMAPReply). * @throws IOException on error */ - public boolean doCommand(IMAPCommand command) throws IOException - { - return IMAPReply.isSuccess(sendCommand(command)); + public int sendCommand(final String command, final String args) throws IOException { + return sendCommandWithID(generateCommandID(), command, args); } /** - * Sends data to the server and returns the reply code. + * Sends a command an arguments to the server and returns the reply code. * - * @param command The IMAP command to send. - * @return The server reply code (see IMAPReply). - * @throws IOException on error + * @param commandID The ID (tag) of the command. + * @param command The IMAP command to send. + * @param args The command arguments. + * @return The server reply code (either IMAPReply.OK, IMAPReply.NO or IMAPReply.BAD). */ - public int sendData(String command) throws IOException - { - return sendCommandWithID(null, command, null); - } + private int sendCommandWithID(final String commandID, final String command, final String args) throws IOException { + final StringBuilder __commandBuffer = new StringBuilder(); + if (commandID != null) { + __commandBuffer.append(commandID); + __commandBuffer.append(' '); + } + __commandBuffer.append(command); - /** - * Returns an array of lines received as a reply to the last command - * sent to the server. The lines have end of lines truncated. - * @return The last server response. - */ - public String[] getReplyStrings() - { - return _replyLines.toArray(new String[_replyLines.size()]); + if (args != null) { + __commandBuffer.append(' '); + __commandBuffer.append(args); + } + __commandBuffer.append(SocketClient.NETASCII_EOL); + + final String message = __commandBuffer.toString(); + __writer.write(message); + __writer.flush(); + + fireCommandSent(command, message); + + getReply(); + return replyCode; } /** - * Returns the reply to the last command sent to the server. - * The value is a single string containing all the reply lines including - * newlines. + * Sends data to the server and returns the reply code. * - * @return The last server response. + * @param command The IMAP command to send. + * @return The server reply code (see IMAPReply). + * @throws IOException on error */ - public String getReplyString() - { - StringBuilder buffer = new StringBuilder(256); - for (String s : _replyLines) - { - buffer.append(s); - buffer.append(SocketClient.NETASCII_EOL); - } - - return buffer.toString(); + public int sendData(final String command) throws IOException { + return sendCommandWithID(null, command, null); } /** - * Sets the current chunk listener. - * If a listener is registered and the implementation returns true, - * then any registered - * {@link org.apache.commons.net.PrintCommandListener PrintCommandListener} - * instances will be invoked with the partial response and a status of + * Sets the current chunk listener. If a listener is registered and the implementation returns true, then any registered + * {@link org.apache.commons.net.PrintCommandListener PrintCommandListener} instances will be invoked with the partial response and a status of * {@link IMAPReply#PARTIAL} to indicate that the final reply code is not yet known. + * * @param listener the class to use, or {@code null} to disable * @see #TRUE_CHUNK_LISTENER * @since 3.4 */ - public void setChunkListener(IMAPChunkListener listener) { - __chunkListener = listener; + public void setChunkListener(final IMAPChunkListener listener) { + chunkListener = listener; } /** - * Generates a new command ID (tag) for a command. - * @return a new command ID (tag) for an IMAP command. + * Sets IMAP client state. This must be one of the <code>_STATE</code> constants. + * + * @param state The new state. */ - protected String generateCommandID() - { - String res = new String (_initialID); - // "increase" the ID for the next call - boolean carry = true; // want to increment initially - for (int i = _initialID.length-1; carry && i>=0; i--) - { - if (_initialID[i] == 'Z') - { - _initialID[i] = 'A'; - } - else - { - _initialID[i]++; - carry = false; // did not wrap round - } - } - return res; + protected void setState(final IMAP.IMAPState state) { + this.state = state; } } /* kate: indent-width 4; replace-tabs on; */ diff --git a/src/main/java/org/apache/commons/net/imap/IMAPClient.java b/src/main/java/org/apache/commons/net/imap/IMAPClient.java index 5ae1487..76cc2d2 100644 --- a/src/main/java/org/apache/commons/net/imap/IMAPClient.java +++ b/src/main/java/org/apache/commons/net/imap/IMAPClient.java @@ -20,593 +20,551 @@ package org.apache.commons.net.imap; import java.io.IOException; /** - * The IMAPClient class provides the basic functionalities found in an - * IMAP client. + * The IMAPClient class provides the basic functionalities found in an IMAP client. */ -public class IMAPClient extends IMAP -{ - - private static final char DQUOTE = '"'; - private static final String DQUOTE_S = "\""; - - // --------- commands available in all states +public class IMAPClient extends IMAP { /** - * Send a CAPABILITY command to the server. - * @return {@code true} if the command was successful,{@code false} if not. - * @throws IOException If a network I/O error occurs + * The message data item names for the FETCH command defined in RFC 3501. */ - public boolean capability() throws IOException - { - return doCommand (IMAPCommand.CAPABILITY); + public enum FETCH_ITEM_NAMES { + /** Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE). */ + ALL, + /** Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE). */ + FAST, + /** Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY). */ + FULL, + /** Non-extensible form of BODYSTRUCTURE or the text of a particular body section. */ + BODY, + /** The [MIME-IMB] body structure of the message. */ + BODYSTRUCTURE, + /** The envelope structure of the message. */ + ENVELOPE, + /** The flags that are set for this message. */ + FLAGS, + /** The internal date of the message. */ + INTERNALDATE, + /** A prefix for RFC-822 item names. */ + RFC822, + /** The unique identifier for the message. */ + UID } /** - * Send a NOOP command to the server. This is useful for keeping - * a connection alive since most IMAP servers will timeout after 10 - * minutes of inactivity. - * @return {@code true} if the command was successful,{@code false} if not. - * @throws IOException If a network I/O error occurs. + * The search criteria defined in RFC 3501. */ - public boolean noop() throws IOException - { - return doCommand (IMAPCommand.NOOP); + public enum SEARCH_CRITERIA { + /** All messages in the mailbox. */ + ALL, + /** Messages with the \Answered flag set. */ + ANSWERED, + /** + * Messages that contain the specified string in the envelope structure's BCC field. + */ + BCC, + /** + * Messages whose internal date (disregarding time and time zone) is earlier than the specified date. + */ + BEFORE, + /** + * Messages that contain the specified string in the body of the message. + */ + BODY, + /** + * Messages that contain the specified string in the envelope structure's CC field. + */ + CC, + /** Messages with the \Deleted flag set. */ + DELETED, + /** Messages with the \Draft flag set. */ + DRAFT, + /** Messages with the \Flagged flag set. */ + FLAGGED, + /** + * Messages that contain the specified string in the envelope structure's FROM field. + */ + FROM, + /** + * Messages that have a header with the specified field-name (as defined in [RFC-2822]) and that contains the specified string in the text of the header + * (what comes after the colon). If the string to search is zero-length, this matches all messages that have a header line with the specified field-name + * regardless of the contents. + */ + HEADER, + /** Messages with the specified keyword flag set. */ + KEYWORD, + /** + * Messages with an [RFC-2822] size larger than the specified number of octets. + */ + LARGER, + /** + * Messages that have the \Recent flag set but not the \Seen flag. This is functionally equivalent to "(RECENT UNSEEN)". + */ + NEW, + /** Messages that do not match the specified search key. */ + NOT, + /** + * Messages that do not have the \Recent flag set. This is functionally equivalent to "NOT RECENT" (as opposed to "NOT NEW"). + */ + OLD, + /** + * Messages whose internal date (disregarding time and time zone) is within the specified date. + */ + ON, + /** Messages that match either search key. */ + OR, + /** Messages that have the \Recent flag set. */ + RECENT, + /** Messages that have the \Seen flag set. */ + SEEN, + /** + * Messages whose [RFC-2822] Date: header (disregarding time and time zone) is earlier than the specified date. + */ + SENTBEFORE, + /** + * Messages whose [RFC-2822] Date: header (disregarding time and time zone) is within the specified date. + */ + SENTON, + /** + * Messages whose [RFC-2822] Date: header (disregarding time and time zone) is within or later than the specified date. + */ + SENTSINCE, + /** + * Messages whose internal date (disregarding time and time zone) is within or later than the specified date. + */ + SINCE, + /** + * Messages with an [RFC-2822] size smaller than the specified number of octets. + */ + SMALLER, + /** + * Messages that contain the specified string in the envelope structure's SUBJECT field. + */ + SUBJECT, + /** + * Messages that contain the specified string in the header or body of the message. + */ + TEXT, + /** + * Messages that contain the specified string in the envelope structure's TO field. + */ + TO, + /** + * Messages with unique identifiers corresponding to the specified unique identifier set. Sequence set ranges are permitted. + */ + UID, + /** Messages that do not have the \Answered flag set. */ + UNANSWERED, + /** Messages that do not have the \Deleted flag set. */ + UNDELETED, + /** Messages that do not have the \Draft flag set. */ + UNDRAFT, + /** Messages that do not have the \Flagged flag set. */ + UNFLAGGED, + /** Messages that do not have the specified keyword flag set. */ + UNKEYWORD, + /** Messages that do not have the \Seen flag set. */ + UNSEEN } + // --------- commands available in all states + /** - * Send a LOGOUT command to the server. To fully disconnect from the server - * you must call disconnect(). - * A logout attempt is valid in any state. If - * the client is in the not authenticated or authenticated state, it enters the - * logout on a successful logout. - * @return {@code true} if the command was successful,{@code false} if not. - * @throws IOException If a network I/O error occurs. + * The status data items defined in RFC 3501. */ - public boolean logout() throws IOException - { - return doCommand (IMAPCommand.LOGOUT); + public enum STATUS_DATA_ITEMS { + /** The number of messages in the mailbox. */ + MESSAGES, + /** The number of messages with the \Recent flag set. */ + RECENT, + /** The next unique identifier value of the mailbox. */ + UIDNEXT, + /** The unique identifier validity value of the mailbox. */ + UIDVALIDITY, + /** The number of messages which do not have the \Seen flag set. */ + UNSEEN } + private static final char DQUOTE = '"'; + + private static final String DQUOTE_S = "\""; + // --------- commands available in the not-authenticated state // STARTTLS skipped - see IMAPSClient. // AUTHENTICATE skipped - see AuthenticatingIMAPClient. /** - * Login to the IMAP server with the given username and password. You - * must first connect to the server with - * {@link org.apache.commons.net.SocketClient#connect connect } - * before attempting to login. A login attempt is only valid if - * the client is in the NOT_AUTH_STATE. - * After logging in, the client enters the AUTH_STATE. + * Send an APPEND command to the server. * - * @param username The account name being logged in to. - * @param password The plain text password of the account. - * @return True if the login attempt was successful, false if not. - * @throws IOException If a network I/O error occurs in the process of - * logging in. + * @param mailboxName The mailbox name. + * @return {@code true} if the command was successful,{@code false} if not. + * @throws IOException If a network I/O error occurs. + * @deprecated (3.4) Does not work; the message body is not optional. Use {@link #append(String, String, String, String)} instead. */ - public boolean login(String username, String password) throws IOException - { - if (getState() != IMAP.IMAPState.NOT_AUTH_STATE) - { - return false; - } - - if (!doCommand(IMAPCommand.LOGIN, username + " " + password)) - { - return false; - } - - setState(IMAP.IMAPState.AUTH_STATE); - - return true; + @Deprecated + public boolean append(final String mailboxName) throws IOException { + return append(mailboxName, null, null); } // --------- commands available in the authenticated state /** - * Send a SELECT command to the server. - * @param mailboxName The mailbox name to select. + * Send an APPEND command to the server. + * + * @param mailboxName The mailbox name. + * @param flags The flag parenthesized list (optional). + * @param datetime The date/time string (optional). * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. + * @deprecated (3.4) Does not work; the message body is not optional. Use {@link #append(String, String, String, String)} instead. */ - public boolean select(String mailboxName) throws IOException - { - return doCommand (IMAPCommand.SELECT, mailboxName); + @Deprecated + public boolean append(final String mailboxName, final String flags, final String datetime) throws IOException { + String args = mailboxName; + if (flags != null) { + args += " " + flags; + } + if (datetime != null) { + if (datetime.charAt(0) == '{') { + args += " " + datetime; + } else { + args += " {" + datetime + "}"; + } + } + return doCommand(IMAPCommand.APPEND, args); } /** - * Send an EXAMINE command to the server. - * @param mailboxName The mailbox name to examine. + * Send an APPEND command to the server. + * + * @param mailboxName The mailbox name. + * @param flags The flag parenthesized list (optional). + * @param datetime The date/time string (optional). + * @param message The message to append. * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. + * @since 3.4 */ - public boolean examine(String mailboxName) throws IOException - { - return doCommand (IMAPCommand.EXAMINE, mailboxName); + public boolean append(final String mailboxName, final String flags, final String datetime, final String message) throws IOException { + final StringBuilder args = new StringBuilder(quoteMailboxName(mailboxName)); + if (flags != null) { + args.append(" ").append(flags); + } + if (datetime != null) { + args.append(" "); + if (datetime.charAt(0) == DQUOTE) { + args.append(datetime); + } else { + args.append(DQUOTE).append(datetime).append(DQUOTE); + } + } + args.append(" "); + // String literal (probably not used much - if at all) + if (message.startsWith(DQUOTE_S) && message.endsWith(DQUOTE_S)) { + args.append(message); + return doCommand(IMAPCommand.APPEND, args.toString()); + } + args.append('{').append(message.getBytes(IMAP.__DEFAULT_ENCODING).length).append('}'); // length of message + final int status = sendCommand(IMAPCommand.APPEND, args.toString()); + return IMAPReply.isContinuation(status) // expecting continuation response + && IMAPReply.isSuccess(sendData(message)); // if so, send the data } /** - * Send a CREATE command to the server. - * @param mailboxName The mailbox name to create. + * Send a CAPABILITY command to the server. + * * @return {@code true} if the command was successful,{@code false} if not. - * @throws IOException If a network I/O error occurs. + * @throws IOException If a network I/O error occurs */ - public boolean create(String mailboxName) throws IOException - { - return doCommand (IMAPCommand.CREATE, mailboxName); + public boolean capability() throws IOException { + return doCommand(IMAPCommand.CAPABILITY); } /** - * Send a DELETE command to the server. - * @param mailboxName The mailbox name to delete. + * Send a CHECK command to the server. + * * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. */ - public boolean delete(String mailboxName) throws IOException - { - return doCommand (IMAPCommand.DELETE, mailboxName); + public boolean check() throws IOException { + return doCommand(IMAPCommand.CHECK); } /** - * Send a RENAME command to the server. - * @param oldMailboxName The existing mailbox name to rename. - * @param newMailboxName The new mailbox name. + * Send a CLOSE command to the server. + * * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. */ - public boolean rename(String oldMailboxName, String newMailboxName) throws IOException - { - return doCommand (IMAPCommand.RENAME, oldMailboxName + " " + newMailboxName); + public boolean close() throws IOException { + return doCommand(IMAPCommand.CLOSE); } /** - * Send a SUBSCRIBE command to the server. - * @param mailboxName The mailbox name to subscribe to. + * Send a COPY command to the server. + * + * @param sequenceSet The sequence set to fetch. + * @param mailboxName The mailbox name. * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. */ - public boolean subscribe(String mailboxName) throws IOException - { - return doCommand (IMAPCommand.SUBSCRIBE, mailboxName); + public boolean copy(final String sequenceSet, final String mailboxName) throws IOException { + return doCommand(IMAPCommand.COPY, sequenceSet + " " + quoteMailboxName(mailboxName)); } /** - * Send a UNSUBSCRIBE command to the server. - * @param mailboxName The mailbox name to unsubscribe from. + * Send a CREATE command to the server. + * + * @param mailboxName The mailbox name to create. * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. */ - public boolean unsubscribe(String mailboxName) throws IOException - { - return doCommand (IMAPCommand.UNSUBSCRIBE, mailboxName); + public boolean create(final String mailboxName) throws IOException { + return doCommand(IMAPCommand.CREATE, quoteMailboxName(mailboxName)); } /** - * Send a LIST command to the server. - * @param refName The reference name. - * @param mailboxName The mailbox name. + * Send a DELETE command to the server. + * + * @param mailboxName The mailbox name to delete. * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. */ - public boolean list(String refName, String mailboxName) throws IOException - { - return doCommand (IMAPCommand.LIST, refName + " " + mailboxName); + public boolean delete(final String mailboxName) throws IOException { + return doCommand(IMAPCommand.DELETE, quoteMailboxName(mailboxName)); } /** - * Send an LSUB command to the server. - * @param refName The reference name. - * @param mailboxName The mailbox name. + * Send an EXAMINE command to the server. + * + * @param mailboxName The mailbox name to examine. * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. */ - public boolean lsub(String refName, String mailboxName) throws IOException - { - return doCommand (IMAPCommand.LSUB, refName + " " + mailboxName); + public boolean examine(final String mailboxName) throws IOException { + return doCommand(IMAPCommand.EXAMINE, quoteMailboxName(mailboxName)); } /** - * Send a STATUS command to the server. - * @param mailboxName The reference name. - * @param itemNames The status data item names. + * Send an EXPUNGE command to the server. + * * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. */ - public boolean status(String mailboxName, String[] itemNames) throws IOException - { - if (itemNames == null || itemNames.length < 1) { - throw new IllegalArgumentException("STATUS command requires at least one data item name"); - } - - StringBuilder sb = new StringBuilder(); - sb.append(mailboxName); - - sb.append(" ("); - for ( int i = 0; i < itemNames.length; i++ ) - { - if (i > 0) { - sb.append(" "); - } - sb.append(itemNames[i]); - } - sb.append(")"); + public boolean expunge() throws IOException { + return doCommand(IMAPCommand.EXPUNGE); + } - return doCommand (IMAPCommand.STATUS, sb.toString()); + /** + * Send a FETCH command to the server. + * + * @param sequenceSet The sequence set to fetch (e.g. 1:4,6,11,100:*) + * @param itemNames The item names for the FETCH command. (e.g. BODY.PEEK[HEADER.FIELDS (SUBJECT)]) If multiple item names are requested, these must be + * enclosed in parentheses, e.g. "(UID FLAGS BODY.PEEK[])" + * @return {@code true} if the command was successful,{@code false} if not. + * @throws IOException If a network I/O error occurs. + * @see #getReplyString() + * @see #getReplyStrings() + */ + public boolean fetch(final String sequenceSet, final String itemNames) throws IOException { + return doCommand(IMAPCommand.FETCH, sequenceSet + " " + itemNames); } /** - * Send an APPEND command to the server. - * @param mailboxName The mailbox name. - * @param flags The flag parenthesized list (optional). - * @param datetime The date/time string (optional). - * @param message The message to append. + * Send a LIST command to the server. Quotes the parameters if necessary. + * + * @param refName The reference name If empty, indicates that the mailbox name is interpreted as by SELECT. + * @param mailboxName The mailbox name. If empty, this is a special request to return the hierarchy delimiter and the root name of the name given in the + * reference * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. - * @since 3.4 */ - public boolean append(String mailboxName, String flags, String datetime, String message) throws IOException - { - StringBuilder args = new StringBuilder(mailboxName); - if (flags != null) { - args.append(" ").append(flags); - } - if (datetime != null) { - args.append(" "); - if (datetime.charAt(0) == DQUOTE) { - args.append(datetime); - } else { - args.append(DQUOTE).append(datetime).append(DQUOTE); - } + public boolean list(final String refName, final String mailboxName) throws IOException { + return doCommand(IMAPCommand.LIST, quoteMailboxName(refName) + " " + quoteMailboxName(mailboxName)); + } + + /** + * Login to the IMAP server with the given username and password. You must first connect to the server with + * {@link org.apache.commons.net.SocketClient#connect connect } before attempting to login. A login attempt is only valid if the client is in the + * NOT_AUTH_STATE. After logging in, the client enters the AUTH_STATE. + * + * @param username The account name being logged in to. + * @param password The plain text password of the account. + * @return True if the login attempt was successful, false if not. + * @throws IOException If a network I/O error occurs in the process of logging in. + */ + public boolean login(final String username, final String password) throws IOException { + if (getState() != IMAP.IMAPState.NOT_AUTH_STATE) { + return false; } - args.append(" "); - // String literal (probably not used much - it at all) - if (message.startsWith(DQUOTE_S) && message.endsWith(DQUOTE_S)) { - args.append(message); - return doCommand (IMAPCommand.APPEND, args.toString()); + + if (!doCommand(IMAPCommand.LOGIN, username + " " + password)) { + return false; } - args.append('{').append(message.length()).append('}'); // length of message - final int status = sendCommand(IMAPCommand.APPEND, args.toString()); - return IMAPReply.isContinuation(status) // expecting continuation response - && IMAPReply.isSuccess(sendData(message)); // if so, send the data + + setState(IMAP.IMAPState.AUTH_STATE); + + return true; } + // --------- commands available in the selected state + /** - * Send an APPEND command to the server. - * @param mailboxName The mailbox name. - * @param flags The flag parenthesized list (optional). - * @param datetime The date/time string (optional). + * Send a LOGOUT command to the server. To fully disconnect from the server you must call disconnect(). A logout attempt is valid in any state. If the + * client is in the not authenticated or authenticated state, it enters the logout on a successful logout. + * * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. - * @deprecated (3.4) Does not work; the message body is not optional. - * Use {@link #append(String, String, String, String)} instead. */ - @Deprecated - public boolean append(String mailboxName, String flags, String datetime) throws IOException - { - String args = mailboxName; - if (flags != null) { - args += " " + flags; - } - if (datetime != null) { - if (datetime.charAt(0) == '{') { - args += " " + datetime; - } else { - args += " {" + datetime + "}"; - } - } - return doCommand (IMAPCommand.APPEND, args); + public boolean logout() throws IOException { + return doCommand(IMAPCommand.LOGOUT); } /** - * Send an APPEND command to the server. + * Send an LSUB command to the server. Quotes the parameters if necessary. + * + * @param refName The reference name. * @param mailboxName The mailbox name. * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. - * @deprecated (3.4) Does not work; the message body is not optional. - * Use {@link #append(String, String, String, String)} instead. */ - @Deprecated - public boolean append(String mailboxName) throws IOException - { - return append(mailboxName, null, null); + public boolean lsub(final String refName, final String mailboxName) throws IOException { + return doCommand(IMAPCommand.LSUB, quoteMailboxName(refName) + " " + quoteMailboxName(mailboxName)); } - // --------- commands available in the selected state - /** - * Send a CHECK command to the server. + * Send a NOOP command to the server. This is useful for keeping a connection alive since most IMAP servers will timeout after 10 minutes of inactivity. + * * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. */ - public boolean check() throws IOException - { - return doCommand (IMAPCommand.CHECK); + public boolean noop() throws IOException { + return doCommand(IMAPCommand.NOOP); } /** - * Send a CLOSE command to the server. + * Send a RENAME command to the server. + * + * @param oldMailboxName The existing mailbox name to rename. + * @param newMailboxName The new mailbox name. * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. */ - public boolean close() throws IOException - { - return doCommand (IMAPCommand.CLOSE); + public boolean rename(final String oldMailboxName, final String newMailboxName) throws IOException { + return doCommand(IMAPCommand.RENAME, quoteMailboxName(oldMailboxName) + " " + quoteMailboxName(newMailboxName)); } /** - * Send an EXPUNGE command to the server. + * Send a SEARCH command to the server. + * + * @param criteria The search criteria. * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. */ - public boolean expunge() throws IOException - { - return doCommand (IMAPCommand.EXPUNGE); + public boolean search(final String criteria) throws IOException { + return search(null, criteria); } /** * Send a SEARCH command to the server. - * @param charset The charset (optional). + * + * @param charset The charset (optional). * @param criteria The search criteria. * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. */ - public boolean search(String charset, String criteria) throws IOException - { + public boolean search(final String charset, final String criteria) throws IOException { String args = ""; if (charset != null) { args += "CHARSET " + charset; } args += criteria; - return doCommand (IMAPCommand.SEARCH, args); + return doCommand(IMAPCommand.SEARCH, args); } /** - * Send a SEARCH command to the server. - * @param criteria The search criteria. + * Send a SELECT command to the server. + * + * @param mailboxName The mailbox name to select. * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. */ - public boolean search(String criteria) throws IOException - { - return search(null, criteria); + public boolean select(final String mailboxName) throws IOException { + return doCommand(IMAPCommand.SELECT, quoteMailboxName(mailboxName)); } /** - * Send a FETCH command to the server. + * Send a STATUS command to the server. * - * @param sequenceSet The sequence set to fetch (e.g. 1:4,6,11,100:*) - * @param itemNames The item names for the FETCH command. (e.g. BODY.PEEK[HEADER.FIELDS (SUBJECT)]) - * If multiple item names are requested, these must be enclosed in parentheses, e.g. "(UID FLAGS BODY.PEEK[])" + * @param mailboxName The reference name. + * @param itemNames The status data item names. * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. - * @see #getReplyString() - * @see #getReplyStrings() */ - public boolean fetch(String sequenceSet, String itemNames) throws IOException - { - return doCommand (IMAPCommand.FETCH, sequenceSet + " " + itemNames); + public boolean status(final String mailboxName, final String[] itemNames) throws IOException { + if (itemNames == null || itemNames.length < 1) { + throw new IllegalArgumentException("STATUS command requires at least one data item name"); + } + + final StringBuilder sb = new StringBuilder(); + sb.append(quoteMailboxName(mailboxName)); + + sb.append(" ("); + for (int i = 0; i < itemNames.length; i++) { + if (i > 0) { + sb.append(" "); + } + sb.append(itemNames[i]); + } + sb.append(")"); + + return doCommand(IMAPCommand.STATUS, sb.toString()); } /** * Send a STORE command to the server. + * * @param sequenceSet The sequence set to update (e.g. 2:5) - * @param itemNames The item name for the STORE command (i.e. [+|-]FLAGS[.SILENT]) - * @param itemValues The item values for the STORE command. (e.g. (\Deleted) ) + * @param itemNames The item name for the STORE command (i.e. [+|-]FLAGS[.SILENT]) + * @param itemValues The item values for the STORE command. (e.g. (\Deleted) ) * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. */ - public boolean store(String sequenceSet, String itemNames, String itemValues) - throws IOException - { - return doCommand (IMAPCommand.STORE, sequenceSet + " " + itemNames + " " + itemValues); + public boolean store(final String sequenceSet, final String itemNames, final String itemValues) throws IOException { + return doCommand(IMAPCommand.STORE, sequenceSet + " " + itemNames + " " + itemValues); } /** - * Send a COPY command to the server. - * @param sequenceSet The sequence set to fetch. - * @param mailboxName The mailbox name. + * Send a SUBSCRIBE command to the server. + * + * @param mailboxName The mailbox name to subscribe to. * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. */ - public boolean copy(String sequenceSet, String mailboxName) throws IOException - { - return doCommand (IMAPCommand.COPY, sequenceSet + " " + mailboxName); + public boolean subscribe(final String mailboxName) throws IOException { + return doCommand(IMAPCommand.SUBSCRIBE, quoteMailboxName(mailboxName)); } /** * Send a UID command to the server. - * @param command The command for UID. + * + * @param command The command for UID. * @param commandArgs The arguments for the command. * @return {@code true} if the command was successful,{@code false} if not. * @throws IOException If a network I/O error occurs. */ - public boolean uid(String command, String commandArgs) throws IOException - { - return doCommand (IMAPCommand.UID, command + " " + commandArgs); + public boolean uid(final String command, final String commandArgs) throws IOException { + return doCommand(IMAPCommand.UID, command + " " + commandArgs); } /** - * The status data items defined in RFC 3501. - */ - public enum STATUS_DATA_ITEMS - { - /** The number of messages in the mailbox. */ - MESSAGES, - /** The number of messages with the \Recent flag set. */ - RECENT, - /** The next unique identifier value of the mailbox. */ - UIDNEXT, - /** The unique identifier validity value of the mailbox. */ - UIDVALIDITY, - /** The number of messages which do not have the \Seen flag set. */ - UNSEEN; - } - - /** - * The search criteria defined in RFC 3501. - */ - public enum SEARCH_CRITERIA - { - /** All messages in the mailbox. */ - ALL, - /** Messages with the \Answered flag set. */ - ANSWERED, - /** - * Messages that contain the specified string in the envelope - * structure's BCC field. - */ - BCC, - /** - * Messages whose internal date (disregarding time and timezone) - * is earlier than the specified date. - */ - BEFORE, - /** - * Messages that contain the specified string in the body of the - * message. - */ - BODY, - /** - * Messages that contain the specified string in the envelope - * structure's CC field. - */ - CC, - /** Messages with the \Deleted flag set. */ - DELETED, - /** Messages with the \Draft flag set. */ - DRAFT, - /** Messages with the \Flagged flag set. */ - FLAGGED, - /** - * Messages that contain the specified string in the envelope - * structure's FROM field. - */ - FROM, - /** - * Messages that have a header with the specified field-name (as - * defined in [RFC-2822]) and that contains the specified string - * in the text of the header (what comes after the colon). If the - * string to search is zero-length, this matches all messages that - * have a header line with the specified field-name regardless of - * the contents. - */ - HEADER, - /** Messages with the specified keyword flag set. */ - KEYWORD, - /** - * Messages with an [RFC-2822] size larger than the specified - * number of octets. - */ - LARGER, - /** - * Messages that have the \Recent flag set but not the \Seen flag. - * This is functionally equivalent to "(RECENT UNSEEN)". - */ - NEW, - /** Messages that do not match the specified search key. */ - NOT, - /** - * Messages that do not have the \Recent flag set. This is - * functionally equivalent to "NOT RECENT" (as opposed to "NOT - * NEW"). - */ - OLD, - /** - * Messages whose internal date (disregarding time and timezone) - * is within the specified date. - */ - ON, - /** Messages that match either search key. */ - OR, - /** Messages that have the \Recent flag set. */ - RECENT, - /** Messages that have the \Seen flag set. */ - SEEN, - /** - * Messages whose [RFC-2822] Date: header (disregarding time and - * timezone) is earlier than the specified date. - */ - SENTBEFORE, - /** - * Messages whose [RFC-2822] Date: header (disregarding time and - * timezone) is within the specified date. - */ - SENTON, - /** - * Messages whose [RFC-2822] Date: header (disregarding time and - * timezone) is within or later than the specified date. - */ - SENTSINCE, - /** - * Messages whose internal date (disregarding time and timezone) - * is within or later than the specified date. - */ - SINCE, - /** - * Messages with an [RFC-2822] size smaller than the specified - * number of octets. - */ - SMALLER, - /** - * Messages that contain the specified string in the envelope - * structure's SUBJECT field. - */ - SUBJECT, - /** - * Messages that contain the specified string in the header or - * body of the message. - */ - TEXT, - /** - * Messages that contain the specified string in the envelope - * structure's TO field. - */ - TO, - /** - * Messages with unique identifiers corresponding to the specified - * unique identifier set. Sequence set ranges are permitted. - */ - UID, - /** Messages that do not have the \Answered flag set. */ - UNANSWERED, - /** Messages that do not have the \Deleted flag set. */ - UNDELETED, - /** Messages that do not have the \Draft flag set. */ - UNDRAFT, - /** Messages that do not have the \Flagged flag set. */ - UNFLAGGED, - /** Messages that do not have the specified keyword flag set. */ - UNKEYWORD, - /** Messages that do not have the \Seen flag set. */ - UNSEEN; - } - - /** - * The message data item names for the FETCH command defined in RFC 3501. + * Send a UNSUBSCRIBE command to the server. + * + * @param mailboxName The mailbox name to unsubscribe from. + * @return {@code true} if the command was successful,{@code false} if not. + * @throws IOException If a network I/O error occurs. */ - public enum FETCH_ITEM_NAMES - { - /** Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE). */ - ALL, - /** Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE). */ - FAST, - /** Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY). */ - FULL, - /** Non-extensible form of BODYSTRUCTURE or the text of a particular body section. */ - BODY, - /** The [MIME-IMB] body structure of the message. */ - BODYSTRUCTURE, - /** The envelope structure of the message. */ - ENVELOPE, - /** The flags that are set for this message. */ - FLAGS, - /** The internal date of the message. */ - INTERNALDATE, - /** A prefix for RFC-822 item names. */ - RFC822, - /** The unique identifier for the message. */ - UID; + public boolean unsubscribe(final String mailboxName) throws IOException { + return doCommand(IMAPCommand.UNSUBSCRIBE, quoteMailboxName(mailboxName)); } } diff --git a/src/main/java/org/apache/commons/net/imap/IMAPCommand.java b/src/main/java/org/apache/commons/net/imap/IMAPCommand.java index 764800e..750ce61 100644 --- a/src/main/java/org/apache/commons/net/imap/IMAPCommand.java +++ b/src/main/java/org/apache/commons/net/imap/IMAPCommand.java @@ -20,91 +20,69 @@ package org.apache.commons.net.imap; /** * IMAPCommand stores IMAP command codes. */ -public enum IMAPCommand -{ +public enum IMAPCommand { // These enums must either use the same name as the IMAP command // or must provide the correct string as the parameter. // Commands valid in any state: - CAPABILITY(0), - NOOP(0), - LOGOUT(0), + CAPABILITY(0), NOOP(0), LOGOUT(0), // Commands valid in Not Authenticated state - STARTTLS(0), - AUTHENTICATE(1), - LOGIN(2), + STARTTLS(0), AUTHENTICATE(1), LOGIN(2), XOAUTH(1), // commands valid in authenticated state - SELECT(1), - EXAMINE(1), - CREATE(1), - DELETE(1), - RENAME(2), - SUBSCRIBE(1), - UNSUBSCRIBE(1), - LIST(2), - LSUB(2), - STATUS(2), // P2 = list in () - APPEND(2,4), // mbox [(flags)] [date-time] literal + SELECT(1), EXAMINE(1), CREATE(1), DELETE(1), RENAME(2), SUBSCRIBE(1), UNSUBSCRIBE(1), LIST(2), LSUB(2), STATUS(2), // P2 = list in () + APPEND(2, 4), // mbox [(flags)] [date-time] literal // commands valid in selected state (substate of authenticated) - CHECK(0), - CLOSE(0), - EXPUNGE(0), - SEARCH(1, Integer.MAX_VALUE), - FETCH(2), - STORE(3), - COPY(2), - UID(2, Integer.MAX_VALUE), - ; + CHECK(0), CLOSE(0), EXPUNGE(0), SEARCH(1, Integer.MAX_VALUE), FETCH(2), STORE(3), COPY(2), UID(2, Integer.MAX_VALUE),; - private final String imapCommand; + /** + * Get the IMAP protocol string command corresponding to a command code. + * + * @param command the IMAPCommand whose command string is required. + * @return The IMAP protocol string command corresponding to a command code. + */ + public static final String getCommand(final IMAPCommand command) { + return command.getIMAPCommand(); + } + private final String imapCommand; @SuppressWarnings("unused") // not yet used private final int minParamCount; + @SuppressWarnings("unused") // not yet used private final int maxParamCount; - IMAPCommand(){ + IMAPCommand() { this(null); } - IMAPCommand(String name){ - this(name, 0); - } - - IMAPCommand(int paramCount){ + IMAPCommand(final int paramCount) { this(null, paramCount, paramCount); - } + } - IMAPCommand(int minCount, int maxCount){ + IMAPCommand(final int minCount, final int maxCount) { this(null, minCount, maxCount); - } + } - IMAPCommand(String name, int paramCount){ + IMAPCommand(final String name) { + this(name, 0); + } + + IMAPCommand(final String name, final int paramCount) { this(name, paramCount, paramCount); } - IMAPCommand(String name, int minCount, int maxCount){ + IMAPCommand(final String name, final int minCount, final int maxCount) { this.imapCommand = name; this.minParamCount = minCount; this.maxParamCount = maxCount; } - /** - * Get the IMAP protocol string command corresponding to a command code. - * - * @param command the IMAPCommand whose command string is required. - * @return The IMAP protocol string command corresponding to a command code. - */ - public static final String getCommand(IMAPCommand command) { - return command.getIMAPCommand(); - } - /** * Get the IMAP protocol string command for this command * diff --git a/src/main/java/org/apache/commons/net/imap/IMAPReply.java b/src/main/java/org/apache/commons/net/imap/IMAPReply.java index d7a739d..9ef791d 100644 --- a/src/main/java/org/apache/commons/net/imap/IMAPReply.java +++ b/src/main/java/org/apache/commons/net/imap/IMAPReply.java @@ -27,8 +27,7 @@ import org.apache.commons.net.MalformedServerReplyException; * IMAPReply stores IMAP reply code constants. */ -public final class IMAPReply -{ +public final class IMAPReply { /** The reply code indicating success of an operation. */ public static final int OK = 0; @@ -42,9 +41,9 @@ public final class IMAPReply public static final int CONT = 3; /** - * The reply code indicating a partial response. - * This is used when a chunk listener is registered and the listener - * requests that the reply lines are cleared on return. + * The reply code indicating a partial response. This is used when a chunk listener is registered and the listener requests that the reply lines are cleared + * on return. + * * @since 3.4 */ public static final int PARTIAL = 3; @@ -64,81 +63,35 @@ public final class IMAPReply // Start of line for continuation replies private static final String IMAP_CONTINUATION_PREFIX = "+"; - // Cannot be instantiated. - private IMAPReply() - {} - - /** - * Checks if the reply line is untagged - e.g. "* OK ..." - * @param line to be checked - * @return {@code true} if the line is untagged - */ - public static boolean isUntagged(String line) { - return line.startsWith(IMAP_UNTAGGED_PREFIX); - } - - /** - * Checks if the reply line is a continuation, i.e. starts with "+" - * @param line the line to be checked - * @return {@code true} if the line is untagged - */ - public static boolean isContinuation(String line) { - return line.startsWith(IMAP_CONTINUATION_PREFIX); - } - private static final String TAGGED_RESPONSE = "^\\w+ (\\S+).*"; // TODO perhaps be less strict on tag match? + // tag cannot contain: + ( ) { SP CTL % * " \ ] private static final Pattern TAGGED_PATTERN = Pattern.compile(TAGGED_RESPONSE); - /** - * Intepret the String reply code - OK, NO, BAD - in a tagged response as a integer. - * - * @param line the tagged line to be checked - * @return {@link #OK} or {@link #NO} or {@link #BAD} or {@link #CONT} - * @throws IOException if the input has an unexpected format - */ - public static int getReplyCode(String line) throws IOException { - return getReplyCode(line, TAGGED_PATTERN); - } - private static final String UNTAGGED_RESPONSE = "^\\* (\\S+).*"; - private static final Pattern UNTAGGED_PATTERN = Pattern.compile(UNTAGGED_RESPONSE); + private static final Pattern UNTAGGED_PATTERN = Pattern.compile(UNTAGGED_RESPONSE); private static final Pattern LITERAL_PATTERN = Pattern.compile("\\{(\\d+)\\}$"); // {dd} /** - * Checks if the line introduces a literal, i.e. ends with {dd} - * @param line the line to check - * - * @return the literal count, or -1 if there was no literal. - */ - public static int literalCount(String line) { - Matcher m = LITERAL_PATTERN.matcher(line); - if (m.find()) { - return Integer.parseInt(m.group(1)); // Should always parse because we matched \d+ - } - return -1; - } - - /** - * Intepret the String reply code - OK, NO, BAD - in an untagged response as a integer. + * Intepret the String reply code - OK, NO, BAD - in a tagged response as a integer. * - * @param line the untagged line to be checked + * @param line the tagged line to be checked * @return {@link #OK} or {@link #NO} or {@link #BAD} or {@link #CONT} * @throws IOException if the input has an unexpected format */ - public static int getUntaggedReplyCode(String line) throws IOException { - return getReplyCode(line, UNTAGGED_PATTERN); + public static int getReplyCode(final String line) throws IOException { + return getReplyCode(line, TAGGED_PATTERN); } // Helper method to process both tagged and untagged replies. - private static int getReplyCode(String line, Pattern pattern) throws IOException{ + private static int getReplyCode(final String line, final Pattern pattern) throws IOException { if (isContinuation(line)) { return CONT; } - Matcher m = pattern.matcher(line); + final Matcher m = pattern.matcher(line); if (m.matches()) { // TODO would lookingAt() be more efficient? If so, then drop trailing .* from patterns - String code = m.group(1); + final String code = m.group(1); if (code.equals(IMAP_OK)) { return OK; } @@ -149,28 +102,79 @@ public final class IMAPReply return NO; } } - throw new MalformedServerReplyException( - "Received unexpected IMAP protocol response from server: '" + line + "'."); + throw new MalformedServerReplyException("Received unexpected IMAP protocol response from server: '" + line + "'."); } /** - * Checks whether the reply code indicates success or not + * Intepret the String reply code - OK, NO, BAD - in an untagged response as a integer. * - * @param replyCode the code to check - * @return {@code true} if the code equals {@link #OK} + * @param line the untagged line to be checked + * @return {@link #OK} or {@link #NO} or {@link #BAD} or {@link #CONT} + * @throws IOException if the input has an unexpected format */ - public static boolean isSuccess(int replyCode) { - return replyCode == OK; + public static int getUntaggedReplyCode(final String line) throws IOException { + return getReplyCode(line, UNTAGGED_PATTERN); } + /** * Checks if the reply line is a continuation, i.e. starts with "+" + * * @param replyCode the code to be checked * @return {@code true} if the response was a continuation */ - public static boolean isContinuation(int replyCode) { + public static boolean isContinuation(final int replyCode) { return replyCode == CONT; } + /** + * Checks if the reply line is a continuation, i.e. starts with "+" + * + * @param line the line to be checked + * @return {@code true} if the line is untagged + */ + public static boolean isContinuation(final String line) { + return line.startsWith(IMAP_CONTINUATION_PREFIX); + } + + /** + * Checks whether the reply code indicates success or not + * + * @param replyCode the code to check + * @return {@code true} if the code equals {@link #OK} + */ + public static boolean isSuccess(final int replyCode) { + return replyCode == OK; + } + + /** + * Checks if the reply line is untagged - e.g. "* OK ..." + * + * @param line to be checked + * @return {@code true} if the line is untagged + */ + public static boolean isUntagged(final String line) { + return line.startsWith(IMAP_UNTAGGED_PREFIX); + } + + /** + * Checks if the line introduces a literal, i.e. ends with {dd} + * + * @param line the line to check + * + * @return the literal count, or -1 if there was no literal. + */ + public static int literalCount(final String line) { + final Matcher m = LITERAL_PATTERN.matcher(line); + if (m.find()) { + return Integer.parseInt(m.group(1)); // Should always parse because we matched \d+ + } + return -1; + } + + // Cannot be instantiated. + private IMAPReply() { + } + } /* kate: indent-width 4; replace-tabs on; */ diff --git a/src/main/java/org/apache/commons/net/imap/IMAPSClient.java b/src/main/java/org/apache/commons/net/imap/IMAPSClient.java index 89e61de..01dae41 100644 --- a/src/main/java/org/apache/commons/net/imap/IMAPSClient.java +++ b/src/main/java/org/apache/commons/net/imap/IMAPSClient.java @@ -18,8 +18,8 @@ package org.apache.commons.net.imap; import java.io.BufferedWriter; -import java.io.InputStreamReader; import java.io.IOException; +import java.io.InputStreamReader; import java.io.OutputStreamWriter; import javax.net.ssl.HostnameVerifier; @@ -36,25 +36,30 @@ import org.apache.commons.net.util.SSLContextUtils; import org.apache.commons.net.util.SSLSocketUtils; /** - * The IMAPSClient class provides SSL/TLS connection encryption to IMAPClient. - * Copied from FTPSClient.java and modified to suit IMAP. - * If implicit mode is selected (NOT the default), SSL/TLS negotiation starts right - * after the connection has been established. In explicit mode (the default), SSL/TLS - * negotiation starts when the user calls execTLS() and the server accepts the command. - * Implicit usage: + * The IMAPSClient class provides SSL/TLS connection encryption to IMAPClient. Copied from + * <a href="http://commons.apache.org/proper/commons-net/apidocs/index.html?org/apache/commons/net/ftp/FTPSClient.html"> FTPSClient</a> and modified to suit + * IMAP. If implicit mode is selected (NOT the default), SSL/TLS negotiation starts right after the connection has been established. In explicit mode (the + * default), SSL/TLS negotiation starts when the user calls execTLS() and the server accepts the command. + * + * <pre> + * {@code + * //Implicit usage: + * * IMAPSClient c = new IMAPSClient(true); * c.connect("127.0.0.1", 993); - * Explicit usage: + * + * //Explicit usage: + * * IMAPSClient c = new IMAPSClient(); * c.connect("127.0.0.1", 143); * if (c.execTLS()) { /rest of the commands here/ } + * } + * </pre> * - * Warning: the hostname is not verified against the certificate by default, use - * {@link #setHostnameVerifier(HostnameVerifier)} or {@link #setEndpointCheckingEnabled(boolean)} - * (on Java 1.7+) to enable verification. + * <b>Warning</b>: the hostname is not verified against the certificate by default, use {@link #setHostnameVerifier(HostnameVerifier)} or + * {@link #setEndpointCheckingEnabled(boolean)} (on Java 1.7+) to enable verification. */ -public class IMAPSClient extends IMAPClient -{ +public class IMAPSClient extends IMAPClient { /** The default IMAP over SSL port. */ public static final int DEFAULT_IMAPS_PORT = 993; @@ -66,108 +71,104 @@ public class IMAPSClient extends IMAPClient /** The secure socket protocol to be used, like SSL/TLS. */ private final String protocol; /** The context object. */ - private SSLContext context = null; - /** The cipher suites. SSLSockets have a default set of these anyway, - so no initialization required. */ - private String[] suites = null; + private SSLContext context; + /** + * The cipher suites. SSLSockets have a default set of these anyway, so no initialization required. + */ + private String[] suites; /** The protocol versions. */ - private String[] protocols = //null; - null;//{"SSLv2", "SSLv3", "TLSv1", "TLSv1.1", "SSLv2Hello"}; + private String[] protocols // null; + ;// {"SSLv2", "SSLv3", "TLSv1", "TLSv1.1", "SSLv2Hello"}; /** The IMAPS {@link TrustManager} implementation, default null. */ - private TrustManager trustManager = null; + private TrustManager trustManager; /** The {@link KeyManager}, default null. */ - private KeyManager keyManager = null; + private KeyManager keyManager; /** The {@link HostnameVerifier} to use post-TLS, default null (i.e. no verification). */ - private HostnameVerifier hostnameVerifier = null; + private HostnameVerifier hostnameVerifier; /** Use Java 1.7+ HTTPS Endpoint Identification Algorithim. */ private boolean tlsEndpointChecking; /** - * Constructor for IMAPSClient. - * Sets security mode to explicit (isImplicit = false). + * Constructor for IMAPSClient. Sets security mode to explicit (isImplicit = false). */ - public IMAPSClient() - { + public IMAPSClient() { this(DEFAULT_PROTOCOL, false); } /** * Constructor for IMAPSClient. + * * @param implicit The security mode (Implicit/Explicit). */ - public IMAPSClient(boolean implicit) - { + public IMAPSClient(final boolean implicit) { this(DEFAULT_PROTOCOL, implicit); } /** * Constructor for IMAPSClient. - * @param proto the protocol. + * + * @param implicit The security mode(Implicit/Explicit). + * @param ctx A pre-configured SSL Context. */ - public IMAPSClient(String proto) - { - this(proto, false); + public IMAPSClient(final boolean implicit, final SSLContext ctx) { + this(DEFAULT_PROTOCOL, implicit, ctx); } /** * Constructor for IMAPSClient. - * @param proto the protocol. - * @param implicit The security mode(Implicit/Explicit). + * + * @param context A pre-configured SSL Context. */ - public IMAPSClient(String proto, boolean implicit) - { - this(proto, implicit, null); + public IMAPSClient(final SSLContext context) { + this(false, context); } /** * Constructor for IMAPSClient. + * * @param proto the protocol. - * @param implicit The security mode(Implicit/Explicit). - * @param ctx the SSL context */ - public IMAPSClient(String proto, boolean implicit, SSLContext ctx) - { - super(); - setDefaultPort(DEFAULT_IMAPS_PORT); - protocol = proto; - isImplicit = implicit; - context = ctx; + public IMAPSClient(final String proto) { + this(proto, false); } /** * Constructor for IMAPSClient. + * + * @param proto the protocol. * @param implicit The security mode(Implicit/Explicit). - * @param ctx A pre-configured SSL Context. */ - public IMAPSClient(boolean implicit, SSLContext ctx) - { - this(DEFAULT_PROTOCOL, implicit, ctx); + public IMAPSClient(final String proto, final boolean implicit) { + this(proto, implicit, null); } /** * Constructor for IMAPSClient. - * @param context A pre-configured SSL Context. + * + * @param proto the protocol. + * @param implicit The security mode(Implicit/Explicit). + * @param ctx the SSL context */ - public IMAPSClient(SSLContext context) - { - this(false, context); + public IMAPSClient(final String proto, final boolean implicit, final SSLContext ctx) { + setDefaultPort(DEFAULT_IMAPS_PORT); + protocol = proto; + isImplicit = implicit; + context = ctx; } /** - * Because there are so many connect() methods, - * the _connectAction_() method is provided as a means of performing - * some action immediately after establishing a connection, - * rather than reimplementing all of the connect() methods. + * Because there are so many connect() methods, the _connectAction_() method is provided as a means of performing some action immediately after establishing + * a connection, rather than reimplementing all of the connect() methods. + * * @throws IOException If it is thrown by _connectAction_(). * @see org.apache.commons.net.SocketClient#_connectAction_() */ @Override - protected void _connectAction_() throws IOException - { + protected void _connectAction_() throws IOException { // Implicit mode. if (isImplicit) { performSSLNegotiation(); @@ -177,212 +178,191 @@ public class IMAPSClient extends IMAPClient } /** - * Performs a lazy init of the SSL context. - * @throws IOException When could not initialize the SSL context. + * The TLS command execution. + * + * @throws SSLException If the server reply code is not positive. + * @throws IOException If an I/O error occurs while sending the command or performing the negotiation. + * @return TRUE if the command and negotiation succeeded. */ - private void initSSLContext() throws IOException - { - if (context == null) - { - context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager()); + public boolean execTLS() throws SSLException, IOException { + if (sendCommand(IMAPCommand.getCommand(IMAPCommand.STARTTLS)) != IMAPReply.OK) { + return false; + // throw new SSLException(getReplyString()); } + performSSLNegotiation(); + return true; } /** - * SSL/TLS negotiation. Acquires an SSL socket of a - * connection and carries out handshake processing. - * @throws IOException If server negotiation fails. + * Returns the names of the cipher suites which could be enabled for use on this connection. When the underlying {@link java.net.Socket Socket} is not an + * {@link SSLSocket} instance, returns null. + * + * @return An array of cipher suite names, or <code>null</code>. */ - private void performSSLNegotiation() throws IOException - { - initSSLContext(); - - SSLSocketFactory ssf = context.getSocketFactory(); - String host = (_hostname_ != null) ? _hostname_ : getRemoteAddress().getHostAddress(); - int port = getRemotePort(); - SSLSocket socket = - (SSLSocket) ssf.createSocket(_socket_, host, port, true); - socket.setEnableSessionCreation(true); - socket.setUseClientMode(true); - - if (tlsEndpointChecking) { - SSLSocketUtils.enableEndpointNameVerification(socket); - } - - if (protocols != null) { - socket.setEnabledProtocols(protocols); + public String[] getEnabledCipherSuites() { + if (_socket_ instanceof SSLSocket) { + return ((SSLSocket) _socket_).getEnabledCipherSuites(); } - if (suites != null) { - socket.setEnabledCipherSuites(suites); - } - socket.startHandshake(); - - // TODO the following setup appears to duplicate that in the super class methods - _socket_ = socket; - _input_ = socket.getInputStream(); - _output_ = socket.getOutputStream(); - _reader = - new CRLFLineReader(new InputStreamReader(_input_, - __DEFAULT_ENCODING)); - __writer = - new BufferedWriter(new OutputStreamWriter(_output_, - __DEFAULT_ENCODING)); + return null; + } - if (hostnameVerifier != null && !hostnameVerifier.verify(host, socket.getSession())) { - throw new SSLHandshakeException("Hostname doesn't match certificate"); + /** + * Returns the names of the protocol versions which are currently enabled for use on this connection. When the underlying {@link java.net.Socket Socket} is + * not an {@link SSLSocket} instance, returns null. + * + * @return An array of protocols, or <code>null</code>. + */ + public String[] getEnabledProtocols() { + if (_socket_ instanceof SSLSocket) { + return ((SSLSocket) _socket_).getEnabledProtocols(); } + return null; } /** - * Get the {@link KeyManager} instance. - * @return The current {@link KeyManager} instance. + * Get the currently configured {@link HostnameVerifier}. + * + * @return A HostnameVerifier instance. + * @since 3.4 */ - private KeyManager getKeyManager() - { - return keyManager; + public HostnameVerifier getHostnameVerifier() { + return hostnameVerifier; } /** - * Set a {@link KeyManager} to use. - * @param newKeyManager The KeyManager implementation to set. - * @see org.apache.commons.net.util.KeyManagerUtils + * Get the {@link KeyManager} instance. + * + * @return The current {@link KeyManager} instance. */ - public void setKeyManager(KeyManager newKeyManager) - { - keyManager = newKeyManager; + private KeyManager getKeyManager() { + return keyManager; } /** - * Controls which particular cipher suites are enabled for use on this - * connection. Called before server negotiation. - * @param cipherSuites The cipher suites. + * Get the currently configured {@link TrustManager}. + * + * @return A TrustManager instance. */ - public void setEnabledCipherSuites(String[] cipherSuites) - { - suites = new String[cipherSuites.length]; - System.arraycopy(cipherSuites, 0, suites, 0, cipherSuites.length); + public TrustManager getTrustManager() { + return trustManager; } /** - * Returns the names of the cipher suites which could be enabled - * for use on this connection. - * When the underlying {@link java.net.Socket Socket} is not an {@link SSLSocket} instance, returns null. - * @return An array of cipher suite names, or <code>null</code>. + * Performs a lazy init of the SSL context. + * + * @throws IOException When could not initialize the SSL context. */ - public String[] getEnabledCipherSuites() - { - if (_socket_ instanceof SSLSocket) - { - return ((SSLSocket)_socket_).getEnabledCipherSuites(); + private void initSSLContext() throws IOException { + if (context == null) { + context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager()); } - return null; } /** - * Controls which particular protocol versions are enabled for use on this - * connection. I perform setting before a server negotiation. - * @param protocolVersions The protocol versions. + * Return whether or not endpoint identification using the HTTPS algorithm on Java 1.7+ is enabled. The default behavior is for this to be disabled. + * + * @return True if enabled, false if not. + * @since 3.4 */ - public void setEnabledProtocols(String[] protocolVersions) - { - protocols = new String[protocolVersions.length]; - System.arraycopy(protocolVersions, 0, protocols, 0, protocolVersions.length); + public boolean isEndpointCheckingEnabled() { + return tlsEndpointChecking; } /** - * Returns the names of the protocol versions which are currently - * enabled for use on this connection. - * When the underlying {@link java.net.Socket Socket} is not an {@link SSLSocket} instance, returns null. - * @return An array of protocols, or <code>null</code>. + * SSL/TLS negotiation. Acquires an SSL socket of a connection and carries out handshake processing. + * + * @throws IOException If server negotiation fails. */ - public String[] getEnabledProtocols() - { - if (_socket_ instanceof SSLSocket) - { - return ((SSLSocket)_socket_).getEnabledProtocols(); + private void performSSLNegotiation() throws IOException { + initSSLContext(); + + final SSLSocketFactory ssf = context.getSocketFactory(); + final String host = _hostname_ != null ? _hostname_ : getRemoteAddress().getHostAddress(); + final int port = getRemotePort(); + final SSLSocket socket = (SSLSocket) ssf.createSocket(_socket_, host, port, true); + socket.setEnableSessionCreation(true); + socket.setUseClientMode(true); + + if (tlsEndpointChecking) { + SSLSocketUtils.enableEndpointNameVerification(socket); } - return null; - } - /** - * The TLS command execution. - * @throws SSLException If the server reply code is not positive. - * @throws IOException If an I/O error occurs while sending - * the command or performing the negotiation. - * @return TRUE if the command and negotiation succeeded. - */ - public boolean execTLS() throws SSLException, IOException - { - if (sendCommand(IMAPCommand.getCommand(IMAPCommand.STARTTLS)) != IMAPReply.OK) - { - return false; - //throw new SSLException(getReplyString()); + if (protocols != null) { + socket.setEnabledProtocols(protocols); + } + if (suites != null) { + socket.setEnabledCipherSuites(suites); + } + socket.startHandshake(); + + // TODO the following setup appears to duplicate that in the super class methods + _socket_ = socket; + _input_ = socket.getInputStream(); + _output_ = socket.getOutputStream(); + _reader = new CRLFLineReader(new InputStreamReader(_input_, __DEFAULT_ENCODING)); + __writer = new BufferedWriter(new OutputStreamWriter(_output_, __DEFAULT_ENCODING)); + + if (hostnameVerifier != null && !hostnameVerifier.verify(host, socket.getSession())) { + throw new SSLHandshakeException("Hostname doesn't match certificate"); } - performSSLNegotiation(); - return true; } /** - * Get the currently configured {@link TrustManager}. - * @return A TrustManager instance. + * Controls which particular cipher suites are enabled for use on this connection. Called before server negotiation. + * + * @param cipherSuites The cipher suites. */ - public TrustManager getTrustManager() - { - return trustManager; + public void setEnabledCipherSuites(final String[] cipherSuites) { + suites = cipherSuites.clone(); } /** - * Override the default {@link TrustManager} to use. - * @param newTrustManager The TrustManager implementation to set. - * @see org.apache.commons.net.util.TrustManagerUtils + * Controls which particular protocol versions are enabled for use on this connection. I perform setting before a server negotiation. + * + * @param protocolVersions The protocol versions. */ - public void setTrustManager(TrustManager newTrustManager) - { - trustManager = newTrustManager; + public void setEnabledProtocols(final String[] protocolVersions) { + protocols = protocolVersions.clone(); } /** - * Get the currently configured {@link HostnameVerifier}. - * @return A HostnameVerifier instance. + * Automatic endpoint identification checking using the HTTPS algorithm is supported on Java 1.7+. The default behavior is for this to be disabled. + * + * @param enable Enable automatic endpoint identification checking using the HTTPS algorithm on Java 1.7+. * @since 3.4 */ - public HostnameVerifier getHostnameVerifier() - { - return hostnameVerifier; + public void setEndpointCheckingEnabled(final boolean enable) { + tlsEndpointChecking = enable; } /** * Override the default {@link HostnameVerifier} to use. + * * @param newHostnameVerifier The HostnameVerifier implementation to set or <code>null</code> to disable. * @since 3.4 */ - public void setHostnameVerifier(HostnameVerifier newHostnameVerifier) - { + public void setHostnameVerifier(final HostnameVerifier newHostnameVerifier) { hostnameVerifier = newHostnameVerifier; } /** - * Return whether or not endpoint identification using the HTTPS algorithm - * on Java 1.7+ is enabled. The default behaviour is for this to be disabled. + * Set a {@link KeyManager} to use. * - * @return True if enabled, false if not. - * @since 3.4 + * @param newKeyManager The KeyManager implementation to set. + * @see org.apache.commons.net.util.KeyManagerUtils */ - public boolean isEndpointCheckingEnabled() - { - return tlsEndpointChecking; + public void setKeyManager(final KeyManager newKeyManager) { + keyManager = newKeyManager; } /** - * Automatic endpoint identification checking using the HTTPS algorithm - * is supported on Java 1.7+. The default behaviour is for this to be disabled. + * Override the default {@link TrustManager} to use. * - * @param enable Enable automatic endpoint identification checking using the HTTPS algorithm on Java 1.7+. - * @since 3.4 + * @param newTrustManager The TrustManager implementation to set. + * @see org.apache.commons.net.util.TrustManagerUtils */ - public void setEndpointCheckingEnabled(boolean enable) - { - tlsEndpointChecking = enable; + public void setTrustManager(final TrustManager newTrustManager) { + trustManager = newTrustManager; } } /* kate: indent-width 4; replace-tabs on; */ diff --git a/src/main/java/org/apache/commons/net/io/CRLFLineReader.java b/src/main/java/org/apache/commons/net/io/CRLFLineReader.java index 9ba51a9..368d88a 100644 --- a/src/main/java/org/apache/commons/net/io/CRLFLineReader.java +++ b/src/main/java/org/apache/commons/net/io/CRLFLineReader.java @@ -21,55 +21,48 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; +import org.apache.commons.net.util.NetConstants; + /** - * CRLFLineReader implements a readLine() method that requires - * exactly CRLF to terminate an input line. - * This is required for IMAP, which allows bare CR and LF. + * CRLFLineReader implements a readLine() method that requires exactly CRLF to terminate an input line. This is required for IMAP, which allows bare CR and LF. * * @since 3.0 */ -public final class CRLFLineReader extends BufferedReader -{ +public final class CRLFLineReader extends BufferedReader { private static final char LF = '\n'; private static final char CR = '\r'; /** - * Creates a CRLFLineReader that wraps an existing Reader - * input source. - * @param reader The Reader input source. + * Creates a CRLFLineReader that wraps an existing Reader input source. + * + * @param reader The Reader input source. */ - public CRLFLineReader(Reader reader) - { + public CRLFLineReader(final Reader reader) { super(reader); } /** - * Read a line of text. - * A line is considered to be terminated by carriage return followed immediately by a linefeed. - * This contrasts with BufferedReader which also allows other combinations. + * Read a line of text. A line is considered to be terminated by carriage return followed immediately by a linefeed. This contrasts with BufferedReader + * which also allows other combinations. + * * @since 3.0 */ @Override public String readLine() throws IOException { - StringBuilder sb = new StringBuilder(); + final StringBuilder sb = new StringBuilder(); int intch; boolean prevWasCR = false; - synchronized(lock) { // make thread-safe (hopefully!) - while((intch = read()) != -1) - { + synchronized (lock) { // make thread-safe (hopefully!) + while ((intch = read()) != NetConstants.EOS) { if (prevWasCR && intch == LF) { - return sb.substring(0, sb.length()-1); - } - if (intch == CR) { - prevWasCR = true; - } else { - prevWasCR = false; + return sb.substring(0, sb.length() - 1); } + prevWasCR = intch == CR; sb.append((char) intch); } } - String string = sb.toString(); - if (string.length() == 0) { // immediate EOF + final String string = sb.toString(); + if (string.isEmpty()) { // immediate EOF return null; } return string; diff --git a/src/main/java/org/apache/commons/net/io/CopyStreamAdapter.java b/src/main/java/org/apache/commons/net/io/CopyStreamAdapter.java index 328ab01..2f50007 100644 --- a/src/main/java/org/apache/commons/net/io/CopyStreamAdapter.java +++ b/src/main/java/org/apache/commons/net/io/CopyStreamAdapter.java @@ -22,97 +22,73 @@ import java.util.EventListener; import org.apache.commons.net.util.ListenerList; /** - * The CopyStreamAdapter will relay CopyStreamEvents to a list of listeners - * when either of its bytesTransferred() methods are called. Its purpose - * is to facilitate the notification of the progress of a copy operation - * performed by one of the static copyStream() methods in - * org.apache.commons.io.Util to multiple listeners. The static - * copyStream() methods invoke the - * bytesTransfered(long, int) of a CopyStreamListener for performance - * reasons and also because multiple listeners cannot be registered given - * that the methods are static. + * The CopyStreamAdapter will relay CopyStreamEvents to a list of listeners when either of its bytesTransferred() methods are called. Its purpose is to + * facilitate the notification of the progress of a copy operation performed by one of the static copyStream() methods in org.apache.commons.io.Util to multiple + * listeners. The static copyStream() methods invoke the bytesTransfered(long, int) of a CopyStreamListener for performance reasons and also because multiple + * listeners cannot be registered given that the methods are static. * * * @see CopyStreamEvent * @see CopyStreamListener * @see Util - * @version $Id: CopyStreamAdapter.java 1741829 2016-05-01 00:24:44Z sebb $ */ -public class CopyStreamAdapter implements CopyStreamListener -{ +public class CopyStreamAdapter implements CopyStreamListener { private final ListenerList internalListeners; /** * Creates a new copyStreamAdapter. */ - public CopyStreamAdapter() - { + public CopyStreamAdapter() { internalListeners = new ListenerList(); } /** - * This method is invoked by a CopyStreamEvent source after copying - * a block of bytes from a stream. The CopyStreamEvent will contain - * the total number of bytes transferred so far and the number of bytes - * transferred in the last write. The CopyStreamAdapater will relay - * the event to all of its registered listeners, listing itself as the - * source of the event. - * @param event The CopyStreamEvent fired by the copying of a block of - * bytes. + * Registers a CopyStreamListener to receive CopyStreamEvents. Although this method is not declared to be synchronized, it is implemented in a thread safe + * manner. + * + * @param listener The CopyStreamlistener to register. */ - @Override - public void bytesTransferred(CopyStreamEvent event) - { - for (EventListener listener : internalListeners) - { - ((CopyStreamListener) (listener)).bytesTransferred(event); - } + public void addCopyStreamListener(final CopyStreamListener listener) { + internalListeners.addListener(listener); } /** - * This method is not part of the JavaBeans model and is used by the - * static methods in the org.apache.commons.io.Util class for efficiency. - * It is invoked after a block of bytes to inform the listener of the - * transfer. The CopyStreamAdapater will create a CopyStreamEvent - * from the arguments and relay the event to all of its registered + * This method is invoked by a CopyStreamEvent source after copying a block of bytes from a stream. The CopyStreamEvent will contain the total number of + * bytes transferred so far and the number of bytes transferred in the last write. The CopyStreamAdapater will relay the event to all of its registered * listeners, listing itself as the source of the event. - * @param totalBytesTransferred The total number of bytes transferred - * so far by the copy operation. - * @param bytesTransferred The number of bytes copied by the most recent - * write. - * @param streamSize The number of bytes in the stream being copied. - * This may be equal to CopyStreamEvent.UNKNOWN_STREAM_SIZE if - * the size is unknown. + * + * @param event The CopyStreamEvent fired by the copying of a block of bytes. */ @Override - public void bytesTransferred(long totalBytesTransferred, - int bytesTransferred, long streamSize) - { - for (EventListener listener : internalListeners) - { - ((CopyStreamListener) (listener)).bytesTransferred( - totalBytesTransferred, bytesTransferred, streamSize); + public void bytesTransferred(final CopyStreamEvent event) { + for (final EventListener listener : internalListeners) { + ((CopyStreamListener) listener).bytesTransferred(event); } } /** - * Registers a CopyStreamListener to receive CopyStreamEvents. - * Although this method is not declared to be synchronized, it is - * implemented in a thread safe manner. - * @param listener The CopyStreamlistener to register. + * This method is not part of the JavaBeans model and is used by the static methods in the org.apache.commons.io.Util class for efficiency. It is invoked + * after a block of bytes to inform the listener of the transfer. The CopyStreamAdapater will create a CopyStreamEvent from the arguments and relay the + * event to all of its registered listeners, listing itself as the source of the event. + * + * @param totalBytesTransferred The total number of bytes transferred so far by the copy operation. + * @param bytesTransferred The number of bytes copied by the most recent write. + * @param streamSize The number of bytes in the stream being copied. This may be equal to CopyStreamEvent.UNKNOWN_STREAM_SIZE if the size is + * unknown. */ - public void addCopyStreamListener(CopyStreamListener listener) - { - internalListeners.addListener(listener); + @Override + public void bytesTransferred(final long totalBytesTransferred, final int bytesTransferred, final long streamSize) { + for (final EventListener listener : internalListeners) { + ((CopyStreamListener) listener).bytesTransferred(totalBytesTransferred, bytesTransferred, streamSize); + } } /** - * Unregisters a CopyStreamListener. Although this method is not - * synchronized, it is implemented in a thread safe manner. - * @param listener The CopyStreamlistener to unregister. + * Unregisters a CopyStreamListener. Although this method is not synchronized, it is implemented in a thread safe manner. + * + * @param listener The CopyStreamlistener to unregister. */ - public void removeCopyStreamListener(CopyStreamListener listener) - { + public void removeCopyStreamListener(final CopyStreamListener listener) { internalListeners.removeListener(listener); } } diff --git a/src/main/java/org/apache/commons/net/io/CopyStreamEvent.java b/src/main/java/org/apache/commons/net/io/CopyStreamEvent.java index db5f551..39fb2b5 100644 --- a/src/main/java/org/apache/commons/net/io/CopyStreamEvent.java +++ b/src/main/java/org/apache/commons/net/io/CopyStreamEvent.java @@ -20,19 +20,15 @@ package org.apache.commons.net.io; import java.util.EventObject; /** - * A CopyStreamEvent is triggered after every write performed by a - * stream copying operation. The event stores the number of bytes - * transferred by the write triggering the event as well as the total - * number of bytes transferred so far by the copy operation. + * A CopyStreamEvent is triggered after every write performed by a stream copying operation. The event stores the number of bytes transferred by the write + * triggering the event as well as the total number of bytes transferred so far by the copy operation. * * * @see CopyStreamListener * @see CopyStreamAdapter * @see Util - * @version $Id: CopyStreamEvent.java 1652801 2015-01-18 17:10:05Z sebb $ */ -public class CopyStreamEvent extends EventObject -{ +public class CopyStreamEvent extends EventObject { private static final long serialVersionUID = -964927635655051867L; /** @@ -46,18 +42,13 @@ public class CopyStreamEvent extends EventObject /** * Creates a new CopyStreamEvent instance. - * @param source The source of the event. - * @param totalBytesTransferred The total number of bytes transferred so - * far during a copy operation. - * @param bytesTransferred The number of bytes transferred during the - * write that triggered the CopyStreamEvent. - * @param streamSize The number of bytes in the stream being copied. - * This may be set to <code>UNKNOWN_STREAM_SIZE</code> if the - * size is unknown. + * + * @param source The source of the event. + * @param totalBytesTransferred The total number of bytes transferred so far during a copy operation. + * @param bytesTransferred The number of bytes transferred during the write that triggered the CopyStreamEvent. + * @param streamSize The number of bytes in the stream being copied. This may be set to <code>UNKNOWN_STREAM_SIZE</code> if the size is unknown. */ - public CopyStreamEvent(Object source, long totalBytesTransferred, - int bytesTransferred, long streamSize) - { + public CopyStreamEvent(final Object source, final long totalBytesTransferred, final int bytesTransferred, final long streamSize) { super(source); this.bytesTransferred = bytesTransferred; this.totalBytesTransferred = totalBytesTransferred; @@ -65,47 +56,37 @@ public class CopyStreamEvent extends EventObject } /** - * Returns the number of bytes transferred by the write that triggered - * the event. - * @return The number of bytes transferred by the write that triggered - * the vent. + * Returns the number of bytes transferred by the write that triggered the event. + * + * @return The number of bytes transferred by the write that triggered the vent. */ - public int getBytesTransferred() - { + public int getBytesTransferred() { return bytesTransferred; } /** - * Returns the total number of bytes transferred so far by the copy - * operation. - * @return The total number of bytes transferred so far by the copy - * operation. + * Returns the size of the stream being copied. This may be set to <code>UNKNOWN_STREAM_SIZE</code> if the size is unknown. + * + * @return The size of the stream being copied. */ - public long getTotalBytesTransferred() - { - return totalBytesTransferred; + public long getStreamSize() { + return streamSize; } /** - * Returns the size of the stream being copied. - * This may be set to <code>UNKNOWN_STREAM_SIZE</code> if the - * size is unknown. - * @return The size of the stream being copied. + * Returns the total number of bytes transferred so far by the copy operation. + * + * @return The total number of bytes transferred so far by the copy operation. */ - public long getStreamSize() - { - return streamSize; + public long getTotalBytesTransferred() { + return totalBytesTransferred; } /** - * @since 3.0 + * @since 3.0 */ @Override - public String toString(){ - return getClass().getName() + "[source=" + source - + ", total=" + totalBytesTransferred - + ", bytes=" + bytesTransferred - + ", size=" + streamSize - + "]"; + public String toString() { + return getClass().getName() + "[source=" + source + ", total=" + totalBytesTransferred + ", bytes=" + bytesTransferred + ", size=" + streamSize + "]"; } } diff --git a/src/main/java/org/apache/commons/net/io/CopyStreamException.java b/src/main/java/org/apache/commons/net/io/CopyStreamException.java index 54a576a..4ac84a3 100644 --- a/src/main/java/org/apache/commons/net/io/CopyStreamException.java +++ b/src/main/java/org/apache/commons/net/io/CopyStreamException.java @@ -20,52 +20,44 @@ package org.apache.commons.net.io; import java.io.IOException; /** - * The CopyStreamException class is thrown by the org.apache.commons.io.Util - * copyStream() methods. It stores the number of bytes confirmed to - * have been transferred before an I/O error as well as the IOException - * responsible for the failure of a copy operation. + * The CopyStreamException class is thrown by the org.apache.commons.io.Util copyStream() methods. It stores the number of bytes confirmed to have been + * transferred before an I/O error as well as the IOException responsible for the failure of a copy operation. + * * @see Util - * @version $Id: CopyStreamException.java 1299238 2012-03-10 17:12:28Z sebb $ */ -public class CopyStreamException extends IOException -{ +public class CopyStreamException extends IOException { private static final long serialVersionUID = -2602899129433221532L; private final long totalBytesTransferred; /** * Creates a new CopyStreamException instance. - * @param message A message describing the error. - * @param bytesTransferred The total number of bytes transferred before - * an exception was thrown in a copy operation. - * @param exception The IOException thrown during a copy operation. + * + * @param message A message describing the error. + * @param bytesTransferred The total number of bytes transferred before an exception was thrown in a copy operation. + * @param exception The IOException thrown during a copy operation. */ - public CopyStreamException(String message, - long bytesTransferred, - IOException exception) - { + public CopyStreamException(final String message, final long bytesTransferred, final IOException exception) { super(message); initCause(exception); // merge this into super() call once we need 1.6+ totalBytesTransferred = bytesTransferred; } /** - * Returns the total number of bytes confirmed to have - * been transferred by a failed copy operation. - * @return The total number of bytes confirmed to have - * been transferred by a failed copy operation. + * Returns the IOException responsible for the failure of a copy operation. + * + * @return The IOException responsible for the failure of a copy operation. */ - public long getTotalBytesTransferred() - { - return totalBytesTransferred; + public IOException getIOException() { + return (IOException) getCause(); // cast is OK because it was initialized with an IOException } /** - * Returns the IOException responsible for the failure of a copy operation. - * @return The IOException responsible for the failure of a copy operation. + * Returns the total number of bytes confirmed to have been transferred by a failed copy operation. + * + * @return The total number of bytes confirmed to have been transferred by a failed copy operation. */ - public IOException getIOException() - { - return (IOException) getCause(); // cast is OK because it was initialised with an IOException + public long getTotalBytesTransferred() { + return totalBytesTransferred; } } diff --git a/src/main/java/org/apache/commons/net/io/CopyStreamListener.java b/src/main/java/org/apache/commons/net/io/CopyStreamListener.java index 717acaf..b139a50 100644 --- a/src/main/java/org/apache/commons/net/io/CopyStreamListener.java +++ b/src/main/java/org/apache/commons/net/io/CopyStreamListener.java @@ -20,52 +20,34 @@ package org.apache.commons.net.io; import java.util.EventListener; /** - * The CopyStreamListener class can accept CopyStreamEvents to keep track - * of the progress of a stream copying operation. However, it is currently - * not used that way within NetComponents for performance reasons. Rather - * the bytesTransferred(long, int) method is called directly rather than - * passing an event to bytesTransferred(CopyStreamEvent), saving the creation - * of a CopyStreamEvent instance. Also, the only place where - * CopyStreamListener is currently used within NetComponents is in the - * static methods of the uninstantiable org.apache.commons.io.Util class, which - * would preclude the use of addCopyStreamListener and - * removeCopyStreamListener methods. However, future additions may use the - * JavaBean event model, which is why the hooks have been included from the - * beginning. + * The CopyStreamListener class can accept CopyStreamEvents to keep track of the progress of a stream copying operation. However, it is currently not used that + * way within NetComponents for performance reasons. Rather the bytesTransferred(long, int) method is called directly rather than passing an event to + * bytesTransferred(CopyStreamEvent), saving the creation of a CopyStreamEvent instance. Also, the only place where CopyStreamListener is currently used within + * NetComponents is in the static methods of the uninstantiable org.apache.commons.io.Util class, which would preclude the use of addCopyStreamListener and + * removeCopyStreamListener methods. However, future additions may use the JavaBean event model, which is why the hooks have been included from the beginning. * * * @see CopyStreamEvent * @see CopyStreamAdapter * @see Util - * @version $Id: CopyStreamListener.java 1652801 2015-01-18 17:10:05Z sebb $ */ -public interface CopyStreamListener extends EventListener -{ +public interface CopyStreamListener extends EventListener { /** - * This method is invoked by a CopyStreamEvent source after copying - * a block of bytes from a stream. The CopyStreamEvent will contain - * the total number of bytes transferred so far and the number of bytes - * transferred in the last write. - * @param event The CopyStreamEvent fired by the copying of a block of - * bytes. + * This method is invoked by a CopyStreamEvent source after copying a block of bytes from a stream. The CopyStreamEvent will contain the total number of + * bytes transferred so far and the number of bytes transferred in the last write. + * + * @param event The CopyStreamEvent fired by the copying of a block of bytes. */ - public void bytesTransferred(CopyStreamEvent event); - + void bytesTransferred(CopyStreamEvent event); /** - * This method is not part of the JavaBeans model and is used by the - * static methods in the org.apache.commons.io.Util class for efficiency. - * It is invoked after a block of bytes to inform the listener of the - * transfer. - * @param totalBytesTransferred The total number of bytes transferred - * so far by the copy operation. - * @param bytesTransferred The number of bytes copied by the most recent - * write. - * @param streamSize The number of bytes in the stream being copied. - * This may be equal to CopyStreamEvent.UNKNOWN_STREAM_SIZE if - * the size is unknown. + * This method is not part of the JavaBeans model and is used by the static methods in the org.apache.commons.io.Util class for efficiency. It is invoked + * after a block of bytes to inform the listener of the transfer. + * + * @param totalBytesTransferred The total number of bytes transferred so far by the copy operation. + * @param bytesTransferred The number of bytes copied by the most recent write. + * @param streamSize The number of bytes in the stream being copied. This may be equal to CopyStreamEvent.UNKNOWN_STREAM_SIZE if the size is + * unknown. */ - public void bytesTransferred(long totalBytesTransferred, - int bytesTransferred, - long streamSize); + void bytesTransferred(long totalBytesTransferred, int bytesTransferred, long streamSize); } diff --git a/src/main/java/org/apache/commons/net/io/DotTerminatedMessageReader.java b/src/main/java/org/apache/commons/net/io/DotTerminatedMessageReader.java index 49d3b5f..d8d76c5 100644 --- a/src/main/java/org/apache/commons/net/io/DotTerminatedMessageReader.java +++ b/src/main/java/org/apache/commons/net/io/DotTerminatedMessageReader.java @@ -21,24 +21,19 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; +import org.apache.commons.net.util.NetConstants; + /** - * DotTerminatedMessageReader is a class used to read messages from a - * server that are terminated by a single dot followed by a - * <CR><LF> - * sequence and with double dots appearing at the begining of lines which - * do not signal end of message yet start with a dot. Various Internet - * protocols such as NNTP and POP3 produce messages of this type. + * DotTerminatedMessageReader is a class used to read messages from a server that are terminated by a single dot followed by a <CR><LF> sequence and + * with double dots appearing at the begining of lines which do not signal end of message yet start with a dot. Various Internet protocols such as NNTP and POP3 + * produce messages of this type. * <p> - * This class handles stripping of the duplicate period at the beginning - * of lines starting with a period, and ensures you cannot read past the end of the message. + * This class handles stripping of the duplicate period at the beginning of lines starting with a period, and ensures you cannot read past the end of the + * message. * <p> - * Note: versions since 3.0 extend BufferedReader rather than Reader, - * and no longer change the CRLF into the local EOL. Also only DOT CR LF - * acts as EOF. - * @version $Id: DotTerminatedMessageReader.java 1747119 2016-06-07 02:22:24Z ggregory $ + * Note: versions since 3.0 extend BufferedReader rather than Reader, and no longer change the CRLF into the local EOL. Also only DOT CR LF acts as EOF. */ -public final class DotTerminatedMessageReader extends BufferedReader -{ +public final class DotTerminatedMessageReader extends BufferedReader { private static final char LF = '\n'; private static final char CR = '\r'; private static final int DOT = '.'; @@ -48,12 +43,11 @@ public final class DotTerminatedMessageReader extends BufferedReader private boolean seenCR; // was last character CR? /** - * Creates a DotTerminatedMessageReader that wraps an existing Reader - * input source. - * @param reader The Reader input source containing the message. + * Creates a DotTerminatedMessageReader that wraps an existing Reader input source. + * + * @param reader The Reader input source containing the message. */ - public DotTerminatedMessageReader(Reader reader) - { + public DotTerminatedMessageReader(final Reader reader) { super(reader); // Assumes input is at start of message atBeginning = true; @@ -61,46 +55,64 @@ public final class DotTerminatedMessageReader extends BufferedReader } /** - * Reads and returns the next character in the message. If the end of the - * message has been reached, returns -1. Note that a call to this method - * may result in multiple reads from the underlying input stream to decode - * the message properly (removing doubled dots and so on). All of - * this is transparent to the programmer and is only mentioned for - * completeness. - * @return The next character in the message. Returns -1 if the end of the - * message has been reached. - * @throws IOException If an error occurs while reading the underlying - * stream. + * Closes the message for reading. This doesn't actually close the underlying stream. The underlying stream may still be used for communicating with the + * server and therefore is not closed. + * <p> + * If the end of the message has not yet been reached, this method will read the remainder of the message until it reaches the end, so that the underlying + * stream may continue to be used properly for communicating with the server. If you do not fully read a message, you MUST close it, otherwise your program + * will likely hang or behave improperly. + * + * @throws IOException If an error occurs while reading the underlying stream. + */ + @Override + public void close() throws IOException { + synchronized (lock) { + if (!eof) { + while (read() != -1) { + // read to EOF + } + } + eof = true; + atBeginning = false; + } + } + + /** + * Reads and returns the next character in the message. If the end of the message has been reached, returns -1. Note that a call to this method may result + * in multiple reads from the underlying input stream to decode the message properly (removing doubled dots and so on). All of this is transparent to the + * programmer and is only mentioned for completeness. + * + * @return The next character in the message. Returns -1 if the end of the message has been reached. + * @throws IOException If an error occurs while reading the underlying stream. */ @Override public int read() throws IOException { synchronized (lock) { if (eof) { - return -1; // Don't allow read past EOF + return NetConstants.EOS; // Don't allow read past EOF } int chint = super.read(); - if (chint == -1) { // True EOF + if (chint == NetConstants.EOS) { // True EOF eof = true; - return -1; + return NetConstants.EOS; } if (atBeginning) { atBeginning = false; if (chint == DOT) { // Have DOT mark(2); // need to check for CR LF or DOT chint = super.read(); - if (chint == -1) { // Should not happen + switch (chint) { + case NetConstants.EOS: // new Throwable("Trailing DOT").printStackTrace(); eof = true; return DOT; // return the trailing DOT - } - if (chint == DOT) { // Have DOT DOT + case DOT: // no need to reset as we want to lose the first DOT return chint; // i.e. DOT - } - if (chint == CR) { // Have DOT CR + case CR: chint = super.read(); - if (chint == -1) { // Still only DOT CR - should not happen - //new Throwable("Trailing DOT CR").printStackTrace(); + if (chint == NetConstants.EOS) { // Still only DOT CR - should not happen + // new Throwable("Trailing DOT CR").printStackTrace(); reset(); // So CR is picked up next time return DOT; // return the trailing DOT } @@ -108,11 +120,14 @@ public final class DotTerminatedMessageReader extends BufferedReader atBeginning = true; eof = true; // Do we need to clear the mark somehow? - return -1; + return NetConstants.EOS; } + break; + default: + break; } // Should not happen - lone DOT at beginning - //new Throwable("Lone DOT followed by "+(char)chint).printStackTrace(); + // new Throwable("Lone DOT followed by "+(char)chint).printStackTrace(); reset(); return DOT; } // have DOT @@ -132,120 +147,74 @@ public final class DotTerminatedMessageReader extends BufferedReader } } - /** - * Reads the next characters from the message into an array and - * returns the number of characters read. Returns -1 if the end of the - * message has been reached. - * @param buffer The character array in which to store the characters. - * @return The number of characters read. Returns -1 if the - * end of the message has been reached. - * @throws IOException If an error occurs in reading the underlying - * stream. + * Reads the next characters from the message into an array and returns the number of characters read. Returns -1 if the end of the message has been + * reached. + * + * @param buffer The character array in which to store the characters. + * @return The number of characters read. Returns -1 if the end of the message has been reached. + * @throws IOException If an error occurs in reading the underlying stream. */ @Override - public int read(char[] buffer) throws IOException - { + public int read(final char[] buffer) throws IOException { return read(buffer, 0, buffer.length); } /** - * Reads the next characters from the message into an array and - * returns the number of characters read. Returns -1 if the end of the - * message has been reached. The characters are stored in the array - * starting from the given offset and up to the length specified. - * @param buffer The character array in which to store the characters. - * @param offset The offset into the array at which to start storing - * characters. - * @param length The number of characters to read. - * @return The number of characters read. Returns -1 if the - * end of the message has been reached. - * @throws IOException If an error occurs in reading the underlying - * stream. + * Reads the next characters from the message into an array and returns the number of characters read. Returns -1 if the end of the message has been + * reached. The characters are stored in the array starting from the given offset and up to the length specified. + * + * @param buffer The character array in which to store the characters. + * @param offset The offset into the array at which to start storing characters. + * @param length The number of characters to read. + * @return The number of characters read. Returns -1 if the end of the message has been reached. + * @throws IOException If an error occurs in reading the underlying stream. */ @Override - public int read(char[] buffer, int offset, int length) throws IOException - { - if (length < 1) - { + public int read(final char[] buffer, int offset, int length) throws IOException { + if (length < 1) { return 0; } int ch; - synchronized (lock) - { - if ((ch = read()) == -1) - { - return -1; + synchronized (lock) { + if ((ch = read()) == -1) { + return NetConstants.EOS; } - int off = offset; + final int off = offset; - do - { + do { buffer[offset++] = (char) ch; - } - while (--length > 0 && (ch = read()) != -1); - - return (offset - off); - } - } + } while (--length > 0 && (ch = read()) != -1); - /** - * Closes the message for reading. This doesn't actually close the - * underlying stream. The underlying stream may still be used for - * communicating with the server and therefore is not closed. - * <p> - * If the end of the message has not yet been reached, this method - * will read the remainder of the message until it reaches the end, - * so that the underlying stream may continue to be used properly - * for communicating with the server. If you do not fully read - * a message, you MUST close it, otherwise your program will likely - * hang or behave improperly. - * @throws IOException If an error occurs while reading the - * underlying stream. - */ - @Override - public void close() throws IOException - { - synchronized (lock) - { - if (!eof) - { - while (read() != -1) - { - // read to EOF - } - } - eof = true; - atBeginning = false; + return offset - off; } } /** - * Read a line of text. - * A line is considered to be terminated by carriage return followed immediately by a linefeed. - * This contrasts with BufferedReader which also allows other combinations. + * Read a line of text. A line is considered to be terminated by carriage return followed immediately by a linefeed. This contrasts with BufferedReader + * which also allows other combinations. + * * @since 3.0 */ @Override public String readLine() throws IOException { - StringBuilder sb = new StringBuilder(); + final StringBuilder sb = new StringBuilder(); int intch; - synchronized(lock) { // make thread-safe (hopefully!) - while((intch = read()) != -1) - { + synchronized (lock) { // make thread-safe (hopefully!) + while ((intch = read()) != NetConstants.EOS) { if (intch == LF && atBeginning) { - return sb.substring(0, sb.length()-1); + return sb.substring(0, sb.length() - 1); } sb.append((char) intch); } } - String string = sb.toString(); - if (string.length() == 0) { // immediate EOF + final String string = sb.toString(); + if (string.isEmpty()) { // immediate EOF return null; } // Should not happen - EOF without CRLF - //new Throwable(string).printStackTrace(); + // new Throwable(string).printStackTrace(); return string; } } diff --git a/src/main/java/org/apache/commons/net/io/DotTerminatedMessageWriter.java b/src/main/java/org/apache/commons/net/io/DotTerminatedMessageWriter.java index cb98cb8..af8c382 100644 --- a/src/main/java/org/apache/commons/net/io/DotTerminatedMessageWriter.java +++ b/src/main/java/org/apache/commons/net/io/DotTerminatedMessageWriter.java @@ -20,200 +20,159 @@ package org.apache.commons.net.io; import java.io.IOException; import java.io.Writer; -/*** - * DotTerminatedMessageWriter is a class used to write messages to a - * server that are terminated by a single dot followed by a - * <CR><LF> - * sequence and with double dots appearing at the begining of lines which - * do not signal end of message yet start with a dot. Various Internet - * protocols such as NNTP and POP3 produce messages of this type. +/** + * DotTerminatedMessageWriter is a class used to write messages to a server that are terminated by a single dot followed by a <CR><LF> sequence and + * with double dots appearing at the begining of lines which do not signal end of message yet start with a dot. Various Internet protocols such as NNTP and POP3 + * produce messages of this type. * <p> - * This class handles the doubling of line-starting periods, - * converts single linefeeds to NETASCII newlines, and on closing - * will send the final message terminator dot and NETASCII newline - * sequence. + * This class handles the doubling of line-starting periods, converts single linefeeds to NETASCII newlines, and on closing will send the final message + * terminator dot and NETASCII newline sequence. * * - ***/ - -public final class DotTerminatedMessageWriter extends Writer -{ - private static final int __NOTHING_SPECIAL_STATE = 0; - private static final int __LAST_WAS_CR_STATE = 1; - private static final int __LAST_WAS_NL_STATE = 2; + */ - private int __state; - private Writer __output; +public final class DotTerminatedMessageWriter extends Writer { + private static final int NOTHING_SPECIAL_STATE = 0; + private static final int LAST_WAS_CR_STATE = 1; + private static final int LAST_WAS_NL_STATE = 2; + private int state; + private Writer output; - /*** - * Creates a DotTerminatedMessageWriter that wraps an existing Writer - * output destination. + /** + * Creates a DotTerminatedMessageWriter that wraps an existing Writer output destination. * - * @param output The Writer output destination to write the message. - ***/ - public DotTerminatedMessageWriter(Writer output) - { + * @param output The Writer output destination to write the message. + */ + public DotTerminatedMessageWriter(final Writer output) { super(output); - __output = output; - __state = __NOTHING_SPECIAL_STATE; + this.output = output; + this.state = NOTHING_SPECIAL_STATE; } - - /*** - * Writes a character to the output. Note that a call to this method - * may result in multiple writes to the underling Writer in order to - * convert naked linefeeds to NETASCII line separators and to double - * line-leading periods. This is transparent to the programmer and - * is only mentioned for completeness. + /** + * Flushes the underlying output, writing all buffered output, but doesn't actually close the underlying stream. The underlying stream may still be used for + * communicating with the server and therefore is not closed. * - * @param ch The character to write. - * @throws IOException If an error occurs while writing to the - * underlying output. - ***/ + * @throws IOException If an error occurs while writing to the underlying output or closing the Writer. + */ @Override - public void write(int ch) throws IOException - { - synchronized (lock) - { - switch (ch) - { - case '\r': - __state = __LAST_WAS_CR_STATE; - __output.write('\r'); - return ; - case '\n': - if (__state != __LAST_WAS_CR_STATE) { - __output.write('\r'); - } - __output.write('\n'); - __state = __LAST_WAS_NL_STATE; - return ; - case '.': - // Double the dot at the beginning of a line - if (__state == __LAST_WAS_NL_STATE) { - __output.write('.'); - } - //$FALL-THROUGH$ - default: - __state = __NOTHING_SPECIAL_STATE; - __output.write(ch); - return ; + public void close() throws IOException { + synchronized (lock) { + if (output == null) { + return; } + + if (state == LAST_WAS_CR_STATE) { + output.write('\n'); + } else if (state != LAST_WAS_NL_STATE) { + output.write("\r\n"); + } + + output.write(".\r\n"); + + output.flush(); + output = null; } } - - /*** - * Writes a number of characters from a character array to the output - * starting from a given offset. + /** + * Flushes the underlying output, writing all buffered output. * - * @param buffer The character array to write. - * @param offset The offset into the array at which to start copying data. - * @param length The number of characters to write. - * @throws IOException If an error occurs while writing to the underlying - * output. - ***/ + * @throws IOException If an error occurs while writing to the underlying output. + */ @Override - public void write(char[] buffer, int offset, int length) throws IOException - { - synchronized (lock) - { - while (length-- > 0) { - write(buffer[offset++]); - } + public void flush() throws IOException { + synchronized (lock) { + output.flush(); } } - - /*** + /** * Writes a character array to the output. * - * @param buffer The character array to write. - * @throws IOException If an error occurs while writing to the underlying - * output. - ***/ + * @param buffer The character array to write. + * @throws IOException If an error occurs while writing to the underlying output. + */ @Override - public void write(char[] buffer) throws IOException - { + public void write(final char[] buffer) throws IOException { write(buffer, 0, buffer.length); } - - /*** - * Writes a String to the output. + /** + * Writes a number of characters from a character array to the output starting from a given offset. * - * @param string The String to write. - * @throws IOException If an error occurs while writing to the underlying - * output. - ***/ + * @param buffer The character array to write. + * @param offset The offset into the array at which to start copying data. + * @param length The number of characters to write. + * @throws IOException If an error occurs while writing to the underlying output. + */ @Override - public void write(String string) throws IOException - { - write(string.toCharArray()); + public void write(final char[] buffer, int offset, int length) throws IOException { + synchronized (lock) { + while (length-- > 0) { + write(buffer[offset++]); + } + } } - - /*** - * Writes part of a String to the output starting from a given offset. + /** + * Writes a character to the output. Note that a call to this method may result in multiple writes to the underling Writer in order to convert naked + * linefeeds to NETASCII line separators and to double line-leading periods. This is transparent to the programmer and is only mentioned for completeness. * - * @param string The String to write. - * @param offset The offset into the String at which to start copying data. - * @param length The number of characters to write. - * @throws IOException If an error occurs while writing to the underlying - * output. - ***/ + * @param ch The character to write. + * @throws IOException If an error occurs while writing to the underlying output. + */ @Override - public void write(String string, int offset, int length) throws IOException - { - write(string.toCharArray(), offset, length); + public void write(final int ch) throws IOException { + synchronized (lock) { + switch (ch) { + case '\r': + state = LAST_WAS_CR_STATE; + output.write('\r'); + return; + case '\n': + if (state != LAST_WAS_CR_STATE) { + output.write('\r'); + } + output.write('\n'); + state = LAST_WAS_NL_STATE; + return; + case '.': + // Double the dot at the beginning of a line + if (state == LAST_WAS_NL_STATE) { + output.write('.'); + } + //$FALL-THROUGH$ + default: + state = NOTHING_SPECIAL_STATE; + output.write(ch); + } + } } - - /*** - * Flushes the underlying output, writing all buffered output. + /** + * Writes a String to the output. * - * @throws IOException If an error occurs while writing to the underlying - * output. - ***/ + * @param string The String to write. + * @throws IOException If an error occurs while writing to the underlying output. + */ @Override - public void flush() throws IOException - { - synchronized (lock) - { - __output.flush(); - } + public void write(final String string) throws IOException { + write(string.toCharArray()); } - - /*** - * Flushes the underlying output, writing all buffered output, but doesn't - * actually close the underlying stream. The underlying stream may still - * be used for communicating with the server and therefore is not closed. + /** + * Writes part of a String to the output starting from a given offset. * - * @throws IOException If an error occurs while writing to the underlying - * output or closing the Writer. - ***/ + * @param string The String to write. + * @param offset The offset into the String at which to start copying data. + * @param length The number of characters to write. + * @throws IOException If an error occurs while writing to the underlying output. + */ @Override - public void close() throws IOException - { - synchronized (lock) - { - if (__output == null) { - return ; - } - - if (__state == __LAST_WAS_CR_STATE) { - __output.write('\n'); - } else if (__state != __LAST_WAS_NL_STATE) { - __output.write("\r\n"); - } - - __output.write(".\r\n"); - - __output.flush(); - __output = null; - } + public void write(final String string, final int offset, final int length) throws IOException { + write(string.toCharArray(), offset, length); } } diff --git a/src/main/java/org/apache/commons/net/io/FromNetASCIIInputStream.java b/src/main/java/org/apache/commons/net/io/FromNetASCIIInputStream.java index 3d1b231..9beb056 100644 --- a/src/main/java/org/apache/commons/net/io/FromNetASCIIInputStream.java +++ b/src/main/java/org/apache/commons/net/io/FromNetASCIIInputStream.java @@ -20,149 +20,105 @@ package org.apache.commons.net.io; import java.io.IOException; import java.io.InputStream; import java.io.PushbackInputStream; -import java.io.UnsupportedEncodingException; - -/*** - * This class wraps an input stream, replacing all occurrences - * of <CR><LF> (carriage return followed by a linefeed), - * which is the NETASCII standard for representing a newline, with the - * local line separator representation. You would use this class to - * implement ASCII file transfers requiring conversion from NETASCII. +import java.nio.charset.StandardCharsets; + +import org.apache.commons.net.util.NetConstants; + +/** + * This class wraps an input stream, replacing all occurrences of <CR><LF> (carriage return followed by a linefeed), which is the NETASCII standard + * for representing a newline, with the local line separator representation. You would use this class to implement ASCII file transfers requiring conversion + * from NETASCII. * * - ***/ + */ -public final class FromNetASCIIInputStream extends PushbackInputStream -{ +public final class FromNetASCIIInputStream extends PushbackInputStream { static final boolean _noConversionRequired; static final String _lineSeparator; static final byte[] _lineSeparatorBytes; static { - _lineSeparator = System.getProperty("line.separator"); + _lineSeparator = System.lineSeparator(); _noConversionRequired = _lineSeparator.equals("\r\n"); - try { - _lineSeparatorBytes = _lineSeparator.getBytes("US-ASCII"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Broken JVM - cannot find US-ASCII charset!",e); - } + _lineSeparatorBytes = _lineSeparator.getBytes(StandardCharsets.US_ASCII); } - private int __length = 0; - - /*** - * Returns true if the NetASCII line separator differs from the system - * line separator, false if they are the same. This method is useful - * to determine whether or not you need to instantiate a - * FromNetASCIIInputStream object. + /** + * Returns true if the NetASCII line separator differs from the system line separator, false if they are the same. This method is useful to determine + * whether or not you need to instantiate a FromNetASCIIInputStream object. * - * @return True if the NETASCII line separator differs from the local - * system line separator, false if they are the same. - ***/ - public static final boolean isConversionRequired() - { + * @return True if the NETASCII line separator differs from the local system line separator, false if they are the same. + */ + public static boolean isConversionRequired() { return !_noConversionRequired; } - /*** - * Creates a FromNetASCIIInputStream instance that wraps an existing - * InputStream. + private int length; + + /** + * Creates a FromNetASCIIInputStream instance that wraps an existing InputStream. + * * @param input the stream to wrap - ***/ - public FromNetASCIIInputStream(InputStream input) - { + */ + public FromNetASCIIInputStream(final InputStream input) { super(input, _lineSeparatorBytes.length + 1); } - - private int __read() throws IOException - { - int ch; - - ch = super.read(); - - if (ch == '\r') - { - ch = super.read(); - if (ch == '\n') - { - unread(_lineSeparatorBytes); - ch = super.read(); - // This is a kluge for read(byte[], ...) to read the right amount - --__length; - } - else - { - if (ch != -1) { - unread(ch); - } - return '\r'; - } + // PushbackInputStream in JDK 1.1.3 returns the wrong thing + // TODO - can we delete this override now? + /** + * Returns the number of bytes that can be read without blocking EXCEPT when newline conversions have to be made somewhere within the available block of + * bytes. In other words, you really should not rely on the value returned by this method if you are trying to avoid blocking. + */ + @Override + public int available() throws IOException { + if (in == null) { + throw new IOException("Stream closed"); } - - return ch; + return buf.length - pos + in.available(); } - - /*** - * Reads and returns the next byte in the stream. If the end of the - * message has been reached, returns -1. Note that a call to this method - * may result in multiple reads from the underlying input stream in order - * to convert NETASCII line separators to the local line separator format. - * This is transparent to the programmer and is only mentioned for - * completeness. + /** + * Reads and returns the next byte in the stream. If the end of the message has been reached, returns -1. Note that a call to this method may result in + * multiple reads from the underlying input stream in order to convert NETASCII line separators to the local line separator format. This is transparent to + * the programmer and is only mentioned for completeness. * - * @return The next character in the stream. Returns -1 if the end of the - * stream has been reached. - * @throws IOException If an error occurs while reading the underlying - * stream. - ***/ + * @return The next character in the stream. Returns -1 if the end of the stream has been reached. + * @throws IOException If an error occurs while reading the underlying stream. + */ @Override - public int read() throws IOException - { + public int read() throws IOException { if (_noConversionRequired) { return super.read(); } - return __read(); + return readInt(); } - - /*** - * Reads the next number of bytes from the stream into an array and - * returns the number of bytes read. Returns -1 if the end of the - * stream has been reached. + /** + * Reads the next number of bytes from the stream into an array and returns the number of bytes read. Returns -1 if the end of the stream has been reached. * - * @param buffer The byte array in which to store the data. - * @return The number of bytes read. Returns -1 if the - * end of the message has been reached. - * @throws IOException If an error occurs in reading the underlying - * stream. - ***/ + * @param buffer The byte array in which to store the data. + * @return The number of bytes read. Returns -1 if the end of the message has been reached. + * @throws IOException If an error occurs in reading the underlying stream. + */ @Override - public int read(byte buffer[]) throws IOException - { + public int read(final byte buffer[]) throws IOException { return read(buffer, 0, buffer.length); } - - /*** - * Reads the next number of bytes from the stream into an array and returns - * the number of bytes read. Returns -1 if the end of the - * message has been reached. The characters are stored in the array - * starting from the given offset and up to the length specified. + /** + * Reads the next number of bytes from the stream into an array and returns the number of bytes read. Returns -1 if the end of the message has been reached. + * The characters are stored in the array starting from the given offset and up to the length specified. * * @param buffer The byte array in which to store the data. - * @param offset The offset into the array at which to start storing data. - * @param length The number of bytes to read. - * @return The number of bytes read. Returns -1 if the - * end of the stream has been reached. - * @throws IOException If an error occurs while reading the underlying - * stream. - ***/ + * @param offset The offset into the array at which to start storing data. + * @param length The number of bytes to read. + * @return The number of bytes read. Returns -1 if the end of the stream has been reached. + * @throws IOException If an error occurs while reading the underlying stream. + */ @Override - public int read(byte buffer[], int offset, int length) throws IOException - { + public int read(final byte buffer[], int offset, final int length) throws IOException { if (_noConversionRequired) { return super.read(buffer, offset, length); } @@ -171,51 +127,51 @@ public final class FromNetASCIIInputStream extends PushbackInputStream return 0; } - int ch, off; + int ch; + final int off; ch = available(); - __length = (length > ch ? ch : length); + this.length = Math.min(length, ch); // If nothing is available, block to read only one character - if (__length < 1) { - __length = 1; + if (this.length < 1) { + this.length = 1; } - - if ((ch = __read()) == -1) { - return -1; + if ((ch = readInt()) == -1) { + return NetConstants.EOS; } off = offset; - do - { - buffer[offset++] = (byte)ch; - } - while (--__length > 0 && (ch = __read()) != -1); + do { + buffer[offset++] = (byte) ch; + } while (--this.length > 0 && (ch = readInt()) != -1); - - return (offset - off); + return offset - off; } + private int readInt() throws IOException { + int ch; - // PushbackInputStream in JDK 1.1.3 returns the wrong thing - // TODO - can we delete this override now? - /*** - * Returns the number of bytes that can be read without blocking EXCEPT - * when newline conversions have to be made somewhere within the - * available block of bytes. In other words, you really should not - * rely on the value returned by this method if you are trying to avoid - * blocking. - ***/ - @Override - public int available() throws IOException - { - if (in == null) { - throw new IOException("Stream closed"); + ch = super.read(); + + if (ch == '\r') { + ch = super.read(); + if (ch != '\n') { + if (ch != -1) { + unread(ch); + } + return '\r'; + } + unread(_lineSeparatorBytes); + ch = super.read(); + // This is a kluge for read(byte[], ...) to read the right amount + --length; } - return (buf.length - pos) + in.available(); + + return ch; } } diff --git a/src/main/java/org/apache/commons/net/io/FromNetASCIIOutputStream.java b/src/main/java/org/apache/commons/net/io/FromNetASCIIOutputStream.java index 412addc..d1868b9 100644 --- a/src/main/java/org/apache/commons/net/io/FromNetASCIIOutputStream.java +++ b/src/main/java/org/apache/commons/net/io/FromNetASCIIOutputStream.java @@ -21,155 +21,120 @@ import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; -/*** - * This class wraps an output stream, replacing all occurrences - * of <CR><LF> (carriage return followed by a linefeed), - * which is the NETASCII standard for representing a newline, with the - * local line separator representation. You would use this class to - * implement ASCII file transfers requiring conversion from NETASCII. +/** + * This class wraps an output stream, replacing all occurrences of <CR><LF> (carriage return followed by a linefeed), which is the NETASCII standard + * for representing a newline, with the local line separator representation. You would use this class to implement ASCII file transfers requiring conversion + * from NETASCII. * <p> - * Because of the translation process, a call to <code>flush()</code> will - * not flush the last byte written if that byte was a carriage - * return. A call to {@link #close close() }, however, will - * flush the carriage return. + * Because of the translation process, a call to <code>flush()</code> will not flush the last byte written if that byte was a carriage return. A call to + * {@link #close close() }, however, will flush the carriage return. * * - ***/ + */ -public final class FromNetASCIIOutputStream extends FilterOutputStream -{ - private boolean __lastWasCR; +public final class FromNetASCIIOutputStream extends FilterOutputStream { + private boolean lastWasCR; - /*** - * Creates a FromNetASCIIOutputStream instance that wraps an existing - * OutputStream. + /** + * Creates a FromNetASCIIOutputStream instance that wraps an existing OutputStream. * - * @param output The OutputStream to wrap. - ***/ - public FromNetASCIIOutputStream(OutputStream output) - { + * @param output The OutputStream to wrap. + */ + public FromNetASCIIOutputStream(final OutputStream output) { super(output); - __lastWasCR = false; - } - - - private void __write(int ch) throws IOException - { - switch (ch) - { - case '\r': - __lastWasCR = true; - // Don't write anything. We need to see if next one is linefeed - break; - case '\n': - if (__lastWasCR) - { - out.write(FromNetASCIIInputStream._lineSeparatorBytes); - __lastWasCR = false; - break; - } - __lastWasCR = false; - out.write('\n'); - break; - default: - if (__lastWasCR) - { - out.write('\r'); - __lastWasCR = false; - } - out.write(ch); - break; - } + lastWasCR = false; } - - /*** - * Writes a byte to the stream. Note that a call to this method - * might not actually write a byte to the underlying stream until a - * subsequent character is written, from which it can be determined if - * a NETASCII line separator was encountered. - * This is transparent to the programmer and is only mentioned for - * completeness. + /** + * Closes the stream, writing all pending data. * - * @param ch The byte to write. - * @throws IOException If an error occurs while writing to the underlying - * stream. - ***/ + * @throws IOException If an error occurs while closing the stream. + */ @Override - public synchronized void write(int ch) - throws IOException - { - if (FromNetASCIIInputStream._noConversionRequired) - { - out.write(ch); - return ; + public synchronized void close() throws IOException { + if (FromNetASCIIInputStream._noConversionRequired) { + super.close(); + return; } - __write(ch); + if (lastWasCR) { + out.write('\r'); + } + super.close(); } - - /*** + /** * Writes a byte array to the stream. * - * @param buffer The byte array to write. - * @throws IOException If an error occurs while writing to the underlying - * stream. - ***/ + * @param buffer The byte array to write. + * @throws IOException If an error occurs while writing to the underlying stream. + */ @Override - public synchronized void write(byte buffer[]) - throws IOException - { + public synchronized void write(final byte buffer[]) throws IOException { write(buffer, 0, buffer.length); } - - /*** - * Writes a number of bytes from a byte array to the stream starting from - * a given offset. + /** + * Writes a number of bytes from a byte array to the stream starting from a given offset. * - * @param buffer The byte array to write. - * @param offset The offset into the array at which to start copying data. - * @param length The number of bytes to write. - * @throws IOException If an error occurs while writing to the underlying - * stream. - ***/ + * @param buffer The byte array to write. + * @param offset The offset into the array at which to start copying data. + * @param length The number of bytes to write. + * @throws IOException If an error occurs while writing to the underlying stream. + */ @Override - public synchronized void write(byte buffer[], int offset, int length) - throws IOException - { - if (FromNetASCIIInputStream._noConversionRequired) - { + public synchronized void write(final byte buffer[], int offset, int length) throws IOException { + if (FromNetASCIIInputStream._noConversionRequired) { // FilterOutputStream method is very slow. - //super.write(buffer, offset, length); + // super.write(buffer, offset, length); out.write(buffer, offset, length); - return ; + return; } while (length-- > 0) { - __write(buffer[offset++]); + writeInt(buffer[offset++]); } } - - /*** - * Closes the stream, writing all pending data. + /** + * Writes a byte to the stream. Note that a call to this method might not actually write a byte to the underlying stream until a subsequent character is + * written, from which it can be determined if a NETASCII line separator was encountered. This is transparent to the programmer and is only mentioned for + * completeness. * - * @throws IOException If an error occurs while closing the stream. - ***/ + * @param ch The byte to write. + * @throws IOException If an error occurs while writing to the underlying stream. + */ @Override - public synchronized void close() - throws IOException - { - if (FromNetASCIIInputStream._noConversionRequired) - { - super.close(); - return ; + public synchronized void write(final int ch) throws IOException { + if (FromNetASCIIInputStream._noConversionRequired) { + out.write(ch); + return; } - if (__lastWasCR) { - out.write('\r'); + writeInt(ch); + } + + private void writeInt(final int ch) throws IOException { + switch (ch) { + case '\r': + lastWasCR = true; + // Don't write anything. We need to see if next one is linefeed + break; + case '\n': + if (lastWasCR) { + out.write(FromNetASCIIInputStream._lineSeparatorBytes); + lastWasCR = false; + break; + } + out.write('\n'); + break; + default: + if (lastWasCR) { + out.write('\r'); + lastWasCR = false; + } + out.write(ch); + break; } - super.close(); } } diff --git a/src/main/java/org/apache/commons/net/io/SocketInputStream.java b/src/main/java/org/apache/commons/net/io/SocketInputStream.java index b84a4a2..f38c332 100644 --- a/src/main/java/org/apache/commons/net/io/SocketInputStream.java +++ b/src/main/java/org/apache/commons/net/io/SocketInputStream.java @@ -22,47 +22,35 @@ import java.io.IOException; import java.io.InputStream; import java.net.Socket; -/*** - * This class wraps an input stream, storing a reference to its originating - * socket. When the stream is closed, it will also close the socket - * immediately afterward. This class is useful for situations where you - * are dealing with a stream originating from a socket, but do not have - * a reference to the socket, and want to make sure it closes when the - * stream closes. - * +/** + * This class wraps an input stream, storing a reference to its originating socket. When the stream is closed, it will also close the socket immediately + * afterward. This class is useful for situations where you are dealing with a stream originating from a socket, but do not have a reference to the socket, and + * want to make sure it closes when the stream closes. * * @see SocketOutputStream - ***/ - -public class SocketInputStream extends FilterInputStream -{ - private final Socket __socket; + */ +public class SocketInputStream extends FilterInputStream { + private final Socket socket; - /*** - * Creates a SocketInputStream instance wrapping an input stream and - * storing a reference to a socket that should be closed on closing - * the stream. + /** + * Creates a SocketInputStream instance wrapping an input stream and storing a reference to a socket that should be closed on closing the stream. * - * @param socket The socket to close on closing the stream. - * @param stream The input stream to wrap. - ***/ - public SocketInputStream(Socket socket, InputStream stream) - { + * @param socket The socket to close on closing the stream. + * @param stream The input stream to wrap. + */ + public SocketInputStream(final Socket socket, final InputStream stream) { super(stream); - __socket = socket; + this.socket = socket; } - /*** - * Closes the stream and immediately afterward closes the referenced - * socket. + /** + * Closes the stream and immediately afterward closes the referenced socket. * - * @throws IOException If there is an error in closing the stream - * or socket. - ***/ + * @throws IOException If there is an error in closing the stream or socket. + */ @Override - public void close() throws IOException - { + public void close() throws IOException { super.close(); - __socket.close(); + socket.close(); } } diff --git a/src/main/java/org/apache/commons/net/io/SocketOutputStream.java b/src/main/java/org/apache/commons/net/io/SocketOutputStream.java index 6af8f54..8d4eb52 100644 --- a/src/main/java/org/apache/commons/net/io/SocketOutputStream.java +++ b/src/main/java/org/apache/commons/net/io/SocketOutputStream.java @@ -22,67 +22,51 @@ import java.io.IOException; import java.io.OutputStream; import java.net.Socket; -/*** - * This class wraps an output stream, storing a reference to its originating - * socket. When the stream is closed, it will also close the socket - * immediately afterward. This class is useful for situations where you - * are dealing with a stream originating from a socket, but do not have - * a reference to the socket, and want to make sure it closes when the - * stream closes. +/** + * This class wraps an output stream, storing a reference to its originating socket. When the stream is closed, it will also close the socket immediately + * afterward. This class is useful for situations where you are dealing with a stream originating from a socket, but do not have a reference to the socket, and + * want to make sure it closes when the stream closes. * * * @see SocketInputStream - ***/ + */ -public class SocketOutputStream extends FilterOutputStream -{ - private final Socket __socket; +public class SocketOutputStream extends FilterOutputStream { + private final Socket socket; - /*** - * Creates a SocketOutputStream instance wrapping an output stream and - * storing a reference to a socket that should be closed on closing - * the stream. + /** + * Creates a SocketOutputStream instance wrapping an output stream and storing a reference to a socket that should be closed on closing the stream. * - * @param socket The socket to close on closing the stream. - * @param stream The input stream to wrap. - ***/ - public SocketOutputStream(Socket socket, OutputStream stream) - { + * @param socket The socket to close on closing the stream. + * @param stream The input stream to wrap. + */ + public SocketOutputStream(final Socket socket, final OutputStream stream) { super(stream); - __socket = socket; + this.socket = socket; } - - /*** - * Writes a number of bytes from a byte array to the stream starting from - * a given offset. This method bypasses the equivalent method in - * FilterOutputStream because the FilterOutputStream implementation is - * very inefficient. + /** + * Closes the stream and immediately afterward closes the referenced socket. * - * @param buffer The byte array to write. - * @param offset The offset into the array at which to start copying data. - * @param length The number of bytes to write. - * @throws IOException If an error occurs while writing to the underlying - * stream. - ***/ + * @throws IOException If there is an error in closing the stream or socket. + */ @Override - public void write(byte buffer[], int offset, int length) throws IOException - { - out.write(buffer, offset, length); + public void close() throws IOException { + super.close(); + socket.close(); } - - /*** - * Closes the stream and immediately afterward closes the referenced - * socket. + /** + * Writes a number of bytes from a byte array to the stream starting from a given offset. This method bypasses the equivalent method in FilterOutputStream + * because the FilterOutputStream implementation is very inefficient. * - * @throws IOException If there is an error in closing the stream - * or socket. - ***/ + * @param buffer The byte array to write. + * @param offset The offset into the array at which to start copying data. + * @param length The number of bytes to write. + * @throws IOException If an error occurs while writing to the underlying stream. + */ @Override - public void close() throws IOException - { - super.close(); - __socket.close(); + public void write(final byte buffer[], final int offset, final int length) throws IOException { + out.write(buffer, offset, length); } } diff --git a/src/main/java/org/apache/commons/net/io/ToNetASCIIInputStream.java b/src/main/java/org/apache/commons/net/io/ToNetASCIIInputStream.java index f0ec930..3af2bc5 100644 --- a/src/main/java/org/apache/commons/net/io/ToNetASCIIInputStream.java +++ b/src/main/java/org/apache/commons/net/io/ToNetASCIIInputStream.java @@ -21,116 +21,112 @@ import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; -/*** - * This class wraps an input stream, replacing all singly occurring - * <LF> (linefeed) characters with <CR><LF> (carriage return - * followed by linefeed), which is the NETASCII standard for representing - * a newline. - * You would use this class to implement ASCII file transfers requiring - * conversion to NETASCII. +import org.apache.commons.net.util.NetConstants; + +/** + * This class wraps an input stream, replacing all singly occurring <LF> (linefeed) characters with <CR><LF> (carriage return followed by + * linefeed), which is the NETASCII standard for representing a newline. You would use this class to implement ASCII file transfers requiring conversion to + * NETASCII. * * - ***/ - -public final class ToNetASCIIInputStream extends FilterInputStream -{ - private static final int __NOTHING_SPECIAL = 0; - private static final int __LAST_WAS_CR = 1; - private static final int __LAST_WAS_NL = 2; - private int __status; - - /*** - * Creates a ToNetASCIIInputStream instance that wraps an existing - * InputStream. + */ + +public final class ToNetASCIIInputStream extends FilterInputStream { + private static final int NOTHING_SPECIAL = 0; + private static final int LAST_WAS_CR = 1; + private static final int LAST_WAS_NL = 2; + private int status; + + /** + * Creates a ToNetASCIIInputStream instance that wraps an existing InputStream. * - * @param input The InputStream to wrap. - ***/ - public ToNetASCIIInputStream(InputStream input) - { + * @param input The InputStream to wrap. + */ + public ToNetASCIIInputStream(final InputStream input) { super(input); - __status = __NOTHING_SPECIAL; + status = NOTHING_SPECIAL; + } + + @Override + public int available() throws IOException { + final int result; + + result = in.available(); + + if (status == LAST_WAS_NL) { + return result + 1; + } + + return result; } + /** Returns false. Mark is not supported. */ + @Override + public boolean markSupported() { + return false; + } - /*** - * Reads and returns the next byte in the stream. If the end of the - * message has been reached, returns -1. + /** + * Reads and returns the next byte in the stream. If the end of the message has been reached, returns -1. * - * @return The next character in the stream. Returns -1 if the end of the - * stream has been reached. - * @throws IOException If an error occurs while reading the underlying - * stream. - ***/ + * @return The next character in the stream. Returns -1 if the end of the stream has been reached. + * @throws IOException If an error occurs while reading the underlying stream. + */ @Override - public int read() throws IOException - { - int ch; + public int read() throws IOException { + final int ch; - if (__status == __LAST_WAS_NL) - { - __status = __NOTHING_SPECIAL; + if (status == LAST_WAS_NL) { + status = NOTHING_SPECIAL; return '\n'; } ch = in.read(); - switch (ch) - { + switch (ch) { case '\r': - __status = __LAST_WAS_CR; + status = LAST_WAS_CR; return '\r'; case '\n': - if (__status != __LAST_WAS_CR) - { - __status = __LAST_WAS_NL; + if (status != LAST_WAS_CR) { + status = LAST_WAS_NL; return '\r'; } //$FALL-THROUGH$ default: - __status = __NOTHING_SPECIAL; + status = NOTHING_SPECIAL; return ch; } // statement not reached - //return ch; + // return ch; } - - /*** - * Reads the next number of bytes from the stream into an array and - * returns the number of bytes read. Returns -1 if the end of the - * stream has been reached. + /** + * Reads the next number of bytes from the stream into an array and returns the number of bytes read. Returns -1 if the end of the stream has been reached. * - * @param buffer The byte array in which to store the data. - * @return The number of bytes read. Returns -1 if the - * end of the message has been reached. - * @throws IOException If an error occurs in reading the underlying - * stream. - ***/ + * @param buffer The byte array in which to store the data. + * @return The number of bytes read. Returns -1 if the end of the message has been reached. + * @throws IOException If an error occurs in reading the underlying stream. + */ @Override - public int read(byte buffer[]) throws IOException - { + public int read(final byte[] buffer) throws IOException { return read(buffer, 0, buffer.length); } - - /*** - * Reads the next number of bytes from the stream into an array and returns - * the number of bytes read. Returns -1 if the end of the - * message has been reached. The characters are stored in the array - * starting from the given offset and up to the length specified. + /** + * Reads the next number of bytes from the stream into an array and returns the number of bytes read. Returns -1 if the end of the message has been reached. + * The characters are stored in the array starting from the given offset and up to the length specified. * * @param buffer The byte array in which to store the data. - * @param offset The offset into the array at which to start storing data. - * @param length The number of bytes to read. - * @return The number of bytes read. Returns -1 if the - * end of the stream has been reached. - * @throws IOException If an error occurs while reading the underlying - * stream. - ***/ + * @param offset The offset into the array at which to start storing data. + * @param length The number of bytes to read. + * @return The number of bytes read. Returns -1 if the end of the stream has been reached. + * @throws IOException If an error occurs while reading the underlying stream. + */ @Override - public int read(byte buffer[], int offset, int length) throws IOException - { - int ch, off; + public int read(final byte[] buffer, int offset, int length) throws IOException { + int ch; + final int off; if (length < 1) { return 0; @@ -147,39 +143,16 @@ public final class ToNetASCIIInputStream extends FilterInputStream length = 1; } - if ((ch = read()) == -1) { - return -1; + if ((ch = read()) == NetConstants.EOS) { + return NetConstants.EOS; } off = offset; - do - { - buffer[offset++] = (byte)ch; - } - while (--length > 0 && (ch = read()) != -1); + do { + buffer[offset++] = (byte) ch; + } while (--length > 0 && (ch = read()) != NetConstants.EOS); - return (offset - off); - } - - /*** Returns false. Mark is not supported. ***/ - @Override - public boolean markSupported() - { - return false; - } - - @Override - public int available() throws IOException - { - int result; - - result = in.available(); - - if (__status == __LAST_WAS_NL) { - return (result + 1); - } - - return result; + return offset - off; } } diff --git a/src/main/java/org/apache/commons/net/io/ToNetASCIIOutputStream.java b/src/main/java/org/apache/commons/net/io/ToNetASCIIOutputStream.java index 989bfc3..5a94e36 100644 --- a/src/main/java/org/apache/commons/net/io/ToNetASCIIOutputStream.java +++ b/src/main/java/org/apache/commons/net/io/ToNetASCIIOutputStream.java @@ -21,99 +21,75 @@ import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; -/*** - * This class wraps an output stream, replacing all singly occurring - * <LF> (linefeed) characters with <CR><LF> (carriage return - * followed by linefeed), which is the NETASCII standard for representing - * a newline. - * You would use this class to implement ASCII file transfers requiring - * conversion to NETASCII. +/** + * This class wraps an output stream, replacing all singly occurring <LF> (linefeed) characters with <CR><LF> (carriage return followed by + * linefeed), which is the NETASCII standard for representing a newline. You would use this class to implement ASCII file transfers requiring conversion to + * NETASCII. * * - ***/ + */ -public final class ToNetASCIIOutputStream extends FilterOutputStream -{ - private boolean __lastWasCR; +public final class ToNetASCIIOutputStream extends FilterOutputStream { + private boolean lastWasCR; - /*** - * Creates a ToNetASCIIOutputStream instance that wraps an existing - * OutputStream. + /** + * Creates a ToNetASCIIOutputStream instance that wraps an existing OutputStream. * - * @param output The OutputStream to wrap. - ***/ - public ToNetASCIIOutputStream(OutputStream output) - { + * @param output The OutputStream to wrap. + */ + public ToNetASCIIOutputStream(final OutputStream output) { super(output); - __lastWasCR = false; + lastWasCR = false; + } + + /** + * Writes a byte array to the stream. + * + * @param buffer The byte array to write. + * @throws IOException If an error occurs while writing to the underlying stream. + */ + @Override + public synchronized void write(final byte buffer[]) throws IOException { + write(buffer, 0, buffer.length); } + /** + * Writes a number of bytes from a byte array to the stream starting from a given offset. + * + * @param buffer The byte array to write. + * @param offset The offset into the array at which to start copying data. + * @param length The number of bytes to write. + * @throws IOException If an error occurs while writing to the underlying stream. + */ + @Override + public synchronized void write(final byte buffer[], int offset, int length) throws IOException { + while (length-- > 0) { + write(buffer[offset++]); + } + } - /*** - * Writes a byte to the stream. Note that a call to this method - * may result in multiple writes to the underlying input stream in order - * to convert naked newlines to NETASCII line separators. - * This is transparent to the programmer and is only mentioned for - * completeness. + /** + * Writes a byte to the stream. Note that a call to this method may result in multiple writes to the underlying input stream in order to convert naked + * newlines to NETASCII line separators. This is transparent to the programmer and is only mentioned for completeness. * * @param ch The byte to write. - * @throws IOException If an error occurs while writing to the underlying - * stream. - ***/ + * @throws IOException If an error occurs while writing to the underlying stream. + */ @Override - public synchronized void write(int ch) - throws IOException - { - switch (ch) - { + public synchronized void write(final int ch) throws IOException { + switch (ch) { case '\r': - __lastWasCR = true; + lastWasCR = true; out.write('\r'); - return ; + return; case '\n': - if (!__lastWasCR) { + if (!lastWasCR) { out.write('\r'); } //$FALL-THROUGH$ default: - __lastWasCR = false; + lastWasCR = false; out.write(ch); - return ; - } - } - - - /*** - * Writes a byte array to the stream. - * - * @param buffer The byte array to write. - * @throws IOException If an error occurs while writing to the underlying - * stream. - ***/ - @Override - public synchronized void write(byte buffer[]) - throws IOException - { - write(buffer, 0, buffer.length); - } - - - /*** - * Writes a number of bytes from a byte array to the stream starting from - * a given offset. - * - * @param buffer The byte array to write. - * @param offset The offset into the array at which to start copying data. - * @param length The number of bytes to write. - * @throws IOException If an error occurs while writing to the underlying - * stream. - ***/ - @Override - public synchronized void write(byte buffer[], int offset, int length) - throws IOException - { - while (length-- > 0) { - write(buffer[offset++]); } } diff --git a/src/main/java/org/apache/commons/net/io/Util.java b/src/main/java/org/apache/commons/net/io/Util.java index 1f4e5b5..ef8f2b8 100644 --- a/src/main/java/org/apache/commons/net/io/Util.java +++ b/src/main/java/org/apache/commons/net/io/Util.java @@ -25,262 +25,118 @@ import java.io.Reader; import java.io.Writer; import java.net.Socket; -/*** - * The Util class cannot be instantiated and stores short static convenience - * methods that are often quite useful. +import org.apache.commons.net.util.NetConstants; + +/** + * The Util class cannot be instantiated and stores short static convenience methods that are often quite useful. * * * @see CopyStreamException * @see CopyStreamListener * @see CopyStreamAdapter - ***/ + */ -public final class Util -{ +public final class Util { /** - * The default buffer size ({@value}) used by - * {@link #copyStream copyStream } and {@link #copyReader copyReader} - * and by the copyReader/copyStream methods if a zero or negative buffer size is supplied. + * The default buffer size ({@value}) used by {@link #copyStream copyStream } and {@link #copyReader copyReader} and by the copyReader/copyStream methods if + * a zero or negative buffer size is supplied. */ public static final int DEFAULT_COPY_BUFFER_SIZE = 1024; - // Cannot be instantiated - private Util() - { } - - - /*** - * Copies the contents of an InputStream to an OutputStream using a - * copy buffer of a given size and notifies the provided - * CopyStreamListener of the progress of the copy operation by calling - * its bytesTransferred(long, int) method after each write to the - * destination. If you wish to notify more than one listener you should - * use a CopyStreamAdapter as the listener and register the additional - * listeners with the CopyStreamAdapter. - * <p> - * The contents of the InputStream are - * read until the end of the stream is reached, but neither the - * source nor the destination are closed. You must do this yourself - * outside of the method call. The number of bytes read/written is - * returned. + /** + * Closes the object quietly, catching rather than throwing IOException. Intended for use from finally blocks. * - * @param source The source InputStream. - * @param dest The destination OutputStream. - * @param bufferSize The number of bytes to buffer during the copy. - * A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. - * @param streamSize The number of bytes in the stream being copied. - * Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown. - * Not currently used (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)} - * @param listener The CopyStreamListener to notify of progress. If - * this parameter is null, notification is not attempted. - * @param flush Whether to flush the output stream after every - * write. This is necessary for interactive sessions that rely on - * buffered streams. If you don't flush, the data will stay in - * the stream buffer. - * @return number of bytes read/written - * @throws CopyStreamException If an error occurs while reading from the - * source or writing to the destination. The CopyStreamException - * will contain the number of bytes confirmed to have been - * transferred before an - * IOException occurred, and it will also contain the IOException - * that caused the error. These values can be retrieved with - * the CopyStreamException getTotalBytesTransferred() and - * getIOException() methods. - ***/ - public static final long copyStream(InputStream source, OutputStream dest, - int bufferSize, long streamSize, - CopyStreamListener listener, - boolean flush) - throws CopyStreamException - { - int numBytes; - long total = 0; - byte[] buffer = new byte[bufferSize > 0 ? bufferSize : DEFAULT_COPY_BUFFER_SIZE]; - - try - { - while ((numBytes = source.read(buffer)) != -1) - { - // Technically, some read(byte[]) methods may return 0 and we cannot - // accept that as an indication of EOF. - - if (numBytes == 0) - { - int singleByte = source.read(); - if (singleByte < 0) { - break; - } - dest.write(singleByte); - if(flush) { - dest.flush(); - } - ++total; - if (listener != null) { - listener.bytesTransferred(total, 1, streamSize); - } - continue; - } - - dest.write(buffer, 0, numBytes); - if(flush) { - dest.flush(); - } - total += numBytes; - if (listener != null) { - listener.bytesTransferred(total, numBytes, streamSize); - } + * @param closeable the object to close, may be {@code null} + * @since 3.0 + */ + public static void closeQuietly(final Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (final IOException e) { + // Ignored } } - catch (IOException e) - { - throw new CopyStreamException("IOException caught while copying.", - total, e); - } - - return total; } - - /*** - * Copies the contents of an InputStream to an OutputStream using a - * copy buffer of a given size and notifies the provided - * CopyStreamListener of the progress of the copy operation by calling - * its bytesTransferred(long, int) method after each write to the - * destination. If you wish to notify more than one listener you should - * use a CopyStreamAdapter as the listener and register the additional - * listeners with the CopyStreamAdapter. - * <p> - * The contents of the InputStream are - * read until the end of the stream is reached, but neither the - * source nor the destination are closed. You must do this yourself - * outside of the method call. The number of bytes read/written is - * returned. + /** + * Closes the socket quietly, catching rather than throwing IOException. Intended for use from finally blocks. * - * @param source The source InputStream. - * @param dest The destination OutputStream. - * @param bufferSize The number of bytes to buffer during the copy. - * A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. - * @param streamSize The number of bytes in the stream being copied. - * Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown. - * Not currently used (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)} - * @param listener The CopyStreamListener to notify of progress. If - * this parameter is null, notification is not attempted. - * @return number of bytes read/written - * @throws CopyStreamException If an error occurs while reading from the - * source or writing to the destination. The CopyStreamException - * will contain the number of bytes confirmed to have been - * transferred before an - * IOException occurred, and it will also contain the IOException - * that caused the error. These values can be retrieved with - * the CopyStreamException getTotalBytesTransferred() and - * getIOException() methods. - ***/ - public static final long copyStream(InputStream source, OutputStream dest, - int bufferSize, long streamSize, - CopyStreamListener listener) - throws CopyStreamException - { - return copyStream(source, dest, bufferSize, streamSize, listener, - true); + * @param socket the socket to close, may be {@code null} + * @since 3.0 + */ + public static void closeQuietly(final Socket socket) { + if (socket != null) { + try { + socket.close(); + } catch (final IOException e) { + // Ignored + } + } } - - /*** - * Copies the contents of an InputStream to an OutputStream using a - * copy buffer of a given size. The contents of the InputStream are - * read until the end of the stream is reached, but neither the - * source nor the destination are closed. You must do this yourself - * outside of the method call. The number of bytes read/written is - * returned. + /** + * Same as <code> copyReader(source, dest, DEFAULT_COPY_BUFFER_SIZE); </code> * - * @param source The source InputStream. - * @param dest The destination OutputStream. - * @param bufferSize The number of bytes to buffer during the copy. - * A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. - * @return The number of bytes read/written in the copy operation. - * @throws CopyStreamException If an error occurs while reading from the - * source or writing to the destination. The CopyStreamException - * will contain the number of bytes confirmed to have been - * transferred before an - * IOException occurred, and it will also contain the IOException - * that caused the error. These values can be retrieved with - * the CopyStreamException getTotalBytesTransferred() and - * getIOException() methods. - ***/ - public static final long copyStream(InputStream source, OutputStream dest, - int bufferSize) - throws CopyStreamException - { - return copyStream(source, dest, bufferSize, - CopyStreamEvent.UNKNOWN_STREAM_SIZE, null); - } - - - /*** - * Same as <code> copyStream(source, dest, DEFAULT_COPY_BUFFER_SIZE); </code> * @param source where to copy from - * @param dest where to copy to + * @param dest where to copy to * @return number of bytes copied * @throws CopyStreamException on error - ***/ - public static final long copyStream(InputStream source, OutputStream dest) - throws CopyStreamException - { - return copyStream(source, dest, DEFAULT_COPY_BUFFER_SIZE); + */ + public static long copyReader(final Reader source, final Writer dest) throws CopyStreamException { + return copyReader(source, dest, DEFAULT_COPY_BUFFER_SIZE); } + /** + * Copies the contents of a Reader to a Writer using a copy buffer of a given size. The contents of the Reader are read until its end is reached, but + * neither the source nor the destination are closed. You must do this yourself outside of the method call. The number of characters read/written is + * returned. + * + * @param source The source Reader. + * @param dest The destination writer. + * @param bufferSize The number of characters to buffer during the copy. A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. + * @return The number of characters read/written in the copy operation. + * @throws CopyStreamException If an error occurs while reading from the source or writing to the destination. The CopyStreamException will contain the + * number of bytes confirmed to have been transferred before an IOException occurred, and it will also contain the IOException + * that caused the error. These values can be retrieved with the CopyStreamException getTotalBytesTransferred() and + * getIOException() methods. + */ + public static long copyReader(final Reader source, final Writer dest, final int bufferSize) throws CopyStreamException { + return copyReader(source, dest, bufferSize, CopyStreamEvent.UNKNOWN_STREAM_SIZE, null); + } - /*** - * Copies the contents of a Reader to a Writer using a - * copy buffer of a given size and notifies the provided - * CopyStreamListener of the progress of the copy operation by calling - * its bytesTransferred(long, int) method after each write to the - * destination. If you wish to notify more than one listener you should - * use a CopyStreamAdapter as the listener and register the additional - * listeners with the CopyStreamAdapter. + /** + * Copies the contents of a Reader to a Writer using a copy buffer of a given size and notifies the provided CopyStreamListener of the progress of the copy + * operation by calling its bytesTransferred(long, int) method after each write to the destination. If you wish to notify more than one listener you should + * use a CopyStreamAdapter as the listener and register the additional listeners with the CopyStreamAdapter. * <p> - * The contents of the Reader are - * read until its end is reached, but neither the source nor the - * destination are closed. You must do this yourself outside of the - * method call. The number of characters read/written is returned. + * The contents of the Reader are read until its end is reached, but neither the source nor the destination are closed. You must do this yourself outside of + * the method call. The number of characters read/written is returned. * - * @param source The source Reader. - * @param dest The destination writer. - * @param bufferSize The number of characters to buffer during the copy. - * A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. - * @param streamSize The number of characters in the stream being copied. - * Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown. - * Not currently used (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)} - * @param listener The CopyStreamListener to notify of progress. If - * this parameter is null, notification is not attempted. - * @return The number of characters read/written in the copy operation. - * @throws CopyStreamException If an error occurs while reading from the - * source or writing to the destination. The CopyStreamException - * will contain the number of bytes confirmed to have been - * transferred before an - * IOException occurred, and it will also contain the IOException - * that caused the error. These values can be retrieved with - * the CopyStreamException getTotalBytesTransferred() and - * getIOException() methods. - ***/ - public static final long copyReader(Reader source, Writer dest, - int bufferSize, long streamSize, - CopyStreamListener listener) - throws CopyStreamException - { + * @param source The source Reader. + * @param dest The destination writer. + * @param bufferSize The number of characters to buffer during the copy. A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. + * @param streamSize The number of characters in the stream being copied. Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown. Not currently + * used (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)} + * @param listener The CopyStreamListener to notify of progress. If this parameter is null, notification is not attempted. + * @return The number of characters read/written in the copy operation. + * @throws CopyStreamException If an error occurs while reading from the source or writing to the destination. The CopyStreamException will contain the + * number of bytes confirmed to have been transferred before an IOException occurred, and it will also contain the IOException + * that caused the error. These values can be retrieved with the CopyStreamException getTotalBytesTransferred() and + * getIOException() methods. + */ + public static long copyReader(final Reader source, final Writer dest, final int bufferSize, final long streamSize, final CopyStreamListener listener) + throws CopyStreamException { int numChars; long total = 0; - char[] buffer = new char[bufferSize > 0 ? bufferSize : DEFAULT_COPY_BUFFER_SIZE]; + final char[] buffer = new char[bufferSize > 0 ? bufferSize : DEFAULT_COPY_BUFFER_SIZE]; - try - { - while ((numChars = source.read(buffer)) != -1) - { + try { + while ((numChars = source.read(buffer)) != NetConstants.EOS) { // Technically, some read(char[]) methods may return 0 and we cannot // accept that as an indication of EOF. - if (numChars == 0) - { - int singleChar = source.read(); + if (numChars == 0) { + final int singleChar = source.read(); if (singleChar < 0) { break; } @@ -300,91 +156,134 @@ public final class Util listener.bytesTransferred(total, numChars, streamSize); } } - } - catch (IOException e) - { - throw new CopyStreamException("IOException caught while copying.", - total, e); + } catch (final IOException e) { + throw new CopyStreamException("IOException caught while copying.", total, e); } return total; } - - /*** - * Copies the contents of a Reader to a Writer using a - * copy buffer of a given size. The contents of the Reader are - * read until its end is reached, but neither the source nor the - * destination are closed. You must do this yourself outside of the - * method call. The number of characters read/written is returned. + /** + * Same as <code> copyStream(source, dest, DEFAULT_COPY_BUFFER_SIZE); </code> * - * @param source The source Reader. - * @param dest The destination writer. - * @param bufferSize The number of characters to buffer during the copy. - * A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. - * @return The number of characters read/written in the copy operation. - * @throws CopyStreamException If an error occurs while reading from the - * source or writing to the destination. The CopyStreamException - * will contain the number of bytes confirmed to have been - * transferred before an - * IOException occurred, and it will also contain the IOException - * that caused the error. These values can be retrieved with - * the CopyStreamException getTotalBytesTransferred() and - * getIOException() methods. - ***/ - public static final long copyReader(Reader source, Writer dest, - int bufferSize) - throws CopyStreamException - { - return copyReader(source, dest, bufferSize, - CopyStreamEvent.UNKNOWN_STREAM_SIZE, null); - } - - - /*** - * Same as <code> copyReader(source, dest, DEFAULT_COPY_BUFFER_SIZE); </code> * @param source where to copy from - * @param dest where to copy to + * @param dest where to copy to * @return number of bytes copied * @throws CopyStreamException on error - ***/ - public static final long copyReader(Reader source, Writer dest) - throws CopyStreamException - { - return copyReader(source, dest, DEFAULT_COPY_BUFFER_SIZE); + */ + public static long copyStream(final InputStream source, final OutputStream dest) throws CopyStreamException { + return copyStream(source, dest, DEFAULT_COPY_BUFFER_SIZE); } /** - * Closes the object quietly, catching rather than throwing IOException. - * Intended for use from finally blocks. + * Copies the contents of an InputStream to an OutputStream using a copy buffer of a given size. The contents of the InputStream are read until the end of + * the stream is reached, but neither the source nor the destination are closed. You must do this yourself outside of the method call. The number of bytes + * read/written is returned. * - * @param closeable the object to close, may be {@code null} - * @since 3.0 + * @param source The source InputStream. + * @param dest The destination OutputStream. + * @param bufferSize The number of bytes to buffer during the copy. A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. + * @return The number of bytes read/written in the copy operation. + * @throws CopyStreamException If an error occurs while reading from the source or writing to the destination. The CopyStreamException will contain the + * number of bytes confirmed to have been transferred before an IOException occurred, and it will also contain the IOException + * that caused the error. These values can be retrieved with the CopyStreamException getTotalBytesTransferred() and + * getIOException() methods. */ - public static void closeQuietly(Closeable closeable) { - if (closeable != null) { - try { - closeable.close(); - } catch (IOException e) { - // Ignored - } - } + public static long copyStream(final InputStream source, final OutputStream dest, final int bufferSize) throws CopyStreamException { + return copyStream(source, dest, bufferSize, CopyStreamEvent.UNKNOWN_STREAM_SIZE, null); } /** - * Closes the socket quietly, catching rather than throwing IOException. - * Intended for use from finally blocks. + * Copies the contents of an InputStream to an OutputStream using a copy buffer of a given size and notifies the provided CopyStreamListener of the progress + * of the copy operation by calling its bytesTransferred(long, int) method after each write to the destination. If you wish to notify more than one listener + * you should use a CopyStreamAdapter as the listener and register the additional listeners with the CopyStreamAdapter. + * <p> + * The contents of the InputStream are read until the end of the stream is reached, but neither the source nor the destination are closed. You must do this + * yourself outside of the method call. The number of bytes read/written is returned. * - * @param socket the socket to close, may be {@code null} - * @since 3.0 + * @param source The source InputStream. + * @param dest The destination OutputStream. + * @param bufferSize The number of bytes to buffer during the copy. A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. + * @param streamSize The number of bytes in the stream being copied. Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown. Not currently used + * (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)} + * @param listener The CopyStreamListener to notify of progress. If this parameter is null, notification is not attempted. + * @return number of bytes read/written + * @throws CopyStreamException If an error occurs while reading from the source or writing to the destination. The CopyStreamException will contain the + * number of bytes confirmed to have been transferred before an IOException occurred, and it will also contain the IOException + * that caused the error. These values can be retrieved with the CopyStreamException getTotalBytesTransferred() and + * getIOException() methods. */ - public static void closeQuietly(Socket socket) { - if (socket != null) { - try { - socket.close(); - } catch (IOException e) { - // Ignored + public static long copyStream(final InputStream source, final OutputStream dest, final int bufferSize, final long streamSize, + final CopyStreamListener listener) throws CopyStreamException { + return copyStream(source, dest, bufferSize, streamSize, listener, true); + } + + /** + * Copies the contents of an InputStream to an OutputStream using a copy buffer of a given size and notifies the provided CopyStreamListener of the progress + * of the copy operation by calling its bytesTransferred(long, int) method after each write to the destination. If you wish to notify more than one listener + * you should use a CopyStreamAdapter as the listener and register the additional listeners with the CopyStreamAdapter. + * <p> + * The contents of the InputStream are read until the end of the stream is reached, but neither the source nor the destination are closed. You must do this + * yourself outside of the method call. The number of bytes read/written is returned. + * + * @param source The source InputStream. + * @param dest The destination OutputStream. + * @param bufferSize The number of bytes to buffer during the copy. A zero or negative value means to use {@link #DEFAULT_COPY_BUFFER_SIZE}. + * @param streamSize The number of bytes in the stream being copied. Should be set to CopyStreamEvent.UNKNOWN_STREAM_SIZE if unknown. Not currently used + * (though it is passed to {@link CopyStreamListener#bytesTransferred(long, int, long)} + * @param listener The CopyStreamListener to notify of progress. If this parameter is null, notification is not attempted. + * @param flush Whether to flush the output stream after every write. This is necessary for interactive sessions that rely on buffered streams. If you + * don't flush, the data will stay in the stream buffer. + * @return number of bytes read/written + * @throws CopyStreamException If an error occurs while reading from the source or writing to the destination. The CopyStreamException will contain the + * number of bytes confirmed to have been transferred before an IOException occurred, and it will also contain the IOException + * that caused the error. These values can be retrieved with the CopyStreamException getTotalBytesTransferred() and + * getIOException() methods. + */ + public static long copyStream(final InputStream source, final OutputStream dest, final int bufferSize, final long streamSize, + final CopyStreamListener listener, final boolean flush) throws CopyStreamException { + int numBytes; + long total = 0; + final byte[] buffer = new byte[bufferSize > 0 ? bufferSize : DEFAULT_COPY_BUFFER_SIZE]; + + try { + while ((numBytes = source.read(buffer)) != NetConstants.EOS) { + // Technically, some read(byte[]) methods may return 0 and we cannot + // accept that as an indication of EOF. + + if (numBytes == 0) { + final int singleByte = source.read(); + if (singleByte < 0) { + break; + } + dest.write(singleByte); + if (flush) { + dest.flush(); + } + ++total; + if (listener != null) { + listener.bytesTransferred(total, 1, streamSize); + } + continue; + } + + dest.write(buffer, 0, numBytes); + if (flush) { + dest.flush(); + } + total += numBytes; + if (listener != null) { + listener.bytesTransferred(total, numBytes, streamSize); + } } + } catch (final IOException e) { + throw new CopyStreamException("IOException caught while copying.", total, e); } + + return total; + } + + // Cannot be instantiated + private Util() { } } diff --git a/src/main/java/org/apache/commons/net/nntp/Article.java b/src/main/java/org/apache/commons/net/nntp/Article.java index ba23507..8418af7 100644 --- a/src/main/java/org/apache/commons/net/nntp/Article.java +++ b/src/main/java/org/apache/commons/net/nntp/Article.java @@ -19,177 +19,118 @@ package org.apache.commons.net.nntp; import java.io.PrintStream; import java.util.ArrayList; +import java.util.Collections; + +import org.apache.commons.net.util.NetConstants; /** - * This is a class that contains the basic state needed for message retrieval and threading. - * With thanks to Jamie Zawinski (jwz@jwz.org) + * This is a class that contains the basic state needed for message retrieval and threading. With thanks to Jamie Zawinski (jwz@jwz.org) */ public class Article implements Threadable { - private long articleNumber; - private String subject; - private String date; - private String articleId; - private String simplifiedSubject; - private String from; - private ArrayList<String> references; - private boolean isReply = false; - - public Article kid, next; - - public Article() { - articleNumber = -1; // isDummy + /** + * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out. + * + * @param article the root of the article 'tree' + * @since 3.4 + */ + public static void printThread(final Article article) { + printThread(article, 0, System.out); } /** - * Adds a message-id to the list of messages that this message references (i.e. replies to) - * @param msgId the message id to add + * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out. + * + * @param article the root of the article 'tree' + * @param depth the current tree depth */ - public void addReference(String msgId) { - if (msgId == null || msgId.length() == 0) { - return; - } - if (references == null) { - references = new ArrayList<String>(); - } - isReply = true; - for(String s : msgId.split(" ")) { - references.add(s); - } + public static void printThread(final Article article, final int depth) { + printThread(article, depth, System.out); } /** - * Returns the MessageId references as an array of Strings - * @return an array of message-ids + * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out. + * + * @param article the root of the article 'tree' + * @param depth the current tree depth + * @param ps the PrintStream to use + * @since 3.4 */ - public String[] getReferences() { - if (references == null) { - return new String[0]; + public static void printThread(final Article article, final int depth, final PrintStream ps) { + for (int i = 0; i < depth; ++i) { + ps.print("==>"); + } + ps.println(article.getSubject() + "\t" + article.getFrom() + "\t" + article.getArticleId()); + if (article.kid != null) { + printThread(article.kid, depth + 1); + } + if (article.next != null) { + printThread(article.next, depth); } - return references.toArray(new String[references.size()]); } /** - * Attempts to parse the subject line for some typical reply signatures, and strip them out + * Recursive method that traverses a pre-threaded graph (or tree) of connected Article objects and prints them out. * + * @param article the root of the article 'tree' + * @param ps the PrintStream to use + * @since 3.4 */ - private void simplifySubject() { - int start = 0; - String subject = getSubject(); - int len = subject.length(); - - boolean done = false; - - while (!done) { - done = true; - - // skip whitespace - // "Re: " breaks this - while (start < len && subject.charAt(start) == ' ') { - start++; - } - - if (start < (len - 2) - && (subject.charAt(start) == 'r' || subject.charAt(start) == 'R') - && (subject.charAt(start + 1) == 'e' || subject.charAt(start + 1) == 'E')) { + public static void printThread(final Article article, final PrintStream ps) { + printThread(article, 0, ps); + } - if (subject.charAt(start + 2) == ':') { - start += 3; // Skip "Re:" - done = false; - } else if ( - start < (len - 2) - && - (subject.charAt(start + 2) == '[' || subject.charAt(start + 2) == '(')) { - - int i = start + 3; - - while (i < len && subject.charAt(i) >= '0' && subject.charAt(i) <= '9') { - i++; - } - - if (i < (len - 1) - && (subject.charAt(i) == ']' || subject.charAt(i) == ')') - && subject.charAt(i + 1) == ':') - { - start = i + 2; - done = false; - } - } - } + private long articleNumber; + private String subject; + private String date; + private String articleId; - if ("(no subject)".equals(simplifiedSubject)) { - simplifiedSubject = ""; - } + private String simplifiedSubject; - int end = len; + private String from; + private ArrayList<String> references; - while (end > start && subject.charAt(end - 1) < ' ') { - end--; - } + private boolean isReply; - if (start == 0 && end == len) { - simplifiedSubject = subject; - } else { - simplifiedSubject = subject.substring(start, end); - } - } - } + public Article kid, next; - /** - * Recursive method that traverses a pre-threaded graph (or tree) - * of connected Article objects and prints them out. - * @param article the root of the article 'tree' - * @since 3.4 - */ - public static void printThread(Article article) { - printThread(article, 0, System.out); + public Article() { + articleNumber = -1; // isDummy } - /** - * Recursive method that traverses a pre-threaded graph (or tree) - * of connected Article objects and prints them out. - * @param article the root of the article 'tree' - * @param ps the PrintStream to use - * @since 3.4 - */ - public static void printThread(Article article, PrintStream ps) { - printThread(article, 0, ps); + @Deprecated + + public void addHeaderField(final String name, final String val) { } /** - * Recursive method that traverses a pre-threaded graph (or tree) - * of connected Article objects and prints them out. - * @param article the root of the article 'tree' - * @param depth the current tree depth + * Adds a message-id to the list of messages that this message references (i.e. replies to) + * + * @param msgId the message id to add */ - public static void printThread(Article article, int depth) { - printThread(article, depth, System.out); + public void addReference(final String msgId) { + if (msgId == null || msgId.isEmpty()) { + return; + } + if (references == null) { + references = new ArrayList<>(); + } + isReply = true; + Collections.addAll(references, msgId.split(" ")); } - /** - * Recursive method that traverses a pre-threaded graph (or tree) - * of connected Article objects and prints them out. - * @param article the root of the article 'tree' - * @param depth the current tree depth - * @param ps the PrintStream to use - * @since 3.4 - */ - public static void printThread(Article article, int depth, PrintStream ps) { - for (int i = 0; i < depth; ++i) { - ps.print("==>"); - } - ps.println(article.getSubject() + "\t" + article.getFrom()+"\t"+article.getArticleId()); - if (article.kid != null) { - printThread(article.kid, depth + 1); - } - if (article.next != null) { - printThread(article.next, depth); - } + private void flushSubjectCache() { + simplifiedSubject = null; } public String getArticleId() { return articleId; } + @Deprecated + public int getArticleNumber() { + return (int) articleNumber; + } + public long getArticleNumberLong() { return articleNumber; } @@ -202,36 +143,32 @@ public class Article implements Threadable { return from; } - public String getSubject() { - return subject; - } - - public void setArticleId(String string) { - articleId = string; - } - - public void setArticleNumber(long l) { - articleNumber = l; - } - - public void setDate(String string) { - date = string; - } - - public void setFrom(String string) { - from = string; + /** + * Returns the MessageId references as an array of Strings + * + * @return an array of message-ids + */ + public String[] getReferences() { + if (references == null) { + return NetConstants.EMPTY_STRING_ARRAY; + } + return references.toArray(NetConstants.EMPTY_STRING_ARRAY); } - public void setSubject(String string) { - subject = string; + public String getSubject() { + return subject; } - @Override public boolean isDummy() { return (articleNumber == -1); } + @Override + public Threadable makeDummy() { + return new Article(); + } + @Override public String messageThreadId() { return articleId; @@ -242,63 +179,120 @@ public class Article implements Threadable { return getReferences(); } - @Override - public String simplifiedSubject() { - if(simplifiedSubject == null) { - simplifySubject(); - } - return simplifiedSubject; + public void setArticleId(final String string) { + articleId = string; } - - @Override - public boolean subjectIsReply() { - return isReply; + @Deprecated + public void setArticleNumber(final int a) { + articleNumber = a; } + public void setArticleNumber(final long l) { + articleNumber = l; + } @Override - public void setChild(Threadable child) { + public void setChild(final Threadable child) { this.kid = (Article) child; flushSubjectCache(); } - private void flushSubjectCache() { - simplifiedSubject = null; + public void setDate(final String string) { + date = string; } + public void setFrom(final String string) { + from = string; + } @Override - public void setNext(Threadable next) { - this.next = (Article)next; + public void setNext(final Threadable next) { + this.next = (Article) next; flushSubjectCache(); } - - @Override - public Threadable makeDummy() { - return new Article(); + public void setSubject(final String string) { + subject = string; } @Override - public String toString(){ // Useful for Eclipse debugging - return articleNumber + " " +articleId + " " + subject; + public String simplifiedSubject() { + if (simplifiedSubject == null) { + simplifySubject(); + } + return simplifiedSubject; } // DEPRECATED METHODS - for API compatibility only - DO NOT USE - @Deprecated - public int getArticleNumber() { - return (int) articleNumber; + /** + * Attempts to parse the subject line for some typical reply signatures, and strip them out + * + */ + private void simplifySubject() { + int start = 0; + final String subject = getSubject(); + final int len = subject.length(); + + boolean done = false; + + while (!done) { + done = true; + + // skip whitespace + // "Re: " breaks this + while (start < len && subject.charAt(start) == ' ') { + start++; + } + + if (start < (len - 2) && (subject.charAt(start) == 'r' || subject.charAt(start) == 'R') + && (subject.charAt(start + 1) == 'e' || subject.charAt(start + 1) == 'E')) { + + if (subject.charAt(start + 2) == ':') { + start += 3; // Skip "Re:" + done = false; + } else if (start < (len - 2) && (subject.charAt(start + 2) == '[' || subject.charAt(start + 2) == '(')) { + + int i = start + 3; + + while (i < len && subject.charAt(i) >= '0' && subject.charAt(i) <= '9') { + i++; + } + + if (i < (len - 1) && (subject.charAt(i) == ']' || subject.charAt(i) == ')') && subject.charAt(i + 1) == ':') { + start = i + 2; + done = false; + } + } + } + + if ("(no subject)".equals(simplifiedSubject)) { + simplifiedSubject = ""; + } + + int end = len; + + while (end > start && subject.charAt(end - 1) < ' ') { + end--; + } + + if (start == 0 && end == len) { + simplifiedSubject = subject; + } else { + simplifiedSubject = subject.substring(start, end); + } + } } - @Deprecated - public void setArticleNumber(int a) { - articleNumber = a; + @Override + public boolean subjectIsReply() { + return isReply; } - @Deprecated - public void addHeaderField(String name, String val) { + @Override + public String toString() { // Useful for Eclipse debugging + return articleNumber + " " + articleId + " " + subject; } } diff --git a/src/main/java/org/apache/commons/net/nntp/ArticleInfo.java b/src/main/java/org/apache/commons/net/nntp/ArticleInfo.java index c431acc..76c97d0 100644 --- a/src/main/java/org/apache/commons/net/nntp/ArticleInfo.java +++ b/src/main/java/org/apache/commons/net/nntp/ArticleInfo.java @@ -17,10 +17,9 @@ */ package org.apache.commons.net.nntp; + /** - * Class contains details about an article. - * Create an instance of the class and pass it to the appropriate NNTP method. - * The values will be populated on return. + * Class contains details about an article. Create an instance of the class and pass it to the appropriate NNTP method. The values will be populated on return. * */ public class ArticleInfo { diff --git a/src/main/java/org/apache/commons/net/nntp/ArticleIterator.java b/src/main/java/org/apache/commons/net/nntp/ArticleIterator.java index ef82ba6..61fa118 100644 --- a/src/main/java/org/apache/commons/net/nntp/ArticleIterator.java +++ b/src/main/java/org/apache/commons/net/nntp/ArticleIterator.java @@ -19,16 +19,17 @@ package org.apache.commons.net.nntp; import java.util.Iterator; + /** - * Class which wraps an {@code Iterable<String>} of raw article information - * to generate an {@code Iterable<Article>} of the parsed information. + * Class which wraps an {@code Iterable<String>} of raw article information to generate an {@code Iterable<Article>} of the parsed information. + * * @since 3.0 */ class ArticleIterator implements Iterator<Article>, Iterable<Article> { - private final Iterator<String> stringIterator; + private final Iterator<String> stringIterator; - public ArticleIterator(Iterable<String> iterableString) { + public ArticleIterator(final Iterable<String> iterableString) { stringIterator = iterableString.iterator(); } @@ -37,24 +38,24 @@ class ArticleIterator implements Iterator<Article>, Iterable<Article> { return stringIterator.hasNext(); } + @Override + public Iterator<Article> iterator() { + return this; + } + /** * Get the next Article - * @return the next {@link Article}, never {@code null}, if unparseable then isDummy() - * will be true, and the subject will contain the raw info. + * + * @return the next {@link Article}, never {@code null}, if unparseable then isDummy() will be true, and the subject will contain the raw info. */ @Override public Article next() { - String line = stringIterator.next(); - return NNTPClient.__parseArticleEntry(line); + final String line = stringIterator.next(); + return NNTPClient.parseArticleEntry(line); } @Override public void remove() { stringIterator.remove(); } - - @Override - public Iterator<Article> iterator() { - return this; - } } diff --git a/src/main/java/org/apache/commons/net/nntp/ArticlePointer.java b/src/main/java/org/apache/commons/net/nntp/ArticlePointer.java index 9b43ad2..130a0e0 100644 --- a/src/main/java/org/apache/commons/net/nntp/ArticlePointer.java +++ b/src/main/java/org/apache/commons/net/nntp/ArticlePointer.java @@ -18,24 +18,20 @@ package org.apache.commons.net.nntp; /** - * This class is a structure used to return article number and unique - * id information extracted from an NNTP server reply. You will normally - * want this information when issuing a STAT command, implemented by - * {@link NNTPClient#selectArticle selectArticle}. + * This class is a structure used to return article number and unique id information extracted from an NNTP server reply. You will normally want this + * information when issuing a STAT command, implemented by {@link NNTPClient#selectArticle selectArticle}. + * * @see NNTPClient * * @deprecated 3.0 use {@link ArticleInfo} instead */ @Deprecated -public final class ArticlePointer -{ +public final class ArticlePointer { /** The number of the referenced article. */ public int articleNumber; /** - * The unique id of the referenced article, including the enclosing - * < and > symbols which are technically not part of the - * identifier, but are required by all NNTP commands taking an - * article id as an argument. + * The unique id of the referenced article, including the enclosing < and > symbols which are technically not part of the identifier, but are required + * by all NNTP commands taking an article id as an argument. */ public String articleId; } diff --git a/src/main/java/org/apache/commons/net/nntp/NNTP.java b/src/main/java/org/apache/commons/net/nntp/NNTP.java index 3ab7e7c..837e914 100644 --- a/src/main/java/org/apache/commons/net/nntp/NNTP.java +++ b/src/main/java/org/apache/commons/net/nntp/NNTP.java @@ -22,759 +22,502 @@ import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import org.apache.commons.net.MalformedServerReplyException; import org.apache.commons.net.ProtocolCommandSupport; import org.apache.commons.net.SocketClient; import org.apache.commons.net.io.CRLFLineReader; -/*** - * The NNTP class is not meant to be used by itself and is provided - * only so that you may easily implement your own NNTP client if - * you so desire. If you have no need to perform your own implementation, - * you should use {@link org.apache.commons.net.nntp.NNTPClient}. - * The NNTP class is made public to provide access to various NNTP constants - * and to make it easier for adventurous programmers (or those with special - * needs) to interact with the NNTP protocol and implement their own clients. - * A set of methods with names corresponding to the NNTP command names are - * provided to facilitate this interaction. +/** + * The NNTP class is not meant to be used by itself and is provided only so that you may easily implement your own NNTP client if you so desire. If you have no + * need to perform your own implementation, you should use {@link org.apache.commons.net.nntp.NNTPClient}. The NNTP class is made public to provide access to + * various NNTP constants and to make it easier for adventurous programmers (or those with special needs) to interact with the NNTP protocol and implement their + * own clients. A set of methods with names corresponding to the NNTP command names are provided to facilitate this interaction. * <p> - * You should keep in mind that the NNTP server may choose to prematurely - * close a connection if the client has been idle for longer than a - * given time period or if the server is being shutdown by the operator or - * some other reason. The NNTP class will detect a - * premature NNTP server connection closing when it receives a - * {@link org.apache.commons.net.nntp.NNTPReply#SERVICE_DISCONTINUED NNTPReply.SERVICE_DISCONTINUED } - * response to a command. - * When that occurs, the NNTP class method encountering that reply will throw - * an {@link org.apache.commons.net.nntp.NNTPConnectionClosedException} - * . - * <code>NNTPConectionClosedException</code> - * is a subclass of <code> IOException </code> and therefore need not be - * caught separately, but if you are going to catch it separately, its - * catch block must appear before the more general <code> IOException </code> - * catch block. When you encounter an - * {@link org.apache.commons.net.nntp.NNTPConnectionClosedException} - * , you must disconnect the connection with - * {@link #disconnect disconnect() } to properly clean up the - * system resources used by NNTP. Before disconnecting, you may check the - * last reply code and text with - * {@link #getReplyCode getReplyCode } and - * {@link #getReplyString getReplyString }. + * You should keep in mind that the NNTP server may choose to prematurely close a connection if the client has been idle for longer than a given time period or + * if the server is being shutdown by the operator or some other reason. The NNTP class will detect a premature NNTP server connection closing when it receives + * a {@link org.apache.commons.net.nntp.NNTPReply#SERVICE_DISCONTINUED NNTPReply.SERVICE_DISCONTINUED } response to a command. When that occurs, the NNTP class + * method encountering that reply will throw an {@link org.apache.commons.net.nntp.NNTPConnectionClosedException} . <code>NNTPConectionClosedException</code> is + * a subclass of <code> IOException </code> and therefore need not be caught separately, but if you are going to catch it separately, its catch block must + * appear before the more general <code> IOException </code> catch block. When you encounter an + * {@link org.apache.commons.net.nntp.NNTPConnectionClosedException} , you must disconnect the connection with {@link #disconnect disconnect() } to properly + * clean up the system resources used by NNTP. Before disconnecting, you may check the last reply code and text with {@link #getReplyCode getReplyCode } and + * {@link #getReplyString getReplyString }. * <p> - * Rather than list it separately for each method, we mention here that - * every method communicating with the server and throwing an IOException - * can also throw a - * {@link org.apache.commons.net.MalformedServerReplyException} - * , which is a subclass - * of IOException. A MalformedServerReplyException will be thrown when - * the reply received from the server deviates enough from the protocol - * specification that it cannot be interpreted in a useful manner despite - * attempts to be as lenient as possible. + * Rather than list it separately for each method, we mention here that every method communicating with the server and throwing an IOException can also throw a + * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the + * reply received from the server deviates enough from the protocol specification that it cannot be interpreted in a useful manner despite attempts to be as + * lenient as possible. * * @see NNTPClient * @see NNTPConnectionClosedException * @see org.apache.commons.net.MalformedServerReplyException - ***/ + */ -public class NNTP extends SocketClient -{ - /*** The default NNTP port. Its value is 119 according to RFC 977. ***/ +public class NNTP extends SocketClient { + /** The default NNTP port. Its value is 119 according to RFC 977. */ public static final int DEFAULT_PORT = 119; // We have to ensure that the protocol communication is in ASCII // but we use ISO-8859-1 just in case 8-bit characters cross // the wire. - private static final String __DEFAULT_ENCODING = "ISO-8859-1"; + private static final Charset DEFAULT_ENCODING = StandardCharsets.ISO_8859_1; boolean _isAllowedToPost; - int _replyCode; - String _replyString; + private int replyCode; + private String replyString; /** - * Wraps {@link SocketClient#_input_} - * to communicate with server. Initialized by {@link #_connectAction_}. - * All server reads should be done through this variable. + * Wraps {@link SocketClient#_input_} to communicate with server. Initialized by {@link #_connectAction_}. All server reads should be done through this + * variable. */ protected BufferedReader _reader_; /** - * Wraps {@link SocketClient#_output_} - * to communicate with server. Initialized by {@link #_connectAction_}. - * All server reads should be done through this variable. + * Wraps {@link SocketClient#_output_} to communicate with server. Initialized by {@link #_connectAction_}. All server reads should be done through this + * variable. */ protected BufferedWriter _writer_; /** - * A ProtocolCommandSupport object used to manage the registering of - * ProtocolCommandListeners and te firing of ProtocolCommandEvents. + * A ProtocolCommandSupport object used to manage the registering of ProtocolCommandListeners and te firing of ProtocolCommandEvents. */ protected ProtocolCommandSupport _commandSupport_; - /*** - * The default NNTP constructor. Sets the default port to - * <code>DEFAULT_PORT</code> and initializes internal data structures - * for saving NNTP reply information. - ***/ - public NNTP() - { + /** + * The default NNTP constructor. Sets the default port to <code>DEFAULT_PORT</code> and initializes internal data structures for saving NNTP reply + * information. + */ + public NNTP() { setDefaultPort(DEFAULT_PORT); - _replyString = null; + replyString = null; _reader_ = null; _writer_ = null; _isAllowedToPost = false; _commandSupport_ = new ProtocolCommandSupport(this); } - private void __getReply() throws IOException - { - _replyString = _reader_.readLine(); - - if (_replyString == null) { - throw new NNTPConnectionClosedException( - "Connection closed without indication."); - } - - // In case we run into an anomaly we don't want fatal index exceptions - // to be thrown. - if (_replyString.length() < 3) { - throw new MalformedServerReplyException( - "Truncated server reply: " + _replyString); - } - - try - { - _replyCode = Integer.parseInt(_replyString.substring(0, 3)); - } - catch (NumberFormatException e) - { - throw new MalformedServerReplyException( - "Could not parse response code.\nServer Reply: " + _replyString); - } - - fireReplyReceived(_replyCode, _replyString + SocketClient.NETASCII_EOL); - - if (_replyCode == NNTPReply.SERVICE_DISCONTINUED) { - throw new NNTPConnectionClosedException( - "NNTP response 400 received. Server closed connection."); - } - } - - /*** - * Initiates control connections and gets initial reply, determining - * if the client is allowed to post to the server. Initializes - * {@link #_reader_} and {@link #_writer_} to wrap - * {@link SocketClient#_input_} and {@link SocketClient#_output_}. - ***/ + /** + * Initiates control connections and gets initial reply, determining if the client is allowed to post to the server. Initializes {@link #_reader_} and + * {@link #_writer_} to wrap {@link SocketClient#_input_} and {@link SocketClient#_output_}. + */ @Override - protected void _connectAction_() throws IOException - { + protected void _connectAction_() throws IOException { super._connectAction_(); - _reader_ = - new CRLFLineReader(new InputStreamReader(_input_, - __DEFAULT_ENCODING)); - _writer_ = - new BufferedWriter(new OutputStreamWriter(_output_, - __DEFAULT_ENCODING)); - __getReply(); - - _isAllowedToPost = (_replyCode == NNTPReply.SERVER_READY_POSTING_ALLOWED); - } + _reader_ = new CRLFLineReader(new InputStreamReader(_input_, DEFAULT_ENCODING)); + _writer_ = new BufferedWriter(new OutputStreamWriter(_output_, DEFAULT_ENCODING)); + getReply(); - /*** - * Closes the connection to the NNTP server and sets to null - * some internal data so that the memory may be reclaimed by the - * garbage collector. The reply text and code information from the - * last command is voided so that the memory it used may be reclaimed. - * <p> - * @throws IOException If an error occurs while disconnecting. - ***/ - @Override - public void disconnect() throws IOException - { - super.disconnect(); - _reader_ = null; - _writer_ = null; - _replyString = null; - _isAllowedToPost = false; + _isAllowedToPost = replyCode == NNTPReply.SERVER_READY_POSTING_ALLOWED; } - - /*** - * Indicates whether or not the client is allowed to post articles to - * the server it is currently connected to. + /** + * A convenience method to send the NNTP ARTICLE command to the server, receive the initial reply, and return the reply code. * <p> - * @return True if the client can post articles to the server, false - * otherwise. - ***/ - public boolean isAllowedToPost() - { - return _isAllowedToPost; + * + * @return The reply code received from the server. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int article() throws IOException { + return sendCommand(NNTPCommand.ARTICLE); } - - /*** - * Sends an NNTP command to the server, waits for a reply and returns the - * numerical response code. After invocation, for more detailed - * information, the actual reply text can be accessed by calling - * {@link #getReplyString getReplyString }. - * <p> - * @param command The text representation of the NNTP command to send. - * @param args The arguments to the NNTP command. If this parameter is - * set to null, then the command is sent with no argument. - * @return The integer value of the NNTP reply code returned by the server - * in response to the command. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int sendCommand(String command, String args) throws IOException - { - StringBuilder __commandBuffer = new StringBuilder(); - __commandBuffer.append(command); - - if (args != null) - { - __commandBuffer.append(' '); - __commandBuffer.append(args); - } - __commandBuffer.append(SocketClient.NETASCII_EOL); - - String message; - _writer_.write(message = __commandBuffer.toString()); - _writer_.flush(); - - fireCommandSent(command, message); - - __getReply(); - return _replyCode; + /** + * @param a article number + * @return number + * @throws IOException on error + * @deprecated - for API compatibility only - DO NOT USE + */ + @Deprecated + public int article(final int a) throws IOException { + return article((long) a); } - - /*** - * Sends an NNTP command to the server, waits for a reply and returns the - * numerical response code. After invocation, for more detailed - * information, the actual reply text can be accessed by calling - * {@link #getReplyString getReplyString }. + /** + * A convenience method to send the NNTP ARTICLE command to the server, receive the initial reply, and return the reply code. * <p> - * @param command The NNTPCommand constant corresponding to the NNTP command - * to send. - * @param args The arguments to the NNTP command. If this parameter is - * set to null, then the command is sent with no argument. - * @return The integer value of the NNTP reply code returned by the server - * in response to the command. - * in response to the command. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int sendCommand(int command, String args) throws IOException - { - return sendCommand(NNTPCommand.getCommand(command), args); + * + * @param articleNumber The number of the article to request from the currently selected newsgroup. + * @return The reply code received from the server. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int article(final long articleNumber) throws IOException { + return sendCommand(NNTPCommand.ARTICLE, Long.toString(articleNumber)); } - - /*** - * Sends an NNTP command with no arguments to the server, waits for a - * reply and returns the numerical response code. After invocation, for - * more detailed information, the actual reply text can be accessed by - * calling {@link #getReplyString getReplyString }. + /** + * A convenience method to send the NNTP ARTICLE command to the server, receive the initial reply, and return the reply code. * <p> - * @param command The text representation of the NNTP command to send. - * @return The integer value of the NNTP reply code returned by the server - * in response to the command. - * in response to the command. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int sendCommand(String command) throws IOException - { - return sendCommand(command, null); + * + * @param messageId The message identifier of the requested article, including the encapsulating < and > characters. + * @return The reply code received from the server. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int article(final String messageId) throws IOException { + return sendCommand(NNTPCommand.ARTICLE, messageId); } - - /*** - * Sends an NNTP command with no arguments to the server, waits for a - * reply and returns the numerical response code. After invocation, for - * more detailed information, the actual reply text can be accessed by - * calling {@link #getReplyString getReplyString }. + /** + * A convenience method to send the AUTHINFO PASS command to the server, receive the reply, and return the reply code. If this step is required, it should + * immediately follow the AUTHINFO USER command (See RFC 2980) * <p> - * @param command The NNTPCommand constant corresponding to the NNTP command - * to send. - * @return The integer value of the NNTP reply code returned by the server - * in response to the command. - * in response to the command. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int sendCommand(int command) throws IOException - { - return sendCommand(command, null); + * + * @param password a valid password. + * @return The reply code received from the server. The server should return a 281 or 502 for this command. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int authinfoPass(final String password) throws IOException { + final String passParameter = "PASS " + password; + return sendCommand(NNTPCommand.AUTHINFO, passParameter); } - - /*** - * Returns the integer value of the reply code of the last NNTP reply. - * You will usually only use this method after you connect to the - * NNTP server to check that the connection was successful since - * <code> connect </code> is of type void. + /** + * A convenience method to send the AUTHINFO USER command to the server, receive the reply, and return the reply code. (See RFC 2980) * <p> - * @return The integer value of the reply code of the last NNTP reply. - ***/ - public int getReplyCode() - { - return _replyCode; + * + * @param username A valid username. + * @return The reply code received from the server. The server should return a 381 or 281 for this command. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int authinfoUser(final String username) throws IOException { + final String userParameter = "USER " + username; + return sendCommand(NNTPCommand.AUTHINFO, userParameter); } - /*** - * Fetches a reply from the NNTP server and returns the integer reply - * code. After calling this method, the actual reply text can be accessed - * from {@link #getReplyString getReplyString }. Only use this - * method if you are implementing your own NNTP client or if you need to - * fetch a secondary response from the NNTP server. + /** + * A convenience method to send the NNTP BODY command to the server, receive the initial reply, and return the reply code. * <p> - * @return The integer value of the reply code of the fetched NNTP reply. - * in response to the command. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while - * receiving the server reply. - ***/ - public int getReply() throws IOException - { - __getReply(); - return _replyCode; + * + * @return The reply code received from the server. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int body() throws IOException { + return sendCommand(NNTPCommand.BODY); } - - /*** - * Returns the entire text of the last NNTP server response exactly - * as it was received, not including the end of line marker. - * <p> - * @return The entire text from the last NNTP response as a String. - ***/ - public String getReplyString() - { - return _replyString; + /** + * @param a article number + * @return number + * @throws IOException on error + * @deprecated - for API compatibility only - DO NOT USE + */ + @Deprecated + public int body(final int a) throws IOException { + return body((long) a); } - - /*** - * A convenience method to send the NNTP ARTICLE command to the server, - * receive the initial reply, and return the reply code. + /** + * A convenience method to send the NNTP BODY command to the server, receive the initial reply, and return the reply code. * <p> - * @param messageId The message identifier of the requested article, - * including the encapsulating < and > characters. + * + * @param articleNumber The number of the article to request from the currently selected newsgroup. * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int article(String messageId) throws IOException - { - return sendCommand(NNTPCommand.ARTICLE, messageId); + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int body(final long articleNumber) throws IOException { + return sendCommand(NNTPCommand.BODY, Long.toString(articleNumber)); } - /*** - * A convenience method to send the NNTP ARTICLE command to the server, - * receive the initial reply, and return the reply code. + /** + * A convenience method to send the NNTP BODY command to the server, receive the initial reply, and return the reply code. * <p> - * @param articleNumber The number of the article to request from the - * currently selected newsgroup. + * + * @param messageId The message identifier of the requested article, including the encapsulating < and > characters. * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int article(long articleNumber) throws IOException - { - return sendCommand(NNTPCommand.ARTICLE, Long.toString(articleNumber)); + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int body(final String messageId) throws IOException { + return sendCommand(NNTPCommand.BODY, messageId); } - /*** - * A convenience method to send the NNTP ARTICLE command to the server, - * receive the initial reply, and return the reply code. + /** + * Closes the connection to the NNTP server and sets to null some internal data so that the memory may be reclaimed by the garbage collector. The reply text + * and code information from the last command is voided so that the memory it used may be reclaimed. * <p> - * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int article() throws IOException - { - return sendCommand(NNTPCommand.ARTICLE); + * + * @throws IOException If an error occurs while disconnecting. + */ + @Override + public void disconnect() throws IOException { + super.disconnect(); + _reader_ = null; + _writer_ = null; + replyString = null; + _isAllowedToPost = false; } + /** + * Provide command support to super-class + */ + @Override + protected ProtocolCommandSupport getCommandSupport() { + return _commandSupport_; + } + /** + * Fetches a reply from the NNTP server and returns the integer reply code. After calling this method, the actual reply text can be accessed from + * {@link #getReplyString getReplyString }. Only use this method if you are implementing your own NNTP client or if you need to fetch a secondary response + * from the NNTP server. + * <p> + * + * @return The integer value of the reply code of the fetched NNTP reply. in response to the command. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while receiving the server reply. + */ + public int getReply() throws IOException { + replyString = _reader_.readLine(); - /*** - * A convenience method to send the NNTP BODY command to the server, - * receive the initial reply, and return the reply code. - * <p> - * @param messageId The message identifier of the requested article, - * including the encapsulating < and > characters. - * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int body(String messageId) throws IOException - { - return sendCommand(NNTPCommand.BODY, messageId); - } + if (replyString == null) { + throw new NNTPConnectionClosedException("Connection closed without indication."); + } - /*** - * A convenience method to send the NNTP BODY command to the server, - * receive the initial reply, and return the reply code. - * <p> - * @param articleNumber The number of the article to request from the - * currently selected newsgroup. - * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int body(long articleNumber) throws IOException - { - return sendCommand(NNTPCommand.BODY, Long.toString(articleNumber)); - } + // In case we run into an anomaly we don't want fatal index exceptions + // to be thrown. + if (replyString.length() < 3) { + throw new MalformedServerReplyException("Truncated server reply: " + replyString); + } - /*** - * A convenience method to send the NNTP BODY command to the server, - * receive the initial reply, and return the reply code. - * <p> - * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int body() throws IOException - { - return sendCommand(NNTPCommand.BODY); - } + try { + replyCode = Integer.parseInt(replyString.substring(0, 3)); + } catch (final NumberFormatException e) { + throw new MalformedServerReplyException("Could not parse response code.\nServer Reply: " + replyString); + } + fireReplyReceived(replyCode, replyString + SocketClient.NETASCII_EOL); + if (replyCode == NNTPReply.SERVICE_DISCONTINUED) { + throw new NNTPConnectionClosedException("NNTP response 400 received. Server closed connection."); + } + return replyCode; + } - /*** - * A convenience method to send the NNTP HEAD command to the server, - * receive the initial reply, and return the reply code. + /** + * Returns the integer value of the reply code of the last NNTP reply. You will usually only use this method after you connect to the NNTP server to check + * that the connection was successful since <code> connect </code> is of type void. * <p> - * @param messageId The message identifier of the requested article, - * including the encapsulating < and > characters. - * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int head(String messageId) throws IOException - { - return sendCommand(NNTPCommand.HEAD, messageId); + * + * @return The integer value of the reply code of the last NNTP reply. + */ + public int getReplyCode() { + return replyCode; } - /*** - * A convenience method to send the NNTP HEAD command to the server, - * receive the initial reply, and return the reply code. + /** + * Returns the entire text of the last NNTP server response exactly as it was received, not including the end of line marker. * <p> - * @param articleNumber The number of the article to request from the - * currently selected newsgroup. - * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int head(long articleNumber) throws IOException - { - return sendCommand(NNTPCommand.HEAD, Long.toString(articleNumber)); + * + * @return The entire text from the last NNTP response as a String. + */ + public String getReplyString() { + return replyString; } - /*** - * A convenience method to send the NNTP HEAD command to the server, - * receive the initial reply, and return the reply code. + /** + * A convenience method to send the NNTP GROUP command to the server, receive the reply, and return the reply code. * <p> + * + * @param newsgroup The name of the newsgroup to select. * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int head() throws IOException - { - return sendCommand(NNTPCommand.HEAD); + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int group(final String newsgroup) throws IOException { + return sendCommand(NNTPCommand.GROUP, newsgroup); } - - - /*** - * A convenience method to send the NNTP STAT command to the server, - * receive the initial reply, and return the reply code. + /** + * A convenience method to send the NNTP HEAD command to the server, receive the initial reply, and return the reply code. * <p> - * @param messageId The message identifier of the requested article, - * including the encapsulating < and > characters. + * * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int stat(String messageId) throws IOException - { - return sendCommand(NNTPCommand.STAT, messageId); + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int head() throws IOException { + return sendCommand(NNTPCommand.HEAD); } - /*** - * A convenience method to send the NNTP STAT command to the server, - * receive the initial reply, and return the reply code. - * <p> - * @param articleNumber The number of the article to request from the - * currently selected newsgroup. - * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int stat(long articleNumber) throws IOException - { - return sendCommand(NNTPCommand.STAT, Long.toString(articleNumber)); + /** + * @param a article number + * @return number + * @throws IOException on error + * @deprecated - for API compatibility only - DO NOT USE + */ + @Deprecated + public int head(final int a) throws IOException { + return head((long) a); } - /*** - * A convenience method to send the NNTP STAT command to the server, - * receive the initial reply, and return the reply code. + /** + * A convenience method to send the NNTP HEAD command to the server, receive the initial reply, and return the reply code. * <p> + * + * @param articleNumber The number of the article to request from the currently selected newsgroup. * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int stat() throws IOException - { - return sendCommand(NNTPCommand.STAT); + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int head(final long articleNumber) throws IOException { + return sendCommand(NNTPCommand.HEAD, Long.toString(articleNumber)); } - - /*** - * A convenience method to send the NNTP GROUP command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the NNTP HEAD command to the server, receive the initial reply, and return the reply code. * <p> - * @param newsgroup The name of the newsgroup to select. + * + * @param messageId The message identifier of the requested article, including the encapsulating < and > characters. * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int group(String newsgroup) throws IOException - { - return sendCommand(NNTPCommand.GROUP, newsgroup); + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int head(final String messageId) throws IOException { + return sendCommand(NNTPCommand.HEAD, messageId); } - - /*** - * A convenience method to send the NNTP HELP command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the NNTP HELP command to the server, receive the reply, and return the reply code. * <p> + * * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int help() throws IOException - { + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int help() throws IOException { return sendCommand(NNTPCommand.HELP); } - - /*** - * A convenience method to send the NNTP IHAVE command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the NNTP IHAVE command to the server, receive the reply, and return the reply code. * <p> - * @param messageId The article identifier, - * including the encapsulating < and > characters. + * + * @param messageId The article identifier, including the encapsulating < and > characters. * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int ihave(String messageId) throws IOException - { + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int ihave(final String messageId) throws IOException { return sendCommand(NNTPCommand.IHAVE, messageId); } + /** + * Indicates whether or not the client is allowed to post articles to the server it is currently connected to. + * <p> + * + * @return True if the client can post articles to the server, false otherwise. + */ + public boolean isAllowedToPost() { + return _isAllowedToPost; + } - /*** - * A convenience method to send the NNTP LAST command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the NNTP LAST command to the server, receive the reply, and return the reply code. * <p> + * * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int last() throws IOException - { + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int last() throws IOException { return sendCommand(NNTPCommand.LAST); } - - - /*** - * A convenience method to send the NNTP LIST command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the NNTP LIST command to the server, receive the reply, and return the reply code. * <p> + * * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int list() throws IOException - { + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int list() throws IOException { return sendCommand(NNTPCommand.LIST); } - - - /*** - * A convenience method to send the NNTP NEXT command to the server, - * receive the reply, and return the reply code. + /** + * A convenience wrapper for the extended LIST command that takes an argument, allowing us to selectively list multiple groups. * <p> - * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int next() throws IOException - { - return sendCommand(NNTPCommand.NEXT); + * + * @param wildmat A wildmat (pseudo-regex) pattern. See RFC 2980 for details. + * @return the reply code received from the server. + * @throws IOException if the command fails + */ + public int listActive(final String wildmat) throws IOException { + final StringBuilder command = new StringBuilder("ACTIVE "); + command.append(wildmat); + return sendCommand(NNTPCommand.LIST, command.toString()); } - - /*** - * A convenience method to send the "NEWGROUPS" command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the "NEWGROUPS" command to the server, receive the reply, and return the reply code. * <p> - * @param date The date after which to check for new groups. - * Date format is YYMMDD - * @param time The time after which to check for new groups. - * Time format is HHMMSS using a 24-hour clock. - * @param GMT True if the time is in GMT, false if local server time. - * @param distributions Comma-separated distribution list to check for - * new groups. Set to null if no distributions. + * + * @param date The date after which to check for new groups. Date format is YYMMDD + * @param time The time after which to check for new groups. Time format is HHMMSS using a 24-hour clock. + * @param GMT True if the time is in GMT, false if local server time. + * @param distributions Comma-separated distribution list to check for new groups. Set to null if no distributions. * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int newgroups(String date, String time, boolean GMT, - String distributions) throws IOException - { - StringBuilder buffer = new StringBuilder(); + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int newgroups(final String date, final String time, final boolean GMT, final String distributions) throws IOException { + final StringBuilder buffer = new StringBuilder(); buffer.append(date); buffer.append(' '); buffer.append(time); - if (GMT) - { + if (GMT) { buffer.append(' '); buffer.append("GMT"); } - if (distributions != null) - { + if (distributions != null) { buffer.append(" <"); buffer.append(distributions); buffer.append('>'); @@ -783,33 +526,23 @@ public class NNTP extends SocketClient return sendCommand(NNTPCommand.NEWGROUPS, buffer.toString()); } - - /*** - * A convenience method to send the "NEWNEWS" command to the server, - * receive the reply, and return the reply code. - * <p> - * @param newsgroups A comma-separated list of newsgroups to check for new - * news. - * @param date The date after which to check for new news. - * Date format is YYMMDD - * @param time The time after which to check for new news. - * Time format is HHMMSS using a 24-hour clock. - * @param GMT True if the time is in GMT, false if local server time. - * @param distributions Comma-separated distribution list to check for - * new news. Set to null if no distributions. + /** + * A convenience method to send the "NEWNEWS" command to the server, receive the reply, and return the reply code. + * <p> + * + * @param newsgroups A comma-separated list of newsgroups to check for new news. + * @param date The date after which to check for new news. Date format is YYMMDD + * @param time The time after which to check for new news. Time format is HHMMSS using a 24-hour clock. + * @param GMT True if the time is in GMT, false if local server time. + * @param distributions Comma-separated distribution list to check for new news. Set to null if no distributions. * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int newnews(String newsgroups, String date, String time, boolean GMT, - String distributions) throws IOException - { - StringBuilder buffer = new StringBuilder(); + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int newnews(final String newsgroups, final String date, final String time, final boolean GMT, final String distributions) throws IOException { + final StringBuilder buffer = new StringBuilder(); buffer.append(newsgroups); buffer.append(' '); @@ -817,14 +550,12 @@ public class NNTP extends SocketClient buffer.append(' '); buffer.append(time); - if (GMT) - { + if (GMT) { buffer.append(' '); buffer.append("GMT"); } - if (distributions != null) - { + if (distributions != null) { buffer.append(" <"); buffer.append(distributions); buffer.append('>'); @@ -833,195 +564,221 @@ public class NNTP extends SocketClient return sendCommand(NNTPCommand.NEWNEWS, buffer.toString()); } + /** + * A convenience method to send the NNTP NEXT command to the server, receive the reply, and return the reply code. + * <p> + * + * @return The reply code received from the server. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int next() throws IOException { + return sendCommand(NNTPCommand.NEXT); + } - - /*** - * A convenience method to send the NNTP POST command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the NNTP POST command to the server, receive the reply, and return the reply code. * <p> + * * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int post() throws IOException - { + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int post() throws IOException { return sendCommand(NNTPCommand.POST); } - - - /*** - * A convenience method to send the NNTP QUIT command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the NNTP QUIT command to the server, receive the reply, and return the reply code. * <p> + * * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int quit() throws IOException - { + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int quit() throws IOException { return sendCommand(NNTPCommand.QUIT); } - /*** - * A convenience method to send the AUTHINFO USER command to the server, - * receive the reply, and return the reply code. (See RFC 2980) - * <p> - * @param username A valid username. - * @return The reply code received from the server. The server should - * return a 381 or 281 for this command. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int authinfoUser(String username) throws IOException { - String userParameter = "USER " + username; - return sendCommand(NNTPCommand.AUTHINFO, userParameter); + /** + * Sends an NNTP command with no arguments to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed + * information, the actual reply text can be accessed by calling {@link #getReplyString getReplyString }. + * <p> + * + * @param command The NNTPCommand constant corresponding to the NNTP command to send. + * @return The integer value of the NNTP reply code returned by the server in response to the command. in response to the command. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int sendCommand(final int command) throws IOException { + return sendCommand(command, null); } - /*** - * A convenience method to send the AUTHINFO PASS command to the server, - * receive the reply, and return the reply code. If this step is - * required, it should immediately follow the AUTHINFO USER command - * (See RFC 2980) - * <p> - * @param password a valid password. - * @return The reply code received from the server. The server should - * return a 281 or 502 for this command. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int authinfoPass(String password) throws IOException { - String passParameter = "PASS " + password; - return sendCommand(NNTPCommand.AUTHINFO, passParameter); + /** + * Sends an NNTP command to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed information, the + * actual reply text can be accessed by calling {@link #getReplyString getReplyString }. + * <p> + * + * @param command The NNTPCommand constant corresponding to the NNTP command to send. + * @param args The arguments to the NNTP command. If this parameter is set to null, then the command is sent with no argument. + * @return The integer value of the NNTP reply code returned by the server in response to the command. in response to the command. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int sendCommand(final int command, final String args) throws IOException { + return sendCommand(NNTPCommand.getCommand(command), args); } - /*** - * A convenience method to send the NNTP XOVER command to the server, - * receive the reply, and return the reply code. - * <p> - * @param selectedArticles a String representation of the range of - * article headers required. This may be an article number, or a - * range of article numbers in the form "XXXX-YYYY", where XXXX - * and YYYY are valid article numbers in the current group. It - * also may be of the form "XXX-", meaning "return XXX and all - * following articles" In this revision, the last format is not - * possible (yet). - * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int xover(String selectedArticles) throws IOException { - return sendCommand(NNTPCommand.XOVER, selectedArticles); + /** + * Sends an NNTP command with no arguments to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed + * information, the actual reply text can be accessed by calling {@link #getReplyString getReplyString }. + * <p> + * + * @param command The text representation of the NNTP command to send. + * @return The integer value of the NNTP reply code returned by the server in response to the command. in response to the command. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int sendCommand(final String command) throws IOException { + return sendCommand(command, null); } - /*** - * A convenience method to send the NNTP XHDR command to the server, - * receive the reply, and return the reply code. - * <p> - * @param header a String naming a header line (e.g., "subject"). See - * RFC-1036 for a list of valid header lines. - * @param selectedArticles a String representation of the range of - * article headers required. This may be an article number, or a - * range of article numbers in the form "XXXX-YYYY", where XXXX - * and YYYY are valid article numbers in the current group. It - * also may be of the form "XXX-", meaning "return XXX and all - * following articles" In this revision, the last format is not - * possible (yet). - * @return The reply code received from the server. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int xhdr(String header, String selectedArticles) throws IOException { - StringBuilder command = new StringBuilder(header); - command.append(" "); - command.append(selectedArticles); - return sendCommand(NNTPCommand.XHDR, command.toString()); + /** + * Sends an NNTP command to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed information, the + * actual reply text can be accessed by calling {@link #getReplyString getReplyString }. + * <p> + * + * @param command The text representation of the NNTP command to send. + * @param args The arguments to the NNTP command. If this parameter is set to null, then the command is sent with no argument. + * @return The integer value of the NNTP reply code returned by the server in response to the command. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int sendCommand(final String command, final String args) throws IOException { + final StringBuilder __commandBuffer = new StringBuilder(); + __commandBuffer.append(command); + + if (args != null) { + __commandBuffer.append(' '); + __commandBuffer.append(args); + } + __commandBuffer.append(SocketClient.NETASCII_EOL); + + final String message; + _writer_.write(message = __commandBuffer.toString()); + _writer_.flush(); + + fireCommandSent(command, message); + + return getReply(); } /** - * A convenience wrapper for the extended LIST command that takes - * an argument, allowing us to selectively list multiple groups. + * A convenience method to send the NNTP STAT command to the server, receive the initial reply, and return the reply code. * <p> - * @param wildmat A wildmat (pseudo-regex) pattern. See RFC 2980 for - * details. - * @return the reply code received from the server. - * @throws IOException if the command fails + * + * @return The reply code received from the server. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - public int listActive(String wildmat) throws IOException { - StringBuilder command = new StringBuilder("ACTIVE "); - command.append(wildmat); - return sendCommand(NNTPCommand.LIST, command.toString()); + public int stat() throws IOException { + return sendCommand(NNTPCommand.STAT); } // DEPRECATED METHODS - for API compatibility only - DO NOT USE + /** + * @param a article number + * @return number + * @throws IOException on error + * @deprecated - for API compatibility only - DO NOT USE + */ @Deprecated - public int article(int a) throws IOException - { - return article((long) a); + public int stat(final int a) throws IOException { + return stat((long) a); } - @Deprecated - public int body(int a) throws IOException - { - return body((long) a); + /** + * A convenience method to send the NNTP STAT command to the server, receive the initial reply, and return the reply code. + * <p> + * + * @param articleNumber The number of the article to request from the currently selected newsgroup. + * @return The reply code received from the server. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int stat(final long articleNumber) throws IOException { + return sendCommand(NNTPCommand.STAT, Long.toString(articleNumber)); } - @Deprecated - public int head(int a) throws IOException - { - return head((long) a); + /** + * A convenience method to send the NNTP STAT command to the server, receive the initial reply, and return the reply code. + * <p> + * + * @param messageId The message identifier of the requested article, including the encapsulating < and > characters. + * @return The reply code received from the server. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int stat(final String messageId) throws IOException { + return sendCommand(NNTPCommand.STAT, messageId); } - @Deprecated - public int stat(int a) throws IOException - { - return stat((long) a); + /** + * A convenience method to send the NNTP XHDR command to the server, receive the reply, and return the reply code. + * <p> + * + * @param header a String naming a header line (e.g., "subject"). See RFC-1036 for a list of valid header lines. + * @param selectedArticles a String representation of the range of article headers required. This may be an article number, or a range of article numbers in + * the form "XXXX-YYYY", where XXXX and YYYY are valid article numbers in the current group. It also may be of the form "XXX-", + * meaning "return XXX and all following articles" In this revision, the last format is not possible (yet). + * @return The reply code received from the server. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int xhdr(final String header, final String selectedArticles) throws IOException { + final StringBuilder command = new StringBuilder(header); + command.append(" "); + command.append(selectedArticles); + return sendCommand(NNTPCommand.XHDR, command.toString()); } /** - * Provide command support to super-class + * A convenience method to send the NNTP XOVER command to the server, receive the reply, and return the reply code. + * <p> + * + * @param selectedArticles a String representation of the range of article headers required. This may be an article number, or a range of article numbers in + * the form "XXXX-YYYY", where XXXX and YYYY are valid article numbers in the current group. It also may be of the form "XXX-", + * meaning "return XXX and all following articles" In this revision, the last format is not possible (yet). + * @return The reply code received from the server. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - @Override - protected ProtocolCommandSupport getCommandSupport() { - return _commandSupport_; + public int xover(final String selectedArticles) throws IOException { + return sendCommand(NNTPCommand.XOVER, selectedArticles); } } - -/* Emacs configuration - * Local variables: ** - * mode: java ** - * c-basic-offset: 4 ** - * indent-tabs-mode: nil ** - * End: ** - */ diff --git a/src/main/java/org/apache/commons/net/nntp/NNTPClient.java b/src/main/java/org/apache/commons/net/nntp/NNTPClient.java index be3f0de..211c32b 100644 --- a/src/main/java/org/apache/commons/net/nntp/NNTPClient.java +++ b/src/main/java/org/apache/commons/net/nntp/NNTPClient.java @@ -29,752 +29,494 @@ import org.apache.commons.net.MalformedServerReplyException; import org.apache.commons.net.io.DotTerminatedMessageReader; import org.apache.commons.net.io.DotTerminatedMessageWriter; import org.apache.commons.net.io.Util; +import org.apache.commons.net.util.NetConstants; -/*** - * NNTPClient encapsulates all the functionality necessary to post and - * retrieve articles from an NNTP server. As with all classes derived - * from {@link org.apache.commons.net.SocketClient}, - * you must first connect to the server with - * {@link org.apache.commons.net.SocketClient#connect connect } - * before doing anything, and finally - * {@link org.apache.commons.net.nntp.NNTP#disconnect disconnect() } - * after you're completely finished interacting with the server. - * Remember that the - * {@link org.apache.commons.net.nntp.NNTP#isAllowedToPost isAllowedToPost()} - * method is defined in - * {@link org.apache.commons.net.nntp.NNTP}. +/** + * NNTPClient encapsulates all the functionality necessary to post and retrieve articles from an NNTP server. As with all classes derived from + * {@link org.apache.commons.net.SocketClient}, you must first connect to the server with {@link org.apache.commons.net.SocketClient#connect connect } before + * doing anything, and finally {@link org.apache.commons.net.nntp.NNTP#disconnect disconnect() } after you're completely finished interacting with the server. + * Remember that the {@link org.apache.commons.net.nntp.NNTP#isAllowedToPost isAllowedToPost()} method is defined in {@link org.apache.commons.net.nntp.NNTP}. * <p> - * You should keep in mind that the NNTP server may choose to prematurely - * close a connection if the client has been idle for longer than a - * given time period or if the server is being shutdown by the operator or - * some other reason. The NNTP class will detect a - * premature NNTP server connection closing when it receives a - * {@link org.apache.commons.net.nntp.NNTPReply#SERVICE_DISCONTINUED NNTPReply.SERVICE_DISCONTINUED } - * response to a command. - * When that occurs, the NNTP class method encountering that reply will throw - * an {@link org.apache.commons.net.nntp.NNTPConnectionClosedException} - * . - * <code>NNTPConectionClosedException</code> - * is a subclass of <code> IOException </code> and therefore need not be - * caught separately, but if you are going to catch it separately, its - * catch block must appear before the more general <code> IOException </code> - * catch block. When you encounter an - * {@link org.apache.commons.net.nntp.NNTPConnectionClosedException} - * , you must disconnect the connection with - * {@link org.apache.commons.net.nntp.NNTP#disconnect disconnect() } - * to properly clean up the - * system resources used by NNTP. Before disconnecting, you may check the - * last reply code and text with - * {@link org.apache.commons.net.nntp.NNTP#getReplyCode getReplyCode } and - * {@link org.apache.commons.net.nntp.NNTP#getReplyString getReplyString }. + * You should keep in mind that the NNTP server may choose to prematurely close a connection if the client has been idle for longer than a given time period or + * if the server is being shutdown by the operator or some other reason. The NNTP class will detect a premature NNTP server connection closing when it receives + * a {@link org.apache.commons.net.nntp.NNTPReply#SERVICE_DISCONTINUED NNTPReply.SERVICE_DISCONTINUED } response to a command. When that occurs, the NNTP class + * method encountering that reply will throw an {@link org.apache.commons.net.nntp.NNTPConnectionClosedException} . <code>NNTPConectionClosedException</code> is + * a subclass of <code> IOException </code> and therefore need not be caught separately, but if you are going to catch it separately, its catch block must + * appear before the more general <code> IOException </code> catch block. When you encounter an + * {@link org.apache.commons.net.nntp.NNTPConnectionClosedException} , you must disconnect the connection with + * {@link org.apache.commons.net.nntp.NNTP#disconnect disconnect() } to properly clean up the system resources used by NNTP. Before disconnecting, you may check + * the last reply code and text with {@link org.apache.commons.net.nntp.NNTP#getReplyCode getReplyCode } and + * {@link org.apache.commons.net.nntp.NNTP#getReplyString getReplyString }. * <p> - * Rather than list it separately for each method, we mention here that - * every method communicating with the server and throwing an IOException - * can also throw a - * {@link org.apache.commons.net.MalformedServerReplyException} - * , which is a subclass - * of IOException. A MalformedServerReplyException will be thrown when - * the reply received from the server deviates enough from the protocol - * specification that it cannot be interpreted in a useful manner despite - * attempts to be as lenient as possible. + * Rather than list it separately for each method, we mention here that every method communicating with the server and throwing an IOException can also throw a + * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the + * reply received from the server deviates enough from the protocol specification that it cannot be interpreted in a useful manner despite attempts to be as + * lenient as possible. * * @see NNTP * @see NNTPConnectionClosedException * @see org.apache.commons.net.MalformedServerReplyException - ***/ + */ + +public class NNTPClient extends NNTP { -public class NNTPClient extends NNTP -{ + private static final NewsgroupInfo[] EMPTY_NEWSGROUP_INFO_ARRAY = {}; /** - * Parse the reply and store the id and number in the pointer. - * - * @param reply the reply to parse "22n nnn <aaa>" - * @param pointer the pointer to update + * Parse a response line from {@link #retrieveArticleInfo(long, long)}. * - * @throws MalformedServerReplyException if response could not be parsed + * @param line a response line + * @return the parsed {@link Article}, if unparseable then isDummy() will be true, and the subject will contain the raw info. + * @since 3.0 */ - private void __parseArticlePointer(String reply, ArticleInfo pointer) - throws MalformedServerReplyException - { - String tokens[] = reply.split(" "); - if (tokens.length >= 3) { // OK, we can parset the line - int i = 1; // skip reply code - try - { - // Get article number - pointer.articleNumber = Long.parseLong(tokens[i++]); - // Get article id - pointer.articleId = tokens[i++]; - return; // done - } - catch (NumberFormatException e) - { - // drop through and raise exception + static Article parseArticleEntry(final String line) { + // Extract the article information + // Mandatory format (from NNTP RFC 2980) is : + // articleNumber\tSubject\tAuthor\tDate\tID\tReference(s)\tByte Count\tLine Count + + final Article article = new Article(); + article.setSubject(line); // in case parsing fails + final String parts[] = line.split("\t"); + if (parts.length > 6) { + int i = 0; + try { + article.setArticleNumber(Long.parseLong(parts[i++])); + article.setSubject(parts[i++]); + article.setFrom(parts[i++]); + article.setDate(parts[i++]); + article.setArticleId(parts[i++]); + article.addReference(parts[i++]); + } catch (final NumberFormatException e) { + // ignored, already handled } } - throw new MalformedServerReplyException( - "Could not parse article pointer.\nServer reply: " + reply); + return article; } /* - * 211 n f l s group selected - * (n = estimated number of articles in group, - * f = first article number in the group, - * l = last article number in the group, - * s = name of the group.) + * 211 n f l s group selected (n = estimated number of articles in group, f = first article number in the group, l = last article number in the group, s = + * name of the group.) */ - private static void __parseGroupReply(String reply, NewsgroupInfo info) - throws MalformedServerReplyException - { - String tokens[] = reply.split(" "); + private static void parseGroupReply(final String reply, final NewsgroupInfo info) throws MalformedServerReplyException { + final String tokens[] = reply.split(" "); if (tokens.length >= 5) { - int i = 1; // Skip numeric response value - try - { + int i = 1; // Skip numeric response value + try { // Get estimated article count - info._setArticleCount(Long.parseLong(tokens[i++])); + info.setArticleCount(Long.parseLong(tokens[i++])); // Get first article number - info._setFirstArticle(Long.parseLong(tokens[i++])); + info.setFirstArticle(Long.parseLong(tokens[i++])); // Get last article number - info._setLastArticle(Long.parseLong(tokens[i++])); + info.setLastArticle(Long.parseLong(tokens[i++])); // Get newsgroup name - info._setNewsgroup(tokens[i++]); + info.setNewsgroup(tokens[i++]); - info._setPostingPermission(NewsgroupInfo.UNKNOWN_POSTING_PERMISSION); - return ; - } catch (NumberFormatException e) - { - // drop through to report error + info.setPostingPermission(NewsgroupInfo.UNKNOWN_POSTING_PERMISSION); + return; + } catch (final NumberFormatException e) { + // drop through to report error } } - throw new MalformedServerReplyException( - "Could not parse newsgroup info.\nServer reply: " + reply); + throw new MalformedServerReplyException("Could not parse newsgroup info.\nServer reply: " + reply); } - // Format: group last first p - static NewsgroupInfo __parseNewsgroupListEntry(String entry) - { - String tokens[] = entry.split(" "); + static NewsgroupInfo parseNewsgroupListEntry(final String entry) { + final String tokens[] = entry.split(" "); if (tokens.length < 4) { return null; } - NewsgroupInfo result = new NewsgroupInfo(); + final NewsgroupInfo result = new NewsgroupInfo(); int i = 0; - result._setNewsgroup(tokens[i++]); + result.setNewsgroup(tokens[i++]); - try - { - long lastNum = Long.parseLong(tokens[i++]); - long firstNum = Long.parseLong(tokens[i++]); - result._setFirstArticle(firstNum); - result._setLastArticle(lastNum); + try { + final long lastNum = Long.parseLong(tokens[i++]); + final long firstNum = Long.parseLong(tokens[i++]); + result.setFirstArticle(firstNum); + result.setLastArticle(lastNum); if ((firstNum == 0) && (lastNum == 0)) { - result._setArticleCount(0); + result.setArticleCount(0); } else { - result._setArticleCount(lastNum - firstNum + 1); + result.setArticleCount(lastNum - firstNum + 1); } - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { return null; } - switch (tokens[i++].charAt(0)) - { + switch (tokens[i++].charAt(0)) { case 'y': case 'Y': - result._setPostingPermission( - NewsgroupInfo.PERMITTED_POSTING_PERMISSION); + result.setPostingPermission(NewsgroupInfo.PERMITTED_POSTING_PERMISSION); break; case 'n': case 'N': - result._setPostingPermission( - NewsgroupInfo.PROHIBITED_POSTING_PERMISSION); + result.setPostingPermission(NewsgroupInfo.PROHIBITED_POSTING_PERMISSION); break; case 'm': case 'M': - result._setPostingPermission( - NewsgroupInfo.MODERATED_POSTING_PERMISSION); + result.setPostingPermission(NewsgroupInfo.MODERATED_POSTING_PERMISSION); break; default: - result._setPostingPermission( - NewsgroupInfo.UNKNOWN_POSTING_PERMISSION); + result.setPostingPermission(NewsgroupInfo.UNKNOWN_POSTING_PERMISSION); break; } return result; } - /** - * Parse a response line from {@link #retrieveArticleInfo(long, long)}. - * - * @param line a response line - * @return the parsed {@link Article}, if unparseable then isDummy() - * will be true, and the subject will contain the raw info. - * @since 3.0 - */ - static Article __parseArticleEntry(String line) { - // Extract the article information - // Mandatory format (from NNTP RFC 2980) is : - // articleNumber\tSubject\tAuthor\tDate\tID\tReference(s)\tByte Count\tLine Count - - Article article = new Article(); - article.setSubject(line); // in case parsing fails - String parts[] = line.split("\t"); - if (parts.length > 6) { - int i = 0; - try { - article.setArticleNumber(Long.parseLong(parts[i++])); - article.setSubject(parts[i++]); - article.setFrom(parts[i++]); - article.setDate(parts[i++]); - article.setArticleId(parts[i++]); - article.addReference(parts[i++]); - } catch (NumberFormatException e) { - // ignored, already handled - } + @SuppressWarnings("deprecation") + private void ai2ap(final ArticleInfo ai, final ArticlePointer ap) { + if (ap != null) { // ai cannot be null + ap.articleId = ai.articleId; + ap.articleNumber = (int) ai.articleNumber; } - return article; } - private NewsgroupInfo[] __readNewsgroupListing() throws IOException - { - - BufferedReader reader = new DotTerminatedMessageReader(_reader_); - // Start of with a big vector because we may be reading a very large - // amount of groups. - Vector<NewsgroupInfo> list = new Vector<NewsgroupInfo>(2048); - - String line; - try { - while ((line = reader.readLine()) != null) { - NewsgroupInfo tmp = __parseNewsgroupListEntry(line); - if (tmp != null) { - list.addElement(tmp); - } else { - throw new MalformedServerReplyException(line); - } - } - } finally { - reader.close(); - } - int size; - if ((size = list.size()) < 1) { - return new NewsgroupInfo[0]; + private ArticleInfo ap2ai(@SuppressWarnings("deprecation") final ArticlePointer ap) { + if (ap == null) { + return null; } - - NewsgroupInfo[] info = new NewsgroupInfo[size]; - list.copyInto(info); - - return info; + final ArticleInfo ai = new ArticleInfo(); + return ai; } + /** + * Log into a news server by sending the AUTHINFO USER/AUTHINFO PASS command sequence. This is usually sent in response to a 480 reply code from the NNTP + * server. + * <p> + * + * @param username a valid username + * @param password the corresponding password + * @return True for successful login, false for a failure + * @throws IOException on error + */ + public boolean authenticate(final String username, final String password) throws IOException { + int replyCode = authinfoUser(username); - private BufferedReader __retrieve(int command, String articleId, ArticleInfo pointer) - throws IOException - { - if (articleId != null) - { - if (!NNTPReply.isPositiveCompletion(sendCommand(command, articleId))) { - return null; - } - } - else - { - if (!NNTPReply.isPositiveCompletion(sendCommand(command))) { - return null; - } - } - + if (replyCode == NNTPReply.MORE_AUTH_INFO_REQUIRED) { + replyCode = authinfoPass(password); - if (pointer != null) { - __parseArticlePointer(getReplyString(), pointer); + if (replyCode == NNTPReply.AUTHENTICATION_ACCEPTED) { + this._isAllowedToPost = true; + return true; + } } - - return new DotTerminatedMessageReader(_reader_); + return false; } + /** + * There are a few NNTPClient methods that do not complete the entire sequence of NNTP commands to complete a transaction. These commands require some + * action by the programmer after the reception of a positive preliminary command. After the programmer's code completes its actions, it must call this + * method to receive the completion reply from the server and verify the success of the entire transaction. + * <p> + * For example + * + * <pre> + * writer = client.postArticle(); + * if (writer == null) // failure + * return false; + * header = new SimpleNNTPHeader("foobar@foo.com", "Just testing"); + * header.addNewsgroup("alt.test"); + * writer.write(header.toString()); + * writer.write("This is just a test"); + * writer.close(); + * if (!client.completePendingCommand()) // failure + * return false; + * </pre> + * <p> + * + * @return True if successfully completed, false if not. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean completePendingCommand() throws IOException { + return NNTPReply.isPositiveCompletion(getReply()); + } - private BufferedReader __retrieve(int command, long articleNumber, ArticleInfo pointer) - throws IOException - { - if (!NNTPReply.isPositiveCompletion(sendCommand(command, - Long.toString(articleNumber)))) { + public Writer forwardArticle(final String articleId) throws IOException { + if (!NNTPReply.isPositiveIntermediate(ihave(articleId))) { return null; } - if (pointer != null) { - __parseArticlePointer(getReplyString(), pointer); - } - - return new DotTerminatedMessageReader(_reader_); + return new DotTerminatedMessageWriter(_writer_); } - - - /*** - * Retrieves an article from the NNTP server. The article is referenced - * by its unique article identifier (including the enclosing < and >). - * The article number and identifier contained in the server reply - * are returned through an ArticleInfo. The <code> articleId </code> - * field of the ArticleInfo cannot always be trusted because some - * NNTP servers do not correctly follow the RFC 977 reply format. - * <p> - * A DotTerminatedMessageReader is returned from which the article can - * be read. If the article does not exist, null is returned. - * <p> - * You must not issue any commands to the NNTP server (i.e., call any - * other methods) until you finish reading the message from the returned - * BufferedReader instance. - * The NNTP protocol uses the same stream for issuing commands as it does - * for returning results. Therefore the returned BufferedReader actually reads - * directly from the NNTP connection. After the end of message has been - * reached, new commands can be executed and their replies read. If - * you do not follow these requirements, your program will not work - * properly. + /** + * Return article headers for all articles between lowArticleNumber and highArticleNumber, inclusively, using the XOVER command. * <p> - * @param articleId The unique article identifier of the article to - * retrieve. If this parameter is null, the currently selected - * article is retrieved. - * @param pointer A parameter through which to return the article's - * number and unique id. The articleId field cannot always be trusted - * because of server deviations from RFC 977 reply formats. You may - * set this parameter to null if you do not desire to retrieve the - * returned article information. - * @return A DotTerminatedMessageReader instance from which the article - * can be read. null if the article does not exist. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public BufferedReader retrieveArticle(String articleId, ArticleInfo pointer) - throws IOException - { - return __retrieve(NNTPCommand.ARTICLE, articleId, pointer); - - } - - /** - * Same as <code> retrieveArticle(articleId, (ArticleInfo) null) </code> - * Note: the return can be cast to a {@link BufferedReader} - * @param articleId the article id to retrieve - * @return A DotTerminatedMessageReader instance from which the article can be read. - * null if the article does not exist. - * @throws IOException if an IO error occurs + * + * @param lowArticleNumber low + * @param highArticleNumber high + * @return an Iterable of Articles + * @throws IOException if the command failed + * @since 3.0 */ - public Reader retrieveArticle(String articleId) throws IOException - { - return retrieveArticle(articleId, (ArticleInfo) null); + public Iterable<Article> iterateArticleInfo(final long lowArticleNumber, final long highArticleNumber) throws IOException { + final BufferedReader info = retrieveArticleInfo(lowArticleNumber, highArticleNumber); + if (info == null) { + throw new IOException("XOVER command failed: " + getReplyString()); + } + // N.B. info is already DotTerminated, so don't rewrap + return new ArticleIterator(new ReplyIterator(info, false)); } /** - * Same as <code> retrieveArticle((String) null) </code> - * Note: the return can be cast to a {@link BufferedReader} - * @return A DotTerminatedMessageReader instance from which the article can be read. - * null if the article does not exist. - * @throws IOException if an IO error occurs + * List all new articles added to the NNTP server since a particular date subject to the conditions of the specified query. If no new new news is found, no + * entries will be returned. This uses the "NEWNEWS" command. You must add at least one newsgroup to the query, else the command will fail. Each String + * which is returned is a unique message identifier including the enclosing < and >. + * <p> + * + * @param query The query restricting how to search for new news. You must add at least one newsgroup to the query. + * @return An iterator of String instances containing the unique message identifiers for each new article added to the NNTP server. If no new news is found, + * no strings will be returned. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @since 3.0 */ - public Reader retrieveArticle() throws IOException - { - return retrieveArticle((String) null); + public Iterable<String> iterateNewNews(final NewGroupsOrNewsQuery query) throws IOException { + if (NNTPReply.isPositiveCompletion(newnews(query.getNewsgroups(), query.getDate(), query.getTime(), query.isGMT(), query.getDistributions()))) { + return new ReplyIterator(_reader_); + } + throw new IOException("NEWNEWS command failed: " + getReplyString()); } - - /*** - * Retrieves an article from the currently selected newsgroup. The - * article is referenced by its article number. - * The article number and identifier contained in the server reply - * are returned through an ArticleInfo. The <code> articleId </code> - * field of the ArticleInfo cannot always be trusted because some - * NNTP servers do not correctly follow the RFC 977 reply format. - * <p> - * A DotTerminatedMessageReader is returned from which the article can - * be read. If the article does not exist, null is returned. - * <p> - * You must not issue any commands to the NNTP server (i.e., call any - * other methods) until you finish reading the message from the returned - * BufferedReader instance. - * The NNTP protocol uses the same stream for issuing commands as it does - * for returning results. Therefore the returned BufferedReader actually reads - * directly from the NNTP connection. After the end of message has been - * reached, new commands can be executed and their replies read. If - * you do not follow these requirements, your program will not work - * properly. + /** + * List all new newsgroups added to the NNTP server since a particular date subject to the conditions of the specified query. If no new newsgroups were + * added, no entries will be returned. This uses the "NEWGROUPS" command. * <p> - * @param articleNumber The number of the the article to - * retrieve. - * @param pointer A parameter through which to return the article's - * number and unique id. The articleId field cannot always be trusted - * because of server deviations from RFC 977 reply formats. You may - * set this parameter to null if you do not desire to retrieve the - * returned article information. - * @return A DotTerminatedMessageReader instance from which the article - * can be read. null if the article does not exist. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public BufferedReader retrieveArticle(long articleNumber, ArticleInfo pointer) - throws IOException - { - return __retrieve(NNTPCommand.ARTICLE, articleNumber, pointer); + * + * @param query The query restricting how to search for new newsgroups. + * @return An iterable of Strings containing the raw information for each new newsgroup added to the NNTP server. If no newsgroups were added, no entries + * will be returned. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @since 3.0 + */ + public Iterable<String> iterateNewNewsgroupListing(final NewGroupsOrNewsQuery query) throws IOException { + if (NNTPReply.isPositiveCompletion(newgroups(query.getDate(), query.getTime(), query.isGMT(), query.getDistributions()))) { + return new ReplyIterator(_reader_); + } + throw new IOException("NEWGROUPS command failed: " + getReplyString()); } /** - * Same as <code> retrieveArticle(articleNumber, null) </code> - * @param articleNumber the article number to fetch - * @return A DotTerminatedMessageReader instance from which the article - * can be read. null if the article does not exist. - * @throws IOException if an IO error occurs + * List all new newsgroups added to the NNTP server since a particular date subject to the conditions of the specified query. If no new newsgroups were + * added, no entries will be returned. This uses the "NEWGROUPS" command. + * <p> + * + * @param query The query restricting how to search for new newsgroups. + * @return An iterable of NewsgroupInfo instances containing the information for each new newsgroup added to the NNTP server. If no newsgroups were added, + * no entries will be returned. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @since 3.0 */ - public BufferedReader retrieveArticle(long articleNumber) throws IOException - { - return retrieveArticle(articleNumber, null); + public Iterable<NewsgroupInfo> iterateNewNewsgroups(final NewGroupsOrNewsQuery query) throws IOException { + return new NewsgroupIterator(iterateNewNewsgroupListing(query)); } - - - /*** - * Retrieves an article header from the NNTP server. The article is - * referenced - * by its unique article identifier (including the enclosing < and >). - * The article number and identifier contained in the server reply - * are returned through an ArticleInfo. The <code> articleId </code> - * field of the ArticleInfo cannot always be trusted because some - * NNTP servers do not correctly follow the RFC 977 reply format. - * <p> - * A DotTerminatedMessageReader is returned from which the article can - * be read. If the article does not exist, null is returned. - * <p> - * You must not issue any commands to the NNTP server (i.e., call any - * other methods) until you finish reading the message from the returned - * BufferedReader instance. - * The NNTP protocol uses the same stream for issuing commands as it does - * for returning results. Therefore the returned BufferedReader actually reads - * directly from the NNTP connection. After the end of message has been - * reached, new commands can be executed and their replies read. If - * you do not follow these requirements, your program will not work - * properly. + /** + * List all newsgroups served by the NNTP server. If no newsgroups are served, no entries will be returned. The method uses the "LIST" command. * <p> - * @param articleId The unique article identifier of the article whose - * header is being retrieved. If this parameter is null, the - * header of the currently selected article is retrieved. - * @param pointer A parameter through which to return the article's - * number and unique id. The articleId field cannot always be trusted - * because of server deviations from RFC 977 reply formats. You may - * set this parameter to null if you do not desire to retrieve the - * returned article information. - * @return A DotTerminatedMessageReader instance from which the article - * header can be read. null if the article does not exist. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public BufferedReader retrieveArticleHeader(String articleId, ArticleInfo pointer) - throws IOException - { - return __retrieve(NNTPCommand.HEAD, articleId, pointer); - - } - - /** - * Same as <code> retrieveArticleHeader(articleId, (ArticleInfo) null) </code> - * Note: the return can be cast to a {@link BufferedReader} - * @param articleId the article id to fetch - * @return the reader - * @throws IOException if an error occurs + * + * @return An iterable of NewsgroupInfo instances containing the information for each newsgroup served by the NNTP server. If no newsgroups are served, no + * entries will be returned. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @since 3.0 */ - public Reader retrieveArticleHeader(String articleId) throws IOException - { - return retrieveArticleHeader(articleId, (ArticleInfo) null); + public Iterable<String> iterateNewsgroupListing() throws IOException { + if (NNTPReply.isPositiveCompletion(list())) { + return new ReplyIterator(_reader_); + } + throw new IOException("LIST command failed: " + getReplyString()); } /** - * Same as <code> retrieveArticleHeader((String) null) </code> - * Note: the return can be cast to a {@link BufferedReader} - * @return the reader - * @throws IOException if an error occurs + * List the newsgroups that match a given pattern. Uses the "LIST ACTIVE" command. + * <p> + * + * @param wildmat a pseudo-regex pattern (cf. RFC 2980) + * @return An iterable of Strings containing the raw information for each newsgroup served by the NNTP server corresponding to the supplied pattern. If no + * such newsgroups are served, no entries will be returned. + * @throws IOException on error + * @since 3.0 */ - public Reader retrieveArticleHeader() throws IOException - { - return retrieveArticleHeader((String) null); + public Iterable<String> iterateNewsgroupListing(final String wildmat) throws IOException { + if (NNTPReply.isPositiveCompletion(listActive(wildmat))) { + return new ReplyIterator(_reader_); + } + throw new IOException("LIST ACTIVE " + wildmat + " command failed: " + getReplyString()); } - - /*** - * Retrieves an article header from the currently selected newsgroup. The - * article is referenced by its article number. - * The article number and identifier contained in the server reply - * are returned through an ArticleInfo. The <code> articleId </code> - * field of the ArticleInfo cannot always be trusted because some - * NNTP servers do not correctly follow the RFC 977 reply format. - * <p> - * A DotTerminatedMessageReader is returned from which the article can - * be read. If the article does not exist, null is returned. - * <p> - * You must not issue any commands to the NNTP server (i.e., call any - * other methods) until you finish reading the message from the returned - * BufferedReader instance. - * The NNTP protocol uses the same stream for issuing commands as it does - * for returning results. Therefore the returned BufferedReader actually reads - * directly from the NNTP connection. After the end of message has been - * reached, new commands can be executed and their replies read. If - * you do not follow these requirements, your program will not work - * properly. + /** + * List all newsgroups served by the NNTP server. If no newsgroups are served, no entries will be returned. The method uses the "LIST" command. * <p> - * @param articleNumber The number of the the article whose header is - * being retrieved. - * @param pointer A parameter through which to return the article's - * number and unique id. The articleId field cannot always be trusted - * because of server deviations from RFC 977 reply formats. You may - * set this parameter to null if you do not desire to retrieve the - * returned article information. - * @return A DotTerminatedMessageReader instance from which the article - * header can be read. null if the article does not exist. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public BufferedReader retrieveArticleHeader(long articleNumber, - ArticleInfo pointer) - throws IOException - { - return __retrieve(NNTPCommand.HEAD, articleNumber, pointer); + * + * @return An iterable of Strings containing the raw information for each newsgroup served by the NNTP server. If no newsgroups are served, no entries will + * be returned. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @since 3.0 + */ + public Iterable<NewsgroupInfo> iterateNewsgroups() throws IOException { + return new NewsgroupIterator(iterateNewsgroupListing()); } - /** - * Same as <code> retrieveArticleHeader(articleNumber, null) </code> + * List the newsgroups that match a given pattern. Uses the "LIST ACTIVE" command. + * <p> * - * @param articleNumber the article number - * @return the reader - * @throws IOException if an error occurs + * @param wildmat a pseudo-regex pattern (cf. RFC 2980) + * @return An iterable NewsgroupInfo instances containing the information for each newsgroup served by the NNTP server corresponding to the supplied + * pattern. If no such newsgroups are served, no entries will be returned. + * @throws IOException on error + * @since 3.0 */ - public BufferedReader retrieveArticleHeader(long articleNumber) throws IOException - { - return retrieveArticleHeader(articleNumber, null); + public Iterable<NewsgroupInfo> iterateNewsgroups(final String wildmat) throws IOException { + return new NewsgroupIterator(iterateNewsgroupListing(wildmat)); } - - - /*** - * Retrieves an article body from the NNTP server. The article is - * referenced - * by its unique article identifier (including the enclosing < and >). - * The article number and identifier contained in the server reply - * are returned through an ArticleInfo. The <code> articleId </code> - * field of the ArticleInfo cannot always be trusted because some - * NNTP servers do not correctly follow the RFC 977 reply format. - * <p> - * A DotTerminatedMessageReader is returned from which the article can - * be read. If the article does not exist, null is returned. - * <p> - * You must not issue any commands to the NNTP server (i.e., call any - * other methods) until you finish reading the message from the returned - * BufferedReader instance. - * The NNTP protocol uses the same stream for issuing commands as it does - * for returning results. Therefore the returned BufferedReader actually reads - * directly from the NNTP connection. After the end of message has been - * reached, new commands can be executed and their replies read. If - * you do not follow these requirements, your program will not work - * properly. + /** + * List the command help from the server. * <p> - * @param articleId The unique article identifier of the article whose - * body is being retrieved. If this parameter is null, the - * body of the currently selected article is retrieved. - * @param pointer A parameter through which to return the article's - * number and unique id. The articleId field cannot always be trusted - * because of server deviations from RFC 977 reply formats. You may - * set this parameter to null if you do not desire to retrieve the - * returned article information. - * @return A DotTerminatedMessageReader instance from which the article - * body can be read. null if the article does not exist. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public BufferedReader retrieveArticleBody(String articleId, ArticleInfo pointer) - throws IOException - { - return __retrieve(NNTPCommand.BODY, articleId, pointer); - - } - - /** - * Same as <code> retrieveArticleBody(articleId, (ArticleInfo) null) </code> - * Note: the return can be cast to a {@link BufferedReader} - * @param articleId the article id - * @return A DotTerminatedMessageReader instance from which the article - * body can be read. null if the article does not exist. - * @throws IOException if an error occurs + * + * @return The sever help information. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - public Reader retrieveArticleBody(String articleId) throws IOException - { - return retrieveArticleBody(articleId, (ArticleInfo) null); + public String listHelp() throws IOException { + if (!NNTPReply.isInformational(help())) { + return null; + } + + try (final StringWriter help = new StringWriter(); final BufferedReader reader = new DotTerminatedMessageReader(_reader_)) { + Util.copyReader(reader, help); + return help.toString(); + } } /** - * Same as <code> retrieveArticleBody(null) </code> - * Note: the return can be cast to a {@link BufferedReader} - * @return A DotTerminatedMessageReader instance from which the article - * body can be read. null if the article does not exist. - * @throws IOException if an error occurs + * List all new articles added to the NNTP server since a particular date subject to the conditions of the specified query. If no new new news is found, a + * zero length array will be returned. If the command fails, null will be returned. You must add at least one newsgroup to the query, else the command will + * fail. Each String in the returned array is a unique message identifier including the enclosing < and >. This uses the "NEWNEWS" command. + * <p> + * + * @param query The query restricting how to search for new news. You must add at least one newsgroup to the query. + * @return An array of String instances containing the unique message identifiers for each new article added to the NNTP server. If no new news is found, a + * zero length array will be returned. If the command fails, null will be returned. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * + * @see #iterateNewNews(NewGroupsOrNewsQuery) */ - public Reader retrieveArticleBody() throws IOException - { - return retrieveArticleBody(null); - } - + public String[] listNewNews(final NewGroupsOrNewsQuery query) throws IOException { + if (!NNTPReply.isPositiveCompletion(newnews(query.getNewsgroups(), query.getDate(), query.getTime(), query.isGMT(), query.getDistributions()))) { + return null; + } - /*** - * Retrieves an article body from the currently selected newsgroup. The - * article is referenced by its article number. - * The article number and identifier contained in the server reply - * are returned through an ArticleInfo. The <code> articleId </code> - * field of the ArticleInfo cannot always be trusted because some - * NNTP servers do not correctly follow the RFC 977 reply format. - * <p> - * A DotTerminatedMessageReader is returned from which the article can - * be read. If the article does not exist, null is returned. - * <p> - * You must not issue any commands to the NNTP server (i.e., call any - * other methods) until you finish reading the message from the returned - * BufferedReader instance. - * The NNTP protocol uses the same stream for issuing commands as it does - * for returning results. Therefore the returned BufferedReader actually reads - * directly from the NNTP connection. After the end of message has been - * reached, new commands can be executed and their replies read. If - * you do not follow these requirements, your program will not work - * properly. - * <p> - * @param articleNumber The number of the the article whose body is - * being retrieved. - * @param pointer A parameter through which to return the article's - * number and unique id. The articleId field cannot always be trusted - * because of server deviations from RFC 977 reply formats. You may - * set this parameter to null if you do not desire to retrieve the - * returned article information. - * @return A DotTerminatedMessageReader instance from which the article - * body can be read. null if the article does not exist. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public BufferedReader retrieveArticleBody(long articleNumber, - ArticleInfo pointer) - throws IOException - { - return __retrieve(NNTPCommand.BODY, articleNumber, pointer); - } + final Vector<String> list = new Vector<>(); + try (final BufferedReader reader = new DotTerminatedMessageReader(_reader_)) { + String line; + while ((line = reader.readLine()) != null) { + list.addElement(line); + } + } - /** - * Same as <code> retrieveArticleBody(articleNumber, null) </code> - * @param articleNumber the article number - * @return the reader - * @throws IOException if an error occurs - */ - public BufferedReader retrieveArticleBody(long articleNumber) throws IOException - { - return retrieveArticleBody(articleNumber, null); - } + final int size = list.size(); + if (size < 1) { + return NetConstants.EMPTY_STRING_ARRAY; + } + final String[] result = new String[size]; + list.copyInto(result); - /*** - * Select the specified newsgroup to be the target of for future article - * retrieval and posting operations. Also return the newsgroup - * information contained in the server reply through the info parameter. - * <p> - * @param newsgroup The newsgroup to select. - * @param info A parameter through which the newsgroup information of - * the selected newsgroup contained in the server reply is returned. - * Set this to null if you do not desire this information. - * @return True if the newsgroup exists and was selected, false otherwise. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean selectNewsgroup(String newsgroup, NewsgroupInfo info) - throws IOException - { - if (!NNTPReply.isPositiveCompletion(group(newsgroup))) { - return false; - } + return result; + } - if (info != null) { - __parseGroupReply(getReplyString(), info); + /** + * List all new newsgroups added to the NNTP server since a particular date subject to the conditions of the specified query. If no new newsgroups were + * added, a zero length array will be returned. If the command fails, null will be returned. This uses the "NEWGROUPS" command. + * <p> + * + * @param query The query restricting how to search for new newsgroups. + * @return An array of NewsgroupInfo instances containing the information for each new newsgroup added to the NNTP server. If no newsgroups were added, a + * zero length array will be returned. If the command fails, null will be returned. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @see #iterateNewNewsgroups(NewGroupsOrNewsQuery) + * @see #iterateNewNewsgroupListing(NewGroupsOrNewsQuery) + */ + public NewsgroupInfo[] listNewNewsgroups(final NewGroupsOrNewsQuery query) throws IOException { + if (!NNTPReply.isPositiveCompletion(newgroups(query.getDate(), query.getTime(), query.isGMT(), query.getDistributions()))) { + return null; } - return true; + return readNewsgroupListing(); } /** - * Same as <code> selectNewsgroup(newsgroup, null) </code> - * @param newsgroup the newsgroup name - * @return true if newsgroup exist and was selected - * @throws IOException if an error occurs + * List all newsgroups served by the NNTP server. If no newsgroups are served, a zero length array will be returned. If the command fails, null will be + * returned. The method uses the "LIST" command. + * <p> + * + * @return An array of NewsgroupInfo instances containing the information for each newsgroup served by the NNTP server. If no newsgroups are served, a zero + * length array will be returned. If the command fails, null will be returned. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @see #iterateNewsgroupListing() + * @see #iterateNewsgroups() */ - public boolean selectNewsgroup(String newsgroup) throws IOException - { - return selectNewsgroup(newsgroup, null); + public NewsgroupInfo[] listNewsgroups() throws IOException { + if (!NNTPReply.isPositiveCompletion(list())) { + return null; + } + + return readNewsgroupListing(); } - /*** - * List the command help from the server. + /** + * List the newsgroups that match a given pattern. Uses the "LIST ACTIVE" command. * <p> - * @return The sever help information. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public String listHelp() throws IOException - { - if (!NNTPReply.isInformational(help())) { + * + * @param wildmat a pseudo-regex pattern (cf. RFC 2980) + * @return An array of NewsgroupInfo instances containing the information for each newsgroup served by the NNTP server corresponding to the supplied + * pattern. If no such newsgroups are served, a zero length array will be returned. If the command fails, null will be returned. + * @throws IOException on error + * @see #iterateNewsgroupListing(String) + * @see #iterateNewsgroups(String) + */ + public NewsgroupInfo[] listNewsgroups(final String wildmat) throws IOException { + if (!NNTPReply.isPositiveCompletion(listActive(wildmat))) { return null; } - - StringWriter help = new StringWriter(); - BufferedReader reader = new DotTerminatedMessageReader(_reader_); - Util.copyReader(reader, help); - reader.close(); - help.close(); - return help.toString(); + return readNewsgroupListing(); } /** @@ -783,694 +525,589 @@ public class NNTPClient extends NNTP * @return the contents of the Overview format, of {@code null} if the command failed * @throws IOException on error */ - public String[] listOverviewFmt() throws IOException - { - if (!NNTPReply.isPositiveCompletion(sendCommand("LIST", "OVERVIEW.FMT"))){ + public String[] listOverviewFmt() throws IOException { + if (!NNTPReply.isPositiveCompletion(sendCommand("LIST", "OVERVIEW.FMT"))) { return null; } - BufferedReader reader = new DotTerminatedMessageReader(_reader_); - String line; - ArrayList<String> list = new ArrayList<String>(); - while((line=reader.readLine()) != null) { - list.add(line); + try (final BufferedReader reader = new DotTerminatedMessageReader(_reader_)) { + String line; + final ArrayList<String> list = new ArrayList<>(); + while ((line = reader.readLine()) != null) { + list.add(line); + } + return list.toArray(NetConstants.EMPTY_STRING_ARRAY); } - reader.close(); - return list.toArray(new String[list.size()]); } - /*** - * Select an article by its unique identifier (including enclosing - * < and >) and return its article number and id through the - * pointer parameter. This is achieved through the STAT command. - * According to RFC 977, this will NOT set the current article pointer - * on the server. To do that, you must reference the article by its - * number. + /** + * Logs out of the news server gracefully by sending the QUIT command. However, you must still disconnect from the server before you can open a new + * connection. * <p> - * @param articleId The unique article identifier of the article that - * is being selectedd. If this parameter is null, the - * body of the current article is selected - * @param pointer A parameter through which to return the article's - * number and unique id. The articleId field cannot always be trusted - * because of server deviations from RFC 977 reply formats. You may - * set this parameter to null if you do not desire to retrieve the - * returned article information. - * @return True if successful, false if not. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean selectArticle(String articleId, ArticleInfo pointer) - throws IOException - { - if (articleId != null) { - if (!NNTPReply.isPositiveCompletion(stat(articleId))) { - return false; - } - } else { - if (!NNTPReply.isPositiveCompletion(stat())) { - return false; - } - } + * + * @return True if successfully completed, false if not. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean logout() throws IOException { + return NNTPReply.isPositiveCompletion(quit()); + } - if (pointer != null) { - __parseArticlePointer(getReplyString(), pointer); + /** + * Parse the reply and store the id and number in the pointer. + * + * @param reply the reply to parse "22n nnn <aaa>" + * @param pointer the pointer to update + * + * @throws MalformedServerReplyException if response could not be parsed + */ + private void parseArticlePointer(final String reply, final ArticleInfo pointer) throws MalformedServerReplyException { + final String tokens[] = reply.split(" "); + if (tokens.length >= 3) { // OK, we can parset the line + int i = 1; // skip reply code + try { + // Get article number + pointer.articleNumber = Long.parseLong(tokens[i++]); + // Get article id + pointer.articleId = tokens[i++]; + return; // done + } catch (final NumberFormatException e) { + // drop through and raise exception + } } - - return true; + throw new MalformedServerReplyException("Could not parse article pointer.\nServer reply: " + reply); } /** - * Same as <code> selectArticle(articleId, (ArticleInfo) null) </code> - * @param articleId the article Id - * @return true if successful - * @throws IOException on error + * Post an article to the NNTP server. This method returns a DotTerminatedMessageWriter instance to which the article can be written. Null is returned if + * the posting attempt fails. You should check {@link NNTP#isAllowedToPost isAllowedToPost() } before trying to post. However, a posting attempt can fail + * due to malformed headers. + * <p> + * You must not issue any commands to the NNTP server (i.e., call any (other methods) until you finish writing to the returned Writer instance and close it. + * The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore the returned Writer actually writes directly to + * the NNTP connection. After you close the writer, you can execute new commands. If you do not follow these requirements your program will not work + * properly. + * <p> + * Different NNTP servers will require different header formats, but you can use the provided {@link org.apache.commons.net.nntp.SimpleNNTPHeader} class to + * construct the bare minimum acceptable header for most news readers. To construct more complicated headers you should refer to RFC 822. When the Java Mail + * API is finalized, you will be able to use it to compose fully compliant Internet text messages. The DotTerminatedMessageWriter takes care of doubling + * line-leading dots and ending the message with a single dot upon closing, so all you have to worry about is writing the header and the message. + * <p> + * Upon closing the returned Writer, you need to call {@link #completePendingCommand completePendingCommand() } to finalize the posting and verify its + * success or failure from the server reply. + * <p> + * + * @return A DotTerminatedMessageWriter to which the article (including header) can be written. Returns null if the command fails. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - public boolean selectArticle(String articleId) throws IOException - { - return selectArticle(articleId, (ArticleInfo) null); - } - /**** - * Same as <code> selectArticle((String) null, articleId) </code>. Useful - * for retrieving the current article number. - * @param pointer to the article - * @return true if OK - * @throws IOException on error - ***/ - public boolean selectArticle(ArticleInfo pointer) throws IOException - { - return selectArticle(null, pointer); + public Writer postArticle() throws IOException { + if (!NNTPReply.isPositiveIntermediate(post())) { + return null; + } + + return new DotTerminatedMessageWriter(_writer_); } + private NewsgroupInfo[] readNewsgroupListing() throws IOException { - /*** - * Select an article in the currently selected newsgroup by its number. - * and return its article number and id through the - * pointer parameter. This is achieved through the STAT command. - * According to RFC 977, this WILL set the current article pointer - * on the server. Use this command to select an article before retrieving - * it, or to obtain an article's unique identifier given its number. - * <p> - * @param articleNumber The number of the article to select from the - * currently selected newsgroup. - * @param pointer A parameter through which to return the article's - * number and unique id. Although the articleId field cannot always - * be trusted because of server deviations from RFC 977 reply formats, - * we haven't found a server that misformats this information in response - * to this particular command. You may set this parameter to null if - * you do not desire to retrieve the returned article information. - * @return True if successful, false if not. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean selectArticle(long articleNumber, ArticleInfo pointer) - throws IOException - { - if (!NNTPReply.isPositiveCompletion(stat(articleNumber))) { - return false; - } + // Start of with a big vector because we may be reading a very large + // amount of groups. + final Vector<NewsgroupInfo> list = new Vector<>(2048); - if (pointer != null) { - __parseArticlePointer(getReplyString(), pointer); + String line; + try (final BufferedReader reader = new DotTerminatedMessageReader(_reader_)) { + while ((line = reader.readLine()) != null) { + final NewsgroupInfo tmp = parseNewsgroupListEntry(line); + if (tmp == null) { + throw new MalformedServerReplyException(line); + } + list.addElement(tmp); + } + } + final int size; + if ((size = list.size()) < 1) { + return EMPTY_NEWSGROUP_INFO_ARRAY; } - return true; - } - + final NewsgroupInfo[] info = new NewsgroupInfo[size]; + list.copyInto(info); - /*** Same as <code> selectArticle(articleNumber, null) </code> - * @param articleNumber the numger - * @return true if successful - * @throws IOException on error ***/ - public boolean selectArticle(long articleNumber) throws IOException - { - return selectArticle(articleNumber, null); + return info; } - - /*** - * Select the article preceeding the currently selected article in the - * currently selected newsgroup and return its number and unique id - * through the pointer parameter. Because of deviating server - * implementations, the articleId information cannot be trusted. To - * obtain the article identifier, issue a - * <code> selectArticle(pointer.articleNumber, pointer) </code> immediately - * afterward. - * <p> - * @param pointer A parameter through which to return the article's - * number and unique id. The articleId field cannot always be trusted - * because of server deviations from RFC 977 reply formats. You may - * set this parameter to null if you do not desire to retrieve the - * returned article information. - * @return True if successful, false if not (e.g., there is no previous - * article). - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean selectPreviousArticle(ArticleInfo pointer) - throws IOException - { - if (!NNTPReply.isPositiveCompletion(last())) { - return false; + private BufferedReader retrieve(final int command, final long articleNumber, final ArticleInfo pointer) throws IOException { + if (!NNTPReply.isPositiveCompletion(sendCommand(command, Long.toString(articleNumber)))) { + return null; } if (pointer != null) { - __parseArticlePointer(getReplyString(), pointer); + parseArticlePointer(getReplyString(), pointer); } - return true; - } - - /*** Same as <code> selectPreviousArticle((ArticleInfo) null) </code> - * @return true if successful - * @throws IOException on error ***/ - public boolean selectPreviousArticle() throws IOException - { - return selectPreviousArticle((ArticleInfo) null); + return new DotTerminatedMessageReader(_reader_); } - - /*** - * Select the article following the currently selected article in the - * currently selected newsgroup and return its number and unique id - * through the pointer parameter. Because of deviating server - * implementations, the articleId information cannot be trusted. To - * obtain the article identifier, issue a - * <code> selectArticle(pointer.articleNumber, pointer) </code> immediately - * afterward. - * <p> - * @param pointer A parameter through which to return the article's - * number and unique id. The articleId field cannot always be trusted - * because of server deviations from RFC 977 reply formats. You may - * set this parameter to null if you do not desire to retrieve the - * returned article information. - * @return True if successful, false if not (e.g., there is no following - * article). - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean selectNextArticle(ArticleInfo pointer) throws IOException - { - if (!NNTPReply.isPositiveCompletion(next())) { - return false; + private BufferedReader retrieve(final int command, final String articleId, final ArticleInfo pointer) throws IOException { + if (articleId != null) { + if (!NNTPReply.isPositiveCompletion(sendCommand(command, articleId))) { + return null; + } + } else if (!NNTPReply.isPositiveCompletion(sendCommand(command))) { + return null; } if (pointer != null) { - __parseArticlePointer(getReplyString(), pointer); + parseArticlePointer(getReplyString(), pointer); } - return true; + return new DotTerminatedMessageReader(_reader_); } - - /*** Same as <code> selectNextArticle((ArticleInfo) null) </code> - * @return true if successful - * @throws IOException on error ***/ - public boolean selectNextArticle() throws IOException - { - return selectNextArticle((ArticleInfo) null); + /** + * Same as <code> retrieveArticle((String) null) </code> Note: the return can be cast to a {@link BufferedReader} + * + * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist. + * @throws IOException if an IO error occurs + */ + public Reader retrieveArticle() throws IOException { + return retrieveArticle((String) null); } + /** + * @param articleNumber The number of the the article to retrieve + * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist. + * @throws IOException on error + * @deprecated 3.0 use {@link #retrieveArticle(long)} instead + */ + @Deprecated + public Reader retrieveArticle(final int articleNumber) throws IOException { + return retrieveArticle((long) articleNumber); + } - /*** - * List all newsgroups served by the NNTP server. If no newsgroups - * are served, a zero length array will be returned. If the command - * fails, null will be returned. - * The method uses the "LIST" command. - * <p> - * @return An array of NewsgroupInfo instances containing the information - * for each newsgroup served by the NNTP server. If no newsgroups - * are served, a zero length array will be returned. If the command - * fails, null will be returned. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @see #iterateNewsgroupListing() - * @see #iterateNewsgroups() - ***/ - public NewsgroupInfo[] listNewsgroups() throws IOException - { - if (!NNTPReply.isPositiveCompletion(list())) { - return null; - } + /** + * @param articleNumber The number of the the article to retrieve. + * @param pointer A parameter through which to return the article's number and unique id + * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist. + * @throws IOException on error + * @deprecated 3.0 use {@link #retrieveArticle(long, ArticleInfo)} instead + */ + @Deprecated + public Reader retrieveArticle(final int articleNumber, final ArticlePointer pointer) throws IOException { + final ArticleInfo ai = ap2ai(pointer); + final Reader rdr = retrieveArticle(articleNumber, ai); + ai2ap(ai, pointer); + return rdr; + } - return __readNewsgroupListing(); + /** + * Same as <code> retrieveArticle(articleNumber, null) </code> + * + * @param articleNumber the article number to fetch + * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist. + * @throws IOException if an IO error occurs + */ + public BufferedReader retrieveArticle(final long articleNumber) throws IOException { + return retrieveArticle(articleNumber, null); } /** - * List all newsgroups served by the NNTP server. If no newsgroups - * are served, no entries will be returned. - * The method uses the "LIST" command. + * Retrieves an article from the currently selected newsgroup. The article is referenced by its article number. The article number and identifier contained + * in the server reply are returned through an ArticleInfo. The <code> articleId </code> field of the ArticleInfo cannot always be trusted because some NNTP + * servers do not correctly follow the RFC 977 reply format. * <p> - * @return An iterable of NewsgroupInfo instances containing the information - * for each newsgroup served by the NNTP server. If no newsgroups - * are served, no entries will be returned. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @since 3.0 + * A DotTerminatedMessageReader is returned from which the article can be read. If the article does not exist, null is returned. + * <p> + * You must not issue any commands to the NNTP server (i.e., call any other methods) until you finish reading the message from the returned BufferedReader + * instance. The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore the returned BufferedReader actually + * reads directly from the NNTP connection. After the end of message has been reached, new commands can be executed and their replies read. If you do not + * follow these requirements, your program will not work properly. + * <p> + * + * @param articleNumber The number of the the article to retrieve. + * @param pointer A parameter through which to return the article's number and unique id. The articleId field cannot always be trusted because of + * server deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned + * article information. + * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - public Iterable<String> iterateNewsgroupListing() throws IOException { - if (NNTPReply.isPositiveCompletion(list())) { - return new ReplyIterator(_reader_); - } - throw new IOException("LIST command failed: "+getReplyString()); + public BufferedReader retrieveArticle(final long articleNumber, final ArticleInfo pointer) throws IOException { + return retrieve(NNTPCommand.ARTICLE, articleNumber, pointer); } /** - * List all newsgroups served by the NNTP server. If no newsgroups - * are served, no entries will be returned. - * The method uses the "LIST" command. - * <p> - * @return An iterable of Strings containing the raw information - * for each newsgroup served by the NNTP server. If no newsgroups - * are served, no entries will be returned. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @since 3.0 + * Same as <code> retrieveArticle(articleId, (ArticleInfo) null) </code> Note: the return can be cast to a {@link BufferedReader} + * + * @param articleId the article id to retrieve + * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist. + * @throws IOException if an IO error occurs */ - public Iterable<NewsgroupInfo> iterateNewsgroups() throws IOException { - return new NewsgroupIterator(iterateNewsgroupListing()); + public Reader retrieveArticle(final String articleId) throws IOException { + return retrieveArticle(articleId, (ArticleInfo) null); } /** - * List the newsgroups that match a given pattern. - * Uses the "LIST ACTIVE" command. + * Retrieves an article from the NNTP server. The article is referenced by its unique article identifier (including the enclosing < and >). The + * article number and identifier contained in the server reply are returned through an ArticleInfo. The <code> articleId </code> field of the ArticleInfo + * cannot always be trusted because some NNTP servers do not correctly follow the RFC 977 reply format. * <p> - * @param wildmat a pseudo-regex pattern (cf. RFC 2980) - * @return An array of NewsgroupInfo instances containing the information - * for each newsgroup served by the NNTP server corresponding to the - * supplied pattern. If no such newsgroups are served, a zero length - * array will be returned. If the command fails, null will be returned. - * @throws IOException on error - * @see #iterateNewsgroupListing(String) - * @see #iterateNewsgroups(String) + * A DotTerminatedMessageReader is returned from which the article can be read. If the article does not exist, null is returned. + * <p> + * You must not issue any commands to the NNTP server (i.e., call any other methods) until you finish reading the message from the returned BufferedReader + * instance. The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore the returned BufferedReader actually + * reads directly from the NNTP connection. After the end of message has been reached, new commands can be executed and their replies read. If you do not + * follow these requirements, your program will not work properly. + * <p> + * + * @param articleId The unique article identifier of the article to retrieve. If this parameter is null, the currently selected article is retrieved. + * @param pointer A parameter through which to return the article's number and unique id. The articleId field cannot always be trusted because of server + * deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned article + * information. + * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - public NewsgroupInfo[] listNewsgroups(String wildmat) throws IOException - { - if(!NNTPReply.isPositiveCompletion(listActive(wildmat))) { - return null; - } - return __readNewsgroupListing(); - } + public BufferedReader retrieveArticle(final String articleId, final ArticleInfo pointer) throws IOException { + return retrieve(NNTPCommand.ARTICLE, articleId, pointer); + } /** - * List the newsgroups that match a given pattern. - * Uses the "LIST ACTIVE" command. - * <p> - * @param wildmat a pseudo-regex pattern (cf. RFC 2980) - * @return An iterable of Strings containing the raw information - * for each newsgroup served by the NNTP server corresponding to the - * supplied pattern. If no such newsgroups are served, no entries - * will be returned. + * @param articleId The unique article identifier of the article to retrieve + * @param pointer A parameter through which to return the article's number and unique id + * @deprecated 3.0 use {@link #retrieveArticle(String, ArticleInfo)} instead + * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist. * @throws IOException on error - * @since 3.0 */ - public Iterable<String> iterateNewsgroupListing(String wildmat) throws IOException { - if(NNTPReply.isPositiveCompletion(listActive(wildmat))) { - return new ReplyIterator(_reader_); - } - throw new IOException("LIST ACTIVE "+wildmat+" command failed: "+getReplyString()); + @Deprecated + public Reader retrieveArticle(final String articleId, final ArticlePointer pointer) throws IOException { + final ArticleInfo ai = ap2ai(pointer); + final Reader rdr = retrieveArticle(articleId, ai); + ai2ap(ai, pointer); + return rdr; } /** - * List the newsgroups that match a given pattern. - * Uses the "LIST ACTIVE" command. - * <p> - * @param wildmat a pseudo-regex pattern (cf. RFC 2980) - * @return An iterable NewsgroupInfo instances containing the information - * for each newsgroup served by the NNTP server corresponding to the - * supplied pattern. If no such newsgroups are served, no entries - * will be returned. - * @throws IOException on error - * @since 3.0 + * Same as <code> retrieveArticleBody(null) </code> Note: the return can be cast to a {@link BufferedReader} + * + * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist. + * @throws IOException if an error occurs */ - public Iterable<NewsgroupInfo> iterateNewsgroups(String wildmat) throws IOException { - return new NewsgroupIterator(iterateNewsgroupListing(wildmat)); + public Reader retrieveArticleBody() throws IOException { + return retrieveArticleBody(null); } - /*** - * List all new newsgroups added to the NNTP server since a particular - * date subject to the conditions of the specified query. If no new - * newsgroups were added, a zero length array will be returned. If the - * command fails, null will be returned. - * This uses the "NEWGROUPS" command. - * <p> - * @param query The query restricting how to search for new newsgroups. - * @return An array of NewsgroupInfo instances containing the information - * for each new newsgroup added to the NNTP server. If no newsgroups - * were added, a zero length array will be returned. If the command - * fails, null will be returned. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @see #iterateNewNewsgroups(NewGroupsOrNewsQuery) - * @see #iterateNewNewsgroupListing(NewGroupsOrNewsQuery) - ***/ - public NewsgroupInfo[] listNewNewsgroups(NewGroupsOrNewsQuery query) - throws IOException - { - if (!NNTPReply.isPositiveCompletion(newgroups( - query.getDate(), query.getTime(), - query.isGMT(), query.getDistributions()))) - { - return null; - } + /** + * @param a tba + * @return tba + * @throws IOException tba + * @deprecated 3.0 use {@link #retrieveArticleBody(long)} instead + */ + @Deprecated + public Reader retrieveArticleBody(final int a) throws IOException { + return retrieveArticleBody((long) a); + } - return __readNewsgroupListing(); + /** + * @param a tba + * @param ap tba + * @return tba + * @throws IOException tba + * @deprecated 3.0 use {@link #retrieveArticleBody(long, ArticleInfo)} instead + */ + @Deprecated + public Reader retrieveArticleBody(final int a, final ArticlePointer ap) throws IOException { + final ArticleInfo ai = ap2ai(ap); + final Reader rdr = retrieveArticleBody(a, ai); + ai2ap(ai, ap); + return rdr; } /** - * List all new newsgroups added to the NNTP server since a particular - * date subject to the conditions of the specified query. If no new - * newsgroups were added, no entries will be returned. - * This uses the "NEWGROUPS" command. - * <p> - * @param query The query restricting how to search for new newsgroups. - * @return An iterable of Strings containing the raw information - * for each new newsgroup added to the NNTP server. If no newsgroups - * were added, no entries will be returned. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @since 3.0 + * Same as <code> retrieveArticleBody(articleNumber, null) </code> + * + * @param articleNumber the article number + * @return the reader + * @throws IOException if an error occurs */ - public Iterable<String> iterateNewNewsgroupListing(NewGroupsOrNewsQuery query) throws IOException { - if (NNTPReply.isPositiveCompletion(newgroups( - query.getDate(), query.getTime(), - query.isGMT(), query.getDistributions()))) { - return new ReplyIterator(_reader_); - } - throw new IOException("NEWGROUPS command failed: "+getReplyString()); + public BufferedReader retrieveArticleBody(final long articleNumber) throws IOException { + return retrieveArticleBody(articleNumber, null); } /** - * List all new newsgroups added to the NNTP server since a particular - * date subject to the conditions of the specified query. If no new - * newsgroups were added, no entries will be returned. - * This uses the "NEWGROUPS" command. + * Retrieves an article body from the currently selected newsgroup. The article is referenced by its article number. The article number and identifier + * contained in the server reply are returned through an ArticleInfo. The <code> articleId </code> field of the ArticleInfo cannot always be trusted because + * some NNTP servers do not correctly follow the RFC 977 reply format. * <p> - * @param query The query restricting how to search for new newsgroups. - * @return An iterable of NewsgroupInfo instances containing the information - * for each new newsgroup added to the NNTP server. If no newsgroups - * were added, no entries will be returned. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @since 3.0 + * A DotTerminatedMessageReader is returned from which the article can be read. If the article does not exist, null is returned. + * <p> + * You must not issue any commands to the NNTP server (i.e., call any other methods) until you finish reading the message from the returned BufferedReader + * instance. The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore the returned BufferedReader actually + * reads directly from the NNTP connection. After the end of message has been reached, new commands can be executed and their replies read. If you do not + * follow these requirements, your program will not work properly. + * <p> + * + * @param articleNumber The number of the the article whose body is being retrieved. + * @param pointer A parameter through which to return the article's number and unique id. The articleId field cannot always be trusted because of + * server deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned + * article information. + * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - public Iterable<NewsgroupInfo> iterateNewNewsgroups(NewGroupsOrNewsQuery query) throws IOException { - return new NewsgroupIterator(iterateNewNewsgroupListing(query)); + public BufferedReader retrieveArticleBody(final long articleNumber, final ArticleInfo pointer) throws IOException { + return retrieve(NNTPCommand.BODY, articleNumber, pointer); } - /*** - * List all new articles added to the NNTP server since a particular - * date subject to the conditions of the specified query. If no new - * new news is found, a zero length array will be returned. If the - * command fails, null will be returned. You must add at least one - * newsgroup to the query, else the command will fail. Each String - * in the returned array is a unique message identifier including the - * enclosing < and >. - * This uses the "NEWNEWS" command. + /** + * Same as <code> retrieveArticleBody(articleId, (ArticleInfo) null) </code> Note: the return can be cast to a {@link BufferedReader} + * + * @param articleId the article id + * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist. + * @throws IOException if an error occurs + */ + public Reader retrieveArticleBody(final String articleId) throws IOException { + return retrieveArticleBody(articleId, (ArticleInfo) null); + } + + /** + * Retrieves an article body from the NNTP server. The article is referenced by its unique article identifier (including the enclosing < and >). The + * article number and identifier contained in the server reply are returned through an ArticleInfo. The <code> articleId </code> field of the ArticleInfo + * cannot always be trusted because some NNTP servers do not correctly follow the RFC 977 reply format. + * <p> + * A DotTerminatedMessageReader is returned from which the article can be read. If the article does not exist, null is returned. + * <p> + * You must not issue any commands to the NNTP server (i.e., call any other methods) until you finish reading the message from the returned BufferedReader + * instance. The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore the returned BufferedReader actually + * reads directly from the NNTP connection. After the end of message has been reached, new commands can be executed and their replies read. If you do not + * follow these requirements, your program will not work properly. * <p> - * @param query The query restricting how to search for new news. You - * must add at least one newsgroup to the query. - * @return An array of String instances containing the unique message - * identifiers for each new article added to the NNTP server. If no - * new news is found, a zero length array will be returned. If the - * command fails, null will be returned. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. * - * @see #iterateNewNews(NewGroupsOrNewsQuery) - ***/ - public String[] listNewNews(NewGroupsOrNewsQuery query) - throws IOException - { - if (!NNTPReply.isPositiveCompletion( - newnews(query.getNewsgroups(), query.getDate(), query.getTime(), - query.isGMT(), query.getDistributions()))) { - return null; - } + * @param articleId The unique article identifier of the article whose body is being retrieved. If this parameter is null, the body of the currently + * selected article is retrieved. + * @param pointer A parameter through which to return the article's number and unique id. The articleId field cannot always be trusted because of server + * deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned article + * information. + * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public BufferedReader retrieveArticleBody(final String articleId, final ArticleInfo pointer) throws IOException { + return retrieve(NNTPCommand.BODY, articleId, pointer); - Vector<String> list = new Vector<String>(); - BufferedReader reader = new DotTerminatedMessageReader(_reader_); + } - String line; - try { - while ((line = reader.readLine()) != null) { - list.addElement(line); - } - } finally { - reader.close(); - } + /** + * @param articleId The unique article identifier of the article to retrieve + * @param pointer A parameter through which to return the article's number and unique id + * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist. + * @throws IOException on error + * @deprecated 3.0 use {@link #retrieveArticleBody(String, ArticleInfo)} instead + */ + @Deprecated + public Reader retrieveArticleBody(final String articleId, final ArticlePointer pointer) throws IOException { + final ArticleInfo ai = ap2ai(pointer); + final Reader rdr = retrieveArticleBody(articleId, ai); + ai2ap(ai, pointer); + return rdr; + } - int size = list.size(); - if (size < 1) { - return new String[0]; - } + /** + * Same as <code> retrieveArticleHeader((String) null) </code> Note: the return can be cast to a {@link BufferedReader} + * + * @return the reader + * @throws IOException if an error occurs + */ + public Reader retrieveArticleHeader() throws IOException { + return retrieveArticleHeader((String) null); + } - String[] result = new String[size]; - list.copyInto(result); + /** + * @param a tba + * @return tba + * @throws IOException tba + * @deprecated 3.0 use {@link #retrieveArticleHeader(long)} instead + */ + @Deprecated + public Reader retrieveArticleHeader(final int a) throws IOException { + return retrieveArticleHeader((long) a); + } - return result; + /** + * @param a tba + * @param ap tba + * @return tba + * @throws IOException tba + * @deprecated 3.0 use {@link #retrieveArticleHeader(long, ArticleInfo)} instead + */ + @Deprecated + public Reader retrieveArticleHeader(final int a, final ArticlePointer ap) throws IOException { + final ArticleInfo ai = ap2ai(ap); + final Reader rdr = retrieveArticleHeader(a, ai); + ai2ap(ai, ap); + return rdr; } /** - * List all new articles added to the NNTP server since a particular - * date subject to the conditions of the specified query. If no new - * new news is found, no entries will be returned. - * This uses the "NEWNEWS" command. - * You must add at least one newsgroup to the query, else the command will fail. - * Each String which is returned is a unique message identifier including the - * enclosing < and >. - * <p> - * @param query The query restricting how to search for new news. You - * must add at least one newsgroup to the query. - * @return An iterator of String instances containing the unique message - * identifiers for each new article added to the NNTP server. If no - * new news is found, no strings will be returned. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @since 3.0 + * Same as <code> retrieveArticleHeader(articleNumber, null) </code> + * + * @param articleNumber the article number + * @return the reader + * @throws IOException if an error occurs */ - public Iterable<String> iterateNewNews(NewGroupsOrNewsQuery query) throws IOException { - if (NNTPReply.isPositiveCompletion(newnews( - query.getNewsgroups(), query.getDate(), query.getTime(), - query.isGMT(), query.getDistributions()))) { - return new ReplyIterator(_reader_); - } - throw new IOException("NEWNEWS command failed: "+getReplyString()); + public BufferedReader retrieveArticleHeader(final long articleNumber) throws IOException { + return retrieveArticleHeader(articleNumber, null); } - /*** - * There are a few NNTPClient methods that do not complete the - * entire sequence of NNTP commands to complete a transaction. These - * commands require some action by the programmer after the reception - * of a positive preliminary command. After the programmer's code - * completes its actions, it must call this method to receive - * the completion reply from the server and verify the success of the - * entire transaction. + /** + * Retrieves an article header from the currently selected newsgroup. The article is referenced by its article number. The article number and identifier + * contained in the server reply are returned through an ArticleInfo. The <code> articleId </code> field of the ArticleInfo cannot always be trusted because + * some NNTP servers do not correctly follow the RFC 977 reply format. * <p> - * For example - * <pre> - * writer = client.postArticle(); - * if(writer == null) // failure - * return false; - * header = new SimpleNNTPHeader("foobar@foo.com", "Just testing"); - * header.addNewsgroup("alt.test"); - * writer.write(header.toString()); - * writer.write("This is just a test"); - * writer.close(); - * if(!client.completePendingCommand()) // failure - * return false; - * </pre> + * A DotTerminatedMessageReader is returned from which the article can be read. If the article does not exist, null is returned. * <p> - * @return True if successfully completed, false if not. - * @throws NNTPConnectionClosedException - * If the NNTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send NNTP reply code 400. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean completePendingCommand() throws IOException - { - return NNTPReply.isPositiveCompletion(getReply()); + * You must not issue any commands to the NNTP server (i.e., call any other methods) until you finish reading the message from the returned BufferedReader + * instance. The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore the returned BufferedReader actually + * reads directly from the NNTP connection. After the end of message has been reached, new commands can be executed and their replies read. If you do not + * follow these requirements, your program will not work properly. + * <p> + * + * @param articleNumber The number of the the article whose header is being retrieved. + * @param pointer A parameter through which to return the article's number and unique id. The articleId field cannot always be trusted because of + * server deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned + * article information. + * @return A DotTerminatedMessageReader instance from which the article header can be read. null if the article does not exist. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public BufferedReader retrieveArticleHeader(final long articleNumber, final ArticleInfo pointer) throws IOException { + return retrieve(NNTPCommand.HEAD, articleNumber, pointer); } - /*** - * Post an article to the NNTP server. This method returns a - * DotTerminatedMessageWriter instance to which the article can be - * written. Null is returned if the posting attempt fails. You - * should check {@link NNTP#isAllowedToPost isAllowedToPost() } - * before trying to post. However, a posting - * attempt can fail due to malformed headers. - * <p> - * You must not issue any commands to the NNTP server (i.e., call any - * (other methods) until you finish writing to the returned Writer - * instance and close it. The NNTP protocol uses the same stream for - * issuing commands as it does for returning results. Therefore the - * returned Writer actually writes directly to the NNTP connection. - * After you close the writer, you can execute new commands. If you - * do not follow these requirements your program will not work properly. + /** + * Same as <code> retrieveArticleHeader(articleId, (ArticleInfo) null) </code> Note: the return can be cast to a {@link BufferedReader} + * + * @param articleId the article id to fetch + * @return the reader + * @throws IOException if an error occurs + */ + public Reader retrieveArticleHeader(final String articleId) throws IOException { + return retrieveArticleHeader(articleId, (ArticleInfo) null); + } + + /** + * Retrieves an article header from the NNTP server. The article is referenced by its unique article identifier (including the enclosing < and >). The + * article number and identifier contained in the server reply are returned through an ArticleInfo. The <code> articleId </code> field of the ArticleInfo + * cannot always be trusted because some NNTP servers do not correctly follow the RFC 977 reply format. * <p> - * Different NNTP servers will require different header formats, but - * you can use the provided - * {@link org.apache.commons.net.nntp.SimpleNNTPHeader} - * class to construct the bare minimum acceptable header for most - * news readers. To construct more complicated headers you should - * refer to RFC 822. When the Java Mail API is finalized, you will be - * able to use it to compose fully compliant Internet text messages. - * The DotTerminatedMessageWriter takes care of doubling line-leading - * dots and ending the message with a single dot upon closing, so all - * you have to worry about is writing the header and the message. + * A DotTerminatedMessageReader is returned from which the article can be read. If the article does not exist, null is returned. * <p> - * Upon closing the returned Writer, you need to call - * {@link #completePendingCommand completePendingCommand() } - * to finalize the posting and verify its success or failure from - * the server reply. + * You must not issue any commands to the NNTP server (i.e., call any other methods) until you finish reading the message from the returned BufferedReader + * instance. The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore the returned BufferedReader actually + * reads directly from the NNTP connection. After the end of message has been reached, new commands can be executed and their replies read. If you do not + * follow these requirements, your program will not work properly. * <p> - * @return A DotTerminatedMessageWriter to which the article (including - * header) can be written. Returns null if the command fails. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - - public Writer postArticle() throws IOException - { - if (!NNTPReply.isPositiveIntermediate(post())) { - return null; - } + * + * @param articleId The unique article identifier of the article whose header is being retrieved. If this parameter is null, the header of the currently + * selected article is retrieved. + * @param pointer A parameter through which to return the article's number and unique id. The articleId field cannot always be trusted because of server + * deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned article + * information. + * @return A DotTerminatedMessageReader instance from which the article header can be read. null if the article does not exist. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public BufferedReader retrieveArticleHeader(final String articleId, final ArticleInfo pointer) throws IOException { + return retrieve(NNTPCommand.HEAD, articleId, pointer); - return new DotTerminatedMessageWriter(_writer_); } + /** + * @param articleId The unique article identifier of the article to retrieve + * @param pointer A parameter through which to return the article's number and unique id + * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist. + * @throws IOException on error + * @deprecated 3.0 use {@link #retrieveArticleHeader(String, ArticleInfo)} instead + */ + @Deprecated + public Reader retrieveArticleHeader(final String articleId, final ArticlePointer pointer) throws IOException { + final ArticleInfo ai = ap2ai(pointer); + final Reader rdr = retrieveArticleHeader(articleId, ai); + ai2ap(ai, pointer); + return rdr; + } - public Writer forwardArticle(String articleId) throws IOException - { - if (!NNTPReply.isPositiveIntermediate(ihave(articleId))) { - return null; - } - - return new DotTerminatedMessageWriter(_writer_); + /** + * @param lowArticleNumber to fetch + * @return a DotTerminatedReader if successful, null otherwise + * @throws IOException tba + * @deprecated 3.0 use {@link #retrieveArticleInfo(long)} instead + */ + @Deprecated + public Reader retrieveArticleInfo(final int lowArticleNumber) throws IOException { + return retrieveArticleInfo((long) lowArticleNumber); } + /** + * @param lowArticleNumber to fetch + * @param highArticleNumber to fetch + * @return a DotTerminatedReader if successful, null otherwise + * @throws IOException on error + * @deprecated 3.0 use {@link #retrieveArticleInfo(long, long)} instead + */ + @Deprecated + public Reader retrieveArticleInfo(final int lowArticleNumber, final int highArticleNumber) throws IOException { + return retrieveArticleInfo((long) lowArticleNumber, (long) highArticleNumber); + } - /*** - * Logs out of the news server gracefully by sending the QUIT command. - * However, you must still disconnect from the server before you can open - * a new connection. + /** + * Return article headers for a specified post. * <p> - * @return True if successfully completed, false if not. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean logout() throws IOException - { - return NNTPReply.isPositiveCompletion(quit()); + * + * @param articleNumber the article to retrieve headers for + * @return a DotTerminatedReader if successful, null otherwise + * @throws IOException on error + */ + public BufferedReader retrieveArticleInfo(final long articleNumber) throws IOException { + return retrieveArticleInfo(Long.toString(articleNumber)); } - /** - * Log into a news server by sending the AUTHINFO USER/AUTHINFO - * PASS command sequence. This is usually sent in response to a - * 480 reply code from the NNTP server. + * Return article headers for all articles between lowArticleNumber and highArticleNumber, inclusively. Uses the XOVER command. * <p> - * @param username a valid username - * @param password the corresponding password - * @return True for successful login, false for a failure + * + * @param lowArticleNumber low number + * @param highArticleNumber high number + * @return a DotTerminatedReader if successful, null otherwise * @throws IOException on error */ - public boolean authenticate(String username, String password) - throws IOException - { - int replyCode = authinfoUser(username); - - if (replyCode == NNTPReply.MORE_AUTH_INFO_REQUIRED) - { - replyCode = authinfoPass(password); - - if (replyCode == NNTPReply.AUTHENTICATION_ACCEPTED) - { - _isAllowedToPost = true; - return true; - } - } - return false; + public BufferedReader retrieveArticleInfo(final long lowArticleNumber, final long highArticleNumber) throws IOException { + return retrieveArticleInfo(lowArticleNumber + "-" + highArticleNumber); } - /*** + /** * Private implementation of XOVER functionality. * - * See {@link NNTP#xover} - * for legal agument formats. Alternatively, read RFC 2980 :-) + * See {@link NNTP#xover} for legal agument formats. Alternatively, read RFC 2980 :-) * <p> + * * @param articleRange - * @return Returns a DotTerminatedMessageReader if successful, null - * otherwise + * @return Returns a DotTerminatedMessageReader if successful, null otherwise * @throws IOException */ - private BufferedReader __retrieveArticleInfo(String articleRange) - throws IOException - { + private BufferedReader retrieveArticleInfo(final String articleRange) throws IOException { if (!NNTPReply.isPositiveCompletion(xover(articleRange))) { return null; } @@ -1479,71 +1116,72 @@ public class NNTPClient extends NNTP } /** - * Return article headers for a specified post. - * <p> - * @param articleNumber the article to retrieve headers for + * @param a tba + * @param b tba + * @return tba + * @throws IOException tba + * @deprecated 3.0 use {@link #retrieveHeader(String, long)} instead + */ + @Deprecated + public Reader retrieveHeader(final String a, final int b) throws IOException { + return retrieveHeader(a, (long) b); + } + + // DEPRECATED METHODS - for API compatibility only - DO NOT USE + // ============================================================ + + /** + * @param header the header + * @param lowArticleNumber to fetch + * @param highArticleNumber to fetch * @return a DotTerminatedReader if successful, null otherwise * @throws IOException on error + * @deprecated 3.0 use {@link #retrieveHeader(String, long, long)} instead */ - public BufferedReader retrieveArticleInfo(long articleNumber) throws IOException - { - return __retrieveArticleInfo(Long.toString(articleNumber)); + @Deprecated + public Reader retrieveHeader(final String header, final int lowArticleNumber, final int highArticleNumber) throws IOException { + return retrieveHeader(header, (long) lowArticleNumber, (long) highArticleNumber); } /** - * Return article headers for all articles between lowArticleNumber - * and highArticleNumber, inclusively. Uses the XOVER command. + * Return an article header for a specified post. * <p> - * @param lowArticleNumber low number - * @param highArticleNumber high number + * + * @param header the header to retrieve + * @param articleNumber the article to retrieve the header for * @return a DotTerminatedReader if successful, null otherwise * @throws IOException on error */ - public BufferedReader retrieveArticleInfo(long lowArticleNumber, - long highArticleNumber) - throws IOException - { - return - __retrieveArticleInfo(lowArticleNumber + "-" + - highArticleNumber); + public BufferedReader retrieveHeader(final String header, final long articleNumber) throws IOException { + return retrieveHeader(header, Long.toString(articleNumber)); } /** - * Return article headers for all articles between lowArticleNumber - * and highArticleNumber, inclusively, using the XOVER command. + * Return an article header for all articles between lowArticleNumber and highArticleNumber, inclusively. * <p> - * @param lowArticleNumber low - * @param highArticleNumber high - * @return an Iterable of Articles - * @throws IOException if the command failed - * @since 3.0 + * + * @param header the header + * @param lowArticleNumber to fetch + * @param highArticleNumber to fetch + * @return a DotTerminatedReader if successful, null otherwise + * @throws IOException on error */ - public Iterable<Article> iterateArticleInfo(long lowArticleNumber, long highArticleNumber) - throws IOException - { - BufferedReader info = retrieveArticleInfo(lowArticleNumber,highArticleNumber); - if (info == null) { - throw new IOException("XOVER command failed: "+getReplyString()); - } - // N.B. info is already DotTerminated, so don't rewrap - return new ArticleIterator(new ReplyIterator(info, false)); + public BufferedReader retrieveHeader(final String header, final long lowArticleNumber, final long highArticleNumber) throws IOException { + return retrieveHeader(header, lowArticleNumber + "-" + highArticleNumber); } - /*** + /** * Private implementation of XHDR functionality. * - * See {@link NNTP#xhdr} - * for legal agument formats. Alternatively, read RFC 1036. + * See {@link NNTP#xhdr} for legal agument formats. Alternatively, read RFC 1036. * <p> + * * @param header * @param articleRange - * @return Returns a DotTerminatedMessageReader if successful, null - * otherwise + * @return Returns a DotTerminatedMessageReader if successful, null otherwise * @throws IOException */ - private BufferedReader __retrieveHeader(String header, String articleRange) - throws IOException - { + private BufferedReader retrieveHeader(final String header, final String articleRange) throws IOException { if (!NNTPReply.isPositiveCompletion(xhdr(header, articleRange))) { return null; } @@ -1551,296 +1189,283 @@ public class NNTPClient extends NNTP return new DotTerminatedMessageReader(_reader_); } - /** - * Return an article header for a specified post. - * <p> - * @param header the header to retrieve - * @param articleNumber the article to retrieve the header for - * @return a DotTerminatedReader if successful, null otherwise - * @throws IOException on error - */ - public BufferedReader retrieveHeader(String header, long articleNumber) - throws IOException - { - return __retrieveHeader(header, Long.toString(articleNumber)); - } - - /** - * Return an article header for all articles between lowArticleNumber - * and highArticleNumber, inclusively. - * <p> - * @param header the header - * @param lowArticleNumber to fetch - * @param highArticleNumber to fetch - * @return a DotTerminatedReader if successful, null otherwise + /*** + * Same as <code> selectArticle((String) null, articleId) </code>. Useful for retrieving the current article number. + * + * @param pointer to the article + * @return true if OK * @throws IOException on error */ - public BufferedReader retrieveHeader(String header, long lowArticleNumber, - long highArticleNumber) - throws IOException - { - return - __retrieveHeader(header,lowArticleNumber + "-" + highArticleNumber); + public boolean selectArticle(final ArticleInfo pointer) throws IOException { + return selectArticle(null, pointer); } - - - - - // DEPRECATED METHODS - for API compatibility only - DO NOT USE - // ============================================================ - - - /** - * @param header the header - * @param lowArticleNumber to fetch - * @param highArticleNumber to fetch - * @return a DotTerminatedReader if successful, null otherwise + * @param pointer A parameter through which to return the article's number and unique id + * @return True if successful, false if not. * @throws IOException on error - * @deprecated 3.0 use {@link #retrieveHeader(String, long, long)} instead + * @deprecated 3.0 use {@link #selectArticle(ArticleInfo)} instead */ @Deprecated - public Reader retrieveHeader(String header, int lowArticleNumber, int highArticleNumber) - throws IOException - { - return retrieveHeader(header, (long) lowArticleNumber, (long) highArticleNumber); - } + public boolean selectArticle(final ArticlePointer pointer) throws IOException { + final ArticleInfo ai = ap2ai(pointer); + final boolean b = selectArticle(ai); + ai2ap(ai, pointer); + return b; - /** - * @param lowArticleNumber to fetch - * @param highArticleNumber to fetch - * @return a DotTerminatedReader if successful, null otherwise - * @throws IOException on error - * @deprecated 3.0 use {@link #retrieveArticleInfo(long, long)} instead - */ - @Deprecated - public Reader retrieveArticleInfo(int lowArticleNumber, int highArticleNumber) throws IOException { - return retrieveArticleInfo((long) lowArticleNumber, (long) highArticleNumber); } /** * @param a tba - * @param b tba - * @return tba + * @return tba * @throws IOException tba - * @deprecated 3.0 use {@link #retrieveHeader(String, long)} instead + * @deprecated 3.0 use {@link #selectArticle(long)} instead */ @Deprecated - public Reader retrieveHeader(String a, int b) throws IOException { - return retrieveHeader(a, (long) b); + public boolean selectArticle(final int a) throws IOException { + return selectArticle((long) a); } /** * @param a tba - * @param ap tba - * @return tba + * @param ap tba + * @return tba * @throws IOException tba * @deprecated 3.0 use {@link #selectArticle(long, ArticleInfo)} instead */ @Deprecated - public boolean selectArticle(int a, ArticlePointer ap) throws IOException { - ArticleInfo ai = __ap2ai(ap); - boolean b = selectArticle(a, ai); - __ai2ap(ai, ap); + public boolean selectArticle(final int a, final ArticlePointer ap) throws IOException { + final ArticleInfo ai = ap2ai(ap); + final boolean b = selectArticle(a, ai); + ai2ap(ai, ap); return b; } /** - * @param lowArticleNumber to fetch - * @return a DotTerminatedReader if successful, null otherwise - * @throws IOException tba - * @deprecated 3.0 use {@link #retrieveArticleInfo(long)} instead + * Same as <code> selectArticle(articleNumber, null) </code> + * + * @param articleNumber the numger + * @return true if successful + * @throws IOException on error */ - @Deprecated - public Reader retrieveArticleInfo(int lowArticleNumber) throws IOException { - return retrieveArticleInfo((long) lowArticleNumber); + public boolean selectArticle(final long articleNumber) throws IOException { + return selectArticle(articleNumber, null); } /** - * @param a tba - * @return tba - * @throws IOException tba - * @deprecated 3.0 use {@link #selectArticle(long)} instead + * Select an article in the currently selected newsgroup by its number. and return its article number and id through the pointer parameter. This is achieved + * through the STAT command. According to RFC 977, this WILL set the current article pointer on the server. Use this command to select an article before + * retrieving it, or to obtain an article's unique identifier given its number. + * <p> + * + * @param articleNumber The number of the article to select from the currently selected newsgroup. + * @param pointer A parameter through which to return the article's number and unique id. Although the articleId field cannot always be trusted + * because of server deviations from RFC 977 reply formats, we haven't found a server that misformats this information in response to + * this particular command. You may set this parameter to null if you do not desire to retrieve the returned article information. + * @return True if successful, false if not. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - @Deprecated - public boolean selectArticle(int a) throws IOException { - return selectArticle((long) a); - } + public boolean selectArticle(final long articleNumber, final ArticleInfo pointer) throws IOException { + if (!NNTPReply.isPositiveCompletion(stat(articleNumber))) { + return false; + } - /** - * @param a tba - * @return tba - * @throws IOException tba - * @deprecated 3.0 use {@link #retrieveArticleHeader(long)} instead - */ - @Deprecated - public Reader retrieveArticleHeader(int a) throws IOException { - return retrieveArticleHeader((long) a); - } + if (pointer != null) { + parseArticlePointer(getReplyString(), pointer); + } - /** - * @param a tba - * @param ap tba - * @return tba - * @throws IOException tba - * @deprecated 3.0 use {@link #retrieveArticleHeader(long, ArticleInfo)} instead - */ - @Deprecated - public Reader retrieveArticleHeader(int a, ArticlePointer ap) throws IOException { - ArticleInfo ai = __ap2ai(ap); - Reader rdr = retrieveArticleHeader(a, ai); - __ai2ap(ai, ap); - return rdr; + return true; } /** - * @param a tba - * @return tba - * @throws IOException tba - * @deprecated 3.0 use {@link #retrieveArticleBody(long)} instead + * Same as <code> selectArticle(articleId, (ArticleInfo) null) </code> + * + * @param articleId the article Id + * @return true if successful + * @throws IOException on error */ - @Deprecated - public Reader retrieveArticleBody(int a) throws IOException { - return retrieveArticleBody((long) a); + public boolean selectArticle(final String articleId) throws IOException { + return selectArticle(articleId, (ArticleInfo) null); } /** - * @param articleNumber The number of the the article to retrieve. - * @param pointer A parameter through which to return the article's number and unique id - * @return A DotTerminatedMessageReader instance from which the article - * can be read. null if the article does not exist. - * @throws IOException on error - * @deprecated 3.0 use {@link #retrieveArticle(long, ArticleInfo)} instead + * Select an article by its unique identifier (including enclosing < and >) and return its article number and id through the pointer parameter. This + * is achieved through the STAT command. According to RFC 977, this will NOT set the current article pointer on the server. To do that, you must reference + * the article by its number. + * <p> + * + * @param articleId The unique article identifier of the article that is being selectedd. If this parameter is null, the body of the current article is + * selected + * @param pointer A parameter through which to return the article's number and unique id. The articleId field cannot always be trusted because of server + * deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned article + * information. + * @return True if successful, false if not. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - @Deprecated - public Reader retrieveArticle(int articleNumber, ArticlePointer pointer) throws IOException { - ArticleInfo ai = __ap2ai(pointer); - Reader rdr = retrieveArticle(articleNumber, ai); - __ai2ap(ai, pointer); - return rdr; + public boolean selectArticle(final String articleId, final ArticleInfo pointer) throws IOException { + if (articleId != null) { + if (!NNTPReply.isPositiveCompletion(stat(articleId))) { + return false; + } + } else if (!NNTPReply.isPositiveCompletion(stat())) { + return false; + } + + if (pointer != null) { + parseArticlePointer(getReplyString(), pointer); + } + + return true; } /** - * @param articleNumber The number of the the article to retrieve - * @return A DotTerminatedMessageReader instance from which the article - * can be read. null if the article does not exist. + * @param articleId The unique article identifier of the article to retrieve + * @param pointer A parameter through which to return the article's number and unique id + * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist. * @throws IOException on error - * @deprecated 3.0 use {@link #retrieveArticle(long)} instead + * @deprecated 3.0 use {@link #selectArticle(String, ArticleInfo)} instead */ @Deprecated - public Reader retrieveArticle(int articleNumber) throws IOException { - return retrieveArticle((long) articleNumber); + public boolean selectArticle(final String articleId, final ArticlePointer pointer) throws IOException { + final ArticleInfo ai = ap2ai(pointer); + final boolean b = selectArticle(articleId, ai); + ai2ap(ai, pointer); + return b; + } /** - * @param a tba - * @param ap tba - * @return tba - * @throws IOException tba - * @deprecated 3.0 use {@link #retrieveArticleBody(long, ArticleInfo)} instead + * Same as <code> selectNewsgroup(newsgroup, null) </code> + * + * @param newsgroup the newsgroup name + * @return true if newsgroup exist and was selected + * @throws IOException if an error occurs */ - @Deprecated - public Reader retrieveArticleBody(int a, ArticlePointer ap) throws IOException { - ArticleInfo ai = __ap2ai(ap); - Reader rdr = retrieveArticleBody(a, ai); - __ai2ap(ai, ap); - return rdr; + public boolean selectNewsgroup(final String newsgroup) throws IOException { + return selectNewsgroup(newsgroup, null); } /** - * @param articleId The unique article identifier of the article to retrieve - * @param pointer A parameter through which to return the article's number and unique id - * @deprecated 3.0 use {@link #retrieveArticle(String, ArticleInfo)} instead - * @return A DotTerminatedMessageReader instance from which the article can be read. - * null if the article does not exist. - * @throws IOException on error + * Select the specified newsgroup to be the target of for future article retrieval and posting operations. Also return the newsgroup information contained + * in the server reply through the info parameter. + * <p> + * + * @param newsgroup The newsgroup to select. + * @param info A parameter through which the newsgroup information of the selected newsgroup contained in the server reply is returned. Set this to + * null if you do not desire this information. + * @return True if the newsgroup exists and was selected, false otherwise. + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - @Deprecated - public Reader retrieveArticle(String articleId, ArticlePointer pointer) throws IOException { - ArticleInfo ai = __ap2ai(pointer); - Reader rdr = retrieveArticle(articleId, ai); - __ai2ap(ai, pointer); - return rdr; + public boolean selectNewsgroup(final String newsgroup, final NewsgroupInfo info) throws IOException { + if (!NNTPReply.isPositiveCompletion(group(newsgroup))) { + return false; + } + + if (info != null) { + parseGroupReply(getReplyString(), info); + } + + return true; } /** - * @param articleId The unique article identifier of the article to retrieve - * @param pointer A parameter through which to return the article's number and unique id - * @return A DotTerminatedMessageReader instance from which the article - * body can be read. null if the article does not exist. + * Same as <code> selectNextArticle((ArticleInfo) null) </code> + * + * @return true if successful * @throws IOException on error - * @deprecated 3.0 use {@link #retrieveArticleBody(String, ArticleInfo)} instead */ - @Deprecated - public Reader retrieveArticleBody(String articleId, ArticlePointer pointer) throws IOException { - ArticleInfo ai = __ap2ai(pointer); - Reader rdr = retrieveArticleBody(articleId, ai); - __ai2ap(ai, pointer); - return rdr; + public boolean selectNextArticle() throws IOException { + return selectNextArticle((ArticleInfo) null); } /** - * @param articleId The unique article identifier of the article to retrieve - * @param pointer A parameter through which to return the article's number and unique id - * @return A DotTerminatedMessageReader instance from which the article - * body can be read. null if the article does not exist. - * @throws IOException on error - * @deprecated 3.0 use {@link #retrieveArticleHeader(String, ArticleInfo)} instead + * Select the article following the currently selected article in the currently selected newsgroup and return its number and unique id through the pointer + * parameter. Because of deviating server implementations, the articleId information cannot be trusted. To obtain the article identifier, issue a + * <code> selectArticle(pointer.articleNumber, pointer) </code> immediately afterward. + * <p> + * + * @param pointer A parameter through which to return the article's number and unique id. The articleId field cannot always be trusted because of server + * deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned article + * information. + * @return True if successful, false if not (e.g., there is no following article). + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - @Deprecated - public Reader retrieveArticleHeader(String articleId, ArticlePointer pointer) throws IOException { - ArticleInfo ai = __ap2ai(pointer); - Reader rdr = retrieveArticleHeader(articleId, ai); - __ai2ap(ai, pointer); - return rdr; + public boolean selectNextArticle(final ArticleInfo pointer) throws IOException { + if (!NNTPReply.isPositiveCompletion(next())) { + return false; + } + + if (pointer != null) { + parseArticlePointer(getReplyString(), pointer); + } + + return true; } /** - * @param articleId The unique article identifier of the article to retrieve * @param pointer A parameter through which to return the article's number and unique id - * @return A DotTerminatedMessageReader instance from which the article - * body can be read. null if the article does not exist. + * @return True if successful, false if not. * @throws IOException on error - * @deprecated 3.0 use {@link #selectArticle(String, ArticleInfo)} instead + * @deprecated 3.0 use {@link #selectNextArticle(ArticleInfo)} instead */ @Deprecated - public boolean selectArticle(String articleId, ArticlePointer pointer) throws IOException { - ArticleInfo ai = __ap2ai(pointer); - boolean b = selectArticle(articleId, ai); - __ai2ap(ai, pointer); + public boolean selectNextArticle(final ArticlePointer pointer) throws IOException { + final ArticleInfo ai = ap2ai(pointer); + final boolean b = selectNextArticle(ai); + ai2ap(ai, pointer); return b; } /** - * @param pointer A parameter through which to return the article's number and unique id - * @return True if successful, false if not. + * Same as <code> selectPreviousArticle((ArticleInfo) null) </code> + * + * @return true if successful * @throws IOException on error - * @deprecated 3.0 use {@link #selectArticle(ArticleInfo)} instead */ - @Deprecated - public boolean selectArticle(ArticlePointer pointer) throws IOException { - ArticleInfo ai = __ap2ai(pointer); - boolean b = selectArticle(ai); - __ai2ap(ai, pointer); - return b; - + public boolean selectPreviousArticle() throws IOException { + return selectPreviousArticle((ArticleInfo) null); } + // Helper methods + /** - * @param pointer A parameter through which to return the article's number and unique id - * @return True if successful, false if not. - * @throws IOException on error - * @deprecated 3.0 use {@link #selectNextArticle(ArticleInfo)} instead + * Select the article preceeding the currently selected article in the currently selected newsgroup and return its number and unique id through the pointer + * parameter. Because of deviating server implementations, the articleId information cannot be trusted. To obtain the article identifier, issue a + * <code> selectArticle(pointer.articleNumber, pointer) </code> immediately afterward. + * <p> + * + * @param pointer A parameter through which to return the article's number and unique id. The articleId field cannot always be trusted because of server + * deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned article + * information. + * @return True if successful, false if not (e.g., there is no previous article). + * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. */ - @Deprecated - public boolean selectNextArticle(ArticlePointer pointer) throws IOException { - ArticleInfo ai = __ap2ai(pointer); - boolean b = selectNextArticle(ai); - __ai2ap(ai, pointer); - return b; + public boolean selectPreviousArticle(final ArticleInfo pointer) throws IOException { + if (!NNTPReply.isPositiveCompletion(last())) { + return false; + } + + if (pointer != null) { + parseArticlePointer(getReplyString(), pointer); + } + return true; } /** @@ -1850,37 +1475,10 @@ public class NNTPClient extends NNTP * @deprecated 3.0 use {@link #selectPreviousArticle(ArticleInfo)} instead */ @Deprecated - public boolean selectPreviousArticle(ArticlePointer pointer) throws IOException { - ArticleInfo ai = __ap2ai(pointer); - boolean b = selectPreviousArticle(ai); - __ai2ap(ai, pointer); + public boolean selectPreviousArticle(final ArticlePointer pointer) throws IOException { + final ArticleInfo ai = ap2ai(pointer); + final boolean b = selectPreviousArticle(ai); + ai2ap(ai, pointer); return b; } - - // Helper methods - - private ArticleInfo __ap2ai(@SuppressWarnings("deprecation") ArticlePointer ap) { - if (ap == null) { - return null; - } - ArticleInfo ai = new ArticleInfo(); - return ai; - } - - @SuppressWarnings("deprecation") - private void __ai2ap(ArticleInfo ai, ArticlePointer ap){ - if (ap != null) { // ai cannot be null - ap.articleId = ai.articleId; - ap.articleNumber = (int) ai.articleNumber; - } - } } - - -/* Emacs configuration - * Local variables: ** - * mode: java ** - * c-basic-offset: 4 ** - * indent-tabs-mode: nil ** - * End: ** - */ diff --git a/src/main/java/org/apache/commons/net/nntp/NNTPCommand.java b/src/main/java/org/apache/commons/net/nntp/NNTPCommand.java index f49a8cc..c22484b 100644 --- a/src/main/java/org/apache/commons/net/nntp/NNTPCommand.java +++ b/src/main/java/org/apache/commons/net/nntp/NNTPCommand.java @@ -17,63 +17,47 @@ package org.apache.commons.net.nntp; -/*** - * NNTPCommand stores a set of constants for NNTP command codes. To interpret - * the meaning of the codes, familiarity with RFC 977 is assumed. - ***/ +/** + * NNTPCommand stores a set of constants for NNTP command codes. To interpret the meaning of the codes, familiarity with RFC 977 is assumed. + */ -public final class NNTPCommand -{ +public final class NNTPCommand { - public static final int ARTICLE = 0; - public static final int BODY = 1; - public static final int GROUP = 2; - public static final int HEAD = 3; - public static final int HELP = 4; - public static final int IHAVE = 5; - public static final int LAST = 6; - public static final int LIST = 7; + public static final int ARTICLE = 0; + public static final int BODY = 1; + public static final int GROUP = 2; + public static final int HEAD = 3; + public static final int HELP = 4; + public static final int IHAVE = 5; + public static final int LAST = 6; + public static final int LIST = 7; public static final int NEWGROUPS = 8; - public static final int NEWNEWS = 9; - public static final int NEXT = 10; - public static final int POST = 11; - public static final int QUIT = 12; - public static final int SLAVE = 13; - public static final int STAT = 14; - public static final int AUTHINFO = 15; - public static final int XOVER = 16; - public static final int XHDR = 17; - - // Cannot be instantiated - private NNTPCommand() - {} - - private static final String[] _commands = { - "ARTICLE", "BODY", "GROUP", "HEAD", "HELP", "IHAVE", "LAST", "LIST", - "NEWGROUPS", "NEWNEWS", "NEXT", "POST", "QUIT", "SLAVE", "STAT", - "AUTHINFO", "XOVER", "XHDR" - }; - - - /*** - * Retrieve the NNTP protocol command string corresponding to a specified - * command code. + public static final int NEWNEWS = 9; + public static final int NEXT = 10; + public static final int POST = 11; + public static final int QUIT = 12; + public static final int SLAVE = 13; + public static final int STAT = 14; + public static final int AUTHINFO = 15; + public static final int XOVER = 16; + public static final int XHDR = 17; + + private static final String[] commands = { "ARTICLE", "BODY", "GROUP", "HEAD", "HELP", "IHAVE", "LAST", "LIST", "NEWGROUPS", "NEWNEWS", "NEXT", "POST", + "QUIT", "SLAVE", "STAT", "AUTHINFO", "XOVER", "XHDR" }; + + /** + * Retrieve the NNTP protocol command string corresponding to a specified command code. * <p> + * * @param command The command code. - * @return The NNTP protcol command string corresponding to a specified - * command code. - ***/ - public static final String getCommand(int command) - { - return _commands[command]; + * @return The NNTP protcol command string corresponding to a specified command code. + */ + public static String getCommand(final int command) { + return commands[command]; } -} + // Cannot be instantiated + private NNTPCommand() { + } -/* Emacs configuration - * Local variables: ** - * mode: java ** - * c-basic-offset: 4 ** - * indent-tabs-mode: nil ** - * End: ** - */ +} diff --git a/src/main/java/org/apache/commons/net/nntp/NNTPConnectionClosedException.java b/src/main/java/org/apache/commons/net/nntp/NNTPConnectionClosedException.java index 427149f..b5e04e2 100644 --- a/src/main/java/org/apache/commons/net/nntp/NNTPConnectionClosedException.java +++ b/src/main/java/org/apache/commons/net/nntp/NNTPConnectionClosedException.java @@ -19,37 +19,30 @@ package org.apache.commons.net.nntp; import java.io.IOException; -/*** - * NNTPConnectionClosedException is used to indicate the premature or - * unexpected closing of an NNTP connection resulting from a - * {@link org.apache.commons.net.nntp.NNTPReply#SERVICE_DISCONTINUED NNTPReply.SERVICE_DISCONTINUED } - * response (NNTP reply code 400) to a - * failed NNTP command. This exception is derived from IOException and - * therefore may be caught either as an IOException or specifically as an - * NNTPConnectionClosedException. +/** + * NNTPConnectionClosedException is used to indicate the premature or unexpected closing of an NNTP connection resulting from a + * {@link org.apache.commons.net.nntp.NNTPReply#SERVICE_DISCONTINUED NNTPReply.SERVICE_DISCONTINUED } response (NNTP reply code 400) to a failed NNTP command. + * This exception is derived from IOException and therefore may be caught either as an IOException or specifically as an NNTPConnectionClosedException. * * @see NNTP * @see NNTPClient - ***/ + */ -public final class NNTPConnectionClosedException extends IOException -{ +public final class NNTPConnectionClosedException extends IOException { private static final long serialVersionUID = 1029785635891040770L; - /*** Constructs a NNTPConnectionClosedException with no message ***/ - public NNTPConnectionClosedException() - { - super(); + /** Constructs a NNTPConnectionClosedException with no message */ + public NNTPConnectionClosedException() { } - /*** + /** * Constructs a NNTPConnectionClosedException with a specified message. * <p> - * @param message The message explaining the reason for the exception. - ***/ - public NNTPConnectionClosedException(String message) - { + * + * @param message The message explaining the reason for the exception. + */ + public NNTPConnectionClosedException(final String message) { super(message); } diff --git a/src/main/java/org/apache/commons/net/nntp/NNTPReply.java b/src/main/java/org/apache/commons/net/nntp/NNTPReply.java index 0808141..e6d5e71 100644 --- a/src/main/java/org/apache/commons/net/nntp/NNTPReply.java +++ b/src/main/java/org/apache/commons/net/nntp/NNTPReply.java @@ -17,148 +17,118 @@ package org.apache.commons.net.nntp; -/*** - * NNTPReply stores a set of constants for NNTP reply codes. To interpret - * the meaning of the codes, familiarity with RFC 977 is assumed. - * The mnemonic constant names are transcriptions from the code descriptions - * of RFC 977. - ***/ +/** + * NNTPReply stores a set of constants for NNTP reply codes. To interpret the meaning of the codes, familiarity with RFC 977 is assumed. The mnemonic constant + * names are transcriptions from the code descriptions of RFC 977. + */ -public final class NNTPReply -{ +public final class NNTPReply { - public static final int HELP_TEXT_FOLLOWS = 100; - public static final int DEBUG_OUTPUT = 199; - public static final int SERVER_READY_POSTING_ALLOWED = 200; + public static final int HELP_TEXT_FOLLOWS = 100; + public static final int DEBUG_OUTPUT = 199; + public static final int SERVER_READY_POSTING_ALLOWED = 200; public static final int SERVER_READY_POSTING_NOT_ALLOWED = 201; - public static final int SLAVE_STATUS_NOTED = 202; - public static final int CLOSING_CONNECTION = 205; - public static final int GROUP_SELECTED = 211; + public static final int SLAVE_STATUS_NOTED = 202; + public static final int CLOSING_CONNECTION = 205; + public static final int GROUP_SELECTED = 211; public static final int ARTICLE_RETRIEVED_HEAD_AND_BODY_FOLLOW = 220; public static final int ARTICLE_RETRIEVED_HEAD_FOLLOWS = 221; public static final int ARTICLE_RETRIEVED_BODY_FOLLOWS = 222; public static final int ARTICLE_RETRIEVED_REQUEST_TEXT_SEPARATELY = 223; public static final int ARTICLE_LIST_BY_MESSAGE_ID_FOLLOWS = 230; - public static final int NEW_NEWSGROUP_LIST_FOLLOWS = 231; - public static final int ARTICLE_TRANSFERRED_OK = 235; - public static final int ARTICLE_POSTED_OK = 240; - public static final int AUTHENTICATION_ACCEPTED = 281; - public static final int SEND_ARTICLE_TO_TRANSFER = 335; - public static final int SEND_ARTICLE_TO_POST = 340; - public static final int MORE_AUTH_INFO_REQUIRED = 381; - public static final int SERVICE_DISCONTINUED = 400; - public static final int NO_SUCH_NEWSGROUP = 411; - public static final int NO_NEWSGROUP_SELECTED = 412; - public static final int NO_CURRENT_ARTICLE_SELECTED = 420; - public static final int NO_NEXT_ARTICLE = 421; - public static final int NO_PREVIOUS_ARTICLE = 422; - public static final int NO_SUCH_ARTICLE_NUMBER = 423; - public static final int NO_SUCH_ARTICLE_FOUND = 430; - public static final int ARTICLE_NOT_WANTED = 435; - public static final int TRANSFER_FAILED = 436; - public static final int ARTICLE_REJECTED = 437; - public static final int POSTING_NOT_ALLOWED = 440; - public static final int POSTING_FAILED = 441; + public static final int NEW_NEWSGROUP_LIST_FOLLOWS = 231; + public static final int ARTICLE_TRANSFERRED_OK = 235; + public static final int ARTICLE_POSTED_OK = 240; + public static final int AUTHENTICATION_ACCEPTED = 281; + public static final int SEND_ARTICLE_TO_TRANSFER = 335; + public static final int SEND_ARTICLE_TO_POST = 340; + public static final int MORE_AUTH_INFO_REQUIRED = 381; + public static final int SERVICE_DISCONTINUED = 400; + public static final int NO_SUCH_NEWSGROUP = 411; + public static final int NO_NEWSGROUP_SELECTED = 412; + public static final int NO_CURRENT_ARTICLE_SELECTED = 420; + public static final int NO_NEXT_ARTICLE = 421; + public static final int NO_PREVIOUS_ARTICLE = 422; + public static final int NO_SUCH_ARTICLE_NUMBER = 423; + public static final int NO_SUCH_ARTICLE_FOUND = 430; + public static final int ARTICLE_NOT_WANTED = 435; + public static final int TRANSFER_FAILED = 436; + public static final int ARTICLE_REJECTED = 437; + public static final int POSTING_NOT_ALLOWED = 440; + public static final int POSTING_FAILED = 441; /** @since 2.2 - corrected value to 480 */ - public static final int AUTHENTICATION_REQUIRED = 480; - public static final int AUTHENTICATION_REJECTED = 482; - public static final int COMMAND_NOT_RECOGNIZED = 500; - public static final int COMMAND_SYNTAX_ERROR = 501; - public static final int PERMISSION_DENIED = 502; - public static final int PROGRAM_FAULT = 503; + public static final int AUTHENTICATION_REQUIRED = 480; + public static final int AUTHENTICATION_REJECTED = 482; + public static final int COMMAND_NOT_RECOGNIZED = 500; + public static final int COMMAND_SYNTAX_ERROR = 501; + public static final int PERMISSION_DENIED = 502; + public static final int PROGRAM_FAULT = 503; // Cannot be instantiated - private NNTPReply() - {} - - /*** - * Determine if a reply code is an informational response. All - * codes beginning with a 1 are positive informational responses. - * Informational responses are used to provide human readable - * information such as help text. + /** + * Determine if a reply code is an informational response. All codes beginning with a 1 are positive informational responses. Informational responses are + * used to provide human readable information such as help text. * <p> - * @param reply The reply code to test. - * @return True if a reply code is an informational response, false - * if not. - ***/ - public static boolean isInformational(int reply) - { - return (reply >= 100 && reply < 200); + * + * @param reply The reply code to test. + * @return True if a reply code is an informational response, false if not. + */ + public static boolean isInformational(final int reply) { + return reply >= 100 && reply < 200; } - /*** - * Determine if a reply code is a positive completion response. All - * codes beginning with a 2 are positive completion responses. - * The NNTP server will send a positive completion response on the final - * successful completion of a command. + /** + * Determine if a reply code is a negative permanent response. All codes beginning with a 5 are negative permanent responses. The NNTP server will send a + * negative permanent response when it does not implement a command, a command is incorrectly formatted, or a serious program error occurs. * <p> - * @param reply The reply code to test. - * @return True if a reply code is a postive completion response, false - * if not. - ***/ - public static boolean isPositiveCompletion(int reply) - { - return (reply >= 200 && reply < 300); + * + * @param reply The reply code to test. + * @return True if a reply code is a negative permanent response, false if not. + */ + public static boolean isNegativePermanent(final int reply) { + return reply >= 500 && reply < 600; } - /*** - * Determine if a reply code is a positive intermediate response. All - * codes beginning with a 3 are positive intermediate responses. - * The NNTP server will send a positive intermediate response on the - * successful completion of one part of a multi-part command or - * sequence of commands. For example, after a successful POST command, - * a positive intermediate response will be sent to indicate that the - * server is ready to receive the article to be posted. + /** + * Determine if a reply code is a negative transient response. All codes beginning with a 4 are negative transient responses. The NNTP server will send a + * negative transient response on the failure of a correctly formatted command that could not be performed for some reason. For example, retrieving an + * article that does not exist will result in a negative transient response. * <p> - * @param reply The reply code to test. - * @return True if a reply code is a postive intermediate response, false - * if not. - ***/ - public static boolean isPositiveIntermediate(int reply) - { - return (reply >= 300 && reply < 400); + * + * @param reply The reply code to test. + * @return True if a reply code is a negative transient response, false if not. + */ + public static boolean isNegativeTransient(final int reply) { + return reply >= 400 && reply < 500; } - /*** - * Determine if a reply code is a negative transient response. All - * codes beginning with a 4 are negative transient responses. - * The NNTP server will send a negative transient response on the - * failure of a correctly formatted command that could not be performed - * for some reason. For example, retrieving an article that does not - * exist will result in a negative transient response. + /** + * Determine if a reply code is a positive completion response. All codes beginning with a 2 are positive completion responses. The NNTP server will send a + * positive completion response on the final successful completion of a command. * <p> - * @param reply The reply code to test. - * @return True if a reply code is a negative transient response, false - * if not. - ***/ - public static boolean isNegativeTransient(int reply) - { - return (reply >= 400 && reply < 500); + * + * @param reply The reply code to test. + * @return True if a reply code is a positive completion response, false if not. + */ + public static boolean isPositiveCompletion(final int reply) { + return reply >= 200 && reply < 300; } - /*** - * Determine if a reply code is a negative permanent response. All - * codes beginning with a 5 are negative permanent responses. - * The NNTP server will send a negative permanent response when - * it does not implement a command, a command is incorrectly formatted, - * or a serious program error occurs. + /** + * Determine if a reply code is a positive intermediate response. All codes beginning with a 3 are positive intermediate responses. The NNTP server will + * send a positive intermediate response on the successful completion of one part of a multi-part command or sequence of commands. For example, after a + * successful POST command, a positive intermediate response will be sent to indicate that the server is ready to receive the article to be posted. * <p> - * @param reply The reply code to test. - * @return True if a reply code is a negative permanent response, false - * if not. - ***/ - public static boolean isNegativePermanent(int reply) - { - return (reply >= 500 && reply < 600); + * + * @param reply The reply code to test. + * @return True if a reply code is a positive intermediate response, false if not. + */ + public static boolean isPositiveIntermediate(final int reply) { + return reply >= 300 && reply < 400; } -} + private NNTPReply() { + } -/* Emacs configuration - * Local variables: ** - * mode: java ** - * c-basic-offset: 4 ** - * indent-tabs-mode: nil ** - * End: ** - */ +} diff --git a/src/main/java/org/apache/commons/net/nntp/NewGroupsOrNewsQuery.java b/src/main/java/org/apache/commons/net/nntp/NewGroupsOrNewsQuery.java index dbfafa4..a3fd810 100644 --- a/src/main/java/org/apache/commons/net/nntp/NewGroupsOrNewsQuery.java +++ b/src/main/java/org/apache/commons/net/nntp/NewGroupsOrNewsQuery.java @@ -19,50 +19,45 @@ package org.apache.commons.net.nntp; import java.util.Calendar; -/*** - * The NewGroupsOrNewsQuery class. This is used to issue NNTP NEWGROUPS and - * NEWNEWS queries, implemented by - * {@link org.apache.commons.net.nntp.NNTPClient#listNewNewsgroups listNewNewsGroups } - * and - * {@link org.apache.commons.net.nntp.NNTPClient#listNewNews listNewNews } - * respectively. It prevents you from having to format - * date, time, distribution, and newgroup arguments. +/** + * The NewGroupsOrNewsQuery class. This is used to issue NNTP NEWGROUPS and NEWNEWS queries, implemented by + * {@link org.apache.commons.net.nntp.NNTPClient#listNewNewsgroups listNewNewsGroups } and {@link org.apache.commons.net.nntp.NNTPClient#listNewNews listNewNews + * } respectively. It prevents you from having to format date, time, distribution, and newgroup arguments. * <p> * You might use the class as follows: + * * <pre> * query = new NewsGroupsOrNewsQuery(new GregorianCalendar(97, 11, 15), false); * query.addDistribution("comp"); * NewsgroupInfo[] newsgroups = client.listNewgroups(query); * </pre> - * This will retrieve the list of newsgroups starting with the comp. - * distribution prefix created since midnight 11/15/97. + * + * This will retrieve the list of newsgroups starting with the comp. distribution prefix created since midnight 11/15/97. * * @see NNTPClient - ***/ - -public final class NewGroupsOrNewsQuery -{ - private final String __date, __time; - private StringBuffer __distributions; - private StringBuffer __newsgroups; - private final boolean __isGMT; + */ +public final class NewGroupsOrNewsQuery { + private final String date, time; + private StringBuffer distributions; + private StringBuffer newsgroups; + private final boolean isGMT; - /*** + /** * Creates a new query using the given time as a reference point. * <p> - * @param date The date since which new groups or news have arrived. - * @param gmt True if the date should be considered as GMT, false if not. - ***/ - public NewGroupsOrNewsQuery(Calendar date, boolean gmt) - { + * + * @param date The date since which new groups or news have arrived. + * @param gmt True if the date should be considered as GMT, false if not. + */ + public NewGroupsOrNewsQuery(final Calendar date, final boolean gmt) { int num; String str; - StringBuilder buffer; + final StringBuilder buffer; - __distributions = null; - __newsgroups = null; - __isGMT = gmt; + this.distributions = null; + this.newsgroups = null; + this.isGMT = gmt; buffer = new StringBuilder(); @@ -105,7 +100,7 @@ public final class NewGroupsOrNewsQuery buffer.append("01"); } - __date = buffer.toString(); + this.date = buffer.toString(); buffer.setLength(0); @@ -137,7 +132,6 @@ public final class NewGroupsOrNewsQuery buffer.append("00"); } - // Get seconds num = date.get(Calendar.SECOND); str = Integer.toString(num); @@ -152,128 +146,109 @@ public final class NewGroupsOrNewsQuery buffer.append("00"); } - __time = buffer.toString(); + this.time = buffer.toString(); } - - /*** - * Add a newsgroup to the list of newsgroups being queried. Newsgroups - * added this way are only meaningful to the NEWNEWS command. Newsgroup - * names may include the <code> * </code> wildcard, as in - * <code>comp.lang.* </code> or <code> comp.lang.java.* </code>. Adding - * at least one newsgroup is mandatory for the NEWNEWS command. + /** + * Add a distribution group to the query. The distribution part of a newsgroup is the segment of the name preceding the first dot (e.g., comp, alt, rec). + * Only those newsgroups matching one of the distributions or, in the case of NEWNEWS, an article in a newsgroup matching one of the distributions, will be + * reported as a query result. Adding distributions is purely optional. * <p> - * @param newsgroup The newsgroup to add to the list of groups to be - * checked for new news. - ***/ - public void addNewsgroup(String newsgroup) - { - if (__newsgroups != null) { - __newsgroups.append(','); + * + * @param distribution A distribution to add to the query. + */ + public void addDistribution(final String distribution) { + if (distributions != null) { + distributions.append(','); } else { - __newsgroups = new StringBuffer(); + distributions = new StringBuffer(); } - __newsgroups.append(newsgroup); + distributions.append(distribution); } - - /*** - * Add a newsgroup to the list of newsgroups being queried, but indicate - * that group should not be checked for new news. Newsgroups - * added this way are only meaningful to the NEWNEWS command. - * Newsgroup names may include the <code> * </code> wildcard, as in - * <code>comp.lang.* </code> or <code> comp.lang.java.* </code>. + /** + * Add a newsgroup to the list of newsgroups being queried. Newsgroups added this way are only meaningful to the NEWNEWS command. Newsgroup names may + * include the <code> * </code> wildcard, as in <code>comp.lang.* </code> or <code> comp.lang.java.* </code>. Adding at least one newsgroup is mandatory for + * the NEWNEWS command. * <p> - * The following would create a query that searched for new news in - * all comp.lang.java newsgroups except for comp.lang.java.advocacy. - * <pre> - * query.addNewsgroup("comp.lang.java.*"); - * query.omitNewsgroup("comp.lang.java.advocacy"); - * </pre> - * <p> - * @param newsgroup The newsgroup to add to the list of groups to be - * checked for new news, but which should be omitted from - * the search for new news.. - ***/ - public void omitNewsgroup(String newsgroup) - { - addNewsgroup("!" + newsgroup); + * + * @param newsgroup The newsgroup to add to the list of groups to be checked for new news. + */ + public void addNewsgroup(final String newsgroup) { + if (newsgroups != null) { + newsgroups.append(','); + } else { + newsgroups = new StringBuffer(); + } + newsgroups.append(newsgroup); } + /** + * Return the NNTP query formatted date (year, month, day in the form YYMMDD. + * <p> + * + * @return The NNTP query formatted date. + */ + public String getDate() { + return date; + } - /*** - * Add a distribution group to the query. The distribution part of a - * newsgroup is the segment of the name preceding the first dot (e.g., - * comp, alt, rec). Only those newsgroups matching one of the - * distributions or, in the case of NEWNEWS, an article in a newsgroup - * matching one of the distributions, will be reported as a query result. - * Adding distributions is purely optional. + /** + * Return the comma separated list of distributions. This may be null if there are no distributions. * <p> - * @param distribution A distribution to add to the query. - ***/ - public void addDistribution(String distribution) - { - if (__distributions != null) { - __distributions.append(','); - } else { - __distributions = new StringBuffer(); - } - __distributions.append(distribution); + * + * @return The list of distributions, which may be null if no distributions have been specified. + */ + public String getDistributions() { + return distributions == null ? null : distributions.toString(); } - /*** - * Return the NNTP query formatted date (year, month, day in the form - * YYMMDD. + /** + * Return the comma separated list of newsgroups. This may be null if there are no newsgroups * <p> - * @return The NNTP query formatted date. - ***/ - public String getDate() - { - return __date; + * + * @return The list of newsgroups, which may be null if no newsgroups have been specified. + */ + public String getNewsgroups() { + return newsgroups == null ? null : newsgroups.toString(); } - /*** - * Return the NNTP query formatted time (hour, minutes, seconds in the form - * HHMMSS. + /** + * Return the NNTP query formatted time (hour, minutes, seconds in the form HHMMSS. * <p> + * * @return The NNTP query formatted time. - ***/ - public String getTime() - { - return __time; + */ + public String getTime() { + return time; } - /*** + /** * Return whether or not the query date should be treated as GMT. * <p> + * * @return True if the query date is to be treated as GMT, false if not. - ***/ - public boolean isGMT() - { - return __isGMT; + */ + public boolean isGMT() { + return isGMT; } - /*** - * Return the comma separated list of distributions. This may be null - * if there are no distributions. + /** + * Add a newsgroup to the list of newsgroups being queried, but indicate that group should not be checked for new news. Newsgroups added this way are only + * meaningful to the NEWNEWS command. Newsgroup names may include the <code> * </code> wildcard, as in <code>comp.lang.* </code> or + * <code> comp.lang.java.* </code>. * <p> - * @return The list of distributions, which may be null if no distributions - * have been specified. - ***/ - public String getDistributions() - { - return (__distributions == null ? null : __distributions.toString()); - } - - /*** - * Return the comma separated list of newsgroups. This may be null - * if there are no newsgroups + * The following would create a query that searched for new news in all comp.lang.java newsgroups except for comp.lang.java.advocacy. + * + * <pre> + * query.addNewsgroup("comp.lang.java.*"); + * query.omitNewsgroup("comp.lang.java.advocacy"); + * </pre> * <p> - * @return The list of newsgroups, which may be null if no newsgroups - * have been specified. - ***/ - public String getNewsgroups() - { - return (__newsgroups == null ? null : __newsgroups.toString()); + * + * @param newsgroup The newsgroup to add to the list of groups to be checked for new news, but which should be omitted from the search for new news.. + */ + public void omitNewsgroup(final String newsgroup) { + addNewsgroup("!" + newsgroup); } } diff --git a/src/main/java/org/apache/commons/net/nntp/NewsgroupInfo.java b/src/main/java/org/apache/commons/net/nntp/NewsgroupInfo.java index 443cab8..fab1c3f 100644 --- a/src/main/java/org/apache/commons/net/nntp/NewsgroupInfo.java +++ b/src/main/java/org/apache/commons/net/nntp/NewsgroupInfo.java @@ -17,155 +17,128 @@ package org.apache.commons.net.nntp; -/*** - * NewsgroupInfo stores information pertaining to a newsgroup returned by - * the NNTP GROUP, LIST, and NEWGROUPS commands, implemented by - * {@link org.apache.commons.net.nntp.NNTPClient#selectNewsgroup selectNewsgroup } - * , - * {@link org.apache.commons.net.nntp.NNTPClient#listNewsgroups listNewsgroups } - * , and - * {@link org.apache.commons.net.nntp.NNTPClient#listNewNewsgroups listNewNewsgroups } - * respectively. +/** + * NewsgroupInfo stores information pertaining to a newsgroup returned by the NNTP GROUP, LIST, and NEWGROUPS commands, implemented by + * {@link org.apache.commons.net.nntp.NNTPClient#selectNewsgroup selectNewsgroup } , {@link org.apache.commons.net.nntp.NNTPClient#listNewsgroups listNewsgroups + * } , and {@link org.apache.commons.net.nntp.NNTPClient#listNewNewsgroups listNewNewsgroups } respectively. * * @see NNTPClient - ***/ - -public final class NewsgroupInfo -{ - /*** - * A constant indicating that the posting permission of a newsgroup is - * unknown. For example, the NNTP GROUP command does not return posting - * information, so NewsgroupInfo instances obtained from that command - * willhave an UNKNOWN_POSTING_PERMISSION. - ***/ + */ + +public final class NewsgroupInfo { + /** + * A constant indicating that the posting permission of a newsgroup is unknown. For example, the NNTP GROUP command does not return posting information, so + * NewsgroupInfo instances obtained from that command willhave an UNKNOWN_POSTING_PERMISSION. + */ public static final int UNKNOWN_POSTING_PERMISSION = 0; - /*** A constant indicating that a newsgroup is moderated. ***/ + /** A constant indicating that a newsgroup is moderated. */ public static final int MODERATED_POSTING_PERMISSION = 1; - /*** A constant indicating that a newsgroup is public and unmoderated. ***/ + /** A constant indicating that a newsgroup is public and unmoderated. */ public static final int PERMITTED_POSTING_PERMISSION = 2; - /*** + /** * A constant indicating that a newsgroup is closed for general posting. - ***/ + */ public static final int PROHIBITED_POSTING_PERMISSION = 3; - private String __newsgroup; - private long __estimatedArticleCount; - private long __firstArticle; - private long __lastArticle; - private int __postingPermission; + private String newsgroup; + private long estimatedArticleCount; + private long firstArticle; + private long lastArticle; + private int postingPermission; - void _setNewsgroup(String newsgroup) - { - __newsgroup = newsgroup; + @Deprecated + public int getArticleCount() { + return (int) estimatedArticleCount; } - void _setArticleCount(long count) - { - __estimatedArticleCount = count; + /** + * Get the estimated number of articles in the newsgroup. The accuracy of this value will depend on the server implementation. + * <p> + * + * @return The estimated number of articles in the newsgroup. + */ + public long getArticleCountLong() { + return estimatedArticleCount; } - void _setFirstArticle(long first) - { - __firstArticle = first; + @Deprecated + public int getFirstArticle() { + return (int) firstArticle; } - void _setLastArticle(long last) - { - __lastArticle = last; + /** + * Get the number of the first article in the newsgroup. + * <p> + * + * @return The number of the first article in the newsgroup. + */ + public long getFirstArticleLong() { + return firstArticle; } - void _setPostingPermission(int permission) - { - __postingPermission = permission; + @Deprecated + public int getLastArticle() { + return (int) lastArticle; } - /*** - * Get the newsgroup name. + /** + * Get the number of the last article in the newsgroup. * <p> - * @return The name of the newsgroup. - ***/ - public String getNewsgroup() - { - return __newsgroup; + * + * @return The number of the last article in the newsgroup. + */ + public long getLastArticleLong() { + return lastArticle; } - /*** - * Get the estimated number of articles in the newsgroup. The - * accuracy of this value will depend on the server implementation. + /** + * Get the newsgroup name. * <p> - * @return The estimated number of articles in the newsgroup. - ***/ - public long getArticleCountLong() - { - return __estimatedArticleCount; + * + * @return The name of the newsgroup. + */ + public String getNewsgroup() { + return newsgroup; } - /*** - * Get the number of the first article in the newsgroup. + /** + * Get the posting permission of the newsgroup. This will be one of the <code> POSTING_PERMISSION </code> constants. * <p> - * @return The number of the first article in the newsgroup. - ***/ - public long getFirstArticleLong() - { - return __firstArticle; + * + * @return The posting permission status of the newsgroup. + */ + public int getPostingPermission() { + return postingPermission; } - /*** - * Get the number of the last article in the newsgroup. - * <p> - * @return The number of the last article in the newsgroup. - ***/ - public long getLastArticleLong() - { - return __lastArticle; + void setArticleCount(final long count) { + estimatedArticleCount = count; } - /*** - * Get the posting permission of the newsgroup. This will be one of - * the <code> POSTING_PERMISSION </code> constants. - * <p> - * @return The posting permission status of the newsgroup. - ***/ - public int getPostingPermission() - { - return __postingPermission; + void setFirstArticle(final long first) { + firstArticle = first; } /* - public String toString() { - StringBuilder buffer = new StringBuilder(); - buffer.append(__newsgroup); - buffer.append(' '); - buffer.append(__lastArticle); - buffer.append(' '); - buffer.append(__firstArticle); - buffer.append(' '); - switch(__postingPermission) { - case 1: buffer.append('m'); break; - case 2: buffer.append('y'); break; - case 3: buffer.append('n'); break; - } - return buffer.toString(); -} - */ + * public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append(__newsgroup); buffer.append(' '); buffer.append(__lastArticle); + * buffer.append(' '); buffer.append(__firstArticle); buffer.append(' '); switch(__postingPermission) { case 1: buffer.append('m'); break; case 2: + * buffer.append('y'); break; case 3: buffer.append('n'); break; } return buffer.toString(); } + */ // DEPRECATED METHODS - for API compatibility only - DO NOT USE - @Deprecated - public int getArticleCount() { - return (int) __estimatedArticleCount; + void setLastArticle(final long last) { + lastArticle = last; } - @Deprecated - public int getFirstArticle() { - return (int) __firstArticle; + void setNewsgroup(final String newsgroup) { + this.newsgroup = newsgroup; } - @Deprecated - public int getLastArticle() { - return (int) __lastArticle; + void setPostingPermission(final int permission) { + postingPermission = permission; } } diff --git a/src/main/java/org/apache/commons/net/nntp/NewsgroupIterator.java b/src/main/java/org/apache/commons/net/nntp/NewsgroupIterator.java index 8171461..c6f85be 100644 --- a/src/main/java/org/apache/commons/net/nntp/NewsgroupIterator.java +++ b/src/main/java/org/apache/commons/net/nntp/NewsgroupIterator.java @@ -19,16 +19,17 @@ package org.apache.commons.net.nntp; import java.util.Iterator; + /** - * Class which wraps an {@code Iterable<String>} of raw newgroup information - * to generate an {@code Iterable<NewsgroupInfo>} of the parsed information. + * Class which wraps an {@code Iterable<String>} of raw newgroup information to generate an {@code Iterable<NewsgroupInfo>} of the parsed information. + * * @since 3.0 */ class NewsgroupIterator implements Iterator<NewsgroupInfo>, Iterable<NewsgroupInfo> { - private final Iterator<String> stringIterator; + private final Iterator<String> stringIterator; - public NewsgroupIterator(Iterable<String> iterableString) { + public NewsgroupIterator(final Iterable<String> iterableString) { stringIterator = iterableString.iterator(); } @@ -38,18 +39,18 @@ class NewsgroupIterator implements Iterator<NewsgroupInfo>, Iterable<NewsgroupIn } @Override - public NewsgroupInfo next() { - String line = stringIterator.next(); - return NNTPClient.__parseNewsgroupListEntry(line); + public Iterator<NewsgroupInfo> iterator() { + return this; } @Override - public void remove() { - stringIterator.remove(); + public NewsgroupInfo next() { + final String line = stringIterator.next(); + return NNTPClient.parseNewsgroupListEntry(line); } @Override - public Iterator<NewsgroupInfo> iterator() { - return this; + public void remove() { + stringIterator.remove(); } } diff --git a/src/main/java/org/apache/commons/net/nntp/ReplyIterator.java b/src/main/java/org/apache/commons/net/nntp/ReplyIterator.java index b20136d..5c9fcda 100644 --- a/src/main/java/org/apache/commons/net/nntp/ReplyIterator.java +++ b/src/main/java/org/apache/commons/net/nntp/ReplyIterator.java @@ -27,8 +27,8 @@ import org.apache.commons.net.io.DotTerminatedMessageReader; import org.apache.commons.net.io.Util; /** - * Wraps a {@link BufferedReader} and returns an {@code Iterable<String>} - * which returns the individual lines from the reader. + * Wraps a {@link BufferedReader} and returns an {@code Iterable<String>} which returns the individual lines from the reader. + * * @since 3.0 */ class ReplyIterator implements Iterator<String>, Iterable<String> { @@ -39,13 +39,17 @@ class ReplyIterator implements Iterator<String>, Iterable<String> { private Exception savedException; + ReplyIterator(final BufferedReader _reader) throws IOException { + this(_reader, true); + } + /** * - * @param _reader the reader to wrap + * @param _reader the reader to wrap * @param addDotReader whether to additionally wrap the reader in a DotTerminatedMessageReader * @throws IOException */ - ReplyIterator(BufferedReader _reader, boolean addDotReader) throws IOException { + ReplyIterator(final BufferedReader _reader, final boolean addDotReader) throws IOException { reader = addDotReader ? new DotTerminatedMessageReader(_reader) : _reader; line = reader.readLine(); // prime the iterator if (line == null) { @@ -53,24 +57,25 @@ class ReplyIterator implements Iterator<String>, Iterable<String> { } } - ReplyIterator(BufferedReader _reader) throws IOException { - this(_reader, true); - } - @Override public boolean hasNext() { - if (savedException != null){ + if (savedException != null) { throw new NoSuchElementException(savedException.toString()); } return line != null; } + @Override + public Iterator<String> iterator() { + return this; + } + @Override public String next() throws NoSuchElementException { - if (savedException != null){ + if (savedException != null) { throw new NoSuchElementException(savedException.toString()); } - String prev = line; + final String prev = line; if (prev == null) { throw new NoSuchElementException(); } @@ -79,7 +84,7 @@ class ReplyIterator implements Iterator<String>, Iterable<String> { if (line == null) { Util.closeQuietly(reader); } - } catch (IOException ex) { + } catch (final IOException ex) { savedException = ex; // if it fails, save the exception, as it does not apply to this call Util.closeQuietly(reader); } @@ -90,9 +95,4 @@ class ReplyIterator implements Iterator<String>, Iterable<String> { public void remove() { throw new UnsupportedOperationException(); } - - @Override - public Iterator<String> iterator() { - return this; - } } diff --git a/src/main/java/org/apache/commons/net/nntp/SimpleNNTPHeader.java b/src/main/java/org/apache/commons/net/nntp/SimpleNNTPHeader.java index baf8a2c..07cf99f 100644 --- a/src/main/java/org/apache/commons/net/nntp/SimpleNNTPHeader.java +++ b/src/main/java/org/apache/commons/net/nntp/SimpleNNTPHeader.java @@ -17,145 +17,133 @@ package org.apache.commons.net.nntp; -/*** - * This class is used to construct the bare minimum - * acceptable header for most news readers. To construct more - * complicated headers you should refer to RFC 822. When the - * Java Mail API is finalized, you will be - * able to use it to compose fully compliant Internet text messages. +/** + * This class is used to construct the bare minimum acceptable header for most news readers. To construct more complicated headers you should refer to RFC 822. + * When the Java Mail API is finalized, you will be able to use it to compose fully compliant Internet text messages. * <p> - * The main purpose of the class is to faciliatate the article posting - * process, by relieving the programmer from having to explicitly format - * an article header. For example: + * The main purpose of the class is to faciliatate the article posting process, by relieving the programmer from having to explicitly format an article header. + * For example: + * * <pre> * writer = client.postArticle(); - * if(writer == null) // failure - * return false; + * if (writer == null) // failure + * return false; * header = new SimpleNNTPHeader("foobar@foo.com", "Just testing"); * header.addNewsgroup("alt.test"); * header.addHeaderField("Organization", "Foobar, Inc."); * writer.write(header.toString()); * writer.write("This is just a test"); * writer.close(); - * if(!client.completePendingCommand()) // failure - * return false; + * if (!client.completePendingCommand()) // failure + * return false; * </pre> * * @see NNTPClient - ***/ + */ -public class SimpleNNTPHeader -{ - private final String __subject, __from; - private final StringBuilder __newsgroups; - private final StringBuilder __headerFields; - private int __newsgroupCount; +public class SimpleNNTPHeader { + private final String subject, from; + private final StringBuilder newsgroups; + private final StringBuilder headerFields; + private int newsgroupCount; - /*** - * Creates a new SimpleNNTPHeader instance initialized with the given - * from and subject header field values. + /** + * Creates a new SimpleNNTPHeader instance initialized with the given from and subject header field values. * <p> - * @param from The value of the <code>From:</code> header field. This - * should be the article poster's email address. - * @param subject The value of the <code>Subject:</code> header field. - * This should be the subject of the article. - ***/ - public SimpleNNTPHeader(String from, String subject) - { - __from = from; - __subject = subject; - __newsgroups = new StringBuilder(); - __headerFields = new StringBuilder(); - __newsgroupCount = 0; + * + * @param from The value of the <code>From:</code> header field. This should be the article poster's email address. + * @param subject The value of the <code>Subject:</code> header field. This should be the subject of the article. + */ + public SimpleNNTPHeader(final String from, final String subject) { + this.from = from; + this.subject = subject; + this.newsgroups = new StringBuilder(); + this.headerFields = new StringBuilder(); + this.newsgroupCount = 0; } - /*** - * Adds a newsgroup to the article <code>Newsgroups:</code> field. - * <p> - * @param newsgroup The newsgroup to add to the article's newsgroup - * distribution list. - ***/ - public void addNewsgroup(String newsgroup) - { - if (__newsgroupCount++ > 0) { - __newsgroups.append(','); - } - __newsgroups.append(newsgroup); - } - - /*** - * Adds an arbitrary header field with the given value to the article - * header. These headers will be written after the From, Newsgroups, - * and Subject fields when the SimpleNNTPHeader is convertered to a string. - * An example use would be: + /** + * Adds an arbitrary header field with the given value to the article header. These headers will be written after the From, Newsgroups, and Subject fields + * when the SimpleNNTPHeader is convertered to a string. An example use would be: + * * <pre> * header.addHeaderField("Organization", "Foobar, Inc."); * </pre> * <p> - * @param headerField The header field to add, not including the colon. - * @param value The value of the added header field. - ***/ - public void addHeaderField(String headerField, String value) - { - __headerFields.append(headerField); - __headerFields.append(": "); - __headerFields.append(value); - __headerFields.append('\n'); + * + * @param headerField The header field to add, not including the colon. + * @param value The value of the added header field. + */ + public void addHeaderField(final String headerField, final String value) { + headerFields.append(headerField); + headerFields.append(": "); + headerFields.append(value); + headerFields.append('\n'); } + /** + * Adds a newsgroup to the article <code>Newsgroups:</code> field. + * <p> + * + * @param newsgroup The newsgroup to add to the article's newsgroup distribution list. + */ + public void addNewsgroup(final String newsgroup) { + if (newsgroupCount++ > 0) { + newsgroups.append(','); + } + newsgroups.append(newsgroup); + } - /*** + /** * Returns the address used in the <code> From: </code> header field. * <p> + * * @return The from address. - ***/ - public String getFromAddress() - { - return __from; + */ + public String getFromAddress() { + return from; } - /*** - * Returns the subject used in the <code> Subject: </code> header field. + /** + * Returns the contents of the <code> Newsgroups: </code> header field. * <p> - * @return The subject. - ***/ - public String getSubject() - { - return __subject; + * + * @return The comma-separated list of newsgroups to which the article is being posted. + */ + public String getNewsgroups() { + return newsgroups.toString(); } - /*** - * Returns the contents of the <code> Newsgroups: </code> header field. + /** + * Returns the subject used in the <code> Subject: </code> header field. * <p> - * @return The comma-separated list of newsgroups to which the article - * is being posted. - ***/ - public String getNewsgroups() - { - return __newsgroups.toString(); + * + * @return The subject. + */ + public String getSubject() { + return subject; } - /*** - * Converts the SimpleNNTPHeader to a properly formatted header in - * the form of a String, including the blank line used to separate - * the header from the article body. + /** + * Converts the SimpleNNTPHeader to a properly formatted header in the form of a String, including the blank line used to separate the header from the + * article body. * <p> + * * @return The article header in the form of a String. - ***/ + */ @Override - public String toString() - { - StringBuilder header = new StringBuilder(); + public String toString() { + final StringBuilder header = new StringBuilder(); header.append("From: "); - header.append(__from); + header.append(from); header.append("\nNewsgroups: "); - header.append(__newsgroups.toString()); + header.append(newsgroups.toString()); header.append("\nSubject: "); - header.append(__subject); + header.append(subject); header.append('\n'); - if (__headerFields.length() > 0) { - header.append(__headerFields.toString()); + if (headerFields.length() > 0) { + header.append(headerFields.toString()); } header.append('\n'); diff --git a/src/main/java/org/apache/commons/net/nntp/ThreadContainer.java b/src/main/java/org/apache/commons/net/nntp/ThreadContainer.java index f4fc159..cb46198 100644 --- a/src/main/java/org/apache/commons/net/nntp/ThreadContainer.java +++ b/src/main/java/org/apache/commons/net/nntp/ThreadContainer.java @@ -18,11 +18,8 @@ package org.apache.commons.net.nntp; /** - * A placeholder utility class, used for constructing a tree of Threadables - * Original implementation by Jamie Zawinski. - * See the Grendel source for more details - * <a href="http://lxr.mozilla.org/mozilla/source/grendel/sources/grendel/view/Threader.java#511">here</a> - * Threadable objects + * A placeholder utility class, used for constructing a tree of Threadables Original implementation by Jamie Zawinski. See the Grendel source for more details + * <a href="http://lxr.mozilla.org/mozilla/source/grendel/sources/grendel/view/Threader.java#511">here</a> Threadable objects */ class ThreadContainer { Threadable threadable; @@ -33,17 +30,17 @@ class ThreadContainer { /** * - * @param container + * @param target * @return true if child is under self's tree. Detects circular references */ - boolean findChild(ThreadContainer target) { + boolean findChild(final ThreadContainer target) { if (child == null) { return false; - } else if (child == target) { + } + if (child == target) { return true; - } else { - return child.findChild(target); } + return child.findChild(target); } // Copy the ThreadContainer tree structure down into the underlying Threadable objects @@ -85,12 +82,7 @@ class ThreadContainer { void reverseChildren() { if (child != null) { ThreadContainer kid, prev, rest; - for (prev = null, kid = child, rest = kid.next; - kid != null; - prev = kid, - kid = rest, - rest = (rest == null ? null : rest.next)) - { + for (prev = null, kid = child, rest = kid.next; kid != null; prev = kid, kid = rest, rest = rest == null ? null : rest.next) { kid.next = prev; } diff --git a/src/main/java/org/apache/commons/net/nntp/Threadable.java b/src/main/java/org/apache/commons/net/nntp/Threadable.java index 2a28512..83d31b4 100644 --- a/src/main/java/org/apache/commons/net/nntp/Threadable.java +++ b/src/main/java/org/apache/commons/net/nntp/Threadable.java @@ -18,17 +18,23 @@ package org.apache.commons.net.nntp; /** - * A placeholder interface for threadable message objects - * Author: Rory Winston (rwinston@checkfree.com) + * A placeholder interface for threadable message objects Author: Rory Winston (rwinston@checkfree.com) * */ public interface Threadable { - public boolean isDummy(); - public String messageThreadId(); - public String[] messageThreadReferences(); - public String simplifiedSubject(); - public boolean subjectIsReply(); - public void setChild(Threadable child); - public void setNext(Threadable next); - public Threadable makeDummy(); + boolean isDummy(); + + Threadable makeDummy(); + + String messageThreadId(); + + String[] messageThreadReferences(); + + void setChild(Threadable child); + + void setNext(Threadable next); + + String simplifiedSubject(); + + boolean subjectIsReply(); } diff --git a/src/main/java/org/apache/commons/net/nntp/Threader.java b/src/main/java/org/apache/commons/net/nntp/Threader.java index d638594..dea3f79 100644 --- a/src/main/java/org/apache/commons/net/nntp/Threader.java +++ b/src/main/java/org/apache/commons/net/nntp/Threader.java @@ -15,7 +15,6 @@ * limitations under the License. */ - package org.apache.commons.net.nntp; /** @@ -28,79 +27,17 @@ package org.apache.commons.net.nntp; import java.util.Arrays; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; public class Threader { - /** - * The client passes in a list of Threadable objects, and - * the Threader constructs a connected 'graph' of messages - * @param messages list of messages to thread, must not be empty - * @return null if messages == null or root.child == null or messages list is empty - * @since 2.2 - */ - public Threadable thread(List<? extends Threadable> messages) { - return thread((Iterable<? extends Threadable>)messages); - } - - /** - * The client passes in a list of Iterable objects, and - * the Threader constructs a connected 'graph' of messages - * @param messages iterable of messages to thread, must not be empty - * @return null if messages == null or root.child == null or messages list is empty - * @since 3.0 - */ - public Threadable thread(Iterable<? extends Threadable> messages) { - if (messages == null) { - return null; - } - - HashMap<String,ThreadContainer> idTable = new HashMap<String,ThreadContainer>(); - - // walk through each Threadable element - for (Threadable t : messages) { - if (!t.isDummy()) { - buildContainer(t, idTable); - } - } - - if (idTable.isEmpty()) { - return null; - } - - ThreadContainer root = findRootSet(idTable); - idTable.clear(); - idTable = null; - - pruneEmptyContainers(root); - - root.reverseChildren(); - gatherSubjects(root); - - if (root.next != null) { - throw new RuntimeException("root node has a next:" + root); - } - - for (ThreadContainer r = root.child; r != null; r = r.next) { - if (r.threadable == null) { - r.threadable = r.child.threadable.makeDummy(); - } - } - - Threadable result = (root.child == null ? null : root.child.threadable); - root.flush(); - - return result; - } - /** * * @param threadable * @param idTable */ - private void buildContainer(Threadable threadable, HashMap<String,ThreadContainer> idTable) { + private void buildContainer(final Threadable threadable, final HashMap<String, ThreadContainer> idTable) { String id = threadable.messageThreadId(); ThreadContainer container = idTable.get(id); int bogusIdCount = 0; @@ -109,7 +46,7 @@ public class Threader { // be a duplicate id, in which case we will need to generate a bogus placeholder id if (container != null) { if (container.threadable != null) { // oops! duplicate ids... - bogusIdCount++ ; // Avoid dead local store warning + bogusIdCount++; // Avoid dead local store warning id = "<Bogus-id:" + (bogusIdCount) + ">"; container = null; } else { @@ -130,9 +67,8 @@ public class Threader { // don't have them. ThreadContainer parentRef = null; { - String[] references = threadable.messageThreadReferences(); - for (String refString : references) - { + final String[] references = threadable.messageThreadReferences(); + for (final String refString : references) { ThreadContainer ref = idTable.get(refString); // if this id doesnt have a container, create one @@ -144,10 +80,7 @@ public class Threader { // Link references together in the order they appear in the References: header, // IF they dont have a have a parent already && // IF it will not cause a circular reference - if ((parentRef != null) - && (ref.parent == null) - && (parentRef != ref) - && !(ref.findChild(parentRef))) { + if ((parentRef != null) && (ref.parent == null) && (parentRef != ref) && !(ref.findChild(parentRef))) { // Link ref into the parent's child list ref.parent = parentRef; ref.next = parentRef.child; @@ -159,9 +92,7 @@ public class Threader { // parentRef is now set to the container of the last element in the references field. make that // be the parent of this container, unless doing so causes a circular reference - if (parentRef != null - && (parentRef == container || container.findChild(parentRef))) - { + if (parentRef != null && (parentRef == container || container.findChild(parentRef))) { parentRef = null; } @@ -171,20 +102,14 @@ public class Threader { if (container.parent != null) { ThreadContainer rest, prev; - for (prev = null, rest = container.parent.child; - rest != null; - prev = rest, rest = rest.next) { + for (prev = null, rest = container.parent.child; rest != null; prev = rest, rest = rest.next) { if (rest == container) { break; } } if (rest == null) { - throw new RuntimeException( - "Didnt find " - + container - + " in parent" - + container.parent); + throw new RuntimeException("Didnt find " + container + " in parent" + container.parent); } // Unlink this container from the parent's child list @@ -208,20 +133,17 @@ public class Threader { /** * Find the root set of all existing ThreadContainers + * * @param idTable * @return root the ThreadContainer representing the root node */ - private ThreadContainer findRootSet(HashMap<String, ThreadContainer> idTable) { - ThreadContainer root = new ThreadContainer(); - Iterator<Map.Entry<String, ThreadContainer>> iter = idTable.entrySet().iterator(); - - while (iter.hasNext()) { - Map.Entry<String, ThreadContainer> entry = iter.next(); - ThreadContainer c = entry.getValue(); + private ThreadContainer findRootSet(final HashMap<String, ThreadContainer> idTable) { + final ThreadContainer root = new ThreadContainer(); + for (final Map.Entry<String, ThreadContainer> entry : idTable.entrySet()) { + final ThreadContainer c = entry.getValue(); if (c.parent == null) { if (c.next != null) { - throw new RuntimeException( - "c.next is " + c.next.toString()); + throw new RuntimeException("c.next is " + c.next.toString()); } c.next = root.child; root.child = c; @@ -231,76 +153,11 @@ public class Threader { } /** - * Delete any empty or dummy ThreadContainers - * @param parent - */ - private void pruneEmptyContainers(ThreadContainer parent) { - ThreadContainer container, prev, next; - for (prev = null, container = parent.child, next = container.next; - container != null; - prev = container, - container = next, - next = (container == null ? null : container.next)) { - - // Is it empty and without any children? If so,delete it - if (container.threadable == null && container.child == null) { - if (prev == null) { - parent.child = container.next; - } else { - prev.next = container.next; - } - - // Set container to prev so that prev keeps its same value the next time through the loop - container = prev; - } - - // Else if empty, with kids, and (not at root or only one kid) - else if ( - container.threadable == null - && container.child != null - && (container.parent != null - || container.child.next == null)) { - // We have an invalid/expired message with kids. Promote the kids to this level. - ThreadContainer tail; - ThreadContainer kids = container.child; - - // Remove this container and replace with 'kids'. - if (prev == null) { - parent.child = kids; - } else { - prev.next = kids; - } - - // Make each child's parent be this level's parent -> i.e. promote the children. - // Make the last child's next point to this container's next - // i.e. splice kids into the list in place of container - for (tail = kids; tail.next != null; tail = tail.next) { - tail.parent = container.parent; - } - - tail.parent = container.parent; - tail.next = container.next; - - // next currently points to the item after the inserted items in the chain - reset that so we process the newly - // promoted items next time round - next = kids; - - // Set container to prev so that prev keeps its same value the next time through the loop - container = prev; - } else if (container.child != null) { - // A real message , with kids - // Iterate over the children - pruneEmptyContainers(container); - } - } - } - - /** - * If any two members of the root set have the same subject, merge them. - * This is to attempt to accomodate messages without References: headers. + * If any two members of the root set have the same subject, merge them. This is to attempt to accomodate messages without References: headers. + * * @param root */ - private void gatherSubjects(ThreadContainer root) { + private void gatherSubjects(final ThreadContainer root) { int count = 0; @@ -309,7 +166,7 @@ public class Threader { } // TODO verify this will avoid rehashing - HashMap<String, ThreadContainer> subjectTable = new HashMap<String, ThreadContainer>((int) (count * 1.2), (float) 0.9); + HashMap<String, ThreadContainer> subjectTable = new HashMap<>((int) (count * 1.2), (float) 0.9); count = 0; for (ThreadContainer c = root.child; c != null; c = c.next) { @@ -322,13 +179,13 @@ public class Threader { threadable = c.child.threadable; } - String subj = threadable.simplifiedSubject(); + final String subj = threadable.simplifiedSubject(); - if (subj == null || subj.length() == 0) { + if (subj == null || subj.isEmpty()) { continue; } - ThreadContainer old = subjectTable.get(subj); + final ThreadContainer old = subjectTable.get(subj); // Add this container to the table iff: // - There exists no container with this subject @@ -337,12 +194,8 @@ public class Threader { // - The container in the table has a "Re:" version of this subject, and // this container has a non-"Re:" version of this subject. The non-"Re:" version // is the more interesting of the two. - if (old == null - || (c.threadable == null && old.threadable != null) - || (old.threadable != null - && old.threadable.subjectIsReply() - && c.threadable != null - && !c.threadable.subjectIsReply())) { + if (old == null || (c.threadable == null && old.threadable != null) + || (old.threadable != null && old.threadable.subjectIsReply() && c.threadable != null && !c.threadable.subjectIsReply())) { subjectTable.put(subj, c); count++; } @@ -356,9 +209,7 @@ public class Threader { // subjectTable is now populated with one entry for each subject which occurs in the // root set. Iterate over the root set, and gather together the difference. ThreadContainer prev, c, rest; - for (prev = null, c = root.child, rest = c.next; - c != null; - prev = c, c = rest, rest = (rest == null ? null : rest.next)) { + for (prev = null, c = root.child, rest = c.next; c != null; prev = c, c = rest, rest = (rest == null ? null : rest.next)) { Threadable threadable = c.threadable; // is it a dummy node? @@ -366,14 +217,14 @@ public class Threader { threadable = c.child.threadable; } - String subj = threadable.simplifiedSubject(); + final String subj = threadable.simplifiedSubject(); // Dont thread together all subjectless messages - if (subj == null || subj.length() == 0) { + if (subj == null || subj.isEmpty()) { continue; } - ThreadContainer old = subjectTable.get(subj); + final ThreadContainer old = subjectTable.get(subj); if (old == c) { // That's us continue; @@ -391,9 +242,7 @@ public class Threader { if (old.threadable == null && c.threadable == null) { // both dummies - merge them ThreadContainer tail; - for (tail = old.child; - tail != null && tail.next != null; - tail = tail.next) { + for (tail = old.child; tail != null && tail.next != null; tail = tail.next) { // do nothing } @@ -406,26 +255,19 @@ public class Threader { } c.child = null; - } else if ( - old.threadable == null - || (c.threadable != null - && c.threadable.subjectIsReply() - && !old.threadable.subjectIsReply())) { - // Else if old is empty, or c has "Re:" and old does not ==> make this message a child of old + } else if (old.threadable == null || (c.threadable != null && c.threadable.subjectIsReply() && !old.threadable.subjectIsReply())) { + // Else if old is empty, or c has "Re:" and old does not ==> make this message a child of old c.parent = old; c.next = old.child; old.child = c; } else { // else make the old and new messages be children of a new dummy container. // We create a new container object for old.msg and empty the old container - ThreadContainer newc = new ThreadContainer(); + final ThreadContainer newc = new ThreadContainer(); newc.threadable = old.threadable; newc.child = old.child; - for (ThreadContainer tail = newc.child; - tail != null; - tail = tail.next) - { + for (ThreadContainer tail = newc.child; tail != null; tail = tail.next) { tail.parent = newc; } @@ -448,18 +290,138 @@ public class Threader { } + /** + * Delete any empty or dummy ThreadContainers + * + * @param parent + */ + private void pruneEmptyContainers(final ThreadContainer parent) { + ThreadContainer container, prev, next; + for (prev = null, container = parent.child, next = container.next; container != null; prev = container, container = next, next = (container == null + ? null + : container.next)) { + + // Is it empty and without any children? If so,delete it + if (container.threadable == null && container.child == null) { + if (prev == null) { + parent.child = container.next; + } else { + prev.next = container.next; + } + + // Set container to prev so that prev keeps its same value the next time through the loop + container = prev; + } + + // Else if empty, with kids, and (not at root or only one kid) + else if (container.threadable == null && (container.parent != null || container.child.next == null)) { + // We have an invalid/expired message with kids. Promote the kids to this level. + ThreadContainer tail; + final ThreadContainer kids = container.child; + + // Remove this container and replace with 'kids'. + if (prev == null) { + parent.child = kids; + } else { + prev.next = kids; + } + + // Make each child's parent be this level's parent -> i.e. promote the children. + // Make the last child's next point to this container's next + // i.e. splice kids into the list in place of container + for (tail = kids; tail.next != null; tail = tail.next) { + tail.parent = container.parent; + } + + tail.parent = container.parent; + tail.next = container.next; + + // next currently points to the item after the inserted items in the chain - reset that so we process the newly + // promoted items next time round + next = kids; + + // Set container to prev so that prev keeps its same value the next time through the loop + container = prev; + } else if (container.child != null) { + // A real message , with kids + // Iterate over the children + pruneEmptyContainers(container); + } + } + } + + /** + * The client passes in a list of Iterable objects, and the Threader constructs a connected 'graph' of messages + * + * @param messages iterable of messages to thread, must not be empty + * @return null if messages == null or root.child == null or messages list is empty + * @since 3.0 + */ + public Threadable thread(final Iterable<? extends Threadable> messages) { + if (messages == null) { + return null; + } + + HashMap<String, ThreadContainer> idTable = new HashMap<>(); + + // walk through each Threadable element + for (final Threadable t : messages) { + if (!t.isDummy()) { + buildContainer(t, idTable); + } + } + + if (idTable.isEmpty()) { + return null; + } + + final ThreadContainer root = findRootSet(idTable); + idTable.clear(); + idTable = null; + + pruneEmptyContainers(root); + + root.reverseChildren(); + gatherSubjects(root); + + if (root.next != null) { + throw new RuntimeException("root node has a next:" + root); + } + + for (ThreadContainer r = root.child; r != null; r = r.next) { + if (r.threadable == null) { + r.threadable = r.child.threadable.makeDummy(); + } + } + + final Threadable result = (root.child == null ? null : root.child.threadable); + root.flush(); + + return result; + } + + /** + * The client passes in a list of Threadable objects, and the Threader constructs a connected 'graph' of messages + * + * @param messages list of messages to thread, must not be empty + * @return null if messages == null or root.child == null or messages list is empty + * @since 2.2 + */ + public Threadable thread(final List<? extends Threadable> messages) { + return thread((Iterable<? extends Threadable>) messages); + } // DEPRECATED METHODS - for API compatibility only - DO NOT USE /** - * The client passes in an array of Threadable objects, and - * the Threader constructs a connected 'graph' of messages + * The client passes in an array of Threadable objects, and the Threader constructs a connected 'graph' of messages + * * @param messages array of messages to thread, must not be empty * @return null if messages == null or root.child == null or messages array is empty * @deprecated (2.2) prefer {@link #thread(List)} */ @Deprecated - public Threadable thread(Threadable[] messages) { + public Threadable thread(final Threadable[] messages) { if (messages == null) { return null; } diff --git a/src/main/java/org/apache/commons/net/ntp/NTPUDPClient.java b/src/main/java/org/apache/commons/net/ntp/NTPUDPClient.java index aab1c06..162b0e5 100644 --- a/src/main/java/org/apache/commons/net/ntp/NTPUDPClient.java +++ b/src/main/java/org/apache/commons/net/ntp/NTPUDPClient.java @@ -1,4 +1,3 @@ -package org.apache.commons.net.ntp; /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -16,6 +15,7 @@ package org.apache.commons.net.ntp; * limitations under the License. */ +package org.apache.commons.net.ntp; import java.io.IOException; import java.net.DatagramPacket; @@ -23,65 +23,67 @@ import java.net.InetAddress; import org.apache.commons.net.DatagramSocketClient; -/*** - * The NTPUDPClient class is a UDP implementation of a client for the - * Network Time Protocol (NTP) described in RFC 1305 as well as the - * Simple Network Time Protocol (SNTP) in RFC-2030. To use the class, - * merely open a local datagram socket with <a href="#open"> open </a> - * and call <a href="#getTime"> getTime </a> to retrieve the time. Then call - * <a href="org.apache.commons.net.DatagramSocketClient.html#close"> close </a> - * to close the connection properly. - * Successive calls to <a href="#getTime"> getTime </a> are permitted - * without re-establishing a connection. That is because UDP is a - * connectionless protocol and the Network Time Protocol is stateless. +/** + * The NTPUDPClient class is a UDP implementation of a client for the Network Time Protocol (NTP) described in RFC 1305 as well as the Simple Network Time + * Protocol (SNTP) in RFC-2030. To use the class, merely open a local datagram socket with <a href="#open"> open </a> and call <a href="#getTime"> getTime </a> + * to retrieve the time. Then call <a href="org.apache.commons.net.DatagramSocketClient.html#close"> close </a> to close the connection properly. Successive + * calls to <a href="#getTime"> getTime </a> are permitted without re-establishing a connection. That is because UDP is a connectionless protocol and the + * Network Time Protocol is stateless. * - * @version $Revision: 1747119 $ - ***/ + */ -public final class NTPUDPClient extends DatagramSocketClient -{ - /*** The default NTP port. It is set to 123 according to RFC 1305. ***/ +public final class NTPUDPClient extends DatagramSocketClient { + /** The default NTP port. It is set to 123 according to RFC 1305. */ public static final int DEFAULT_PORT = 123; - private int _version = NtpV3Packet.VERSION_3; + private int version = NtpV3Packet.VERSION_3; - /*** - * Retrieves the time information from the specified server and port and - * returns it. The time is the number of miliiseconds since - * 00:00 (midnight) 1 January 1900 UTC, as specified by RFC 1305. - * This method reads the raw NTP packet and constructs a <i>TimeInfo</i> - * object that allows access to all the fields of the NTP message header. + /** + * Retrieves the time information from the specified server on the default NTP port and returns it. The time is the number of miliiseconds since 00:00 + * (midnight) 1 January 1900 UTC, as specified by RFC 1305. This method reads the raw NTP packet and constructs a <i>TimeInfo</i> object that allows access + * to all the fields of the NTP message header. * <p> + * * @param host The address of the server. - * @param port The port of the service. * @return The time value retrieved from the server. * @throws IOException If an error occurs while retrieving the time. - ***/ - public TimeInfo getTime(InetAddress host, int port) throws IOException - { + */ + public TimeInfo getTime(final InetAddress host) throws IOException { + return getTime(host, NtpV3Packet.NTP_PORT); + } + + /** + * Retrieves the time information from the specified server and port and returns it. The time is the number of miliiseconds since 00:00 (midnight) 1 January + * 1900 UTC, as specified by RFC 1305. This method reads the raw NTP packet and constructs a <i>TimeInfo</i> object that allows access to all the fields of + * the NTP message header. + * <p> + * + * @param host The address of the server. + * @param port The port of the service. + * @return The time value retrieved from the server. + * @throws IOException If an error occurs while retrieving the time or if received packet does not match the request. + */ + public TimeInfo getTime(final InetAddress host, final int port) throws IOException { // if not connected then open to next available UDP port - if (!isOpen()) - { + if (!isOpen()) { open(); } - NtpV3Packet message = new NtpV3Impl(); + final NtpV3Packet message = new NtpV3Impl(); message.setMode(NtpV3Packet.MODE_CLIENT); - message.setVersion(_version); - DatagramPacket sendPacket = message.getDatagramPacket(); + message.setVersion(version); + final DatagramPacket sendPacket = message.getDatagramPacket(); sendPacket.setAddress(host); sendPacket.setPort(port); - NtpV3Packet recMessage = new NtpV3Impl(); - DatagramPacket receivePacket = recMessage.getDatagramPacket(); + final NtpV3Packet recMessage = new NtpV3Impl(); + final DatagramPacket receivePacket = recMessage.getDatagramPacket(); /* - * Must minimize the time between getting the current time, - * timestamping the packet, and sending it out which - * introduces an error in the delay time. - * No extraneous logging and initializations here !!! + * Must minimize the time between getting the current time, timestamping the packet, and sending it out which introduces an error in the delay time. No + * extraneous logging and initializations here !!! */ - TimeStamp now = TimeStamp.getCurrentTime(); + final TimeStamp now = TimeStamp.getCurrentTime(); // Note that if you do not set the transmit time field then originating time // in server response is all 0's which is "Thu Feb 07 01:28:16 EST 2036". @@ -90,50 +92,34 @@ public final class NTPUDPClient extends DatagramSocketClient _socket_.send(sendPacket); _socket_.receive(receivePacket); - long returnTime = System.currentTimeMillis(); - // create TimeInfo message container but don't pre-compute the details yet - TimeInfo info = new TimeInfo(recMessage, returnTime, false); + final long returnTimeMillis = System.currentTimeMillis(); - return info; - } + // Prevent invalid time information if response does not match request + if (!now.equals(recMessage.getOriginateTimeStamp())) { + throw new IOException("Originate time does not match the request"); + } - /*** - * Retrieves the time information from the specified server on the - * default NTP port and returns it. The time is the number of miliiseconds - * since 00:00 (midnight) 1 January 1900 UTC, as specified by RFC 1305. - * This method reads the raw NTP packet and constructs a <i>TimeInfo</i> - * object that allows access to all the fields of the NTP message header. - * <p> - * @param host The address of the server. - * @return The time value retrieved from the server. - * @throws IOException If an error occurs while retrieving the time. - ***/ - public TimeInfo getTime(InetAddress host) throws IOException - { - return getTime(host, NtpV3Packet.NTP_PORT); + // create TimeInfo message container but don't pre-compute the details yet + return new TimeInfo(recMessage, returnTimeMillis, false); } - /*** - * Returns the NTP protocol version number that client sets on request packet - * that is sent to remote host (e.g. 3=NTP v3, 4=NTP v4, etc.) + /** + * Returns the NTP protocol version number that client sets on request packet that is sent to remote host (e.g. 3=NTP v3, 4=NTP v4, etc.) * - * @return the NTP protocol version number that client sets on request packet. + * @return the NTP protocol version number that client sets on request packet. * @see #setVersion(int) - ***/ - public int getVersion() - { - return _version; + */ + public int getVersion() { + return version; } - /*** - * Sets the NTP protocol version number that client sets on request packet - * communicate with remote host. + /** + * Sets the NTP protocol version number that client sets on request packet communicate with remote host. * * @param version the NTP protocol version number - ***/ - public void setVersion(int version) - { - _version = version; + */ + public void setVersion(final int version) { + this.version = version; } } diff --git a/src/main/java/org/apache/commons/net/ntp/NtpUtils.java b/src/main/java/org/apache/commons/net/ntp/NtpUtils.java index bbedb09..6c5ad47 100644 --- a/src/main/java/org/apache/commons/net/ntp/NtpUtils.java +++ b/src/main/java/org/apache/commons/net/ntp/NtpUtils.java @@ -1,4 +1,3 @@ -package org.apache.commons.net.ntp; /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -16,62 +15,83 @@ package org.apache.commons.net.ntp; * limitations under the License. */ +package org.apache.commons.net.ntp; -/*** +/** * Common NtpUtils Helper class. * - * @version $Revision: 1652855 $ */ public final class NtpUtils { - /*** - * Returns 32-bit integer address to IPv4 address string "%d.%d.%d.%d" format. - * - * @param address the 32-bit address - * @return the raw IP address in a string format. - */ - public static String getHostAddress(int address) - { - return ((address >>> 24) & 0xFF) + "." + - ((address >>> 16) & 0xFF) + "." + - ((address >>> 8) & 0xFF) + "." + - ((address >>> 0) & 0xFF); - } + /** + * Returns 32-bit integer address to IPv4 address string "%d.%d.%d.%d" format. + * + * @param address the 32-bit address + * @return the raw IP address in a string format. + */ + public static String getHostAddress(final int address) { + return ((address >>> 24) & 0xFF) + "." + ((address >>> 16) & 0xFF) + "." + ((address >>> 8) & 0xFF) + "." + ((address >>> 0) & 0xFF); + } + + /** + * Return human-readable name of message mode type (RFC 1305). + * + * @param mode the mode type + * @return mode name + */ + public static String getModeName(final int mode) { + switch (mode) { + case NtpV3Packet.MODE_RESERVED: + return "Reserved"; + case NtpV3Packet.MODE_SYMMETRIC_ACTIVE: + return "Symmetric Active"; + case NtpV3Packet.MODE_SYMMETRIC_PASSIVE: + return "Symmetric Passive"; + case NtpV3Packet.MODE_CLIENT: + return "Client"; + case NtpV3Packet.MODE_SERVER: + return "Server"; + case NtpV3Packet.MODE_BROADCAST: + return "Broadcast"; + case NtpV3Packet.MODE_CONTROL_MESSAGE: + return "Control"; + case NtpV3Packet.MODE_PRIVATE: + return "Private"; + default: + return "Unknown"; + } + } - /*** + /** * Returns NTP packet reference identifier as IP address. * - * @param packet NTP packet - * @return the packet reference id (as IP address) in "%d.%d.%d.%d" format. + * @param packet NTP packet + * @return the packet reference id (as IP address) in "%d.%d.%d.%d" format. */ - public static String getRefAddress(NtpV3Packet packet) - { - int address = (packet == null) ? 0 : packet.getReferenceId(); - return getHostAddress(address); - } + public static String getRefAddress(final NtpV3Packet packet) { + final int address = (packet == null) ? 0 : packet.getReferenceId(); + return getHostAddress(address); + } - /*** - * Get refId as reference clock string (e.g. GPS, WWV, LCL). If string is - * invalid (non-ASCII character) then returns empty string "". - * For details refer to the <A HREF="http://www.eecis.udel.edu/~mills/ntp/html/refclock.html#list">Comprehensive - * List of Clock Drivers</A>. + /** + * Get refId as reference clock string (e.g. GPS, WWV, LCL). If string is invalid (non-ASCII character) then returns empty string "". For details refer to + * the <A HREF="http://www.eecis.udel.edu/~mills/ntp/html/refclock.html#list">Comprehensive List of Clock Drivers</A>. * * @param message the message to check * @return reference clock string if primary NTP server */ - public static String getReferenceClock(NtpV3Packet message) { + public static String getReferenceClock(final NtpV3Packet message) { if (message == null) { return ""; } - int refId = message.getReferenceId(); + final int refId = message.getReferenceId(); if (refId == 0) { return ""; } - StringBuilder buf = new StringBuilder(4); + final StringBuilder buf = new StringBuilder(4); // start at highest-order byte (0x4c434c00 -> LCL) - for (int shiftBits = 24; shiftBits >= 0; shiftBits -= 8) - { - char c = (char) ((refId >>> shiftBits) & 0xff); + for (int shiftBits = 24; shiftBits >= 0; shiftBits -= 8) { + final char c = (char) ((refId >>> shiftBits) & 0xff); if (c == 0) { // 0-terminated ASCII string break; } @@ -83,34 +103,4 @@ public final class NtpUtils { return buf.toString(); } - /*** - * Return human-readable name of message mode type (RFC 1305). - * - * @param mode the mode type - * @return mode name - */ - public static String getModeName(int mode) - { - switch (mode) { - case NtpV3Packet.MODE_RESERVED: - return "Reserved"; - case NtpV3Packet.MODE_SYMMETRIC_ACTIVE: - return "Symmetric Active"; - case NtpV3Packet.MODE_SYMMETRIC_PASSIVE: - return "Symmetric Passive"; - case NtpV3Packet.MODE_CLIENT: - return "Client"; - case NtpV3Packet.MODE_SERVER: - return "Server"; - case NtpV3Packet.MODE_BROADCAST: - return "Broadcast"; - case NtpV3Packet.MODE_CONTROL_MESSAGE: - return "Control"; - case NtpV3Packet.MODE_PRIVATE: - return "Private"; - default: - return "Unknown"; - } - } - } diff --git a/src/main/java/org/apache/commons/net/ntp/NtpV3Impl.java b/src/main/java/org/apache/commons/net/ntp/NtpV3Impl.java index 772e5df..04f6e20 100644 --- a/src/main/java/org/apache/commons/net/ntp/NtpV3Impl.java +++ b/src/main/java/org/apache/commons/net/ntp/NtpV3Impl.java @@ -1,4 +1,3 @@ -package org.apache.commons.net.ntp; /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -16,16 +15,15 @@ package org.apache.commons.net.ntp; * limitations under the License. */ +package org.apache.commons.net.ntp; + import java.net.DatagramPacket; -/*** - * Implementation of NtpV3Packet with methods converting Java objects to/from - * the Network Time Protocol (NTP) data message header format described in RFC-1305. +/** + * Implementation of NtpV3Packet with methods converting Java objects to/from the Network Time Protocol (NTP) data message header format described in RFC-1305. * - * @version $Revision: 1741829 $ */ -public class NtpV3Impl implements NtpV3Packet -{ +public class NtpV3Impl implements NtpV3Packet { private static final int MODE_INDEX = 0; private static final int MODE_SHIFT = 0; @@ -52,331 +50,347 @@ public class NtpV3Impl implements NtpV3Packet // private static final int KEY_IDENTIFIER_INDEX = 48; // private static final int MESSAGE_DIGEST = 54; /* len 16 bytes */ + /** + * Convert byte to unsigned integer. Java only has signed types so we have to do more work to get unsigned ops. + * + * @param b input byte + * @return unsigned int value of byte + */ + protected static final int ui(final byte b) { + final int i = b & 0xFF; + return i; + } + + /** + * Convert byte to unsigned long. Java only has signed types so we have to do more work to get unsigned ops + * + * @param b input byte + * @return unsigned long value of byte + */ + protected static final long ul(final byte b) { + final long i = b & 0xFF; + return i; + } + private final byte[] buf = new byte[48]; private volatile DatagramPacket dp; /** Creates a new instance of NtpV3Impl */ - public NtpV3Impl() - { + public NtpV3Impl() { } - /*** - * Returns mode as defined in RFC-1305 which is a 3-bit integer - * whose value is indicated by the MODE_xxx parameters. + /** + * Compares this object against the specified object. The result is <code>true</code> if and only if the argument is not <code>null</code> and is a + * <code>NtpV3Impl</code> object that contains the same values as this object. * - * @return mode as defined in RFC-1305. + * @param obj the object to compare with. + * @return <code>true</code> if the objects are the same; <code>false</code> otherwise. + * @since 3.4 */ @Override - public int getMode() - { - return (ui(buf[MODE_INDEX]) >> MODE_SHIFT) & 0x7; + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final NtpV3Impl other = (NtpV3Impl) obj; + return java.util.Arrays.equals(buf, other.buf); } - /*** - * Return human-readable name of message mode type as described in - * RFC 1305. - * @return mode name as string. + /** + * Returns the datagram packet with the NTP details already filled in. + * + * @return a datagram packet. */ @Override - public String getModeName() - { - return NtpUtils.getModeName(getMode()); + public synchronized DatagramPacket getDatagramPacket() { + if (dp == null) { + dp = new DatagramPacket(buf, buf.length); + dp.setPort(NTP_PORT); + } + return dp; } - /*** - * Set mode as defined in RFC-1305. - * - * @param mode the mode to set + /** + * @return 4 bytes as 32-bit int */ - @Override - public void setMode(int mode) - { - buf[MODE_INDEX] = (byte) (buf[MODE_INDEX] & 0xF8 | mode & 0x7); + private int getInt(final int index) { + final int i = ui(buf[index]) << 24 | ui(buf[index + 1]) << 16 | ui(buf[index + 2]) << 8 | ui(buf[index + 3]); + + return i; } - /*** - * Returns leap indicator as defined in RFC-1305 which is a two-bit code: - * 0=no warning - * 1=last minute has 61 seconds - * 2=last minute has 59 seconds - * 3=alarm condition (clock not synchronized) + /** + * Returns leap indicator as defined in RFC-1305 which is a two-bit code: 0=no warning 1=last minute has 61 seconds 2=last minute has 59 seconds 3=alarm + * condition (clock not synchronized) * * @return leap indicator as defined in RFC-1305. */ @Override - public int getLeapIndicator() - { + public int getLeapIndicator() { return (ui(buf[LI_INDEX]) >> LI_SHIFT) & 0x3; } - /*** - * Set leap indicator as defined in RFC-1305. + /** + * Get Long value represented by bits starting at specified index. * - * @param li leap indicator. + * @return 8 bytes as 64-bit long */ - @Override - public void setLeapIndicator(int li) - { - buf[LI_INDEX] = (byte) (buf[LI_INDEX] & 0x3F | ((li & 0x3) << LI_SHIFT)); + private long getLong(final int index) { + final long i = ul(buf[index]) << 56 | ul(buf[index + 1]) << 48 | ul(buf[index + 2]) << 40 | ul(buf[index + 3]) << 32 | ul(buf[index + 4]) << 24 + | ul(buf[index + 5]) << 16 | ul(buf[index + 6]) << 8 | ul(buf[index + 7]); + return i; } - /*** - * Returns poll interval as defined in RFC-1305, which is an eight-bit - * signed integer indicating the maximum interval between successive - * messages, in seconds to the nearest power of two (e.g. value of six - * indicates an interval of 64 seconds. The values that can appear in - * this field range from NTP_MINPOLL to NTP_MAXPOLL inclusive. + /** + * Returns mode as defined in RFC-1305 which is a 3-bit integer whose value is indicated by the MODE_xxx parameters. * - * @return poll interval as defined in RFC-1305. + * @return mode as defined in RFC-1305. */ @Override - public int getPoll() - { - return buf[POLL_INDEX]; + public int getMode() { + return (ui(buf[MODE_INDEX]) >> MODE_SHIFT) & 0x7; } - /*** - * Set poll interval as defined in RFC-1305. + /** + * Return human-readable name of message mode type as described in RFC 1305. * - * @param poll poll interval. + * @return mode name as string. */ @Override - public void setPoll(int poll) - { - buf[POLL_INDEX] = (byte) (poll & 0xFF); + public String getModeName() { + return NtpUtils.getModeName(getMode()); } - /*** - * Returns precision as defined in RFC-1305 encoded as an 8-bit signed - * integer (seconds to nearest power of two). - * Values normally range from -6 to -20. + /** + * Returns the originate time as defined in RFC-1305. * - * @return precision as defined in RFC-1305. + * @return the originate time. Never returns null. */ @Override - public int getPrecision() - { - return buf[PRECISION_INDEX]; + public TimeStamp getOriginateTimeStamp() { + return getTimestamp(ORIGINATE_TIMESTAMP_INDEX); } - /*** - * Set precision as defined in RFC-1305. - * @param precision the precision to set - * @since 3.4 + /** + * Returns poll interval as defined in RFC-1305, which is an eight-bit signed integer indicating the maximum interval between successive messages, in + * seconds to the nearest power of two (e.g. value of six indicates an interval of 64 seconds. The values that can appear in this field range from + * NTP_MINPOLL to NTP_MAXPOLL inclusive. + * + * @return poll interval as defined in RFC-1305. */ @Override - public void setPrecision(int precision) - { - buf[PRECISION_INDEX] = (byte) (precision & 0xFF); + public int getPoll() { + return buf[POLL_INDEX]; } - /*** - * Returns NTP version number as defined in RFC-1305. + /** + * Returns precision as defined in RFC-1305 encoded as an 8-bit signed integer (seconds to nearest power of two). Values normally range from -6 to -20. * - * @return NTP version number. + * @return precision as defined in RFC-1305. */ @Override - public int getVersion() - { - return (ui(buf[VERSION_INDEX]) >> VERSION_SHIFT) & 0x7; + public int getPrecision() { + return buf[PRECISION_INDEX]; } - /*** - * Set NTP version as defined in RFC-1305. + /** + * Returns receive timestamp as defined in RFC-1305. * - * @param version NTP version. + * @return the receive time. Never returns null. */ @Override - public void setVersion(int version) - { - buf[VERSION_INDEX] = (byte) (buf[VERSION_INDEX] & 0xC7 | ((version & 0x7) << VERSION_SHIFT)); + public TimeStamp getReceiveTimeStamp() { + return getTimestamp(RECEIVE_TIMESTAMP_INDEX); } - /*** - * Returns Stratum as defined in RFC-1305, which indicates the stratum level - * of the local clock, with values defined as follows: 0=unspecified, - * 1=primary ref clock, and all others a secondary reference (via NTP). + /** + * Returns the reference id as defined in RFC-1305, which is a 32-bit integer whose value is dependent on several criteria. * - * @return Stratum level as defined in RFC-1305. + * @return the reference id as defined in RFC-1305. */ @Override - public int getStratum() - { - return ui(buf[STRATUM_INDEX]); + public int getReferenceId() { + return getInt(REFERENCE_ID_INDEX); } - /*** - * Set stratum level as defined in RFC-1305. + /** + * Returns the reference id string. String cannot be null but value is dependent on the version of the NTP spec supported and stratum level. Value can be an + * empty string, clock type string, IP address, or a hex string. * - * @param stratum stratum level. + * @return the reference id string. */ @Override - public void setStratum(int stratum) - { - buf[STRATUM_INDEX] = (byte) (stratum & 0xFF); + public String getReferenceIdString() { + final int version = getVersion(); + final int stratum = getStratum(); + if (version == VERSION_3 || version == VERSION_4) { + if (stratum == 0 || stratum == 1) { + return idAsString(); // 4-character ASCII string (e.g. GPS, USNO) + } + // in NTPv4 servers this is latest transmit timestamp of ref source + if (version == VERSION_4) { + return idAsHex(); + } + } + + // Stratum 2 and higher this is a four-octet IPv4 address + // of the primary reference host. + if (stratum >= 2) { + return idAsIPAddress(); + } + return idAsHex(); } - /*** - * Return root delay as defined in RFC-1305, which is the total roundtrip delay - * to the primary reference source, in seconds. Values can take positive and - * negative values, depending on clock precision and skew. + /** + * Returns the reference time as defined in RFC-1305. * - * @return root delay as defined in RFC-1305. + * @return the reference time as <code>TimeStamp</code> object. Never returns null. */ @Override - public int getRootDelay() - { - return getInt(ROOT_DELAY_INDEX); + public TimeStamp getReferenceTimeStamp() { + return getTimestamp(REFERENCE_TIMESTAMP_INDEX); } - /*** - * Set root delay as defined in RFC-1305. + /** + * Return root delay as defined in RFC-1305, which is the total roundtrip delay to the primary reference source, in seconds. Values can take positive and + * negative values, depending on clock precision and skew. * - * @param delay root delay - * @since 3.4 + * @return root delay as defined in RFC-1305. */ @Override - public void setRootDelay(int delay) - { - setInt(ROOT_DELAY_INDEX, delay); + public int getRootDelay() { + return getInt(ROOT_DELAY_INDEX); } /** - * Return root delay as defined in RFC-1305 in milliseconds, which is - * the total roundtrip delay to the primary reference source, in - * seconds. Values can take positive and negative values, depending - * on clock precision and skew. + * Return root delay as defined in RFC-1305 in milliseconds, which is the total roundtrip delay to the primary reference source, in seconds. Values can take + * positive and negative values, depending on clock precision and skew. * * @return root delay in milliseconds */ @Override - public double getRootDelayInMillisDouble() - { - double l = getRootDelay(); + public double getRootDelayInMillisDouble() { + final double l = getRootDelay(); return l / 65.536; } - /*** + /** * Returns root dispersion as defined in RFC-1305. + * * @return root dispersion. */ @Override - public int getRootDispersion() - { + public int getRootDispersion() { return getInt(ROOT_DISPERSION_INDEX); } - /*** - * Set root dispersion as defined in RFC-1305. + /** + * Returns root dispersion (as defined in RFC-1305) in milliseconds. * - * @param dispersion root dispersion - * @since 3.4 + * @return root dispersion in milliseconds */ @Override - public void setRootDispersion(int dispersion) - { - setInt(ROOT_DISPERSION_INDEX, dispersion); + public long getRootDispersionInMillis() { + final long l = getRootDispersion(); + return (l * 1000) / 65536L; } - /*** - * Returns root dispersion (as defined in RFC-1305) in milliseconds. + /** + * Returns root dispersion (as defined in RFC-1305) in milliseconds as double precision value. * * @return root dispersion in milliseconds */ @Override - public long getRootDispersionInMillis() - { - long l = getRootDispersion(); - return (l * 1000) / 65536L; + public double getRootDispersionInMillisDouble() { + final double l = getRootDispersion(); + return l / 65.536; } - /*** - * Returns root dispersion (as defined in RFC-1305) in milliseconds - * as double precision value. + /** + * Returns Stratum as defined in RFC-1305, which indicates the stratum level of the local clock, with values defined as follows: 0=unspecified, 1=primary + * ref clock, and all others a secondary reference (via NTP). * - * @return root dispersion in milliseconds + * @return Stratum level as defined in RFC-1305. */ @Override - public double getRootDispersionInMillisDouble() - { - double l = getRootDispersion(); - return l / 65.536; + public int getStratum() { + return ui(buf[STRATUM_INDEX]); } - /*** - * Set reference clock identifier field with 32-bit unsigned integer value. - * See RFC-1305 for description. + /** + * Get NTP Timestamp at specified starting index. * - * @param refId reference clock identifier. + * @param index index into data array + * @return TimeStamp object for 64 bits starting at index + */ + private TimeStamp getTimestamp(final int index) { + return new TimeStamp(getLong(index)); + } + + /** + * Returns the transmit timestamp as defined in RFC-1305. + * + * @return the transmit timestamp as defined in RFC-1305. Never returns a null object. */ @Override - public void setReferenceId(int refId) - { - setInt(REFERENCE_ID_INDEX, refId); + public TimeStamp getTransmitTimeStamp() { + return getTimestamp(TRANSMIT_TIMESTAMP_INDEX); } - /*** - * Returns the reference id as defined in RFC-1305, which is - * a 32-bit integer whose value is dependent on several criteria. + /** + * Return type of time packet. The values (e.g. NTP, TIME, ICMP, ...) correspond to the protocol used to obtain the timing information. * - * @return the reference id as defined in RFC-1305. + * @return packet type string identifier which in this case is "NTP". */ @Override - public int getReferenceId() - { - return getInt(REFERENCE_ID_INDEX); + public String getType() { + return "NTP"; } - /*** - * Returns the reference id string. String cannot be null but - * value is dependent on the version of the NTP spec supported - * and stratum level. Value can be an empty string, clock type string, - * IP address, or a hex string. + /** + * Returns NTP version number as defined in RFC-1305. * - * @return the reference id string. + * @return NTP version number. */ @Override - public String getReferenceIdString() - { - int version = getVersion(); - int stratum = getStratum(); - if (version == VERSION_3 || version == VERSION_4) { - if (stratum == 0 || stratum == 1) { - return idAsString(); // 4-character ASCII string (e.g. GPS, USNO) - } - // in NTPv4 servers this is latest transmit timestamp of ref source - if (version == VERSION_4) { - return idAsHex(); - } - } + public int getVersion() { + return (ui(buf[VERSION_INDEX]) >> VERSION_SHIFT) & 0x7; + } - // Stratum 2 and higher this is a four-octet IPv4 address - // of the primary reference host. - if (stratum >= 2) { - return idAsIPAddress(); - } - return idAsHex(); + /** + * Computes a hashcode for this object. The result is the exclusive OR of the values of this object stored as a byte array. + * + * @return a hash code value for this object. + * @since 3.4 + */ + @Override + public int hashCode() { + return java.util.Arrays.hashCode(buf); + } + + private String idAsHex() { + return Integer.toHexString(getReferenceId()); } - /*** + /** * Returns Reference id as dotted IP address. + * * @return refId as IP address string. */ - private String idAsIPAddress() - { - return ui(buf[REFERENCE_ID_INDEX]) + "." + - ui(buf[REFERENCE_ID_INDEX + 1]) + "." + - ui(buf[REFERENCE_ID_INDEX + 2]) + "." + - ui(buf[REFERENCE_ID_INDEX + 3]); + private String idAsIPAddress() { + return ui(buf[REFERENCE_ID_INDEX]) + "." + ui(buf[REFERENCE_ID_INDEX + 1]) + "." + ui(buf[REFERENCE_ID_INDEX + 2]) + "." + + ui(buf[REFERENCE_ID_INDEX + 3]); } - private String idAsString() - { - StringBuilder id = new StringBuilder(); + private String idAsString() { + final StringBuilder id = new StringBuilder(); for (int i = 0; i <= 3; i++) { - char c = (char) buf[REFERENCE_ID_INDEX + i]; - if (c == 0) { // 0-terminated string + final char c = (char) buf[REFERENCE_ID_INDEX + i]; + if (c == 0) { // 0-terminated string break; } id.append(c); @@ -384,183 +398,163 @@ public class NtpV3Impl implements NtpV3Packet return id.toString(); } - private String idAsHex() - { - return Integer.toHexString(getReferenceId()); - } - - /*** - * Returns the transmit timestamp as defined in RFC-1305. + /** + * Set the contents of this object from source datagram packet. * - * @return the transmit timestamp as defined in RFC-1305. - * Never returns a null object. + * @param srcDp source DatagramPacket to copy contents from, never null. + * @throws IllegalArgumentException if srcDp is null or byte length is less than minimum length of 48 bytes */ @Override - public TimeStamp getTransmitTimeStamp() - { - return getTimestamp(TRANSMIT_TIMESTAMP_INDEX); + public void setDatagramPacket(final DatagramPacket srcDp) { + if (srcDp == null || srcDp.getLength() < buf.length) { + throw new IllegalArgumentException(); + } + final byte[] incomingBuf = srcDp.getData(); + int len = srcDp.getLength(); + if (len > buf.length) { + len = buf.length; + } + System.arraycopy(incomingBuf, 0, buf, 0, len); + final DatagramPacket dp = getDatagramPacket(); + dp.setAddress(srcDp.getAddress()); + final int port = srcDp.getPort(); + dp.setPort(port > 0 ? port : NTP_PORT); + dp.setData(buf); } - /*** - * Set transmit time with NTP timestamp. - * If <code>ts</code> is null then zero time is used. + /** + * Set integer value at index position. * - * @param ts NTP timestamp + * @param idx index position + * @param value 32-bit int value */ - @Override - public void setTransmitTime(TimeStamp ts) - { - setTimestamp(TRANSMIT_TIMESTAMP_INDEX, ts); + private void setInt(final int idx, int value) { + for (int i = 3; i >= 0; i--) { + buf[idx + i] = (byte) (value & 0xff); + value >>>= 8; // shift right one-byte + } } - /*** - * Set originate timestamp given NTP TimeStamp object. - * If <code>ts</code> is null then zero time is used. + /** + * Set leap indicator as defined in RFC-1305. * - * @param ts NTP timestamp + * @param li leap indicator. */ @Override - public void setOriginateTimeStamp(TimeStamp ts) - { - setTimestamp(ORIGINATE_TIMESTAMP_INDEX, ts); + public void setLeapIndicator(final int li) { + buf[LI_INDEX] = (byte) (buf[LI_INDEX] & 0x3F | ((li & 0x3) << LI_SHIFT)); } - /*** - * Returns the originate time as defined in RFC-1305. + /** + * Set mode as defined in RFC-1305. * - * @return the originate time. - * Never returns null. + * @param mode the mode to set */ @Override - public TimeStamp getOriginateTimeStamp() - { - return getTimestamp(ORIGINATE_TIMESTAMP_INDEX); + public void setMode(final int mode) { + buf[MODE_INDEX] = (byte) (buf[MODE_INDEX] & 0xF8 | mode & 0x7); } - /*** - * Returns the reference time as defined in RFC-1305. + /** + * Set originate timestamp given NTP TimeStamp object. If <code>ts</code> is null then zero time is used. * - * @return the reference time as <code>TimeStamp</code> object. - * Never returns null. + * @param ts NTP timestamp */ @Override - public TimeStamp getReferenceTimeStamp() - { - return getTimestamp(REFERENCE_TIMESTAMP_INDEX); + public void setOriginateTimeStamp(final TimeStamp ts) { + setTimestamp(ORIGINATE_TIMESTAMP_INDEX, ts); } - /*** - * Set Reference time with NTP timestamp. If <code>ts</code> is null - * then zero time is used. + /** + * Set poll interval as defined in RFC-1305. * - * @param ts NTP timestamp + * @param poll poll interval. */ @Override - public void setReferenceTime(TimeStamp ts) - { - setTimestamp(REFERENCE_TIMESTAMP_INDEX, ts); + public void setPoll(final int poll) { + buf[POLL_INDEX] = (byte) (poll & 0xFF); } - /*** - * Returns receive timestamp as defined in RFC-1305. + /** + * Set precision as defined in RFC-1305. * - * @return the receive time. - * Never returns null. + * @param precision the precision to set + * @since 3.4 */ @Override - public TimeStamp getReceiveTimeStamp() - { - return getTimestamp(RECEIVE_TIMESTAMP_INDEX); + public void setPrecision(final int precision) { + buf[PRECISION_INDEX] = (byte) (precision & 0xFF); } - /*** - * Set receive timestamp given NTP TimeStamp object. - * If <code>ts</code> is null then zero time is used. + /** + * Set receive timestamp given NTP TimeStamp object. If <code>ts</code> is null then zero time is used. * * @param ts timestamp */ @Override - public void setReceiveTimeStamp(TimeStamp ts) - { + public void setReceiveTimeStamp(final TimeStamp ts) { setTimestamp(RECEIVE_TIMESTAMP_INDEX, ts); } - /*** - * Return type of time packet. The values (e.g. NTP, TIME, ICMP, ...) - * correspond to the protocol used to obtain the timing information. + /** + * Set reference clock identifier field with 32-bit unsigned integer value. See RFC-1305 for description. * - * @return packet type string identifier which in this case is "NTP". + * @param refId reference clock identifier. */ @Override - public String getType() - { - return "NTP"; + public void setReferenceId(final int refId) { + setInt(REFERENCE_ID_INDEX, refId); } - /*** - * @return 4 bytes as 32-bit int + /** + * Set Reference time with NTP timestamp. If <code>ts</code> is null then zero time is used. + * + * @param ts NTP timestamp */ - private int getInt(int index) - { - int i = ui(buf[index]) << 24 | - ui(buf[index + 1]) << 16 | - ui(buf[index + 2]) << 8 | - ui(buf[index + 3]); - - return i; + @Override + public void setReferenceTime(final TimeStamp ts) { + setTimestamp(REFERENCE_TIMESTAMP_INDEX, ts); } - /*** - * Set integer value at index position. + /** + * Set root delay as defined in RFC-1305. * - * @param idx index position - * @param value 32-bit int value + * @param delay root delay + * @since 3.4 */ - private void setInt(int idx, int value) - { - for (int i=3; i >= 0; i--) { - buf[idx + i] = (byte) (value & 0xff); - value >>>= 8; // shift right one-byte - } + @Override + public void setRootDelay(final int delay) { + setInt(ROOT_DELAY_INDEX, delay); } /** - * Get NTP Timestamp at specified starting index. + * Set root dispersion as defined in RFC-1305. * - * @param index index into data array - * @return TimeStamp object for 64 bits starting at index + * @param dispersion root dispersion + * @since 3.4 */ - private TimeStamp getTimestamp(int index) - { - return new TimeStamp(getLong(index)); + @Override + public void setRootDispersion(final int dispersion) { + setInt(ROOT_DISPERSION_INDEX, dispersion); } - /*** - * Get Long value represented by bits starting at specified index. + /** + * Set stratum level as defined in RFC-1305. * - * @return 8 bytes as 64-bit long + * @param stratum stratum level. */ - private long getLong(int index) - { - long i = ul(buf[index]) << 56 | - ul(buf[index + 1]) << 48 | - ul(buf[index + 2]) << 40 | - ul(buf[index + 3]) << 32 | - ul(buf[index + 4]) << 24 | - ul(buf[index + 5]) << 16 | - ul(buf[index + 6]) << 8 | - ul(buf[index + 7]); - return i; + @Override + public void setStratum(final int stratum) { + buf[STRATUM_INDEX] = (byte) (stratum & 0xFF); } - /*** + /** * Sets the NTP timestamp at the given array index. * * @param index index into the byte array. - * @param t TimeStamp. + * @param t TimeStamp. */ - private void setTimestamp(int index, TimeStamp t) - { + private void setTimestamp(final int index, final TimeStamp t) { long ntpTime = (t == null) ? 0 : t.ntpValue(); // copy 64-bits from Long value into 8 x 8-bit bytes of array // one byte at a time shifting 8-bits for each position. @@ -568,132 +562,39 @@ public class NtpV3Impl implements NtpV3Packet buf[index + i] = (byte) (ntpTime & 0xFF); ntpTime >>>= 8; // shift to next byte } - // buf[index] |= 0x80; // only set if 1900 baseline.... - } - - /*** - * Returns the datagram packet with the NTP details already filled in. - * - * @return a datagram packet. - */ - @Override - public synchronized DatagramPacket getDatagramPacket() - { - if (dp == null) { - dp = new DatagramPacket(buf, buf.length); - dp.setPort(NTP_PORT); - } - return dp; - } - - /*** - * Set the contents of this object from source datagram packet. - * - * @param srcDp source DatagramPacket to copy contents from, never null. - * @throws IllegalArgumentException if srcDp is null or byte length is less than minimum length of 48 bytes - */ - @Override - public void setDatagramPacket(DatagramPacket srcDp) - { - if (srcDp == null || srcDp.getLength() < buf.length) { - throw new IllegalArgumentException(); - } - byte[] incomingBuf = srcDp.getData(); - int len = srcDp.getLength(); - if (len > buf.length) { - len = buf.length; - } - System.arraycopy(incomingBuf, 0, buf, 0, len); - DatagramPacket dp = getDatagramPacket(); - dp.setAddress(srcDp.getAddress()); - int port = srcDp.getPort(); - dp.setPort(port > 0 ? port : NTP_PORT); - dp.setData(buf); + // buf[index] |= 0x80; // only set if 1900 baseline.... } - /*** - * Compares this object against the specified object. - * The result is <code>true</code> if and only if the argument is - * not <code>null</code> and is a <code>NtpV3Impl</code> object that - * contains the same values as this object. + /** + * Set transmit time with NTP timestamp. If <code>ts</code> is null then zero time is used. * - * @param obj the object to compare with. - * @return <code>true</code> if the objects are the same; - * <code>false</code> otherwise. - * @since 3.4 + * @param ts NTP timestamp */ @Override - public boolean equals(Object obj) - { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - NtpV3Impl other = (NtpV3Impl) obj; - return java.util.Arrays.equals(buf, other.buf); + public void setTransmitTime(final TimeStamp ts) { + setTimestamp(TRANSMIT_TIMESTAMP_INDEX, ts); } - /*** - * Computes a hashcode for this object. The result is the exclusive - * OR of the values of this object stored as a byte array. + /** + * Set NTP version as defined in RFC-1305. * - * @return a hash code value for this object. - * @since 3.4 + * @param version NTP version. */ @Override - public int hashCode() - { - return java.util.Arrays.hashCode(buf); - } - - /*** - * Convert byte to unsigned integer. - * Java only has signed types so we have to do - * more work to get unsigned ops. - * - * @param b input byte - * @return unsigned int value of byte - */ - protected static final int ui(byte b) - { - int i = b & 0xFF; - return i; - } - - /*** - * Convert byte to unsigned long. - * Java only has signed types so we have to do - * more work to get unsigned ops - * - * @param b input byte - * @return unsigned long value of byte - */ - protected static final long ul(byte b) - { - long i = b & 0xFF; - return i; + public void setVersion(final int version) { + buf[VERSION_INDEX] = (byte) (buf[VERSION_INDEX] & 0xC7 | ((version & 0x7) << VERSION_SHIFT)); } - /*** + /** * Returns details of NTP packet as a string. * * @return details of NTP packet as a string. */ @Override - public String toString() - { - return "[" + - "version:" + getVersion() + - ", mode:" + getMode() + - ", poll:" + getPoll() + - ", precision:" + getPrecision() + - ", delay:" + getRootDelay() + - ", dispersion(ms):" + getRootDispersionInMillisDouble() + - ", id:" + getReferenceIdString() + - ", xmitTime:" + getTransmitTimeStamp().toDateString() + - " ]"; + public String toString() { + return "[" + "version:" + getVersion() + ", mode:" + getMode() + ", poll:" + getPoll() + ", precision:" + getPrecision() + ", delay:" + getRootDelay() + + ", dispersion(ms):" + getRootDispersionInMillisDouble() + ", id:" + getReferenceIdString() + ", xmitTime:" + + getTransmitTimeStamp().toDateString() + " ]"; } } diff --git a/src/main/java/org/apache/commons/net/ntp/NtpV3Packet.java b/src/main/java/org/apache/commons/net/ntp/NtpV3Packet.java index 70c29dd..71f785a 100644 --- a/src/main/java/org/apache/commons/net/ntp/NtpV3Packet.java +++ b/src/main/java/org/apache/commons/net/ntp/NtpV3Packet.java @@ -1,4 +1,3 @@ -package org.apache.commons.net.ntp; /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -16,245 +15,253 @@ package org.apache.commons.net.ntp; * limitations under the License. */ +package org.apache.commons.net.ntp; import java.net.DatagramPacket; /** - * Interface for a NtpV3Packet with get/set methods corresponding to the fields - * in the NTP Data Message Header described in RFC 1305. + * Interface for a NtpV3Packet with get/set methods corresponding to the fields in the NTP Data Message Header described in RFC 1305. * - * @version $Revision: 1652868 $ */ -public interface NtpV3Packet -{ +public interface NtpV3Packet { /** * Standard NTP UDP port */ - public static final int NTP_PORT = 123; + int NTP_PORT = 123; - public static final int LI_NO_WARNING = 0; - public static final int LI_LAST_MINUTE_HAS_61_SECONDS = 1; - public static final int LI_LAST_MINUTE_HAS_59_SECONDS = 2; - public static final int LI_ALARM_CONDITION = 3; + int LI_NO_WARNING = 0; + int LI_LAST_MINUTE_HAS_61_SECONDS = 1; + int LI_LAST_MINUTE_HAS_59_SECONDS = 2; + int LI_ALARM_CONDITION = 3; /* mode options */ - public static final int MODE_RESERVED = 0; - public static final int MODE_SYMMETRIC_ACTIVE = 1; - public static final int MODE_SYMMETRIC_PASSIVE = 2; - public static final int MODE_CLIENT = 3; - public static final int MODE_SERVER = 4; - public static final int MODE_BROADCAST = 5; - public static final int MODE_CONTROL_MESSAGE = 6; - public static final int MODE_PRIVATE = 7; + int MODE_RESERVED = 0; + int MODE_SYMMETRIC_ACTIVE = 1; + int MODE_SYMMETRIC_PASSIVE = 2; + int MODE_CLIENT = 3; + int MODE_SERVER = 4; + int MODE_BROADCAST = 5; + int MODE_CONTROL_MESSAGE = 6; + int MODE_PRIVATE = 7; - public static final int NTP_MINPOLL = 4; // 16 seconds - public static final int NTP_MAXPOLL = 14; // 16284 seconds + int NTP_MINPOLL = 4; // 16 seconds + int NTP_MAXPOLL = 14; // 16284 seconds - public static final int NTP_MINCLOCK = 1; - public static final int NTP_MAXCLOCK = 10; + int NTP_MINCLOCK = 1; + int NTP_MAXCLOCK = 10; - public static final int VERSION_3 = 3; - public static final int VERSION_4 = 4; + int VERSION_3 = 3; + int VERSION_4 = 4; - /* possible getType values such that other time-related protocols can - * have its information represented as NTP packets + /* + * possible getType values such that other time-related protocols can have its information represented as NTP packets */ - public static final String TYPE_NTP = "NTP"; // RFC-1305/2030 - public static final String TYPE_ICMP = "ICMP"; // RFC-792 - public static final String TYPE_TIME = "TIME"; // RFC-868 - public static final String TYPE_DAYTIME = "DAYTIME"; // RFC-867 + String TYPE_NTP = "NTP"; // RFC-1305/2030 + String TYPE_ICMP = "ICMP"; // RFC-792 + String TYPE_TIME = "TIME"; // RFC-868 + String TYPE_DAYTIME = "DAYTIME"; // RFC-867 /** * @return a datagram packet with the NTP parts already filled in */ - public DatagramPacket getDatagramPacket(); + DatagramPacket getDatagramPacket(); /** - * Set the contents of this object from the datagram packet - * @param dp the packet + * @return leap indicator as defined in RFC-1305 */ - public void setDatagramPacket(DatagramPacket dp); + int getLeapIndicator(); /** - * @return leap indicator as defined in RFC-1305 + * @return mode as defined in RFC-1305 */ - public int getLeapIndicator(); + int getMode(); /** - * Set leap indicator. - * @param li - leap indicator code + * @return mode as human readable string; e.g. 3=Client */ - public void setLeapIndicator(int li); + String getModeName(); /** - * @return mode as defined in RFC-1305 + * @return the originate time as defined in RFC-1305 */ - public int getMode(); + TimeStamp getOriginateTimeStamp(); /** - * @return mode as human readable string; e.g. 3=Client + * @return poll interval as defined in RFC-1305. Field range between NTP_MINPOLL and NTP_MAXPOLL. */ - public String getModeName(); + int getPoll(); /** - * Set mode as defined in RFC-1305 - * @param mode the mode to set + * @return precision as defined in RFC-1305 */ - public void setMode(int mode); + int getPrecision(); /** - * @return poll interval as defined in RFC-1305. - * Field range between NTP_MINPOLL and NTP_MAXPOLL. + * @return the receive time as defined in RFC-1305 */ - public int getPoll(); + TimeStamp getReceiveTimeStamp(); /** - * Set poll interval as defined in RFC-1305. - * Field range between NTP_MINPOLL and NTP_MAXPOLL. - * @param poll the interval to set + * @return the reference id (32-bit code) as defined in RFC-1305 */ - public void setPoll(int poll); + int getReferenceId(); /** - * @return precision as defined in RFC-1305 + * @return the reference id string */ - public int getPrecision(); + String getReferenceIdString(); /** - * Set precision as defined in RFC-1305 - * @param precision Precision - * @since 3.4 + * @return the reference time as defined in RFC-1305 */ - void setPrecision(int precision); + TimeStamp getReferenceTimeStamp(); /** * @return root delay as defined in RFC-1305 */ - public int getRootDelay(); - - /** - * Set root delay as defined in RFC-1305 - * @param delay the delay to set - * @since 3.4 - */ - void setRootDelay(int delay); + int getRootDelay(); /** * @return root delay in milliseconds */ - public double getRootDelayInMillisDouble(); + double getRootDelayInMillisDouble(); /** * @return root dispersion as defined in RFC-1305 */ - public int getRootDispersion(); + int getRootDispersion(); /** - * - * @param dispersion the value to set - * @since 3.4 + * @return root dispersion in milliseconds */ - void setRootDispersion(int dispersion); + long getRootDispersionInMillis(); /** * @return root dispersion in milliseconds */ - public long getRootDispersionInMillis(); + double getRootDispersionInMillisDouble(); /** - * @return root dispersion in milliseconds + * @return stratum as defined in RFC-1305 */ - public double getRootDispersionInMillisDouble(); + int getStratum(); /** - * @return version as defined in RFC-1305 + * @return the transmit timestamp as defined in RFC-1305 */ - public int getVersion(); + TimeStamp getTransmitTimeStamp(); /** - * Set version as defined in RFC-1305 - * @param version the version to set + * Return type of time packet. The values (e.g. NTP, TIME, ICMP, ...) correspond to the protocol used to obtain the timing information. + * + * @return packet type string identifier */ - public void setVersion(int version); + String getType(); /** - * @return stratum as defined in RFC-1305 + * @return version as defined in RFC-1305 */ - public int getStratum(); + int getVersion(); /** - * Set stratum as defined in RFC-1305 - * @param stratum the stratum to set + * Set the contents of this object from the datagram packet + * + * @param dp the packet */ - public void setStratum(int stratum); + void setDatagramPacket(DatagramPacket dp); /** - * @return the reference id string + * Set leap indicator. + * + * @param li - leap indicator code */ - public String getReferenceIdString(); + void setLeapIndicator(int li); /** - * @return the reference id (32-bit code) as defined in RFC-1305 + * Set mode as defined in RFC-1305 + * + * @param mode the mode to set */ - public int getReferenceId(); + void setMode(int mode); /** - * Set reference clock identifier field. - * @param refId the clock id field to set + * Set originate timestamp given NTP TimeStamp object. + * + * @param ts - timestamp */ - public void setReferenceId(int refId); + void setOriginateTimeStamp(TimeStamp ts); /** - * @return the transmit timestamp as defined in RFC-1305 + * Set poll interval as defined in RFC-1305. Field range between NTP_MINPOLL and NTP_MAXPOLL. + * + * @param poll the interval to set */ - public TimeStamp getTransmitTimeStamp(); + void setPoll(int poll); /** - * @return the reference time as defined in RFC-1305 + * Set precision as defined in RFC-1305 + * + * @param precision Precision + * @since 3.4 */ - public TimeStamp getReferenceTimeStamp(); + void setPrecision(int precision); /** - * @return the originate time as defined in RFC-1305 + * Set receive timestamp given NTP TimeStamp object. + * + * @param ts - timestamp */ - public TimeStamp getOriginateTimeStamp(); + void setReceiveTimeStamp(TimeStamp ts); /** - * @return the receive time as defined in RFC-1305 + * Set reference clock identifier field. + * + * @param refId the clock id field to set */ - public TimeStamp getReceiveTimeStamp(); + void setReferenceId(int refId); /** - * Set the transmit timestamp given NTP TimeStamp object. + * Set the reference timestamp given NTP TimeStamp object. + * * @param ts - timestamp */ - public void setTransmitTime(TimeStamp ts); + void setReferenceTime(TimeStamp ts); /** - * Set the reference timestamp given NTP TimeStamp object. - * @param ts - timestamp + * Set root delay as defined in RFC-1305 + * + * @param delay the delay to set + * @since 3.4 */ - public void setReferenceTime(TimeStamp ts); + void setRootDelay(int delay); /** - * Set originate timestamp given NTP TimeStamp object. - * @param ts - timestamp + * + * @param dispersion the value to set + * @since 3.4 */ - public void setOriginateTimeStamp(TimeStamp ts); + void setRootDispersion(int dispersion); /** - * Set receive timestamp given NTP TimeStamp object. + * Set stratum as defined in RFC-1305 + * + * @param stratum the stratum to set + */ + void setStratum(int stratum); + + /** + * Set the transmit timestamp given NTP TimeStamp object. + * * @param ts - timestamp */ - public void setReceiveTimeStamp(TimeStamp ts); + void setTransmitTime(TimeStamp ts); /** - * Return type of time packet. The values (e.g. NTP, TIME, ICMP, ...) - * correspond to the protocol used to obtain the timing information. + * Set version as defined in RFC-1305 * - * @return packet type string identifier + * @param version the version to set */ - public String getType(); + void setVersion(int version); } diff --git a/src/main/java/org/apache/commons/net/ntp/TimeInfo.java b/src/main/java/org/apache/commons/net/ntp/TimeInfo.java index 1341275..57bfdef 100644 --- a/src/main/java/org/apache/commons/net/ntp/TimeInfo.java +++ b/src/main/java/org/apache/commons/net/ntp/TimeInfo.java @@ -1,4 +1,3 @@ -package org.apache.commons.net.ntp; /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -16,6 +15,7 @@ package org.apache.commons.net.ntp; * limitations under the License. */ +package org.apache.commons.net.ntp; import java.net.DatagramPacket; import java.net.InetAddress; @@ -23,325 +23,280 @@ import java.util.ArrayList; import java.util.List; /** - * Wrapper class to network time packet messages (NTP, etc) that computes - * related timing info and stats. - * - * @version $Revision: 1652863 $ + * Wrapper class to network time packet messages (NTP, etc) that computes related timing info and stats. */ public class TimeInfo { - private final NtpV3Packet _message; - private List<String> _comments; - private Long _delay; - private Long _offset; + private final NtpV3Packet message; + private List<String> comments; + private Long delayMillis; + private Long offsetMillis; /** * time at which time message packet was received by local machine */ - private final long _returnTime; + private final long returnTimeMillis; /** * flag indicating that the TimeInfo details was processed and delay/offset were computed */ - private boolean _detailsComputed; + private boolean detailsComputed; /** * Create TimeInfo object with raw packet message and destination time received. * - * @param message NTP message packet - * @param returnTime destination receive time + * @param message NTP message packet + * @param returnTimeMillis destination receive time * @throws IllegalArgumentException if message is null */ - public TimeInfo(NtpV3Packet message, long returnTime) { - this(message, returnTime, null, true); + public TimeInfo(final NtpV3Packet message, final long returnTimeMillis) { + this(message, returnTimeMillis, null, true); } /** - * Create TimeInfo object with raw packet message and destination time received. + * Create TimeInfo object with raw packet message and destination time received. Auto-computes details if computeDetails flag set otherwise this is delayed + * until computeDetails() is called. Delayed computation is for fast intialization when sub-millisecond timing is needed. * - * @param message NTP message packet - * @param returnTime destination receive time - * @param comments List of errors/warnings identified during processing + * @param msgPacket NTP message packet + * @param returnTimeMillis destination receive time + * @param doComputeDetails flag to pre-compute delay/offset values * @throws IllegalArgumentException if message is null */ - public TimeInfo(NtpV3Packet message, long returnTime, List<String> comments) - { - this(message, returnTime, comments, true); + public TimeInfo(final NtpV3Packet msgPacket, final long returnTimeMillis, final boolean doComputeDetails) { + this(msgPacket, returnTimeMillis, null, doComputeDetails); } /** * Create TimeInfo object with raw packet message and destination time received. - * Auto-computes details if computeDetails flag set otherwise this is delayed - * until computeDetails() is called. Delayed computation is for fast - * intialization when sub-millisecond timing is needed. * - * @param msgPacket NTP message packet - * @param returnTime destination receive time - * @param doComputeDetails flag to pre-compute delay/offset values + * @param message NTP message packet + * @param returnTimeMillis destination receive time + * @param comments List of errors/warnings identified during processing * @throws IllegalArgumentException if message is null */ - public TimeInfo(NtpV3Packet msgPacket, long returnTime, boolean doComputeDetails) - { - this(msgPacket, returnTime, null, doComputeDetails); + public TimeInfo(final NtpV3Packet message, final long returnTimeMillis, final List<String> comments) { + this(message, returnTimeMillis, comments, true); } /** - * Create TimeInfo object with raw packet message and destination time received. - * Auto-computes details if computeDetails flag set otherwise this is delayed - * until computeDetails() is called. Delayed computation is for fast - * intialization when sub-millisecond timing is needed. + * Create TimeInfo object with raw packet message and destination time received. Auto-computes details if computeDetails flag set otherwise this is delayed + * until computeDetails() is called. Delayed computation is for fast intialization when sub-millisecond timing is needed. * - * @param message NTP message packet - * @param returnTime destination receive time - * @param comments list of comments used to store errors/warnings with message - * @param doComputeDetails flag to pre-compute delay/offset values + * @param message NTP message packet + * @param returnTimeMillis destination receive time + * @param comments list of comments used to store errors/warnings with message + * @param doComputeDetails flag to pre-compute delay/offset values * @throws IllegalArgumentException if message is null */ - public TimeInfo(NtpV3Packet message, long returnTime, List<String> comments, - boolean doComputeDetails) - { + public TimeInfo(final NtpV3Packet message, final long returnTimeMillis, final List<String> comments, final boolean doComputeDetails) { if (message == null) { throw new IllegalArgumentException("message cannot be null"); } - this._returnTime = returnTime; - this._message = message; - this._comments = comments; + this.returnTimeMillis = returnTimeMillis; + this.message = message; + this.comments = comments; if (doComputeDetails) { computeDetails(); } } /** - * Add comment (error/warning) to list of comments associated - * with processing of NTP parameters. If comment list not create - * then one will be created. + * Add comment (error/warning) to list of comments associated with processing of NTP parameters. If comment list not create then one will be created. * * @param comment the comment */ - public void addComment(String comment) - { - if (_comments == null) { - _comments = new ArrayList<String>(); + public void addComment(final String comment) { + if (comments == null) { + comments = new ArrayList<>(); } - _comments.add(comment); + comments.add(comment); } /** - * Compute and validate details of the NTP message packet. Computed - * fields include the offset and delay. + * Compute and validate details of the NTP message packet. Computed fields include the offset and delay. */ - public void computeDetails() - { - if (_detailsComputed) { + public void computeDetails() { + if (detailsComputed) { return; // details already computed - do nothing } - _detailsComputed = true; - if (_comments == null) { - _comments = new ArrayList<String>(); + detailsComputed = true; + if (comments == null) { + comments = new ArrayList<>(); } - TimeStamp origNtpTime = _message.getOriginateTimeStamp(); - long origTime = origNtpTime.getTime(); + final TimeStamp origNtpTime = message.getOriginateTimeStamp(); + final long origTimeMillis = origNtpTime.getTime(); // Receive Time is time request received by server (t2) - TimeStamp rcvNtpTime = _message.getReceiveTimeStamp(); - long rcvTime = rcvNtpTime.getTime(); + final TimeStamp rcvNtpTime = message.getReceiveTimeStamp(); + final long rcvTimeMillis = rcvNtpTime.getTime(); // Transmit time is time reply sent by server (t3) - TimeStamp xmitNtpTime = _message.getTransmitTimeStamp(); - long xmitTime = xmitNtpTime.getTime(); + final TimeStamp xmitNtpTime = message.getTransmitTimeStamp(); + final long xmitTimeMillis = xmitNtpTime.getTime(); /* - * Round-trip network delay and local clock offset (or time drift) is calculated - * according to this standard NTP equation: + * Round-trip network delay and local clock offset (or time drift) is calculated according to this standard NTP equation: * - * LocalClockOffset = ((ReceiveTimestamp - OriginateTimestamp) + - * (TransmitTimestamp - DestinationTimestamp)) / 2 + * LocalClockOffset = ((ReceiveTimestamp - OriginateTimestamp) + (TransmitTimestamp - DestinationTimestamp)) / 2 * - * equations from RFC-1305 (NTPv3) - * roundtrip delay = (t4 - t1) - (t3 - t2) - * local clock offset = ((t2 - t1) + (t3 - t4)) / 2 + * equations from RFC-1305 (NTPv3) roundtrip delay = (t4 - t1) - (t3 - t2) local clock offset = ((t2 - t1) + (t3 - t4)) / 2 * * It takes into account network delays and assumes that they are symmetrical. * - * Note the typo in SNTP RFCs 1769/2030 which state that the delay - * is (T4 - T1) - (T2 - T3) with the "T2" and "T3" switched. + * Note the typo in SNTP RFCs 1769/2030 which state that the delay is (T4 - T1) - (T2 - T3) with the "T2" and "T3" switched. */ - if (origNtpTime.ntpValue() == 0) - { + if (origNtpTime.ntpValue() == 0) { // without originate time cannot determine when packet went out // might be via a broadcast NTP packet... - if (xmitNtpTime.ntpValue() != 0) - { - _offset = Long.valueOf(xmitTime - _returnTime); - _comments.add("Error: zero orig time -- cannot compute delay"); + if (xmitNtpTime.ntpValue() != 0) { + offsetMillis = Long.valueOf(xmitTimeMillis - returnTimeMillis); + comments.add("Error: zero orig time -- cannot compute delay"); } else { - _comments.add("Error: zero orig time -- cannot compute delay/offset"); + comments.add("Error: zero orig time -- cannot compute delay/offset"); } } else if (rcvNtpTime.ntpValue() == 0 || xmitNtpTime.ntpValue() == 0) { - _comments.add("Warning: zero rcvNtpTime or xmitNtpTime"); + comments.add("Warning: zero rcvNtpTime or xmitNtpTime"); // assert destTime >= origTime since network delay cannot be negative - if (origTime > _returnTime) { - _comments.add("Error: OrigTime > DestRcvTime"); + if (origTimeMillis > returnTimeMillis) { + comments.add("Error: OrigTime > DestRcvTime"); } else { // without receive or xmit time cannot figure out processing time // so delay is simply the network travel time - _delay = Long.valueOf(_returnTime - origTime); + delayMillis = Long.valueOf(returnTimeMillis - origTimeMillis); } // TODO: is offset still valid if rcvNtpTime=0 || xmitNtpTime=0 ??? // Could always hash origNtpTime (sendTime) but if host doesn't set it // then it's an malformed ntp host anyway and we don't care? // If server is in broadcast mode then we never send out a query in first place... - if (rcvNtpTime.ntpValue() != 0) - { + if (rcvNtpTime.ntpValue() != 0) { // xmitTime is 0 just use rcv time - _offset = Long.valueOf(rcvTime - origTime); - } else if (xmitNtpTime.ntpValue() != 0) - { + offsetMillis = Long.valueOf(rcvTimeMillis - origTimeMillis); + } else if (xmitNtpTime.ntpValue() != 0) { // rcvTime is 0 just use xmitTime time - _offset = Long.valueOf(xmitTime - _returnTime); + offsetMillis = Long.valueOf(xmitTimeMillis - returnTimeMillis); } - } else - { - long delayValue = _returnTime - origTime; - // assert xmitTime >= rcvTime: difference typically < 1ms - if (xmitTime < rcvTime) - { - // server cannot send out a packet before receiving it... - _comments.add("Error: xmitTime < rcvTime"); // time-travel not allowed - } else - { - // subtract processing time from round-trip network delay - long delta = xmitTime - rcvTime; - // in normal cases the processing delta is less than - // the total roundtrip network travel time. - if (delta <= delayValue) - { - delayValue -= delta; // delay = (t4 - t1) - (t3 - t2) - } else - { - // if delta - delayValue == 1 ms then it's a round-off error - // e.g. delay=3ms, processing=4ms - if (delta - delayValue == 1) - { - // delayValue == 0 -> local clock saw no tick change but destination clock did - if (delayValue != 0) - { - _comments.add("Info: processing time > total network time by 1 ms -> assume zero delay"); - delayValue = 0; - } - } else { - _comments.add("Warning: processing time > total network time"); + } else { + long delayValueMillis = returnTimeMillis - origTimeMillis; + // assert xmitTime >= rcvTime: difference typically < 1ms + if (xmitTimeMillis < rcvTimeMillis) { + // server cannot send out a packet before receiving it... + comments.add("Error: xmitTime < rcvTime"); // time-travel not allowed + } else { + // subtract processing time from round-trip network delay + final long deltaMillis = xmitTimeMillis - rcvTimeMillis; + // in normal cases the processing delta is less than + // the total roundtrip network travel time. + if (deltaMillis <= delayValueMillis) { + delayValueMillis -= deltaMillis; // delay = (t4 - t1) - (t3 - t2) + } else // if delta - delayValue == 1 ms then it's a round-off error + // e.g. delay=3ms, processing=4ms + if (deltaMillis - delayValueMillis == 1) { + // delayValue == 0 -> local clock saw no tick change but destination clock did + if (delayValueMillis != 0) { + comments.add("Info: processing time > total network time by 1 ms -> assume zero delay"); + delayValueMillis = 0; } - } - } - _delay = Long.valueOf(delayValue); - if (origTime > _returnTime) { - _comments.add("Error: OrigTime > DestRcvTime"); + } else { + comments.add("Warning: processing time > total network time"); + } + } + delayMillis = Long.valueOf(delayValueMillis); + if (origTimeMillis > returnTimeMillis) { + comments.add("Error: OrigTime > DestRcvTime"); } - _offset = Long.valueOf(((rcvTime - origTime) + (xmitTime - _returnTime)) / 2); + offsetMillis = Long.valueOf(((rcvTimeMillis - origTimeMillis) + (xmitTimeMillis - returnTimeMillis)) / 2); } } /** - * Return list of comments (if any) during processing of NTP packet. + * Compares this object against the specified object. The result is <code>true</code> if and only if the argument is not <code>null</code> and is a + * <code>TimeStamp</code> object that contains the same values as this object. * - * @return List or null if not yet computed + * @param obj the object to compare with. + * @return <code>true</code> if the objects are the same; <code>false</code> otherwise. + * @since 3.4 */ - public List<String> getComments() - { - return _comments; + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final TimeInfo other = (TimeInfo) obj; + return returnTimeMillis == other.returnTimeMillis && message.equals(other.message); } /** - * Get round-trip network delay. If null then could not compute the delay. + * Get host address from message datagram if available * - * @return Long or null if delay not available. + * @return host address of available otherwise null + * @since 3.4 */ - public Long getDelay() - { - return _delay; + public InetAddress getAddress() { + final DatagramPacket pkt = message.getDatagramPacket(); + return pkt == null ? null : pkt.getAddress(); } /** - * Get clock offset needed to adjust local clock to match remote clock. If null then could not - * compute the offset. + * Return list of comments (if any) during processing of NTP packet. * - * @return Long or null if offset not available. + * @return List or null if not yet computed */ - public Long getOffset() - { - return _offset; + public List<String> getComments() { + return comments; } /** - * Returns NTP message packet. + * Get round-trip network delay. If null then could not compute the delay. * - * @return NTP message packet. + * @return Long or null if delay not available. */ - public NtpV3Packet getMessage() - { - return _message; + public Long getDelay() { + return delayMillis; } /** - * Get host address from message datagram if available - * @return host address of available otherwise null - * @since 3.4 + * Returns NTP message packet. + * + * @return NTP message packet. */ - public InetAddress getAddress() { - DatagramPacket pkt = _message.getDatagramPacket(); - return pkt == null ? null : pkt.getAddress(); + public NtpV3Packet getMessage() { + return message; } /** - * Returns time at which time message packet was received by local machine. + * Get clock offset needed to adjust local clock to match remote clock. If null then could not compute the offset. * - * @return packet return time. + * @return Long or null if offset not available. */ - public long getReturnTime() - { - return _returnTime; + public Long getOffset() { + return offsetMillis; } /** - * Compares this object against the specified object. - * The result is <code>true</code> if and only if the argument is - * not <code>null</code> and is a <code>TimeStamp</code> object that - * contains the same values as this object. + * Returns time at which time message packet was received by local machine. * - * @param obj the object to compare with. - * @return <code>true</code> if the objects are the same; - * <code>false</code> otherwise. - * @since 3.4 + * @return packet return time. */ - @Override - public boolean equals(Object obj) - { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - TimeInfo other = (TimeInfo) obj; - return _returnTime == other._returnTime && _message.equals(other._message); + public long getReturnTime() { + return returnTimeMillis; } /** - * Computes a hashcode for this object. The result is the exclusive - * OR of the return time and the message hash code. + * Computes a hashcode for this object. The result is the exclusive OR of the return time and the message hash code. * - * @return a hash code value for this object. + * @return a hash code value for this object. * @since 3.4 */ @Override - public int hashCode() - { + public int hashCode() { final int prime = 31; - int result = (int)_returnTime; - result = prime * result + _message.hashCode(); + int result = (int) returnTimeMillis; + result = prime * result + message.hashCode(); return result; } diff --git a/src/main/java/org/apache/commons/net/ntp/TimeStamp.java b/src/main/java/org/apache/commons/net/ntp/TimeStamp.java index 63710e4..7c02487 100644 --- a/src/main/java/org/apache/commons/net/ntp/TimeStamp.java +++ b/src/main/java/org/apache/commons/net/ntp/TimeStamp.java @@ -1,4 +1,3 @@ -package org.apache.commons.net.ntp; /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -16,440 +15,394 @@ package org.apache.commons.net.ntp; * limitations under the License. */ +package org.apache.commons.net.ntp; - +import java.io.Serializable; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.TimeZone; -/*** - * TimeStamp class represents the Network Time Protocol (NTP) timestamp - * as defined in RFC-1305 and SNTP (RFC-2030). It is represented as a - * 64-bit unsigned fixed-point number in seconds relative to 0-hour on 1-January-1900. - * The 32-bit low-order bits are the fractional seconds whose precision is - * about 200 picoseconds. Assumes overflow date when date passes MAX_LONG - * and reverts back to 0 is 2036 and not 1900. Test for most significant - * bit: if MSB=0 then 2036 basis is used otherwise 1900 if MSB=1. +/** + * TimeStamp class represents the Network Time Protocol (NTP) timestamp as defined in RFC-1305 and SNTP (RFC-2030). It is represented as a 64-bit unsigned + * fixed-point number in seconds relative to 0-hour on 1-January-1900. The 32-bit low-order bits are the fractional seconds whose precision is about 200 + * picoseconds. Assumes overflow date when date passes MAX_LONG and reverts back to 0 is 2036 and not 1900. Test for most significant bit: if MSB=0 then 2036 + * basis is used otherwise 1900 if MSB=1. * <p> - * Methods exist to convert NTP timestamps to and from the equivalent Java date - * representation, which is the number of milliseconds since the standard base - * time known as "the epoch", namely January 1, 1970, 00:00:00 GMT. + * Methods exist to convert NTP timestamps to and from the equivalent Java date representation, which is the number of milliseconds since the standard base time + * known as "the epoch", namely January 1, 1970, 00:00:00 GMT. * </p> * - * @version $Revision: 1741829 $ * @see java.util.Date */ -public class TimeStamp implements java.io.Serializable, Comparable<TimeStamp> -{ +public class TimeStamp implements Serializable, Comparable<TimeStamp> { private static final long serialVersionUID = 8139806907588338737L; /** - * baseline NTP time if bit-0=0 is 7-Feb-2036 @ 06:28:16 UTC + * Baseline NTP time if bit-0=0 is 7-Feb-2036 @ 06:28:16 UTC */ protected static final long msb0baseTime = 2085978496000L; /** - * baseline NTP time if bit-0=1 is 1-Jan-1900 @ 01:00:00 UTC + * Baseline NTP time if bit-0=1 is 1-Jan-1900 @ 01:00:00 UTC */ protected static final long msb1baseTime = -2208988800000L; /** - * Default NTP date string format. E.g. Fri, Sep 12 2003 21:06:23.860. - * See <code>java.text.SimpleDateFormat</code> for code descriptions. + * Default NTP date string format. E.g. Fri, Sep 12 2003 21:06:23.860. See <code>java.text.SimpleDateFormat</code> for code descriptions. */ public static final String NTP_DATE_FORMAT = "EEE, MMM dd yyyy HH:mm:ss.SSS"; /** - * NTP timestamp value: 64-bit unsigned fixed-point number as defined in RFC-1305 - * with high-order 32 bits the seconds field and the low-order 32-bits the - * fractional field. - */ - private final long ntpTime; - - private DateFormat simpleFormatter; - private DateFormat utcFormatter; - - // initialization of static time bases - /* - static { - TimeZone utcZone = TimeZone.getTimeZone("UTC"); - Calendar calendar = Calendar.getInstance(utcZone); - calendar.set(1900, Calendar.JANUARY, 1, 0, 0, 0); - calendar.set(Calendar.MILLISECOND, 0); - msb1baseTime = calendar.getTime().getTime(); - calendar.set(2036, Calendar.FEBRUARY, 7, 6, 28, 16); - calendar.set(Calendar.MILLISECOND, 0); - msb0baseTime = calendar.getTime().getTime(); - } - */ - - /*** - * Constructs a newly allocated NTP timestamp object - * that represents the native 64-bit long argument. - * @param ntpTime the timestamp - */ - public TimeStamp(long ntpTime) - { - this.ntpTime = ntpTime; - } - - /*** - * Constructs a newly allocated NTP timestamp object - * that represents the value represented by the string - * in hexdecimal form (e.g. "c1a089bd.fc904f6d"). - * @param hexStamp the hex timestamp + * Left-pad 8-character hex string with 0's * - * @throws NumberFormatException - if the string does not contain a parsable timestamp. + * @param buf - StringBuilder which is appended with leading 0's. + * @param l - a long. */ - public TimeStamp(String hexStamp) throws NumberFormatException - { - ntpTime = decodeNtpHexString(hexStamp); + private static void appendHexString(final StringBuilder buf, final long l) { + final String s = Long.toHexString(l); + for (int i = s.length(); i < 8; i++) { + buf.append('0'); + } + buf.append(s); } - /*** - * Constructs a newly allocated NTP timestamp object - * that represents the Java Date argument. + /** + * Convert NTP timestamp hexstring (e.g. "c1a089bd.fc904f6d") to the NTP 64-bit unsigned fixed-point number. * - * @param d - the Date to be represented by the Timestamp object. - */ - public TimeStamp(Date d) - { - ntpTime = (d == null) ? 0 : toNtpTime(d.getTime()); - } - - /*** - * Returns the value of this Timestamp as a long value. + * @param hexString the string to convert * - * @return the 64-bit long value represented by this object. + * @return NTP 64-bit timestamp value. + * @throws NumberFormatException - if the string does not contain a parsable timestamp. */ - public long ntpValue() - { - return ntpTime; - } + protected static long decodeNtpHexString(final String hexString) throws NumberFormatException { + if (hexString == null) { + throw new NumberFormatException("null"); + } + final int ind = hexString.indexOf('.'); + if (ind == -1) { + if (hexString.isEmpty()) { + return 0; + } + return Long.parseLong(hexString, 16) << 32; // no decimal + } - /*** - * Returns high-order 32-bits representing the seconds of this NTP timestamp. - * - * @return seconds represented by this NTP timestamp. - */ - public long getSeconds() - { - return (ntpTime >>> 32) & 0xffffffffL; + return Long.parseLong(hexString.substring(0, ind), 16) << 32 | Long.parseLong(hexString.substring(ind + 1), 16); } - /*** - * Returns low-order 32-bits representing the fractional seconds. + /** + * Constructs a NTP timestamp object and initializes it so that it represents the time at which it was allocated, measured to the nearest millisecond. * - * @return fractional seconds represented by this NTP timestamp. + * @return NTP timestamp object set to the current time. + * @see System#currentTimeMillis() */ - public long getFraction() - { - return ntpTime & 0xffffffffL; + public static TimeStamp getCurrentTime() { + return getNtpTime(System.currentTimeMillis()); } - /*** - * Convert NTP timestamp to Java standard time. - * - * @return NTP Timestamp in Java time + // initialization of static time bases + /* + * static { TimeZone utcZone = TimeZone.getTimeZone("UTC"); Calendar calendar = Calendar.getInstance(utcZone); calendar.set(1900, Calendar.JANUARY, 1, 0, 0, + * 0); calendar.set(Calendar.MILLISECOND, 0); msb1baseTime = calendar.getTime().getTime(); calendar.set(2036, Calendar.FEBRUARY, 7, 6, 28, 16); + * calendar.set(Calendar.MILLISECOND, 0); msb0baseTime = calendar.getTime().getTime(); } */ - public long getTime() - { - return getTime(ntpTime); - } - /*** - * Convert NTP timestamp to Java Date object. + /** + * Helper method to convert Java time to NTP timestamp object. Note that Java time (milliseconds) by definition has less precision then NTP time + * (picoseconds) so converting Ntptime to Javatime and back to Ntptime loses precision. For example, Tue, Dec 17 2002 09:07:24.810 is represented by a + * single Java-based time value of f22cd1fc8a, but its NTP equivalent are all values from c1a9ae1c.cf5c28f5 to c1a9ae1c.cf9db22c. * - * @return NTP Timestamp in Java Date + * @param dateMillis the milliseconds since January 1, 1970, 00:00:00 GMT. + * @return NTP timestamp object at the specified date. */ - public Date getDate() - { - long time = getTime(ntpTime); - return new Date(time); + public static TimeStamp getNtpTime(final long dateMillis) { + return new TimeStamp(toNtpTime(dateMillis)); } - /*** - * Convert 64-bit NTP timestamp to Java standard time. + /** + * Converts 64-bit NTP timestamp to Java standard time. * - * Note that java time (milliseconds) by definition has less precision - * then NTP time (picoseconds) so converting NTP timestamp to java time and back - * to NTP timestamp loses precision. For example, Tue, Dec 17 2002 09:07:24.810 EST - * is represented by a single Java-based time value of f22cd1fc8a, but its - * NTP equivalent are all values ranging from c1a9ae1c.cf5c28f5 to c1a9ae1c.cf9db22c. + * Note that java time (milliseconds) by definition has less precision then NTP time (picoseconds) so converting NTP timestamp to java time and back to NTP + * timestamp loses precision. For example, Tue, Dec 17 2002 09:07:24.810 EST is represented by a single Java-based time value of f22cd1fc8a, but its NTP + * equivalent are all values ranging from c1a9ae1c.cf5c28f5 to c1a9ae1c.cf9db22c. * * @param ntpTimeValue the input time - * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT - * represented by this NTP timestamp value. + * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT represented by this NTP timestamp value. */ - public static long getTime(long ntpTimeValue) - { - long seconds = (ntpTimeValue >>> 32) & 0xffffffffL; // high-order 32-bits - long fraction = ntpTimeValue & 0xffffffffL; // low-order 32-bits + public static long getTime(final long ntpTimeValue) { + final long seconds = (ntpTimeValue >>> 32) & 0xffffffffL; // high-order 32-bits + long fraction = ntpTimeValue & 0xffffffffL; // low-order 32-bits // Use round-off on fractional part to preserve going to lower precision fraction = Math.round(1000D * fraction / 0x100000000L); /* - * If the most significant bit (MSB) on the seconds field is set we use - * a different time base. The following text is a quote from RFC-2030 (SNTP v4): + * If the most significant bit (MSB) on the seconds field is set we use a different time base. The following text is a quote from RFC-2030 (SNTP v4): * - * If bit 0 is set, the UTC time is in the range 1968-2036 and UTC time - * is reckoned from 0h 0m 0s UTC on 1 January 1900. If bit 0 is not set, - * the time is in the range 2036-2104 and UTC time is reckoned from - * 6h 28m 16s UTC on 7 February 2036. + * If bit 0 is set, the UTC time is in the range 1968-2036 and UTC time is reckoned from 0h 0m 0s UTC on 1 January 1900. If bit 0 is not set, the time + * is in the range 2036-2104 and UTC time is reckoned from 6h 28m 16s UTC on 7 February 2036. */ - long msb = seconds & 0x80000000L; + final long msb = seconds & 0x80000000L; if (msb == 0) { // use base: 7-Feb-2036 @ 06:28:16 UTC return msb0baseTime + (seconds * 1000) + fraction; - } else { - // use base: 1-Jan-1900 @ 01:00:00 UTC - return msb1baseTime + (seconds * 1000) + fraction; } + // use base: 1-Jan-1900 @ 01:00:00 UTC + return msb1baseTime + (seconds * 1000) + fraction; } - /*** - * Helper method to convert Java time to NTP timestamp object. - * Note that Java time (milliseconds) by definition has less precision - * then NTP time (picoseconds) so converting Ntptime to Javatime and back - * to Ntptime loses precision. For example, Tue, Dec 17 2002 09:07:24.810 - * is represented by a single Java-based time value of f22cd1fc8a, but its - * NTP equivalent are all values from c1a9ae1c.cf5c28f5 to c1a9ae1c.cf9db22c. - * @param date the milliseconds since January 1, 1970, 00:00:00 GMT. - * @return NTP timestamp object at the specified date. - */ - public static TimeStamp getNtpTime(long date) - { - return new TimeStamp(toNtpTime(date)); - } - - /*** - * Constructs a NTP timestamp object and initializes it so that - * it represents the time at which it was allocated, measured to the - * nearest millisecond. - * @return NTP timestamp object set to the current time. - * @see java.lang.System#currentTimeMillis() - */ - public static TimeStamp getCurrentTime() - { - return getNtpTime(System.currentTimeMillis()); - } - - /*** - * Convert NTP timestamp hexstring (e.g. "c1a089bd.fc904f6d") to the NTP - * 64-bit unsigned fixed-point number. - * @param hexString the string to convert - * - * @return NTP 64-bit timestamp value. - * @throws NumberFormatException - if the string does not contain a parsable timestamp. - */ - protected static long decodeNtpHexString(String hexString) - throws NumberFormatException - { - if (hexString == null) { - throw new NumberFormatException("null"); - } - int ind = hexString.indexOf('.'); - if (ind == -1) { - if (hexString.length() == 0) { - return 0; - } - return Long.parseLong(hexString, 16) << 32; // no decimal - } - - return Long.parseLong(hexString.substring(0, ind), 16) << 32 | - Long.parseLong(hexString.substring(ind + 1), 16); - } - - /*** - * Parses the string argument as a NTP hexidecimal timestamp representation string - * (e.g. "c1a089bd.fc904f6d"). + /** + * Parses the string argument as a NTP hexidecimal timestamp representation string (e.g. "c1a089bd.fc904f6d"). * * @param s - hexstring. * @return the Timestamp represented by the argument in hexidecimal. * @throws NumberFormatException - if the string does not contain a parsable timestamp. */ - public static TimeStamp parseNtpString(String s) - throws NumberFormatException - { + public static TimeStamp parseNtpString(final String s) throws NumberFormatException { return new TimeStamp(decodeNtpHexString(s)); } - /*** + /** * Converts Java time to 64-bit NTP time representation. * - * @param t Java time + * @param millis Java time * @return NTP timestamp representation of Java time value. */ - protected static long toNtpTime(long t) - { - boolean useBase1 = t < msb0baseTime; // time < Feb-2036 - long baseTime; + protected static long toNtpTime(final long millis) { + final boolean useBase1 = millis < msb0baseTime; // time < Feb-2036 + final long baseTimeMillis; if (useBase1) { - baseTime = t - msb1baseTime; // dates <= Feb-2036 + baseTimeMillis = millis - msb1baseTime; // dates <= Feb-2036 } else { // if base0 needed for dates >= Feb-2036 - baseTime = t - msb0baseTime; + baseTimeMillis = millis - msb0baseTime; } - long seconds = baseTime / 1000; - long fraction = ((baseTime % 1000) * 0x100000000L) / 1000; + long seconds = baseTimeMillis / 1000; + final long fraction = ((baseTimeMillis % 1000) * 0x100000000L) / 1000; if (useBase1) { seconds |= 0x80000000L; // set high-order bit if msb1baseTime 1900 used } - long time = seconds << 32 | fraction; - return time; + return seconds << 32 | fraction; + } + + /** + * Converts 64-bit NTP timestamp value to a <code>String</code>. The NTP timestamp value is represented as hex string with seconds separated by fractional + * seconds by a decimal point; e.g. c1a089bd.fc904f6d == Tue, Dec 10 2002 10:41:49.986 + * + * @param ntpTime the 64 bit timestamp + * + * @return NTP timestamp 64-bit long value as hex string with seconds separated by fractional seconds. + */ + public static String toString(final long ntpTime) { + final StringBuilder buf = new StringBuilder(); + // high-order second bits (32..63) as hexstring + appendHexString(buf, (ntpTime >>> 32) & 0xffffffffL); + + // low-order fractional seconds bits (0..31) as hexstring + buf.append('.'); + appendHexString(buf, ntpTime & 0xffffffffL); + + return buf.toString(); + } + + /** + * NTP timestamp value: 64-bit unsigned fixed-point number as defined in RFC-1305 with high-order 32 bits the seconds field and the low-order 32-bits the + * fractional field. + */ + private final long ntpTime; + + private DateFormat simpleFormatter; + + private DateFormat utcFormatter; + + /** + * Constructs a newly allocated NTP timestamp object that represents the Java Date argument. + * + * @param d - the Date to be represented by the Timestamp object. + */ + public TimeStamp(final Date d) { + ntpTime = d == null ? 0 : toNtpTime(d.getTime()); + } + + /** + * Constructs a newly allocated NTP timestamp object that represents the native 64-bit long argument. + * + * @param ntpTime the timestamp + */ + public TimeStamp(final long ntpTime) { + this.ntpTime = ntpTime; + } + + /** + * Constructs a newly allocated NTP timestamp object that represents the value represented by the string in hexdecimal form (e.g. "c1a089bd.fc904f6d"). + * + * @param hexStamp the hex timestamp + * + * @throws NumberFormatException - if the string does not contain a parsable timestamp. + */ + public TimeStamp(final String hexStamp) throws NumberFormatException { + ntpTime = decodeNtpHexString(hexStamp); } - /*** - * Computes a hashcode for this Timestamp. The result is the exclusive - * OR of the two halves of the primitive <code>long</code> value - * represented by this <code>TimeStamp</code> object. That is, the hashcode - * is the value of the expression: - * <blockquote><pre> - * {@code (int)(this.ntpValue()^(this.ntpValue() >>> 32))} - * </pre></blockquote> + /** + * Compares two Timestamps numerically. * - * @return a hash code value for this object. + * @param anotherTimeStamp - the <code>TimeStamp</code> to be compared. + * @return the value <code>0</code> if the argument TimeStamp is equal to this TimeStamp; a value less than <code>0</code> if this TimeStamp is numerically + * less than the TimeStamp argument; and a value greater than <code>0</code> if this TimeStamp is numerically greater than the TimeStamp argument + * (signed comparison). */ @Override - public int hashCode() - { - return (int) (ntpTime ^ (ntpTime >>> 32)); + public int compareTo(final TimeStamp anotherTimeStamp) { + final long thisVal = this.ntpTime; + final long anotherVal = anotherTimeStamp.ntpTime; + return (Long.compare(thisVal, anotherVal)); } - /*** - * Compares this object against the specified object. - * The result is <code>true</code> if and only if the argument is - * not <code>null</code> and is a <code>Long</code> object that - * contains the same <code>long</code> value as this object. + /** + * Compares this object against the specified object. The result is <code>true</code> if and only if the argument is not <code>null</code> and is a + * <code>Long</code> object that contains the same <code>long</code> value as this object. * - * @param obj the object to compare with. - * @return <code>true</code> if the objects are the same; - * <code>false</code> otherwise. + * @param obj the object to compare with. + * @return <code>true</code> if the objects are the same; <code>false</code> otherwise. */ @Override - public boolean equals(Object obj) - { + public boolean equals(final Object obj) { if (obj instanceof TimeStamp) { return ntpTime == ((TimeStamp) obj).ntpValue(); } return false; } - /*** - * Converts this <code>TimeStamp</code> object to a <code>String</code>. - * The NTP timestamp 64-bit long value is represented as hex string with - * seconds separated by fractional seconds by a decimal point; - * e.g. c1a089bd.fc904f6d == Tue, Dec 10 2002 10:41:49.986 + /** + * Converts NTP timestamp to Java Date object. * - * @return NTP timestamp 64-bit long value as hex string with seconds - * separated by fractional seconds. + * @return NTP Timestamp in Java Date */ - @Override - public String toString() - { - return toString(ntpTime); + public Date getDate() { + return new Date(getTime(ntpTime)); } - /*** - * Left-pad 8-character hex string with 0's + /** + * Returns low-order 32-bits representing the fractional seconds. * - * @param buf - StringBuilder which is appended with leading 0's. - * @param l - a long. + * @return fractional seconds represented by this NTP timestamp. */ - private static void appendHexString(StringBuilder buf, long l) - { - String s = Long.toHexString(l); - for (int i = s.length(); i < 8; i++) { - buf.append('0'); - } - buf.append(s); + public long getFraction() { + return ntpTime & 0xffffffffL; } - /*** - * Converts 64-bit NTP timestamp value to a <code>String</code>. - * The NTP timestamp value is represented as hex string with - * seconds separated by fractional seconds by a decimal point; - * e.g. c1a089bd.fc904f6d == Tue, Dec 10 2002 10:41:49.986 - * @param ntpTime the 64 bit timestamp + /** + * Returns high-order 32-bits representing the seconds of this NTP timestamp. * - * @return NTP timestamp 64-bit long value as hex string with seconds - * separated by fractional seconds. + * @return seconds represented by this NTP timestamp. */ - public static String toString(long ntpTime) - { - StringBuilder buf = new StringBuilder(); - // high-order second bits (32..63) as hexstring - appendHexString(buf, (ntpTime >>> 32) & 0xffffffffL); + public long getSeconds() { + return (ntpTime >>> 32) & 0xffffffffL; + } - // low-order fractional seconds bits (0..31) as hexstring - buf.append('.'); - appendHexString(buf, ntpTime & 0xffffffffL); + /** + * Converts NTP timestamp to Java standard time. + * + * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT represented by this NTP timestamp value. + */ + public long getTime() { + return getTime(ntpTime); + } - return buf.toString(); + /** + * Computes a hashcode for this Timestamp. The result is the exclusive OR of the two halves of the primitive <code>long</code> value represented by this + * <code>TimeStamp</code> object. That is, the hashcode is the value of the expression: <blockquote> + * + * <pre> + * {@code + * (int) (this.ntpValue() ^ (this.ntpValue() >>> 32)) + * } + * </pre> + * + * </blockquote> + * + * @return a hash code value for this object. + */ + @Override + public int hashCode() { + return (int) (ntpTime ^ (ntpTime >>> 32)); } - /*** - * Converts this <code>TimeStamp</code> object to a <code>String</code> - * of the form: - * <blockquote><pre> - * EEE, MMM dd yyyy HH:mm:ss.SSS</pre></blockquote> - * See java.text.SimpleDataFormat for code descriptions. + /** + * Returns the value of this Timestamp as a long value. * - * @return a string representation of this date. + * @return the 64-bit long value represented by this object. */ - public String toDateString() - { + public long ntpValue() { + return ntpTime; + } + + private void readObject(final java.io.ObjectInputStream in) { + throw new UnsupportedOperationException("Serialization is not supported"); + } + + /** + * Converts this <code>TimeStamp</code> object to a <code>String</code> of the form: <blockquote> + * + * <pre> + * EEE, MMM dd yyyy HH:mm:ss.SSS + * </pre> + * + * </blockquote> See java.text.SimpleDataFormat for code descriptions. + * + * @return a string representation of this date. + */ + public String toDateString() { if (simpleFormatter == null) { simpleFormatter = new SimpleDateFormat(NTP_DATE_FORMAT, Locale.US); simpleFormatter.setTimeZone(TimeZone.getDefault()); } - Date ntpDate = getDate(); + final Date ntpDate = getDate(); return simpleFormatter.format(ntpDate); } - /*** - * Converts this <code>TimeStamp</code> object to a <code>String</code> - * of the form: - * <blockquote><pre> - * EEE, MMM dd yyyy HH:mm:ss.SSS UTC</pre></blockquote> - * See java.text.SimpleDataFormat for code descriptions. + /** + * Converts this <code>TimeStamp</code> object to a <code>String</code>. The NTP timestamp 64-bit long value is represented as hex string with seconds + * separated by fractional seconds by a decimal point; e.g. c1a089bd.fc904f6d == Tue, Dec 10 2002 10:41:49.986 + * + * @return NTP timestamp 64-bit long value as hex string with seconds separated by fractional seconds. + */ + @Override + public String toString() { + return toString(ntpTime); + } + + /* + * Serialization is unnecessary for this class. Reject attempts to do so until such time as the Serializable attribute can be dropped. + */ + + /** + * Converts this <code>TimeStamp</code> object to a <code>String</code> of the form: <blockquote> * - * @return a string representation of this date in UTC. + * <pre> + * EEE, MMM dd yyyy HH:mm:ss.SSS UTC + * </pre> + * + * </blockquote> See java.text.SimpleDataFormat for code descriptions. + * + * @return a string representation of this date in UTC. */ - public String toUTCString() - { + public String toUTCString() { if (utcFormatter == null) { - utcFormatter = new SimpleDateFormat(NTP_DATE_FORMAT + " 'UTC'", - Locale.US); + utcFormatter = new SimpleDateFormat(NTP_DATE_FORMAT + " 'UTC'", Locale.US); utcFormatter.setTimeZone(TimeZone.getTimeZone("UTC")); } - Date ntpDate = getDate(); + final Date ntpDate = getDate(); return utcFormatter.format(ntpDate); } - /*** - * Compares two Timestamps numerically. - * - * @param anotherTimeStamp - the <code>TimeStamp</code> to be compared. - * @return the value <code>0</code> if the argument TimeStamp is equal to - * this TimeStamp; a value less than <code>0</code> if this TimeStamp - * is numerically less than the TimeStamp argument; and a - * value greater than <code>0</code> if this TimeStamp is - * numerically greater than the TimeStamp argument - * (signed comparison). - */ - @Override - public int compareTo(TimeStamp anotherTimeStamp) - { - long thisVal = this.ntpTime; - long anotherVal = anotherTimeStamp.ntpTime; - return (thisVal < anotherVal ? -1 : (thisVal == anotherVal ? 0 : 1)); + private void writeObject(final java.io.ObjectOutputStream out) { + throw new UnsupportedOperationException("Serialization is not supported"); } } diff --git a/src/main/java/org/apache/commons/net/pop3/ExtendedPOP3Client.java b/src/main/java/org/apache/commons/net/pop3/ExtendedPOP3Client.java index a806c11..33817f2 100644 --- a/src/main/java/org/apache/commons/net/pop3/ExtendedPOP3Client.java +++ b/src/main/java/org/apache/commons/net/pop3/ExtendedPOP3Client.java @@ -21,129 +21,112 @@ import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; + import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.net.util.Base64; - /** - * A POP3 Cilent class with protocol and authentication extensions support - * (RFC2449 and RFC2195). + * A POP3 Cilent class with protocol and authentication extensions support (RFC2449 and RFC2195). + * * @see POP3Client * @since 3.0 */ -public class ExtendedPOP3Client extends POP3SClient -{ +public class ExtendedPOP3Client extends POP3SClient { + /** + * The enumeration of currently-supported authentication methods. + */ + public enum AUTH_METHOD { + /** The standarised (RFC4616) PLAIN method, which sends the password unencrypted (insecure). */ + PLAIN("PLAIN"), + + /** The standarised (RFC2195) CRAM-MD5 method, which doesn't send the password (secure). */ + CRAM_MD5("CRAM-MD5"); + + private final String methodName; + + AUTH_METHOD(final String methodName) { + this.methodName = methodName; + } + + /** + * Gets the name of the given authentication method suitable for the server. + * + * @return The name of the given authentication method suitable for the server. + */ + public final String getAuthName() { + return this.methodName; + } + } + /** - * The default ExtendedPOP3Client constructor. - * Creates a new Extended POP3 Client. + * The default ExtendedPOP3Client constructor. Creates a new Extended POP3 Client. + * * @throws NoSuchAlgorithmException on error */ - public ExtendedPOP3Client() throws NoSuchAlgorithmException - { - super(); + public ExtendedPOP3Client() throws NoSuchAlgorithmException { } - /*** - * Authenticate to the POP3 server by sending the AUTH command with the - * selected mechanism, using the given username and the given password. + /** + * Authenticate to the POP3 server by sending the AUTH command with the selected mechanism, using the given username and the given password. * <p> - * @param method the {@link AUTH_METHOD} to use + * + * @param method the {@link AUTH_METHOD} to use * @param username the user name * @param password the password * @return True if successfully completed, false if not. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @throws NoSuchAlgorithmException If the CRAM hash algorithm - * cannot be instantiated by the Java runtime system. - * @throws InvalidKeyException If the CRAM hash algorithm - * failed to use the given password. - * @throws InvalidKeySpecException If the CRAM hash algorithm - * failed to use the given password. - ***/ - public boolean auth(AUTH_METHOD method, - String username, String password) - throws IOException, NoSuchAlgorithmException, - InvalidKeyException, InvalidKeySpecException - { - if (sendCommand(POP3Command.AUTH, method.getAuthName()) - != POP3Reply.OK_INT) { + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @throws NoSuchAlgorithmException If the CRAM hash algorithm cannot be instantiated by the Java runtime system. + * @throws InvalidKeyException If the CRAM hash algorithm failed to use the given password. + * @throws InvalidKeySpecException If the CRAM hash algorithm failed to use the given password. + */ + public boolean auth(final AUTH_METHOD method, final String username, final String password) + throws IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException { + if (sendCommand(POP3Command.AUTH, method.getAuthName()) != POP3Reply.OK_INT) { return false; } - switch(method) { - case PLAIN: - // the server sends an empty response ("+ "), so we don't have to read it. - return sendCommand( - new String( - Base64.encodeBase64(("\000" + username + "\000" + password).getBytes(getCharset())), - getCharset()) - ) == POP3Reply.OK; - case CRAM_MD5: - // get the CRAM challenge - byte[] serverChallenge = Base64.decodeBase64(getReplyString().substring(2).trim()); - // get the Mac instance - Mac hmac_md5 = Mac.getInstance("HmacMD5"); - hmac_md5.init(new SecretKeySpec(password.getBytes(getCharset()), "HmacMD5")); - // compute the result: - byte[] hmacResult = _convertToHexString(hmac_md5.doFinal(serverChallenge)).getBytes(getCharset()); - // join the byte arrays to form the reply - byte[] usernameBytes = username.getBytes(getCharset()); - byte[] toEncode = new byte[usernameBytes.length + 1 /* the space */ + hmacResult.length]; - System.arraycopy(usernameBytes, 0, toEncode, 0, usernameBytes.length); - toEncode[usernameBytes.length] = ' '; - System.arraycopy(hmacResult, 0, toEncode, usernameBytes.length + 1, hmacResult.length); - // send the reply and read the server code: - return sendCommand(Base64.encodeBase64StringUnChunked(toEncode)) == POP3Reply.OK; - default: - return false; + switch (method) { + case PLAIN: + // the server sends an empty response ("+ "), so we don't have to read it. + return sendCommand(new String(Base64.encodeBase64(("\000" + username + "\000" + password).getBytes(getCharset())), getCharset())) == POP3Reply.OK; + case CRAM_MD5: + // get the CRAM challenge + final byte[] serverChallenge = Base64.decodeBase64(getReplyString().substring(2).trim()); + // get the Mac instance + final Mac hmac_md5 = Mac.getInstance("HmacMD5"); + hmac_md5.init(new SecretKeySpec(password.getBytes(getCharset()), "HmacMD5")); + // compute the result: + final byte[] hmacResult = convertToHexString(hmac_md5.doFinal(serverChallenge)).getBytes(getCharset()); + // join the byte arrays to form the reply + final byte[] usernameBytes = username.getBytes(getCharset()); + final byte[] toEncode = new byte[usernameBytes.length + 1 /* the space */ + hmacResult.length]; + System.arraycopy(usernameBytes, 0, toEncode, 0, usernameBytes.length); + toEncode[usernameBytes.length] = ' '; + System.arraycopy(hmacResult, 0, toEncode, usernameBytes.length + 1, hmacResult.length); + // send the reply and read the server code: + return sendCommand(Base64.encodeBase64StringUnChunked(toEncode)) == POP3Reply.OK; + default: + return false; } } /** - * Converts the given byte array to a String containing the hex values of the bytes. - * For example, the byte 'A' will be converted to '41', because this is the ASCII code - * (and the byte value) of the capital letter 'A'. + * Converts the given byte array to a String containing the hex values of the bytes. For example, the byte 'A' will be converted to '41', because this is + * the ASCII code (and the byte value) of the capital letter 'A'. + * * @param a The byte array to convert. * @return The resulting String of hex codes. */ - private String _convertToHexString(byte[] a) - { - StringBuilder result = new StringBuilder(a.length*2); - for (byte element : a) - { - if ( (element & 0x0FF) <= 15 ) { + private String convertToHexString(final byte[] a) { + final StringBuilder result = new StringBuilder(a.length * 2); + for (final byte element : a) { + if ((element & 0x0FF) <= 15) { result.append("0"); } result.append(Integer.toHexString(element & 0x0FF)); } return result.toString(); } - - /** - * The enumeration of currently-supported authentication methods. - */ - public static enum AUTH_METHOD - { - /** The standarised (RFC4616) PLAIN method, which sends the password unencrypted (insecure). */ - PLAIN("PLAIN"), - - /** The standarised (RFC2195) CRAM-MD5 method, which doesn't send the password (secure). */ - CRAM_MD5("CRAM-MD5"); - - private final String methodName; - - AUTH_METHOD(String methodName){ - this.methodName = methodName; - } - /** - * Gets the name of the given authentication method suitable for the server. - * @return The name of the given authentication method suitable for the server. - */ - public final String getAuthName() - { - return this.methodName; - } - } } diff --git a/src/main/java/org/apache/commons/net/pop3/POP3.java b/src/main/java/org/apache/commons/net/pop3/POP3.java index 7363581..e43f549 100644 --- a/src/main/java/org/apache/commons/net/pop3/POP3.java +++ b/src/main/java/org/apache/commons/net/pop3/POP3.java @@ -23,6 +23,8 @@ import java.io.EOFException; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -30,326 +32,274 @@ import org.apache.commons.net.MalformedServerReplyException; import org.apache.commons.net.ProtocolCommandSupport; import org.apache.commons.net.SocketClient; import org.apache.commons.net.io.CRLFLineReader; +import org.apache.commons.net.util.NetConstants; -/*** - * The POP3 class is not meant to be used by itself and is provided - * only so that you may easily implement your own POP3 client if - * you so desire. If you have no need to perform your own implementation, - * you should use {@link org.apache.commons.net.pop3.POP3Client}. +/** + * The POP3 class is not meant to be used by itself and is provided only so that you may easily implement your own POP3 client if you so desire. If you have no + * need to perform your own implementation, you should use {@link org.apache.commons.net.pop3.POP3Client}. * <p> - * Rather than list it separately for each method, we mention here that - * every method communicating with the server and throwing an IOException - * can also throw a - * {@link org.apache.commons.net.MalformedServerReplyException} - * , which is a subclass - * of IOException. A MalformedServerReplyException will be thrown when - * the reply received from the server deviates enough from the protocol - * specification that it cannot be interpreted in a useful manner despite - * attempts to be as lenient as possible. + * Rather than list it separately for each method, we mention here that every method communicating with the server and throwing an IOException can also throw a + * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the + * reply received from the server deviates enough from the protocol specification that it cannot be interpreted in a useful manner despite attempts to be as + * lenient as possible. * * * @see POP3Client * @see org.apache.commons.net.MalformedServerReplyException - ***/ + */ -public class POP3 extends SocketClient -{ - /*** The default POP3 port. Set to 110 according to RFC 1288. ***/ +public class POP3 extends SocketClient { + /** The default POP3 port. Set to 110 according to RFC 1288. */ public static final int DEFAULT_PORT = 110; - /*** - * A constant representing the state where the client is not yet connected - * to a POP3 server. - ***/ + /** + * A constant representing the state where the client is not yet connected to a POP3 server. + */ public static final int DISCONNECTED_STATE = -1; - /*** A constant representing the POP3 authorization state. ***/ + /** A constant representing the POP3 authorization state. */ public static final int AUTHORIZATION_STATE = 0; - /*** A constant representing the POP3 transaction state. ***/ + /** A constant representing the POP3 transaction state. */ public static final int TRANSACTION_STATE = 1; - /*** A constant representing the POP3 update state. ***/ + /** A constant representing the POP3 update state. */ public static final int UPDATE_STATE = 2; - static final String _OK = "+OK"; + static final String OK = "+OK"; // The reply indicating intermediate response to a command. - static final String _OK_INT = "+ "; - static final String _ERROR = "-ERR"; + static final String OK_INT = "+ "; + static final String ERROR = "-ERR"; // We have to ensure that the protocol communication is in ASCII // but we use ISO-8859-1 just in case 8-bit characters cross // the wire. - static final String _DEFAULT_ENCODING = "ISO-8859-1"; + static final Charset DEFAULT_ENCODING = StandardCharsets.ISO_8859_1; - private int __popState; - BufferedWriter _writer; + private int popState; + BufferedWriter writer; - BufferedReader _reader; - int _replyCode; - String _lastReplyLine; - List<String> _replyLines; + BufferedReader reader; + int replyCode; + String lastReplyLine; + List<String> replyLines; /** - * A ProtocolCommandSupport object used to manage the registering of - * ProtocolCommandListeners and te firing of ProtocolCommandEvents. + * A ProtocolCommandSupport object used to manage the registering of ProtocolCommandListeners and the firing of ProtocolCommandEvents. */ protected ProtocolCommandSupport _commandSupport_; - /*** - * The default POP3Client constructor. Initializes the state - * to <code>DISCONNECTED_STATE</code>. - ***/ - public POP3() - { + /** + * The default POP3Client constructor. Initializes the state to <code>DISCONNECTED_STATE</code>. + */ + public POP3() { setDefaultPort(DEFAULT_PORT); - __popState = DISCONNECTED_STATE; - _reader = null; - _writer = null; - _replyLines = new ArrayList<String>(); + popState = DISCONNECTED_STATE; + reader = null; + writer = null; + replyLines = new ArrayList<>(); _commandSupport_ = new ProtocolCommandSupport(this); } - private void __getReply() throws IOException - { - String line; - - _replyLines.clear(); - line = _reader.readLine(); - - if (line == null) { - throw new EOFException("Connection closed without indication."); - } - - if (line.startsWith(_OK)) { - _replyCode = POP3Reply.OK; - } else if (line.startsWith(_ERROR)) { - _replyCode = POP3Reply.ERROR; - } else if (line.startsWith(_OK_INT)) { - _replyCode = POP3Reply.OK_INT; - } else { - throw new - MalformedServerReplyException( - "Received invalid POP3 protocol response from server." + line); - } - - _replyLines.add(line); - _lastReplyLine = line; - - fireReplyReceived(_replyCode, getReplyString()); - } - - - /*** - * Performs connection initialization and sets state to - * <code> AUTHORIZATION_STATE </code>. - ***/ + /** + * Performs connection initialization and sets state to <code> AUTHORIZATION_STATE </code>. + */ @Override - protected void _connectAction_() throws IOException - { + protected void _connectAction_() throws IOException { super._connectAction_(); - _reader = - new CRLFLineReader(new InputStreamReader(_input_, - _DEFAULT_ENCODING)); - _writer = - new BufferedWriter(new OutputStreamWriter(_output_, - _DEFAULT_ENCODING)); - __getReply(); + reader = new CRLFLineReader(new InputStreamReader(_input_, DEFAULT_ENCODING)); + writer = new BufferedWriter(new OutputStreamWriter(_output_, DEFAULT_ENCODING)); + getReply(); setState(AUTHORIZATION_STATE); } - /** - * Set the internal POP3 state. - * @param state the new state. This must be one of the <code>_STATE</code> constants. - */ - public void setState(int state) - { - __popState = state; - } - - - /*** - * Returns the current POP3 client state. + * Disconnects the client from the server, and sets the state to <code> DISCONNECTED_STATE </code>. The reply text information from the last issued command + * is voided to allow garbage collection of the memory used to store that information. * - * @return The current POP3 client state. - ***/ - public int getState() - { - return __popState; + * @throws IOException If there is an error in disconnecting. + */ + @Override + public void disconnect() throws IOException { + super.disconnect(); + reader = null; + writer = null; + lastReplyLine = null; + replyLines.clear(); + setState(DISCONNECTED_STATE); } - - /*** + /** * Retrieves the additional lines of a multi-line server reply. + * * @throws IOException on error - ***/ - public void getAdditionalReply() throws IOException - { + */ + public void getAdditionalReply() throws IOException { String line; - line = _reader.readLine(); - while (line != null) - { - _replyLines.add(line); + line = reader.readLine(); + while (line != null) { + replyLines.add(line); if (line.equals(".")) { break; } - line = _reader.readLine(); + line = reader.readLine(); } } - - /*** - * Disconnects the client from the server, and sets the state to - * <code> DISCONNECTED_STATE </code>. The reply text information - * from the last issued command is voided to allow garbage collection - * of the memory used to store that information. - * - * @throws IOException If there is an error in disconnecting. - ***/ + /** + * Provide command support to super-class + */ @Override - public void disconnect() throws IOException - { - super.disconnect(); - _reader = null; - _writer = null; - _lastReplyLine = null; - _replyLines.clear(); - setState(DISCONNECTED_STATE); + protected ProtocolCommandSupport getCommandSupport() { + return _commandSupport_; } + private void getReply() throws IOException { + final String line; - /*** - * Sends a command an arguments to the server and returns the reply code. - * - * @param command The POP3 command to send. - * @param args The command arguments. - * @return The server reply code (either POP3Reply.OK, POP3Reply.ERROR or POP3Reply.OK_INT). - * @throws IOException on error - ***/ - public int sendCommand(String command, String args) throws IOException - { - if (_writer == null) { - throw new IllegalStateException("Socket is not connected"); + replyLines.clear(); + line = reader.readLine(); + + if (line == null) { + throw new EOFException("Connection closed without indication."); } - StringBuilder __commandBuffer = new StringBuilder(); - __commandBuffer.append(command); - if (args != null) - { - __commandBuffer.append(' '); - __commandBuffer.append(args); + if (line.startsWith(OK)) { + replyCode = POP3Reply.OK; + } else if (line.startsWith(ERROR)) { + replyCode = POP3Reply.ERROR; + } else if (line.startsWith(OK_INT)) { + replyCode = POP3Reply.OK_INT; + } else { + throw new MalformedServerReplyException("Received invalid POP3 protocol response from server." + line); } - __commandBuffer.append(SocketClient.NETASCII_EOL); - String message = __commandBuffer.toString(); - _writer.write(message); - _writer.flush(); + replyLines.add(line); + lastReplyLine = line; - fireCommandSent(command, message); - - __getReply(); - return _replyCode; + fireReplyReceived(replyCode, getReplyString()); } - /*** - * Sends a command with no arguments to the server and returns the - * reply code. + /** + * Returns the reply to the last command sent to the server. The value is a single string containing all the reply lines including newlines. If the reply is + * a single line, but its format ndicates it should be a multiline reply, then you must call {@link #getAdditionalReply getAdditionalReply() } to fetch the + * rest of the reply, and then call <code>getReplyString</code> again. You only have to worry about this if you are implementing your own client using the + * {@link #sendCommand sendCommand } methods. * - * @param command The POP3 command to send. - * @return The server reply code (either POP3Reply.OK, POP3Reply.ERROR or POP3Reply.OK_INT). - * @throws IOException on error - ***/ - public int sendCommand(String command) throws IOException - { - return sendCommand(command, null); + * @return The last server response. + */ + public String getReplyString() { + final StringBuilder buffer = new StringBuilder(256); + + for (final String entry : replyLines) { + buffer.append(entry); + buffer.append(SocketClient.NETASCII_EOL); + } + + return buffer.toString(); } - /*** - * Sends a command an arguments to the server and returns the reply code. + /** + * Returns an array of lines received as a reply to the last command sent to the server. The lines have end of lines truncated. If the reply is a single + * line, but its format ndicates it should be a multiline reply, then you must call {@link #getAdditionalReply getAdditionalReply() } to fetch the rest of + * the reply, and then call <code>getReplyStrings</code> again. You only have to worry about this if you are implementing your own client using the + * {@link #sendCommand sendCommand } methods. * - * @param command The POP3 command to send - * (one of the POP3Command constants). - * @param args The command arguments. - * @return The server reply code (either POP3Reply.OK, POP3Reply.ERROR or POP3Reply.OK_INT). - * @throws IOException on error - ***/ - public int sendCommand(int command, String args) throws IOException - { - return sendCommand(POP3Command._commands[command], args); + * @return The last server response. + */ + public String[] getReplyStrings() { + return replyLines.toArray(NetConstants.EMPTY_STRING_ARRAY); } - /*** - * Sends a command with no arguments to the server and returns the - * reply code. + /** + * Returns the current POP3 client state. * - * @param command The POP3 command to send - * (one of the POP3Command constants). - * @return The server reply code (either POP3Reply.OK, POP3Reply.ERROR or POP3Reply.OK_INT). - * @throws IOException on error - ***/ - public int sendCommand(int command) throws IOException - { - return sendCommand(POP3Command._commands[command], null); + * @return The current POP3 client state. + */ + public int getState() { + return popState; } - - /*** - * Returns an array of lines received as a reply to the last command - * sent to the server. The lines have end of lines truncated. If - * the reply is a single line, but its format ndicates it should be - * a multiline reply, then you must call - * {@link #getAdditionalReply getAdditionalReply() } to - * fetch the rest of the reply, and then call <code>getReplyStrings</code> - * again. You only have to worry about this if you are implementing - * your own client using the {@link #sendCommand sendCommand } methods. + /** + * Removes a ProtocolCommandListener. * - * @return The last server response. - ***/ - public String[] getReplyStrings() - { - return _replyLines.toArray(new String[_replyLines.size()]); + * Delegates this incorrectly named method - removeProtocolCommandistener (note the missing "L")- to the correct method + * {@link SocketClient#removeProtocolCommandListener} + * + * @param listener The ProtocolCommandListener to remove + */ + public void removeProtocolCommandistener(final org.apache.commons.net.ProtocolCommandListener listener) { + removeProtocolCommandListener(listener); } - /*** - * Returns the reply to the last command sent to the server. - * The value is a single string containing all the reply lines including - * newlines. If the reply is a single line, but its format ndicates it - * should be a multiline reply, then you must call - * {@link #getAdditionalReply getAdditionalReply() } to - * fetch the rest of the reply, and then call <code>getReplyString</code> - * again. You only have to worry about this if you are implementing - * your own client using the {@link #sendCommand sendCommand } methods. + /** + * Sends a command with no arguments to the server and returns the reply code. * - * @return The last server response. - ***/ - public String getReplyString() - { - StringBuilder buffer = new StringBuilder(256); + * @param command The POP3 command to send (one of the POP3Command constants). + * @return The server reply code (either POP3Reply.OK, POP3Reply.ERROR or POP3Reply.OK_INT). + * @throws IOException on error + */ + public int sendCommand(final int command) throws IOException { + return sendCommand(POP3Command.commands[command], null); + } - for (String entry : _replyLines) - { - buffer.append(entry); - buffer.append(SocketClient.NETASCII_EOL); - } + /** + * Sends a command an arguments to the server and returns the reply code. + * + * @param command The POP3 command to send (one of the POP3Command constants). + * @param args The command arguments. + * @return The server reply code (either POP3Reply.OK, POP3Reply.ERROR or POP3Reply.OK_INT). + * @throws IOException on error + */ + public int sendCommand(final int command, final String args) throws IOException { + return sendCommand(POP3Command.commands[command], args); + } - return buffer.toString(); + /** + * Sends a command with no arguments to the server and returns the reply code. + * + * @param command The POP3 command to send. + * @return The server reply code (either POP3Reply.OK, POP3Reply.ERROR or POP3Reply.OK_INT). + * @throws IOException on error + */ + public int sendCommand(final String command) throws IOException { + return sendCommand(command, null); } /** - * Removes a ProtocolCommandListener. + * Sends a command an arguments to the server and returns the reply code. * - * Delegates this incorrectly named method - removeProtocolCommandistener (note the missing "L")- to - * the correct method {@link SocketClient#removeProtocolCommandListener} - * @param listener The ProtocolCommandListener to remove + * @param command The POP3 command to send. + * @param args The command arguments. + * @return The server reply code (either POP3Reply.OK, POP3Reply.ERROR or POP3Reply.OK_INT). + * @throws IOException on error */ - public void removeProtocolCommandistener(org.apache.commons.net.ProtocolCommandListener listener){ - removeProtocolCommandListener(listener); + public int sendCommand(final String command, final String args) throws IOException { + if (writer == null) { + throw new IllegalStateException("Socket is not connected"); + } + final StringBuilder __commandBuffer = new StringBuilder(); + __commandBuffer.append(command); + + if (args != null) { + __commandBuffer.append(' '); + __commandBuffer.append(args); + } + __commandBuffer.append(SocketClient.NETASCII_EOL); + + final String message = __commandBuffer.toString(); + writer.write(message); + writer.flush(); + + fireCommandSent(command, message); + + getReply(); + return replyCode; } /** - * Provide command support to super-class + * Set the internal POP3 state. + * + * @param state the new state. This must be one of the <code>_STATE</code> constants. */ - @Override - protected ProtocolCommandSupport getCommandSupport() { - return _commandSupport_; + public void setState(final int state) { + popState = state; } } - diff --git a/src/main/java/org/apache/commons/net/pop3/POP3Client.java b/src/main/java/org/apache/commons/net/pop3/POP3Client.java index 16339bc..ee887ba 100644 --- a/src/main/java/org/apache/commons/net/pop3/POP3Client.java +++ b/src/main/java/org/apache/commons/net/pop3/POP3Client.java @@ -21,40 +21,32 @@ import java.io.IOException; import java.io.Reader; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Arrays; import java.util.ListIterator; import java.util.StringTokenizer; import org.apache.commons.net.io.DotTerminatedMessageReader; -/*** - * The POP3Client class implements the client side of the Internet POP3 - * Protocol defined in RFC 1939. All commands are supported, including - * the APOP command which requires MD5 encryption. See RFC 1939 for - * more details on the POP3 protocol. +/** + * The POP3Client class implements the client side of the Internet POP3 Protocol defined in RFC 1939. All commands are supported, including the APOP command + * which requires MD5 encryption. See RFC 1939 for more details on the POP3 protocol. * <p> - * Rather than list it separately for each method, we mention here that - * every method communicating with the server and throwing an IOException - * can also throw a - * {@link org.apache.commons.net.MalformedServerReplyException} - * , which is a subclass - * of IOException. A MalformedServerReplyException will be thrown when - * the reply received from the server deviates enough from the protocol - * specification that it cannot be interpreted in a useful manner despite - * attempts to be as lenient as possible. + * Rather than list it separately for each method, we mention here that every method communicating with the server and throwing an IOException can also throw a + * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the + * reply received from the server deviates enough from the protocol specification that it cannot be interpreted in a useful manner despite attempts to be as + * lenient as possible. * * * @see POP3MessageInfo * @see org.apache.commons.net.io.DotTerminatedMessageReader * @see org.apache.commons.net.MalformedServerReplyException - ***/ + */ -public class POP3Client extends POP3 -{ +public class POP3Client extends POP3 { - private static POP3MessageInfo __parseStatus(String line) - { + private static POP3MessageInfo parseStatus(final String line) { int num, size; - StringTokenizer tokenizer; + final StringTokenizer tokenizer; tokenizer = new StringTokenizer(line); @@ -64,8 +56,7 @@ public class POP3Client extends POP3 num = size = 0; - try - { + try { num = Integer.parseInt(tokenizer.nextToken()); if (!tokenizer.hasMoreElements()) { @@ -73,19 +64,16 @@ public class POP3Client extends POP3 } size = Integer.parseInt(tokenizer.nextToken()); - } - catch (NumberFormatException e) - { + } catch (final NumberFormatException e) { return null; } return new POP3MessageInfo(num, size); } - private static POP3MessageInfo __parseUID(String line) - { + private static POP3MessageInfo parseUID(String line) { int num; - StringTokenizer tokenizer; + final StringTokenizer tokenizer; tokenizer = new StringTokenizer(line); @@ -95,8 +83,7 @@ public class POP3Client extends POP3 num = 0; - try - { + try { num = Integer.parseInt(tokenizer.nextToken()); if (!tokenizer.hasMoreElements()) { @@ -104,24 +91,21 @@ public class POP3Client extends POP3 } line = tokenizer.nextToken(); - } - catch (NumberFormatException e) - { + } catch (final NumberFormatException e) { return null; } return new POP3MessageInfo(num, line); } - /*** + /** * Send a CAPA command to the POP3 server. + * * @return True if the command was successful, false if not. - * @throws IOException If a network I/O error occurs in the process of - * sending the CAPA command. + * @throws IOException If a network I/O error occurs in the process of sending the CAPA command. * @since 3.1 (was previously in ExtendedPOP3Client) - ***/ - public boolean capa() throws IOException - { + */ + public boolean capa() throws IOException { if (sendCommand(POP3Command.CAPA) == POP3Reply.OK) { getAdditionalReply(); return true; @@ -130,25 +114,132 @@ public class POP3Client extends POP3 } - /*** - * Login to the POP3 server with the given username and password. You - * must first connect to the server with - * {@link org.apache.commons.net.SocketClient#connect connect } - * before attempting to login. A login attempt is only valid if - * the client is in the - * {@link org.apache.commons.net.pop3.POP3#AUTHORIZATION_STATE AUTHORIZATION_STATE } - * . After logging in, the client enters the - * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } - * . + /** + * Delete a message from the POP3 server. The message is only marked for deletion by the server. If you decide to unmark the message, you must issuse a + * {@link #reset reset } command. Messages marked for deletion are only deleted by the server on {@link #logout logout }. A delete attempt can only succeed + * if the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } . * - * @param username The account name being logged in to. - * @param password The plain text password of the account. + * @param messageId The message number to delete. + * @return True if the deletion attempt was successful, false if not. + * @throws IOException If a network I/O error occurs in the process of sending the delete command. + */ + public boolean deleteMessage(final int messageId) throws IOException { + if (getState() == TRANSACTION_STATE) { + return sendCommand(POP3Command.DELE, Integer.toString(messageId)) == POP3Reply.OK; + } + return false; + } + + /** + * List an individual message. A list attempt can only succeed if the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE + * TRANSACTION_STATE } . Returns a POP3MessageInfo instance containing the number of the listed message and the size of the message in bytes. Returns null + * if the list attempt fails (e.g., if the specified message number does not exist). + * + * @param messageId The number of the message list. + * @return A POP3MessageInfo instance containing the number of the listed message and the size of the message in bytes. Returns null if the list attempt + * fails. + * @throws IOException If a network I/O error occurs in the process of sending the list command. + */ + public POP3MessageInfo listMessage(final int messageId) throws IOException { + if (getState() != TRANSACTION_STATE) { + return null; + } + if (sendCommand(POP3Command.LIST, Integer.toString(messageId)) != POP3Reply.OK) { + return null; + } + return parseStatus(lastReplyLine.substring(3)); + } + + /** + * List all messages. A list attempt can only succeed if the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } + * . Returns an array of POP3MessageInfo instances, each containing the number of a message and its size in bytes. If there are no messages, this method + * returns a zero length array. If the list attempt fails, it returns null. + * + * @return An array of POP3MessageInfo instances representing all messages in the order they appear in the mailbox, each containing the number of a message + * and its size in bytes. If there are no messages, this method returns a zero length array. If the list attempt fails, it returns null. + * @throws IOException If a network I/O error occurs in the process of sending the list command. + */ + public POP3MessageInfo[] listMessages() throws IOException { + if (getState() != TRANSACTION_STATE) { + return null; + } + if (sendCommand(POP3Command.LIST) != POP3Reply.OK) { + return null; + } + getAdditionalReply(); + + // This could be a zero length array if no messages present + final POP3MessageInfo[] messages = new POP3MessageInfo[replyLines.size() - 2]; // skip first and last lines + + final ListIterator<String> en = replyLines.listIterator(1); // Skip first line + + // Fetch lines. + Arrays.setAll(messages, i -> parseStatus(en.next())); + + return messages; + } + + /** + * List the unique identifier for a message. A list attempt can only succeed if the client is in the + * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } . Returns a POP3MessageInfo instance containing the number of the listed + * message and the unique identifier for that message. Returns null if the list attempt fails (e.g., if the specified message number does not exist). + * + * @param messageId The number of the message list. + * @return A POP3MessageInfo instance containing the number of the listed message and the unique identifier for that message. Returns null if the list + * attempt fails. + * @throws IOException If a network I/O error occurs in the process of sending the list unique identifier command. + */ + public POP3MessageInfo listUniqueIdentifier(final int messageId) throws IOException { + if (getState() != TRANSACTION_STATE) { + return null; + } + if (sendCommand(POP3Command.UIDL, Integer.toString(messageId)) != POP3Reply.OK) { + return null; + } + return parseUID(lastReplyLine.substring(3)); + } + + /** + * List the unique identifiers for all messages. A list attempt can only succeed if the client is in the + * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } . Returns an array of POP3MessageInfo instances, each containing the number + * of a message and its unique identifier. If there are no messages, this method returns a zero length array. If the list attempt fails, it returns null. + * + * @return An array of POP3MessageInfo instances representing all messages in the order they appear in the mailbox, each containing the number of a message + * and its unique identifier If there are no messages, this method returns a zero length array. If the list attempt fails, it returns null. + * @throws IOException If a network I/O error occurs in the process of sending the list unique identifier command. + */ + public POP3MessageInfo[] listUniqueIdentifiers() throws IOException { + if (getState() != TRANSACTION_STATE) { + return null; + } + if (sendCommand(POP3Command.UIDL) != POP3Reply.OK) { + return null; + } + getAdditionalReply(); + + // This could be a zero length array if no messages present + final POP3MessageInfo[] messages = new POP3MessageInfo[replyLines.size() - 2]; // skip first and last lines + + final ListIterator<String> en = replyLines.listIterator(1); // skip first line + + // Fetch lines. + Arrays.setAll(messages, i -> parseUID(en.next())); + + return messages; + } + + /** + * Login to the POP3 server with the given username and password. You must first connect to the server with + * {@link org.apache.commons.net.SocketClient#connect connect } before attempting to login. A login attempt is only valid if the client is in the + * {@link org.apache.commons.net.pop3.POP3#AUTHORIZATION_STATE AUTHORIZATION_STATE } . After logging in, the client enters the + * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } . + * + * @param username The account name being logged in to. + * @param password The plain text password of the account. * @return True if the login attempt was successful, false if not. - * @throws IOException If a network I/O error occurs in the process of - * logging in. - ***/ - public boolean login(String username, String password) throws IOException - { + * @throws IOException If a network I/O error occurs in the process of logging in. + */ + public boolean login(final String username, final String password) throws IOException { if (getState() != AUTHORIZATION_STATE) { return false; } @@ -166,47 +257,31 @@ public class POP3Client extends POP3 return true; } - - /*** - * Login to the POP3 server with the given username and authentication - * information. Use this method when connecting to a server requiring - * authentication using the APOP command. Because the timestamp - * produced in the greeting banner varies from server to server, it is - * not possible to consistently extract the information. Therefore, - * after connecting to the server, you must call - * {@link org.apache.commons.net.pop3.POP3#getReplyString getReplyString } - * and parse out the timestamp information yourself. + /** + * Login to the POP3 server with the given username and authentication information. Use this method when connecting to a server requiring authentication + * using the APOP command. Because the timestamp produced in the greeting banner varies from server to server, it is not possible to consistently extract + * the information. Therefore, after connecting to the server, you must call {@link org.apache.commons.net.pop3.POP3#getReplyString getReplyString } and + * parse out the timestamp information yourself. * <p> - * You must first connect to the server with - * {@link org.apache.commons.net.SocketClient#connect connect } - * before attempting to login. A login attempt is only valid if - * the client is in the - * {@link org.apache.commons.net.pop3.POP3#AUTHORIZATION_STATE AUTHORIZATION_STATE } - * . After logging in, the client enters the - * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } - * . After connecting, you must parse out the - * server specific information to use as a timestamp, and pass that - * information to this method. The secret is a shared secret known - * to you and the server. See RFC 1939 for more details regarding - * the APOP command. + * You must first connect to the server with {@link org.apache.commons.net.SocketClient#connect connect } before attempting to login. A login attempt is + * only valid if the client is in the {@link org.apache.commons.net.pop3.POP3#AUTHORIZATION_STATE AUTHORIZATION_STATE } . After logging in, the client + * enters the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } . After connecting, you must parse out the server specific + * information to use as a timestamp, and pass that information to this method. The secret is a shared secret known to you and the server. See RFC 1939 for + * more details regarding the APOP command. * * @param username The account name being logged in to. - * @param timestamp The timestamp string to combine with the secret. - * @param secret The shared secret which produces the MD5 digest when - * combined with the timestamp. + * @param timestamp The timestamp string to combine with the secret. + * @param secret The shared secret which produces the MD5 digest when combined with the timestamp. * @return True if the login attempt was successful, false if not. - * @throws IOException If a network I/O error occurs in the process of - * logging in. - * @throws NoSuchAlgorithmException If the MD5 encryption algorithm - * cannot be instantiated by the Java runtime system. - ***/ - public boolean login(String username, String timestamp, String secret) - throws IOException, NoSuchAlgorithmException - { + * @throws IOException If a network I/O error occurs in the process of logging in. + * @throws NoSuchAlgorithmException If the MD5 encryption algorithm cannot be instantiated by the Java runtime system. + */ + public boolean login(final String username, String timestamp, final String secret) throws IOException, NoSuchAlgorithmException { int i; - byte[] digest; - StringBuilder buffer, digestBuffer; - MessageDigest md5; + final byte[] digest; + final StringBuilder buffer; + final StringBuilder digestBuffer; + final MessageDigest md5; if (getState() != AUTHORIZATION_STATE) { return false; @@ -218,7 +293,7 @@ public class POP3Client extends POP3 digestBuffer = new StringBuilder(128); for (i = 0; i < digest.length; i++) { - int digit = digest[i] & 0xff; + final int digit = digest[i] & 0xff; if (digit <= 15) { // Add leading zero if necessary (NET-351) digestBuffer.append("0"); } @@ -239,346 +314,120 @@ public class POP3Client extends POP3 return true; } - - /*** - * Logout of the POP3 server. To fully disconnect from the server - * you must call - * {@link org.apache.commons.net.pop3.POP3#disconnect disconnect }. - * A logout attempt is valid in any state. If - * the client is in the - * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } - * , it enters the - * {@link org.apache.commons.net.pop3.POP3#UPDATE_STATE UPDATE_STATE } - * on a successful logout. + /** + * Logout of the POP3 server. To fully disconnect from the server you must call {@link org.apache.commons.net.pop3.POP3#disconnect disconnect }. A logout + * attempt is valid in any state. If the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } , it enters the + * {@link org.apache.commons.net.pop3.POP3#UPDATE_STATE UPDATE_STATE } on a successful logout. * * @return True if the logout attempt was successful, false if not. - * @throws IOException If a network I/O error occurs in the process - * of logging out. - ***/ - public boolean logout() throws IOException - { + * @throws IOException If a network I/O error occurs in the process of logging out. + */ + public boolean logout() throws IOException { if (getState() == TRANSACTION_STATE) { setState(UPDATE_STATE); } sendCommand(POP3Command.QUIT); - return (_replyCode == POP3Reply.OK); + return replyCode == POP3Reply.OK; } - - /*** - * Send a NOOP command to the POP3 server. This is useful for keeping - * a connection alive since most POP3 servers will timeout after 10 - * minutes of inactivity. A noop attempt will only succeed if - * the client is in the - * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } - * . + /** + * Send a NOOP command to the POP3 server. This is useful for keeping a connection alive since most POP3 servers will timeout after 10 minutes of + * inactivity. A noop attempt will only succeed if the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } . * * @return True if the noop attempt was successful, false if not. - * @throws IOException If a network I/O error occurs in the process of - * sending the NOOP command. - ***/ - public boolean noop() throws IOException - { - if (getState() == TRANSACTION_STATE) { - return (sendCommand(POP3Command.NOOP) == POP3Reply.OK); - } - return false; - } - - - /*** - * Delete a message from the POP3 server. The message is only marked - * for deletion by the server. If you decide to unmark the message, you - * must issuse a {@link #reset reset } command. Messages marked - * for deletion are only deleted by the server on - * {@link #logout logout }. - * A delete attempt can only succeed if the client is in the - * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } - * . - * - * @param messageId The message number to delete. - * @return True if the deletion attempt was successful, false if not. - * @throws IOException If a network I/O error occurs in the process of - * sending the delete command. - ***/ - public boolean deleteMessage(int messageId) throws IOException - { + * @throws IOException If a network I/O error occurs in the process of sending the NOOP command. + */ + public boolean noop() throws IOException { if (getState() == TRANSACTION_STATE) { - return (sendCommand(POP3Command.DELE, Integer.toString(messageId)) - == POP3Reply.OK); + return sendCommand(POP3Command.NOOP) == POP3Reply.OK; } return false; } - - /*** - * Reset the POP3 session. This is useful for undoing any message - * deletions that may have been performed. A reset attempt can only - * succeed if the client is in the - * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } - * . + /** + * Reset the POP3 session. This is useful for undoing any message deletions that may have been performed. A reset attempt can only succeed if the client is + * in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } . * * @return True if the reset attempt was successful, false if not. - * @throws IOException If a network I/O error occurs in the process of - * sending the reset command. - ***/ - public boolean reset() throws IOException - { + * @throws IOException If a network I/O error occurs in the process of sending the reset command. + */ + public boolean reset() throws IOException { if (getState() == TRANSACTION_STATE) { - return (sendCommand(POP3Command.RSET) == POP3Reply.OK); + return sendCommand(POP3Command.RSET) == POP3Reply.OK; } return false; } - /*** - * Get the mailbox status. A status attempt can only - * succeed if the client is in the - * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } - * . Returns a POP3MessageInfo instance - * containing the number of messages in the mailbox and the total - * size of the messages in bytes. Returns null if the status the - * attempt fails. - * - * @return A POP3MessageInfo instance containing the number of - * messages in the mailbox and the total size of the messages - * in bytes. Returns null if the status the attempt fails. - * @throws IOException If a network I/O error occurs in the process of - * sending the status command. - ***/ - public POP3MessageInfo status() throws IOException - { - if (getState() != TRANSACTION_STATE) { - return null; - } - if (sendCommand(POP3Command.STAT) != POP3Reply.OK) { - return null; - } - return __parseStatus(_lastReplyLine.substring(3)); - } - - - /*** - * List an individual message. A list attempt can only - * succeed if the client is in the - * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } - * . Returns a POP3MessageInfo instance - * containing the number of the listed message and the - * size of the message in bytes. Returns null if the list - * attempt fails (e.g., if the specified message number does - * not exist). - * - * @param messageId The number of the message list. - * @return A POP3MessageInfo instance containing the number of the - * listed message and the size of the message in bytes. Returns - * null if the list attempt fails. - * @throws IOException If a network I/O error occurs in the process of - * sending the list command. - ***/ - public POP3MessageInfo listMessage(int messageId) throws IOException - { - if (getState() != TRANSACTION_STATE) { - return null; - } - if (sendCommand(POP3Command.LIST, Integer.toString(messageId)) - != POP3Reply.OK) { - return null; - } - return __parseStatus(_lastReplyLine.substring(3)); - } - - - /*** - * List all messages. A list attempt can only - * succeed if the client is in the - * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } - * . Returns an array of POP3MessageInfo instances, - * each containing the number of a message and its size in bytes. - * If there are no messages, this method returns a zero length array. - * If the list attempt fails, it returns null. - * - * @return An array of POP3MessageInfo instances representing all messages - * in the order they appear in the mailbox, - * each containing the number of a message and its size in bytes. - * If there are no messages, this method returns a zero length array. - * If the list attempt fails, it returns null. - * @throws IOException If a network I/O error occurs in the process of - * sending the list command. - ***/ - public POP3MessageInfo[] listMessages() throws IOException - { - if (getState() != TRANSACTION_STATE) { - return null; - } - if (sendCommand(POP3Command.LIST) != POP3Reply.OK) { - return null; - } - getAdditionalReply(); - - // This could be a zero length array if no messages present - POP3MessageInfo[] messages = new POP3MessageInfo[_replyLines.size() - 2]; // skip first and last lines - - ListIterator<String> en = _replyLines.listIterator(1); // Skip first line - - // Fetch lines. - for (int line = 0; line < messages.length; line++) { - messages[line] = __parseStatus(en.next()); - } - - return messages; - } - - /*** - * List the unique identifier for a message. A list attempt can only - * succeed if the client is in the - * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } - * . Returns a POP3MessageInfo instance - * containing the number of the listed message and the - * unique identifier for that message. Returns null if the list - * attempt fails (e.g., if the specified message number does - * not exist). - * - * @param messageId The number of the message list. - * @return A POP3MessageInfo instance containing the number of the - * listed message and the unique identifier for that message. - * Returns null if the list attempt fails. - * @throws IOException If a network I/O error occurs in the process of - * sending the list unique identifier command. - ***/ - public POP3MessageInfo listUniqueIdentifier(int messageId) - throws IOException - { - if (getState() != TRANSACTION_STATE) { - return null; - } - if (sendCommand(POP3Command.UIDL, Integer.toString(messageId)) - != POP3Reply.OK) { - return null; - } - return __parseUID(_lastReplyLine.substring(3)); - } - - - /*** - * List the unique identifiers for all messages. A list attempt can only - * succeed if the client is in the + /** + * Retrieve a message from the POP3 server. A retrieve message attempt can only succeed if the client is in the * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } - * . Returns an array of POP3MessageInfo instances, - * each containing the number of a message and its unique identifier. - * If there are no messages, this method returns a zero length array. - * If the list attempt fails, it returns null. + * <p> + * You must not issue any commands to the POP3 server (i.e., call any other methods) until you finish reading the message from the returned BufferedReader + * instance. The POP3 protocol uses the same stream for issuing commands as it does for returning results. Therefore the returned BufferedReader actually + * reads directly from the POP3 connection. After the end of message has been reached, new commands can be executed and their replies read. If you do not + * follow these requirements, your program will not work properly. * - * @return An array of POP3MessageInfo instances representing all messages - * in the order they appear in the mailbox, - * each containing the number of a message and its unique identifier - * If there are no messages, this method returns a zero length array. - * If the list attempt fails, it returns null. - * @throws IOException If a network I/O error occurs in the process of - * sending the list unique identifier command. - ***/ - public POP3MessageInfo[] listUniqueIdentifiers() throws IOException - { + * @param messageId The number of the message to fetch. + * @return A DotTerminatedMessageReader instance from which the entire message can be read. This can safely be cast to a {@link java.io.BufferedReader + * BufferedReader} in order to use the {@link java.io.BufferedReader#readLine() BufferedReader#readLine()} method. Returns null if the retrieval + * attempt fails (e.g., if the specified message number does not exist). + * @throws IOException If a network I/O error occurs in the process of sending the retrieve message command. + */ + public Reader retrieveMessage(final int messageId) throws IOException { if (getState() != TRANSACTION_STATE) { return null; } - if (sendCommand(POP3Command.UIDL) != POP3Reply.OK) { + if (sendCommand(POP3Command.RETR, Integer.toString(messageId)) != POP3Reply.OK) { return null; } - getAdditionalReply(); - // This could be a zero length array if no messages present - POP3MessageInfo[] messages = new POP3MessageInfo[_replyLines.size() - 2]; // skip first and last lines - - ListIterator<String> en = _replyLines.listIterator(1); // skip first line - - // Fetch lines. - for (int line = 0; line < messages.length; line++) { - messages[line] = __parseUID(en.next()); - } - - return messages; + return new DotTerminatedMessageReader(reader); } - /** - * Retrieve a message from the POP3 server. A retrieve message attempt - * can only succeed if the client is in the + * Retrieve only the specified top number of lines of a message from the POP3 server. A retrieve top lines attempt can only succeed if the client is in the * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } * <p> - * You must not issue any commands to the POP3 server (i.e., call any - * other methods) until you finish reading the message from the - * returned BufferedReader instance. - * The POP3 protocol uses the same stream for issuing commands as it does - * for returning results. Therefore the returned BufferedReader actually reads - * directly from the POP3 connection. After the end of message has been - * reached, new commands can be executed and their replies read. If - * you do not follow these requirements, your program will not work - * properly. + * You must not issue any commands to the POP3 server (i.e., call any other methods) until you finish reading the message from the returned BufferedReader + * instance. The POP3 protocol uses the same stream for issuing commands as it does for returning results. Therefore the returned BufferedReader actually + * reads directly from the POP3 connection. After the end of message has been reached, new commands can be executed and their replies read. If you do not + * follow these requirements, your program will not work properly. * - * @param messageId The number of the message to fetch. - * @return A DotTerminatedMessageReader instance - * from which the entire message can be read. - * This can safely be cast to a {@link java.io.BufferedReader BufferedReader} in order to - * use the {@link java.io.BufferedReader#readLine() BufferedReader#readLine()} method. - * Returns null if the retrieval attempt fails (e.g., if the specified - * message number does not exist). - * @throws IOException If a network I/O error occurs in the process of - * sending the retrieve message command. + * @param messageId The number of the message to fetch. + * @param numLines The top number of lines to fetch. This must be >= 0. + * @return A DotTerminatedMessageReader instance from which the specified top number of lines of the message can be read. This can safely be cast to a + * {@link java.io.BufferedReader BufferedReader} in order to use the {@link java.io.BufferedReader#readLine() BufferedReader#readLine()} method. + * Returns null if the retrieval attempt fails (e.g., if the specified message number does not exist). + * @throws IOException If a network I/O error occurs in the process of sending the top command. */ - public Reader retrieveMessage(int messageId) throws IOException - { - if (getState() != TRANSACTION_STATE) { + public Reader retrieveMessageTop(final int messageId, final int numLines) throws IOException { + if (numLines < 0 || getState() != TRANSACTION_STATE) { return null; } - if (sendCommand(POP3Command.RETR, Integer.toString(messageId)) != POP3Reply.OK) { + if (sendCommand(POP3Command.TOP, Integer.toString(messageId) + " " + Integer.toString(numLines)) != POP3Reply.OK) { return null; } - return new DotTerminatedMessageReader(_reader); + return new DotTerminatedMessageReader(reader); } - /** - * Retrieve only the specified top number of lines of a message from the - * POP3 server. A retrieve top lines attempt - * can only succeed if the client is in the - * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } - * <p> - * You must not issue any commands to the POP3 server (i.e., call any - * other methods) until you finish reading the message from the returned - * BufferedReader instance. - * The POP3 protocol uses the same stream for issuing commands as it does - * for returning results. Therefore the returned BufferedReader actually reads - * directly from the POP3 connection. After the end of message has been - * reached, new commands can be executed and their replies read. If - * you do not follow these requirements, your program will not work - * properly. + * Get the mailbox status. A status attempt can only succeed if the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE + * TRANSACTION_STATE } . Returns a POP3MessageInfo instance containing the number of messages in the mailbox and the total size of the messages in bytes. + * Returns null if the status the attempt fails. * - * @param messageId The number of the message to fetch. - * @param numLines The top number of lines to fetch. This must be >= 0. - * @return A DotTerminatedMessageReader instance - * from which the specified top number of lines of the message can be - * read. - * This can safely be cast to a {@link java.io.BufferedReader BufferedReader} in order to - * use the {@link java.io.BufferedReader#readLine() BufferedReader#readLine()} method. - * Returns null if the retrieval attempt fails (e.g., if the specified - * message number does not exist). - * @throws IOException If a network I/O error occurs in the process of - * sending the top command. + * @return A POP3MessageInfo instance containing the number of messages in the mailbox and the total size of the messages in bytes. Returns null if the + * status the attempt fails. + * @throws IOException If a network I/O error occurs in the process of sending the status command. */ - public Reader retrieveMessageTop(int messageId, int numLines) - throws IOException - { - if (numLines < 0 || getState() != TRANSACTION_STATE) { + public POP3MessageInfo status() throws IOException { + if (getState() != TRANSACTION_STATE) { return null; } - if (sendCommand(POP3Command.TOP, Integer.toString(messageId) + " " + - Integer.toString(numLines)) != POP3Reply.OK) { + if (sendCommand(POP3Command.STAT) != POP3Reply.OK) { return null; } - - return new DotTerminatedMessageReader(_reader); + return parseStatus(lastReplyLine.substring(3)); } - } - diff --git a/src/main/java/org/apache/commons/net/pop3/POP3Command.java b/src/main/java/org/apache/commons/net/pop3/POP3Command.java index f6b52eb..6de3944 100644 --- a/src/main/java/org/apache/commons/net/pop3/POP3Command.java +++ b/src/main/java/org/apache/commons/net/pop3/POP3Command.java @@ -17,74 +17,72 @@ package org.apache.commons.net.pop3; -/*** +/** * POP3Command stores POP3 command code constants. * * - ***/ + */ -public final class POP3Command -{ - /*** Send user name. ***/ +public final class POP3Command { + /** Send user name. */ public static final int USER = 0; - /*** Send password. ***/ + /** Send password. */ public static final int PASS = 1; - /*** Quit session. ***/ + /** Quit session. */ public static final int QUIT = 2; - /*** Get status. ***/ + /** Get status. */ public static final int STAT = 3; - /*** List message(s). ***/ + /** List message(s). */ public static final int LIST = 4; - /*** Retrieve message(s). ***/ + /** Retrieve message(s). */ public static final int RETR = 5; - /*** Delete message(s). ***/ + /** Delete message(s). */ public static final int DELE = 6; - /*** No operation. Used as a session keepalive. ***/ + /** No operation. Used as a session keepalive. */ public static final int NOOP = 7; - /*** Reset session. ***/ + /** Reset session. */ public static final int RSET = 8; - /*** Authorization. ***/ + /** Authorization. */ public static final int APOP = 9; - /*** Retrieve top number lines from message. ***/ + /** Retrieve top number lines from message. */ public static final int TOP = 10; - /*** List unique message identifier(s). ***/ + /** List unique message identifier(s). */ public static final int UIDL = 11; /** * The capabilities command. + * * @since 3.0 */ public static final int CAPA = 12; /** * Authentication + * * @since 3.0 */ public static final int AUTH = 13; - private static final int _NEXT_ = AUTH + 1; // update as necessary when adding new entries + private static final int NEXT = AUTH + 1; // update as necessary when adding new entries - static final String[] _commands = { - "USER", "PASS", "QUIT", "STAT", "LIST", "RETR", "DELE", "NOOP", "RSET", - "APOP", "TOP", "UIDL", "CAPA", "AUTH", - }; + static final String[] commands = { "USER", "PASS", "QUIT", "STAT", "LIST", "RETR", "DELE", "NOOP", "RSET", "APOP", "TOP", "UIDL", "CAPA", "AUTH", }; static { - if (_commands.length != _NEXT_) { + if (commands.length != NEXT) { throw new RuntimeException("Error in array definition"); } } - // Cannot be instantiated. - private POP3Command() - {} - - /*** + /** * Get the POP3 protocol string command corresponding to a command code. + * * @param command the command code * * @return The POP3 protocol string command corresponding to a command code. - ***/ - public static final String getCommand(int command) - { - return _commands[command]; + */ + public static String getCommand(final int command) { + return commands[command]; + } + + // Cannot be instantiated. + private POP3Command() { } } diff --git a/src/main/java/org/apache/commons/net/pop3/POP3MessageInfo.java b/src/main/java/org/apache/commons/net/pop3/POP3MessageInfo.java index 08567a4..8f5f420 100644 --- a/src/main/java/org/apache/commons/net/pop3/POP3MessageInfo.java +++ b/src/main/java/org/apache/commons/net/pop3/POP3MessageInfo.java @@ -17,68 +17,57 @@ package org.apache.commons.net.pop3; -/*** - * POP3MessageInfo is used to return information about messages stored on - * a POP3 server. Its fields are used to mean slightly different things - * depending on the information being returned. +/** + * POP3MessageInfo is used to return information about messages stored on a POP3 server. Its fields are used to mean slightly different things depending on the + * information being returned. * <p> - * In response to a status command, <code> number </code> - * contains the number of messages in the mailbox, <code> size </code> - * contains the size of the mailbox in bytes, and <code> identifier </code> - * is null. + * In response to a status command, <code> number </code> contains the number of messages in the mailbox, <code> size </code> contains the size of the mailbox + * in bytes, and <code> identifier </code> is null. * <p> - * In response to a message listings, <code> number </code> - * contains the message number, <code> size </code> contains the - * size of the message in bytes, and <code> identifier </code> is null. + * In response to a message listings, <code> number </code> contains the message number, <code> size </code> contains the size of the message in bytes, and + * <code> identifier </code> is null. * <p> - * In response to unique identifier listings, <code> number </code> contains - * the message number, <code> size </code> is undefined, and - * <code> identifier </code> contains the message's unique identifier. + * In response to unique identifier listings, <code> number </code> contains the message number, <code> size </code> is undefined, and <code> identifier </code> + * contains the message's unique identifier. * * - ***/ + */ -public final class POP3MessageInfo -{ +public final class POP3MessageInfo { public int number; public int size; public String identifier; - /*** - * Creates a POP3MessageInfo instance with <code>number</code> and - * <code> size </code> set to 0, and <code>identifier</code> set to - * null. - ***/ - public POP3MessageInfo() - { + /** + * Creates a POP3MessageInfo instance with <code>number</code> and <code> size </code> set to 0, and <code>identifier</code> set to null. + */ + public POP3MessageInfo() { this(0, null, 0); } - /*** - * Creates a POP3MessageInfo instance with <code>number</code> set - * to <code> num </code>, <code> size </code> set to <code> octets </code>, - * and <code>identifier</code> set to null. - * @param num the number + /** + * Creates a POP3MessageInfo instance with <code>number</code> set to <code> num </code>, <code> size </code> set to <code> octets </code>, and + * <code>identifier</code> set to null. + * + * @param num the number * @param octets the size - ***/ - public POP3MessageInfo(int num, int octets) - { + */ + public POP3MessageInfo(final int num, final int octets) { this(num, null, octets); } - /*** - * Creates a POP3MessageInfo instance with <code>number</code> set - * to <code> num </code>, <code> size </code> undefined, - * and <code>identifier</code> set to <code>uid</code>. + /** + * Creates a POP3MessageInfo instance with <code>number</code> set to <code> num </code>, <code> size </code> undefined, and <code>identifier</code> set to + * <code>uid</code>. + * * @param num the number * @param uid the UID - ***/ - public POP3MessageInfo(int num, String uid) - { + */ + public POP3MessageInfo(final int num, final String uid) { this(num, uid, -1); } - private POP3MessageInfo(int num, String uid, int size) { + private POP3MessageInfo(final int num, final String uid, final int size) { this.number = num; this.size = size; this.identifier = uid; diff --git a/src/main/java/org/apache/commons/net/pop3/POP3Reply.java b/src/main/java/org/apache/commons/net/pop3/POP3Reply.java index 77f869d..757ccaa 100644 --- a/src/main/java/org/apache/commons/net/pop3/POP3Reply.java +++ b/src/main/java/org/apache/commons/net/pop3/POP3Reply.java @@ -17,25 +17,25 @@ package org.apache.commons.net.pop3; -/*** +/** * POP3Reply stores POP3 reply code constants. - ***/ + */ -public final class POP3Reply -{ - /*** The reply code indicating success of an operation. ***/ +public final class POP3Reply { + /** The reply code indicating success of an operation. */ public static final int OK = 0; - /*** The reply code indicating failure of an operation. ***/ + /** The reply code indicating failure of an operation. */ public static final int ERROR = 1; /** * The reply code indicating intermediate response to a command. + * * @since 3.0 */ public static final int OK_INT = 2; // Cannot be instantiated. - private POP3Reply() - {} + private POP3Reply() { + } } diff --git a/src/main/java/org/apache/commons/net/pop3/POP3SClient.java b/src/main/java/org/apache/commons/net/pop3/POP3SClient.java index ff08d6f..841aee3 100644 --- a/src/main/java/org/apache/commons/net/pop3/POP3SClient.java +++ b/src/main/java/org/apache/commons/net/pop3/POP3SClient.java @@ -36,29 +36,21 @@ import org.apache.commons.net.util.SSLContextUtils; import org.apache.commons.net.util.SSLSocketUtils; /** - * POP3 over SSL processing. Copied from FTPSClient.java and modified to suit POP3. - * If implicit mode is selected (NOT the default), SSL/TLS negotiation starts right - * after the connection has been established. In explicit mode (the default), SSL/TLS - * negotiation starts when the user calls execTLS() and the server accepts the command. - * Implicit usage: - * POP3SClient c = new POP3SClient(true); - * c.connect("127.0.0.1", 995); - * Explicit usage: - * POP3SClient c = new POP3SClient(); - * c.connect("127.0.0.1", 110); - * if (c.execTLS()) { /rest of the commands here/ } + * POP3 over SSL processing. Copied from FTPSClient.java and modified to suit POP3. If implicit mode is selected (NOT the default), SSL/TLS negotiation starts + * right after the connection has been established. In explicit mode (the default), SSL/TLS negotiation starts when the user calls execTLS() and the server + * accepts the command. Implicit usage: POP3SClient c = new POP3SClient(true); c.connect("127.0.0.1", 995); Explicit usage: POP3SClient c = new POP3SClient(); + * c.connect("127.0.0.1", 110); if (c.execTLS()) { /rest of the commands here/ } + * + * Warning: the hostname is not verified against the certificate by default, use {@link #setHostnameVerifier(HostnameVerifier)} or + * {@link #setEndpointCheckingEnabled(boolean)} (on Java 1.7+) to enable verification. * - * Warning: the hostname is not verified against the certificate by default, use - * {@link #setHostnameVerifier(HostnameVerifier)} or {@link #setEndpointCheckingEnabled(boolean)} - * (on Java 1.7+) to enable verification. * @since 3.0 */ -public class POP3SClient extends POP3Client -{ +public class POP3SClient extends POP3Client { // from http://www.iana.org/assignments/port-numbers - // pop3s 995/tcp pop3 protocol over TLS/SSL (was spop3) - // pop3s 995/udp pop3 protocol over TLS/SSL (was spop3) + // pop3s 995/tcp pop3 protocol over TLS/SSL (was spop3) + // pop3s 995/udp pop3 protocol over TLS/SSL (was spop3) private static final int DEFAULT_POP3S_PORT = 995; @@ -70,74 +62,90 @@ public class POP3SClient extends POP3Client /** The secure socket protocol to be used, like SSL/TLS. */ private final String protocol; /** The context object. */ - private SSLContext context = null; - /** The cipher suites. SSLSockets have a default set of these anyway, - so no initialization required. */ - private String[] suites = null; + private SSLContext context; + /** + * The cipher suites. SSLSockets have a default set of these anyway, so no initialization required. + */ + private String[] suites; /** The protocol versions. */ - private String[] protocols = //null; - null;//{"SSLv2", "SSLv3", "TLSv1", "TLSv1.1", "SSLv2Hello"}; + private String[] protocols // null; + ;// {"SSLv2", "SSLv3", "TLSv1", "TLSv1.1", "SSLv2Hello"}; /** The FTPS {@link TrustManager} implementation, default null. */ - private TrustManager trustManager = null; + private TrustManager trustManager; /** The {@link KeyManager}, default null. */ - private KeyManager keyManager = null; + private KeyManager keyManager; /** The {@link HostnameVerifier} to use post-TLS, default null (i.e. no verification). */ - private HostnameVerifier hostnameVerifier = null; + private HostnameVerifier hostnameVerifier; /** Use Java 1.7+ HTTPS Endpoint Identification Algorithim. */ private boolean tlsEndpointChecking; /** - * Constructor for POP3SClient, using {@link #DEFAULT_PROTOCOL} i.e. TLS - * Sets security mode to explicit. + * Constructor for POP3SClient, using {@link #DEFAULT_PROTOCOL} i.e. TLS Sets security mode to explicit. */ - public POP3SClient() - { + public POP3SClient() { this(DEFAULT_PROTOCOL, false); } /** * Constructor for POP3SClient, using {@link #DEFAULT_PROTOCOL} i.e. TLS + * * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit */ - public POP3SClient(boolean implicit) - { + public POP3SClient(final boolean implicit) { this(DEFAULT_PROTOCOL, implicit); } /** - * Constructor for POP3SClient. - * Sets security mode to explicit. + * Constructor for POP3SClient, using {@link #DEFAULT_PROTOCOL} i.e. TLS + * + * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit + * @param ctx A pre-configured SSL Context. + */ + public POP3SClient(final boolean implicit, final SSLContext ctx) { + this(DEFAULT_PROTOCOL, implicit, ctx); + } + + /** + * Constructor for POP3SClient, using {@link #DEFAULT_PROTOCOL} - TLS - and isImplicit = false + * + * @param context A pre-configured SSL Context. + * @see #POP3SClient(boolean, SSLContext) + */ + public POP3SClient(final SSLContext context) { + this(false, context); + } + + /** + * Constructor for POP3SClient. Sets security mode to explicit. + * * @param proto the protocol. */ - public POP3SClient(String proto) - { + public POP3SClient(final String proto) { this(proto, false); } /** * Constructor for POP3SClient. - * @param proto the protocol. + * + * @param proto the protocol. * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit */ - public POP3SClient(String proto, boolean implicit) - { + public POP3SClient(final String proto, final boolean implicit) { this(proto, implicit, null); } /** - * Constructor for POP3SClient. - * Sets the default port to {@link #DEFAULT_POP3S_PORT} - 995 - if using implicit mode - * @param proto the protocol. + * Constructor for POP3SClient. Sets the default port to {@link #DEFAULT_POP3S_PORT} - 995 - if using implicit mode + * + * @param proto the protocol. * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit - * @param ctx the context to be used + * @param ctx the context to be used */ - public POP3SClient(String proto, boolean implicit, SSLContext ctx) - { - super(); + public POP3SClient(final String proto, final boolean implicit, final SSLContext ctx) { protocol = proto; isImplicit = implicit; context = ctx; @@ -147,38 +155,17 @@ public class POP3SClient extends POP3Client } /** - * Constructor for POP3SClient, using {@link #DEFAULT_PROTOCOL} i.e. TLS - * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit - * @param ctx A pre-configured SSL Context. - */ - public POP3SClient(boolean implicit, SSLContext ctx) - { - this(DEFAULT_PROTOCOL, implicit, ctx); - } - - /** - * Constructor for POP3SClient, using {@link #DEFAULT_PROTOCOL} - TLS - and isImplicit = false - * @param context A pre-configured SSL Context. - * @see #POP3SClient(boolean, SSLContext) - */ - public POP3SClient(SSLContext context) - { - this(false, context); - } - - /** - * Because there are so many connect() methods, - * the _connectAction_() method is provided as a means of performing - * some action immediately after establishing a connection, - * rather than reimplementing all of the connect() methods. + * Because there are so many connect() methods, the _connectAction_() method is provided as a means of performing some action immediately after establishing + * a connection, rather than reimplementing all of the connect() methods. + * * @throws IOException If it is thrown by _connectAction_(). * @see org.apache.commons.net.SocketClient#_connectAction_() */ @Override - protected void _connectAction_() throws IOException - { + protected void _connectAction_() throws IOException { // Implicit mode. if (isImplicit) { + applySocketAttributes(); performSSLNegotiation(); } super._connectAction_(); @@ -186,208 +173,191 @@ public class POP3SClient extends POP3Client } /** - * Performs a lazy init of the SSL context. - * @throws IOException When could not initialize the SSL context. + * The TLS command execution. + * + * @throws SSLException If the server reply code is not positive. + * @throws IOException If an I/O error occurs while sending the command or performing the negotiation. + * @return TRUE if the command and negotiation succeeded. */ - private void initSSLContext() throws IOException - { - if (context == null) - { - context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager()); + public boolean execTLS() throws SSLException, IOException { + if (sendCommand("STLS") != POP3Reply.OK) { + return false; + // throw new SSLException(getReplyString()); } + performSSLNegotiation(); + return true; } /** - * SSL/TLS negotiation. Acquires an SSL socket of a - * connection and carries out handshake processing. - * @throws IOException If server negotiation fails. + * Returns the names of the cipher suites which could be enabled for use on this connection. When the underlying {@link java.net.Socket Socket} is not an + * {@link SSLSocket} instance, returns null. + * + * @return An array of cipher suite names, or <code>null</code>. */ - private void performSSLNegotiation() throws IOException - { - initSSLContext(); - - SSLSocketFactory ssf = context.getSocketFactory(); - String host = (_hostname_ != null) ? _hostname_ : getRemoteAddress().getHostAddress(); - int port = getRemotePort(); - SSLSocket socket = - (SSLSocket) ssf.createSocket(_socket_, host, port, true); - socket.setEnableSessionCreation(true); - socket.setUseClientMode(true); - - if (tlsEndpointChecking) { - SSLSocketUtils.enableEndpointNameVerification(socket); - } - - if (protocols != null) { - socket.setEnabledProtocols(protocols); - } - if (suites != null) { - socket.setEnabledCipherSuites(suites); + public String[] getEnabledCipherSuites() { + if (_socket_ instanceof SSLSocket) { + return ((SSLSocket) _socket_).getEnabledCipherSuites(); } - socket.startHandshake(); - - // TODO the following setup appears to duplicate that in the super class methods - _socket_ = socket; - _input_ = socket.getInputStream(); - _output_ = socket.getOutputStream(); - _reader = new CRLFLineReader(new InputStreamReader(_input_, _DEFAULT_ENCODING)); - _writer = new BufferedWriter(new OutputStreamWriter(_output_, _DEFAULT_ENCODING)); + return null; + } - if (hostnameVerifier != null && !hostnameVerifier.verify(host, socket.getSession())) { - throw new SSLHandshakeException("Hostname doesn't match certificate"); + /** + * Returns the names of the protocol versions which are currently enabled for use on this connection. When the underlying {@link java.net.Socket Socket} is + * not an {@link SSLSocket} instance, returns null. + * + * @return An array of protocols, or <code>null</code>. + */ + public String[] getEnabledProtocols() { + if (_socket_ instanceof SSLSocket) { + return ((SSLSocket) _socket_).getEnabledProtocols(); } + return null; } /** - * Get the {@link KeyManager} instance. - * @return The current {@link KeyManager} instance. + * Get the currently configured {@link HostnameVerifier}. + * + * @return A HostnameVerifier instance. + * @since 3.4 */ - private KeyManager getKeyManager() - { - return keyManager; + public HostnameVerifier getHostnameVerifier() { + return hostnameVerifier; } /** - * Set a {@link KeyManager} to use. - * @param newKeyManager The KeyManager implementation to set. - * @see org.apache.commons.net.util.KeyManagerUtils + * Get the {@link KeyManager} instance. + * + * @return The current {@link KeyManager} instance. */ - public void setKeyManager(KeyManager newKeyManager) - { - keyManager = newKeyManager; + private KeyManager getKeyManager() { + return keyManager; } /** - * Controls which particular cipher suites are enabled for use on this - * connection. Called before server negotiation. - * @param cipherSuites The cipher suites. + * Get the currently configured {@link TrustManager}. + * + * @return A TrustManager instance. */ - public void setEnabledCipherSuites(String[] cipherSuites) - { - suites = new String[cipherSuites.length]; - System.arraycopy(cipherSuites, 0, suites, 0, cipherSuites.length); + public TrustManager getTrustManager() { + return trustManager; } /** - * Returns the names of the cipher suites which could be enabled - * for use on this connection. - * When the underlying {@link java.net.Socket Socket} is not an {@link SSLSocket} instance, returns null. - * @return An array of cipher suite names, or <code>null</code>. + * Performs a lazy init of the SSL context. + * + * @throws IOException When could not initialize the SSL context. */ - public String[] getEnabledCipherSuites() - { - if (_socket_ instanceof SSLSocket) - { - return ((SSLSocket)_socket_).getEnabledCipherSuites(); + private void initSSLContext() throws IOException { + if (context == null) { + context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager()); } - return null; } /** - * Controls which particular protocol versions are enabled for use on this - * connection. I perform setting before a server negotiation. - * @param protocolVersions The protocol versions. + * Return whether or not endpoint identification using the HTTPS algorithm on Java 1.7+ is enabled. The default behavior is for this to be disabled. + * + * @return True if enabled, false if not. + * @since 3.4 */ - public void setEnabledProtocols(String[] protocolVersions) - { - protocols = new String[protocolVersions.length]; - System.arraycopy(protocolVersions, 0, protocols, 0, protocolVersions.length); + public boolean isEndpointCheckingEnabled() { + return tlsEndpointChecking; } /** - * Returns the names of the protocol versions which are currently - * enabled for use on this connection. - * When the underlying {@link java.net.Socket Socket} is not an {@link SSLSocket} instance, returns null. - * @return An array of protocols, or <code>null</code>. + * SSL/TLS negotiation. Acquires an SSL socket of a connection and carries out handshake processing. + * + * @throws IOException If server negotiation fails. */ - public String[] getEnabledProtocols() - { - if (_socket_ instanceof SSLSocket) - { - return ((SSLSocket)_socket_).getEnabledProtocols(); + private void performSSLNegotiation() throws IOException { + initSSLContext(); + + final SSLSocketFactory ssf = context.getSocketFactory(); + final String host = _hostname_ != null ? _hostname_ : getRemoteAddress().getHostAddress(); + final int port = getRemotePort(); + final SSLSocket socket = (SSLSocket) ssf.createSocket(_socket_, host, port, true); + socket.setEnableSessionCreation(true); + socket.setUseClientMode(true); + + if (tlsEndpointChecking) { + SSLSocketUtils.enableEndpointNameVerification(socket); } - return null; - } - /** - * The TLS command execution. - * @throws SSLException If the server reply code is not positive. - * @throws IOException If an I/O error occurs while sending - * the command or performing the negotiation. - * @return TRUE if the command and negotiation succeeded. - */ - public boolean execTLS() throws SSLException, IOException - { - if (sendCommand("STLS") != POP3Reply.OK) - { - return false; - //throw new SSLException(getReplyString()); + if (protocols != null) { + socket.setEnabledProtocols(protocols); + } + if (suites != null) { + socket.setEnabledCipherSuites(suites); + } + socket.startHandshake(); + + // TODO the following setup appears to duplicate that in the super class methods + _socket_ = socket; + _input_ = socket.getInputStream(); + _output_ = socket.getOutputStream(); + reader = new CRLFLineReader(new InputStreamReader(_input_, DEFAULT_ENCODING)); + writer = new BufferedWriter(new OutputStreamWriter(_output_, DEFAULT_ENCODING)); + + if (hostnameVerifier != null && !hostnameVerifier.verify(host, socket.getSession())) { + throw new SSLHandshakeException("Hostname doesn't match certificate"); } - performSSLNegotiation(); - return true; } /** - * Get the currently configured {@link TrustManager}. - * @return A TrustManager instance. + * Controls which particular cipher suites are enabled for use on this connection. Called before server negotiation. + * + * @param cipherSuites The cipher suites. */ - public TrustManager getTrustManager() - { - return trustManager; + public void setEnabledCipherSuites(final String[] cipherSuites) { + suites = cipherSuites.clone(); } /** - * Override the default {@link TrustManager} to use. - * @param newTrustManager The TrustManager implementation to set. - * @see org.apache.commons.net.util.TrustManagerUtils + * Controls which particular protocol versions are enabled for use on this connection. I perform setting before a server negotiation. + * + * @param protocolVersions The protocol versions. */ - public void setTrustManager(TrustManager newTrustManager) - { - trustManager = newTrustManager; + public void setEnabledProtocols(final String[] protocolVersions) { + protocols = protocolVersions.clone(); } /** - * Get the currently configured {@link HostnameVerifier}. - * @return A HostnameVerifier instance. + * Automatic endpoint identification checking using the HTTPS algorithm is supported on Java 1.7+. The default behavior is for this to be disabled. + * + * @param enable Enable automatic endpoint identification checking using the HTTPS algorithm on Java 1.7+. * @since 3.4 */ - public HostnameVerifier getHostnameVerifier() - { - return hostnameVerifier; + public void setEndpointCheckingEnabled(final boolean enable) { + tlsEndpointChecking = enable; } /** * Override the default {@link HostnameVerifier} to use. + * * @param newHostnameVerifier The HostnameVerifier implementation to set or <code>null</code> to disable. * @since 3.4 */ - public void setHostnameVerifier(HostnameVerifier newHostnameVerifier) - { + public void setHostnameVerifier(final HostnameVerifier newHostnameVerifier) { hostnameVerifier = newHostnameVerifier; } /** - * Return whether or not endpoint identification using the HTTPS algorithm - * on Java 1.7+ is enabled. The default behaviour is for this to be disabled. + * Set a {@link KeyManager} to use. * - * @return True if enabled, false if not. - * @since 3.4 + * @param newKeyManager The KeyManager implementation to set. + * @see org.apache.commons.net.util.KeyManagerUtils */ - public boolean isEndpointCheckingEnabled() - { - return tlsEndpointChecking; + public void setKeyManager(final KeyManager newKeyManager) { + keyManager = newKeyManager; } /** - * Automatic endpoint identification checking using the HTTPS algorithm - * is supported on Java 1.7+. The default behaviour is for this to be disabled. + * Override the default {@link TrustManager} to use. * - * @param enable Enable automatic endpoint identification checking using the HTTPS algorithm on Java 1.7+. - * @since 3.4 + * @param newTrustManager The TrustManager implementation to set. + * @see org.apache.commons.net.util.TrustManagerUtils */ - public void setEndpointCheckingEnabled(boolean enable) - { - tlsEndpointChecking = enable; + public void setTrustManager(final TrustManager newTrustManager) { + trustManager = newTrustManager; } } diff --git a/src/main/java/org/apache/commons/net/smtp/AuthenticatingSMTPClient.java b/src/main/java/org/apache/commons/net/smtp/AuthenticatingSMTPClient.java index 30dde84..73c34d3 100644 --- a/src/main/java/org/apache/commons/net/smtp/AuthenticatingSMTPClient.java +++ b/src/main/java/org/apache/commons/net/smtp/AuthenticatingSMTPClient.java @@ -22,264 +22,192 @@ import java.net.InetAddress; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; + import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.SSLContext; import org.apache.commons.net.util.Base64; - /** * An SMTP Client class with authentication support (RFC4954). * * @see SMTPClient * @since 3.0 */ -public class AuthenticatingSMTPClient extends SMTPSClient -{ +public class AuthenticatingSMTPClient extends SMTPSClient { /** - * The default AuthenticatingSMTPClient constructor. - * Creates a new Authenticating SMTP Client. + * The enumeration of currently-supported authentication methods. */ - public AuthenticatingSMTPClient() - { - super(); + public enum AUTH_METHOD { + /** The standarised (RFC4616) PLAIN method, which sends the password unencrypted (insecure). */ + PLAIN, + /** The standarised (RFC2195) CRAM-MD5 method, which doesn't send the password (secure). */ + CRAM_MD5, + /** The unstandarised Microsoft LOGIN method, which sends the password unencrypted (insecure). */ + LOGIN, + /** XOAuth method which accepts a signed and base64ed OAuth URL. */ + XOAUTH, + /** XOAuth 2 method which accepts a signed and base64ed OAuth JSON. */ + XOAUTH2; + + /** + * Gets the name of the given authentication method suitable for the server. + * + * @param method The authentication method to get the name for. + * @return The name of the given authentication method suitable for the server. + */ + public static final String getAuthName(final AUTH_METHOD method) { + if (method.equals(AUTH_METHOD.PLAIN)) { + return "PLAIN"; + } + if (method.equals(AUTH_METHOD.CRAM_MD5)) { + return "CRAM-MD5"; + } + if (method.equals(AUTH_METHOD.LOGIN)) { + return "LOGIN"; + } + if (method.equals(AUTH_METHOD.XOAUTH)) { + return "XOAUTH"; + } + if (method.equals(AUTH_METHOD.XOAUTH2)) { + return "XOAUTH2"; + } + return null; + } } /** - * Overloaded constructor that takes a protocol specification - * @param protocol The protocol to use + * The default AuthenticatingSMTPClient constructor. Creates a new Authenticating SMTP Client. */ - public AuthenticatingSMTPClient(String protocol) { - super(protocol); + public AuthenticatingSMTPClient() { } /** - * Overloaded constructor that takes a protocol specification and the implicit argument - * @param proto the protocol. + * Overloaded constructor that takes the implicit argument, and using {@link #DEFAULT_PROTOCOL} i.e. TLS + * * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit + * @param ctx A pre-configured SSL Context. * @since 3.3 */ - public AuthenticatingSMTPClient(String proto, boolean implicit) - { - super(proto, implicit); + public AuthenticatingSMTPClient(final boolean implicit, final SSLContext ctx) { + super(implicit, ctx); } /** - * Overloaded constructor that takes the protocol specification, the implicit argument and encoding - * @param proto the protocol. + * Overloaded constructor that takes a protocol specification + * + * @param protocol The protocol to use + */ + public AuthenticatingSMTPClient(final String protocol) { + super(protocol); + } + + /** + * Overloaded constructor that takes a protocol specification and the implicit argument + * + * @param proto the protocol. * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit - * @param encoding the encoding * @since 3.3 */ - public AuthenticatingSMTPClient(String proto, boolean implicit, String encoding) - { - super(proto, implicit, encoding); + public AuthenticatingSMTPClient(final String proto, final boolean implicit) { + super(proto, implicit); } /** - * Overloaded constructor that takes the implicit argument, and using {@link #DEFAULT_PROTOCOL} i.e. TLS + * Overloaded constructor that takes the protocol specification, the implicit argument and encoding + * + * @param proto the protocol. * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit - * @param ctx A pre-configured SSL Context. + * @param encoding the encoding * @since 3.3 */ - public AuthenticatingSMTPClient(boolean implicit, SSLContext ctx) - { - super(implicit, ctx); + public AuthenticatingSMTPClient(final String proto, final boolean implicit, final String encoding) { + super(proto, implicit, encoding); } /** * Overloaded constructor that takes a protocol specification and encoding + * * @param protocol The protocol to use * @param encoding The encoding to use * @since 3.3 */ - public AuthenticatingSMTPClient(String protocol, String encoding) { + public AuthenticatingSMTPClient(final String protocol, final String encoding) { super(protocol, false, encoding); } - /*** - * A convenience method to send the ESMTP EHLO command to the server, - * receive the reply, and return the reply code. - * <p> - * @param hostname The hostname of the sender. - * @return The reply code received from the server. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int ehlo(String hostname) throws IOException - { - return sendCommand(SMTPCommand.EHLO, hostname); - } - - /*** - * Login to the ESMTP server by sending the EHLO command with the - * given hostname as an argument. Before performing any mail commands, - * you must first login. - * <p> - * @param hostname The hostname with which to greet the SMTP server. - * @return True if successfully completed, false if not. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean elogin(String hostname) throws IOException - { - return SMTPReply.isPositiveCompletion(ehlo(hostname)); - } - - - /*** - * Login to the ESMTP server by sending the EHLO command with the - * client hostname as an argument. Before performing any mail commands, - * you must first login. - * <p> - * @return True if successfully completed, false if not. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean elogin() throws IOException - { - String name; - InetAddress host; - - host = getLocalAddress(); - name = host.getHostName(); - - if (name == null) { - return false; - } - - return SMTPReply.isPositiveCompletion(ehlo(name)); - } - - /*** - * Returns the integer values of the enhanced reply code of the last SMTP reply. - * @return The integer values of the enhanced reply code of the last SMTP reply. - * First digit is in the first array element. - ***/ - public int[] getEnhancedReplyCode() - { - String reply = getReplyString().substring(4); - String[] parts = reply.substring(0, reply.indexOf(' ')).split ("\\."); - int[] res = new int[parts.length]; - for (int i = 0; i < parts.length; i++) - { - res[i] = Integer.parseInt (parts[i]); - } - return res; - } - - /*** - * Authenticate to the SMTP server by sending the AUTH command with the - * selected mechanism, using the given username and the given password. + /** + * Authenticate to the SMTP server by sending the AUTH command with the selected mechanism, using the given username and the given password. * - * @param method the method to use, one of the {@link AuthenticatingSMTPClient.AUTH_METHOD} enum values - * @param username the user name. - * If the method is XOAUTH, then this is used as the plain text oauth protocol parameter string - * which is Base64-encoded for transmission. - * @param password the password for the username. - * Ignored for XOAUTH. + * @param method the method to use, one of the {@link AuthenticatingSMTPClient.AUTH_METHOD} enum values + * @param username the user name. If the method is XOAUTH/XOAUTH2, then this is used as the plain text oauth protocol parameter string which is + * Base64-encoded for transmission. + * @param password the password for the username. Ignored for XOAUTH/XOAUTH2. * * @return True if successfully completed, false if not. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - * @throws NoSuchAlgorithmException If the CRAM hash algorithm - * cannot be instantiated by the Java runtime system. - * @throws InvalidKeyException If the CRAM hash algorithm - * failed to use the given password. - * @throws InvalidKeySpecException If the CRAM hash algorithm - * failed to use the given password. - ***/ - public boolean auth(AuthenticatingSMTPClient.AUTH_METHOD method, - String username, String password) - throws IOException, NoSuchAlgorithmException, - InvalidKeyException, InvalidKeySpecException - { - if (!SMTPReply.isPositiveIntermediate(sendCommand(SMTPCommand.AUTH, - AUTH_METHOD.getAuthName(method)))) { + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + * @throws NoSuchAlgorithmException If the CRAM hash algorithm cannot be instantiated by the Java runtime system. + * @throws InvalidKeyException If the CRAM hash algorithm failed to use the given password. + * @throws InvalidKeySpecException If the CRAM hash algorithm failed to use the given password. + */ + public boolean auth(final AuthenticatingSMTPClient.AUTH_METHOD method, final String username, final String password) + throws IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException { + if (!SMTPReply.isPositiveIntermediate(sendCommand(SMTPCommand.AUTH, AUTH_METHOD.getAuthName(method)))) { return false; } - if (method.equals(AUTH_METHOD.PLAIN)) - { + if (method.equals(AUTH_METHOD.PLAIN)) { // the server sends an empty response ("334 "), so we don't have to read it. - return SMTPReply.isPositiveCompletion(sendCommand( - Base64.encodeBase64StringUnChunked(("\000" + username + "\000" + password).getBytes(getCharset())) - )); + return SMTPReply + .isPositiveCompletion(sendCommand(Base64.encodeBase64StringUnChunked(("\000" + username + "\000" + password).getBytes(getCharset())))); } - else if (method.equals(AUTH_METHOD.CRAM_MD5)) - { + if (method.equals(AUTH_METHOD.CRAM_MD5)) { // get the CRAM challenge - byte[] serverChallenge = Base64.decodeBase64(getReplyString().substring(4).trim()); + final byte[] serverChallenge = Base64.decodeBase64(getReplyString().substring(4).trim()); // get the Mac instance - Mac hmac_md5 = Mac.getInstance("HmacMD5"); + final Mac hmac_md5 = Mac.getInstance("HmacMD5"); hmac_md5.init(new SecretKeySpec(password.getBytes(getCharset()), "HmacMD5")); // compute the result: - byte[] hmacResult = _convertToHexString(hmac_md5.doFinal(serverChallenge)).getBytes(getCharset()); + final byte[] hmacResult = convertToHexString(hmac_md5.doFinal(serverChallenge)).getBytes(getCharset()); // join the byte arrays to form the reply - byte[] usernameBytes = username.getBytes(getCharset()); - byte[] toEncode = new byte[usernameBytes.length + 1 /* the space */ + hmacResult.length]; + final byte[] usernameBytes = username.getBytes(getCharset()); + final byte[] toEncode = new byte[usernameBytes.length + 1 /* the space */ + hmacResult.length]; System.arraycopy(usernameBytes, 0, toEncode, 0, usernameBytes.length); toEncode[usernameBytes.length] = ' '; System.arraycopy(hmacResult, 0, toEncode, usernameBytes.length + 1, hmacResult.length); // send the reply and read the server code: - return SMTPReply.isPositiveCompletion(sendCommand( - Base64.encodeBase64StringUnChunked(toEncode))); + return SMTPReply.isPositiveCompletion(sendCommand(Base64.encodeBase64StringUnChunked(toEncode))); } - else if (method.equals(AUTH_METHOD.LOGIN)) - { + if (method.equals(AUTH_METHOD.LOGIN)) { // the server sends fixed responses (base64("Username") and // base64("Password")), so we don't have to read them. - if (!SMTPReply.isPositiveIntermediate(sendCommand( - Base64.encodeBase64StringUnChunked(username.getBytes(getCharset()))))) { + if (!SMTPReply.isPositiveIntermediate(sendCommand(Base64.encodeBase64StringUnChunked(username.getBytes(getCharset()))))) { return false; } - return SMTPReply.isPositiveCompletion(sendCommand( - Base64.encodeBase64StringUnChunked(password.getBytes(getCharset())))); + return SMTPReply.isPositiveCompletion(sendCommand(Base64.encodeBase64StringUnChunked(password.getBytes(getCharset())))); } - else if (method.equals(AUTH_METHOD.XOAUTH)) - { - return SMTPReply.isPositiveIntermediate(sendCommand( - Base64.encodeBase64StringUnChunked(username.getBytes(getCharset())) - )); - } else { - return false; // safety check + if (method.equals(AUTH_METHOD.XOAUTH) || method.equals(AUTH_METHOD.XOAUTH2)) { + return SMTPReply.isPositiveIntermediate(sendCommand(Base64.encodeBase64StringUnChunked(username.getBytes(getCharset())))); } + return false; // safety check } /** - * Converts the given byte array to a String containing the hex values of the bytes. - * For example, the byte 'A' will be converted to '41', because this is the ASCII code - * (and the byte value) of the capital letter 'A'. + * Converts the given byte array to a String containing the hex values of the bytes. For example, the byte 'A' will be converted to '41', because this is + * the ASCII code (and the byte value) of the capital letter 'A'. + * * @param a The byte array to convert. * @return The resulting String of hex codes. */ - private String _convertToHexString(byte[] a) - { - StringBuilder result = new StringBuilder(a.length*2); - for (byte element : a) - { - if ( (element & 0x0FF) <= 15 ) { + private String convertToHexString(final byte[] a) { + final StringBuilder result = new StringBuilder(a.length * 2); + for (final byte element : a) { + if ((element & 0x0FF) <= 15) { result.append("0"); } result.append(Integer.toHexString(element & 0x0FF)); @@ -288,38 +216,70 @@ public class AuthenticatingSMTPClient extends SMTPSClient } /** - * The enumeration of currently-supported authentication methods. + * A convenience method to send the ESMTP EHLO command to the server, receive the reply, and return the reply code. + * <p> + * + * @param hostname The hostname of the sender. + * @return The reply code received from the server. + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - public static enum AUTH_METHOD - { - /** The standarised (RFC4616) PLAIN method, which sends the password unencrypted (insecure). */ - PLAIN, - /** The standarised (RFC2195) CRAM-MD5 method, which doesn't send the password (secure). */ - CRAM_MD5, - /** The unstandarised Microsoft LOGIN method, which sends the password unencrypted (insecure). */ - LOGIN, - /** XOAuth method which accepts a signed and base64ed OAuth URL. */ - XOAUTH; + public int ehlo(final String hostname) throws IOException { + return sendCommand(SMTPCommand.EHLO, hostname); + } - /** - * Gets the name of the given authentication method suitable for the server. - * @param method The authentication method to get the name for. - * @return The name of the given authentication method suitable for the server. - */ - public static final String getAuthName(AUTH_METHOD method) - { - if (method.equals(AUTH_METHOD.PLAIN)) { - return "PLAIN"; - } else if (method.equals(AUTH_METHOD.CRAM_MD5)) { - return "CRAM-MD5"; - } else if (method.equals(AUTH_METHOD.LOGIN)) { - return "LOGIN"; - } else if (method.equals(AUTH_METHOD.XOAUTH)) { - return "XOAUTH"; - } else { - return null; - } + /** + * Login to the ESMTP server by sending the EHLO command with the client hostname as an argument. Before performing any mail commands, you must first login. + * <p> + * + * @return True if successfully completed, false if not. + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean elogin() throws IOException { + final String name; + final InetAddress host; + + host = getLocalAddress(); + name = host.getHostName(); + + if (name == null) { + return false; } + + return SMTPReply.isPositiveCompletion(ehlo(name)); + } + + /** + * Login to the ESMTP server by sending the EHLO command with the given hostname as an argument. Before performing any mail commands, you must first login. + * <p> + * + * @param hostname The hostname with which to greet the SMTP server. + * @return True if successfully completed, false if not. + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean elogin(final String hostname) throws IOException { + return SMTPReply.isPositiveCompletion(ehlo(hostname)); + } + + /** + * Returns the integer values of the enhanced reply code of the last SMTP reply. + * + * @return The integer values of the enhanced reply code of the last SMTP reply. First digit is in the first array element. + */ + public int[] getEnhancedReplyCode() { + final String reply = getReplyString().substring(4); + final String[] parts = reply.substring(0, reply.indexOf(' ')).split("\\."); + final int[] res = new int[parts.length]; + Arrays.setAll(res, i -> Integer.parseInt(parts[i])); + return res; } } diff --git a/src/main/java/org/apache/commons/net/smtp/RelayPath.java b/src/main/java/org/apache/commons/net/smtp/RelayPath.java index 0fbe98d..b8d3ed7 100644 --- a/src/main/java/org/apache/commons/net/smtp/RelayPath.java +++ b/src/main/java/org/apache/commons/net/smtp/RelayPath.java @@ -20,78 +20,72 @@ package org.apache.commons.net.smtp; import java.util.Enumeration; import java.util.Vector; -/*** - * A class used to represent forward and reverse relay paths. The - * SMTP MAIL command requires a reverse relay path while the SMTP RCPT - * command requires a forward relay path. See RFC 821 for more details. - * In general, you will not have to deal with relay paths. +/** + * A class used to represent forward and reverse relay paths. The SMTP MAIL command requires a reverse relay path while the SMTP RCPT command requires a forward + * relay path. See RFC 821 for more details. In general, you will not have to deal with relay paths. * * @see SMTPClient - ***/ + */ -public final class RelayPath -{ - Vector<String> _path; - String _emailAddress; +public final class RelayPath { + private final Vector<String> path; + private final String emailAddress; - /*** - * Create a relay path with the specified email address as the ultimate - * destination. + /** + * Create a relay path with the specified email address as the ultimate destination. * <p> + * * @param emailAddress The destination email address. - ***/ - public RelayPath(String emailAddress) - { - _path = new Vector<String>(); - _emailAddress = emailAddress; + */ + public RelayPath(final String emailAddress) { + this.path = new Vector<>(); + this.emailAddress = emailAddress; } - /*** - * Add a mail relay host to the relay path. Hosts are added left to - * right. For example, the following will create the path + /** + * Add a mail relay host to the relay path. Hosts are added left to right. For example, the following will create the path * <code><b> < @bar.com,@foo.com:foobar@foo.com > </b></code> + * * <pre> * path = new RelayPath("foobar@foo.com"); * path.addRelay("bar.com"); * path.addRelay("foo.com"); * </pre> * <p> + * * @param hostname The host to add to the relay path. - ***/ - public void addRelay(String hostname) - { - _path.addElement(hostname); + */ + public void addRelay(final String hostname) { + path.addElement(hostname); } - /*** + /** * Return the properly formatted string representation of the relay path. * <p> + * * @return The properly formatted string representation of the relay path. - ***/ + */ @Override - public String toString() - { - StringBuilder buffer = new StringBuilder(); - Enumeration<String> hosts; + public String toString() { + final StringBuilder buffer = new StringBuilder(); + final Enumeration<String> hosts; buffer.append('<'); - hosts = _path.elements(); + hosts = path.elements(); - if (hosts.hasMoreElements()) - { + if (hosts.hasMoreElements()) { buffer.append('@'); buffer.append(hosts.nextElement()); - while (hosts.hasMoreElements()) - { + while (hosts.hasMoreElements()) { buffer.append(",@"); buffer.append(hosts.nextElement()); } buffer.append(':'); } - buffer.append(_emailAddress); + buffer.append(emailAddress); buffer.append('>'); return buffer.toString(); diff --git a/src/main/java/org/apache/commons/net/smtp/SMTP.java b/src/main/java/org/apache/commons/net/smtp/SMTP.java index c09dca4..93728b2 100644 --- a/src/main/java/org/apache/commons/net/smtp/SMTP.java +++ b/src/main/java/org/apache/commons/net/smtp/SMTP.java @@ -28,68 +28,43 @@ import org.apache.commons.net.MalformedServerReplyException; import org.apache.commons.net.ProtocolCommandSupport; import org.apache.commons.net.SocketClient; import org.apache.commons.net.io.CRLFLineReader; - -/*** - * SMTP provides the basic the functionality necessary to implement your - * own SMTP client. To derive the full benefits of the SMTP class requires - * some knowledge of the FTP protocol defined in RFC 821. However, there - * is no reason why you should have to use the SMTP class. The - * {@link org.apache.commons.net.smtp.SMTPClient} class, - * derived from SMTP, - * implements all the functionality required of an SMTP client. The - * SMTP class is made public to provide access to various SMTP constants - * and to make it easier for adventurous programmers (or those with - * special needs) to interact with the SMTP protocol and implement their - * own clients. A set of methods with names corresponding to the SMTP - * command names are provided to facilitate this interaction. +import org.apache.commons.net.util.NetConstants; + +/** + * SMTP provides the basic the functionality necessary to implement your own SMTP client. To derive the full benefits of the SMTP class requires some knowledge + * of the FTP protocol defined in RFC 821. However, there is no reason why you should have to use the SMTP class. The + * {@link org.apache.commons.net.smtp.SMTPClient} class, derived from SMTP, implements all the functionality required of an SMTP client. The SMTP class is made + * public to provide access to various SMTP constants and to make it easier for adventurous programmers (or those with special needs) to interact with the SMTP + * protocol and implement their own clients. A set of methods with names corresponding to the SMTP command names are provided to facilitate this interaction. * <p> - * You should keep in mind that the SMTP server may choose to prematurely - * close a connection for various reasons. The SMTP class will detect a - * premature SMTP server connection closing when it receives a - * {@link org.apache.commons.net.smtp.SMTPReply#SERVICE_NOT_AVAILABLE SMTPReply.SERVICE_NOT_AVAILABLE } - * response to a command. - * When that occurs, the SMTP class method encountering that reply will throw - * an {@link org.apache.commons.net.smtp.SMTPConnectionClosedException} - * . - * <code>SMTPConectionClosedException</code> - * is a subclass of <code> IOException </code> and therefore need not be - * caught separately, but if you are going to catch it separately, its - * catch block must appear before the more general <code> IOException </code> - * catch block. When you encounter an - * {@link org.apache.commons.net.smtp.SMTPConnectionClosedException} - * , you must disconnect the connection with - * {@link org.apache.commons.net.SocketClient#disconnect disconnect() } - * to properly clean up the system resources used by SMTP. Before - * disconnecting, you may check the - * last reply code and text with - * {@link #getReplyCode getReplyCode }, - * {@link #getReplyString getReplyString }, - * and {@link #getReplyStrings getReplyStrings}. + * You should keep in mind that the SMTP server may choose to prematurely close a connection for various reasons. The SMTP class will detect a premature SMTP + * server connection closing when it receives a {@link org.apache.commons.net.smtp.SMTPReply#SERVICE_NOT_AVAILABLE SMTPReply.SERVICE_NOT_AVAILABLE } response to + * a command. When that occurs, the SMTP class method encountering that reply will throw an {@link org.apache.commons.net.smtp.SMTPConnectionClosedException} . + * <code>SMTPConectionClosedException</code> is a subclass of <code> IOException </code> and therefore need not be caught separately, but if you are going to + * catch it separately, its catch block must appear before the more general <code> IOException </code> catch block. When you encounter an + * {@link org.apache.commons.net.smtp.SMTPConnectionClosedException} , you must disconnect the connection with + * {@link org.apache.commons.net.SocketClient#disconnect disconnect() } to properly clean up the system resources used by SMTP. Before disconnecting, you may + * check the last reply code and text with {@link #getReplyCode getReplyCode }, {@link #getReplyString getReplyString }, and {@link #getReplyStrings + * getReplyStrings}. * <p> - * Rather than list it separately for each method, we mention here that - * every method communicating with the server and throwing an IOException - * can also throw a - * {@link org.apache.commons.net.MalformedServerReplyException} - * , which is a subclass - * of IOException. A MalformedServerReplyException will be thrown when - * the reply received from the server deviates enough from the protocol - * specification that it cannot be interpreted in a useful manner despite - * attempts to be as lenient as possible. + * Rather than list it separately for each method, we mention here that every method communicating with the server and throwing an IOException can also throw a + * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the + * reply received from the server deviates enough from the protocol specification that it cannot be interpreted in a useful manner despite attempts to be as + * lenient as possible. * * @see SMTPClient * @see SMTPConnectionClosedException * @see org.apache.commons.net.MalformedServerReplyException - ***/ + */ -public class SMTP extends SocketClient -{ - /*** The default SMTP port (25). ***/ +public class SMTP extends SocketClient { + /** The default SMTP port (25). */ public static final int DEFAULT_PORT = 25; // We have to ensure that the protocol communication is in ASCII // but we use ISO-8859-1 just in case 8-bit characters cross // the wire. - private static final String __DEFAULT_ENCODING = "ISO-8859-1"; + private static final String DEFAULT_ENCODING = "ISO-8859-1"; /** * The encoding to use (user-settable). @@ -99,690 +74,528 @@ public class SMTP extends SocketClient protected final String encoding; /** - * A ProtocolCommandSupport object used to manage the registering of - * ProtocolCommandListeners and te firing of ProtocolCommandEvents. + * A ProtocolCommandSupport object used to manage the registering of ProtocolCommandListeners and te firing of ProtocolCommandEvents. */ protected ProtocolCommandSupport _commandSupport_; - BufferedReader _reader; - BufferedWriter _writer; - - private int _replyCode; - private final ArrayList<String> _replyLines; - private boolean _newReplyString; - private String _replyString; - - /*** - * The default SMTP constructor. Sets the default port to - * <code>DEFAULT_PORT</code> and initializes internal data structures - * for saving SMTP reply information. - ***/ - public SMTP() - { - this(__DEFAULT_ENCODING); + BufferedReader reader; + BufferedWriter writer; + + private int replyCode; + private final ArrayList<String> replyLines; + private boolean newReplyString; + private String replyString; + + /** + * The default SMTP constructor. Sets the default port to <code>DEFAULT_PORT</code> and initializes internal data structures for saving SMTP reply + * information. + */ + public SMTP() { + this(DEFAULT_ENCODING); } /** * Overloaded constructor where the user may specify a default encoding. + * * @param encoding the encoing to use * @since 2.0 */ - public SMTP(String encoding) { + public SMTP(final String encoding) { setDefaultPort(DEFAULT_PORT); - _replyLines = new ArrayList<String>(); - _newReplyString = false; - _replyString = null; + replyLines = new ArrayList<>(); + newReplyString = false; + replyString = null; _commandSupport_ = new ProtocolCommandSupport(this); this.encoding = encoding; } + /** Initiates control connections and gets initial reply. */ + @Override + protected void _connectAction_() throws IOException { + super._connectAction_(); + reader = new CRLFLineReader(new InputStreamReader(_input_, encoding)); + writer = new BufferedWriter(new OutputStreamWriter(_output_, encoding)); + getReply(); + } + /** - * Send a command to the server. May also be used to send text data. + * A convenience method to send the SMTP DATA command to the server, receive the reply, and return the reply code. + * <p> * - * @param command the command to send (as a plain String) - * @param args the command arguments, may be {@code null} - * @param includeSpace if {@code true}, add a space between the command and its arguments - * @return the reply code - * @throws IOException + * @return The reply code received from the server. + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - private int __sendCommand(String command, String args, boolean includeSpace) - throws IOException - { - StringBuilder __commandBuffer = new StringBuilder(); - __commandBuffer.append(command); - - if (args != null) - { - if (includeSpace) { - __commandBuffer.append(' '); - } - __commandBuffer.append(args); - } - - __commandBuffer.append(SocketClient.NETASCII_EOL); - - String message; - _writer.write(message = __commandBuffer.toString()); - _writer.flush(); - - fireCommandSent(command, message); + public int data() throws IOException { + return sendCommand(SMTPCommand.DATA); + } - __getReply(); - return _replyCode; + /** + * Closes the connection to the SMTP server and sets to null some internal data so that the memory may be reclaimed by the garbage collector. The reply text + * and code information from the last command is voided so that the memory it used may be reclaimed. + * <p> + * + * @throws IOException If an error occurs while disconnecting. + */ + @Override + public void disconnect() throws IOException { + super.disconnect(); + reader = null; + writer = null; + replyString = null; + replyLines.clear(); + newReplyString = false; } /** + * A convenience method to send the SMTP VRFY command to the server, receive the reply, and return the reply code. + * <p> * - * @param command the command to send (as an int defined in {@link SMPTCommand}) - * @param args the command arguments, may be {@code null} - * @param includeSpace if {@code true}, add a space between the command and its arguments - * @return the reply code - * @throws IOException + * @param name The name to expand. + * @return The reply code received from the server. + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int expn(final String name) throws IOException { + return sendCommand(SMTPCommand.EXPN, name); + } + + /** + * Provide command support to super-class */ - private int __sendCommand(int command, String args, boolean includeSpace) - throws IOException - { - return __sendCommand(SMTPCommand.getCommand(command), args, includeSpace); + @Override + protected ProtocolCommandSupport getCommandSupport() { + return _commandSupport_; } - private void __getReply() throws IOException - { - int length; + /** + * Fetches a reply from the SMTP server and returns the integer reply code. After calling this method, the actual reply text can be accessed from either + * calling {@link #getReplyString getReplyString } or {@link #getReplyStrings getReplyStrings }. Only use this method if you are implementing your own SMTP + * client or if you need to fetch a secondary response from the SMTP server. + * <p> + * + * @return The integer value of the reply code of the fetched SMTP reply. + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while receiving the server reply. + */ + public int getReply() throws IOException { + final int length; - _newReplyString = true; - _replyLines.clear(); + newReplyString = true; + replyLines.clear(); - String line = _reader.readLine(); + String line = reader.readLine(); if (line == null) { - throw new SMTPConnectionClosedException( - "Connection closed without indication."); + throw new SMTPConnectionClosedException("Connection closed without indication."); } // In case we run into an anomaly we don't want fatal index exceptions // to be thrown. length = line.length(); if (length < 3) { - throw new MalformedServerReplyException( - "Truncated server reply: " + line); + throw new MalformedServerReplyException("Truncated server reply: " + line); } - try - { - String code = line.substring(0, 3); - _replyCode = Integer.parseInt(code); - } - catch (NumberFormatException e) - { - throw new MalformedServerReplyException( - "Could not parse response code.\nServer Reply: " + line); + try { + final String code = line.substring(0, 3); + replyCode = Integer.parseInt(code); + } catch (final NumberFormatException e) { + throw new MalformedServerReplyException("Could not parse response code.\nServer Reply: " + line); } - _replyLines.add(line); + replyLines.add(line); // Get extra lines if message continues. - if (length > 3 && line.charAt(3) == '-') - { - do - { - line = _reader.readLine(); + if (length > 3 && line.charAt(3) == '-') { + do { + line = reader.readLine(); if (line == null) { - throw new SMTPConnectionClosedException( - "Connection closed without indication."); + throw new SMTPConnectionClosedException("Connection closed without indication."); } - _replyLines.add(line); + replyLines.add(line); // The length() check handles problems that could arise from readLine() // returning too soon after encountering a naked CR or some other // anomaly. - } - while (!(line.length() >= 4 && line.charAt(3) != '-' && - Character.isDigit(line.charAt(0)))); + } while (!(line.length() >= 4 && line.charAt(3) != '-' && Character.isDigit(line.charAt(0)))); // This is too strong a condition because a non-conforming server // could screw things up like ftp.funet.fi does for FTP // line.startsWith(code))); } - fireReplyReceived(_replyCode, getReplyString()); + fireReplyReceived(replyCode, getReplyString()); - if (_replyCode == SMTPReply.SERVICE_NOT_AVAILABLE) { - throw new SMTPConnectionClosedException( - "SMTP response 421 received. Server closed connection."); + if (replyCode == SMTPReply.SERVICE_NOT_AVAILABLE) { + throw new SMTPConnectionClosedException("SMTP response 421 received. Server closed connection."); } + return replyCode; } - /*** Initiates control connections and gets initial reply. ***/ - @Override - protected void _connectAction_() throws IOException - { - super._connectAction_(); - _reader = - new CRLFLineReader(new InputStreamReader(_input_, - encoding)); - _writer = - new BufferedWriter(new OutputStreamWriter(_output_, - encoding)); - __getReply(); - - } - - - /*** - * Closes the connection to the SMTP server and sets to null - * some internal data so that the memory may be reclaimed by the - * garbage collector. The reply text and code information from the - * last command is voided so that the memory it used may be reclaimed. - * <p> - * @throws IOException If an error occurs while disconnecting. - ***/ - @Override - public void disconnect() throws IOException - { - super.disconnect(); - _reader = null; - _writer = null; - _replyString = null; - _replyLines.clear(); - _newReplyString = false; - } - - - /*** - * Sends an SMTP command to the server, waits for a reply and returns the - * numerical response code. After invocation, for more detailed - * information, the actual reply text can be accessed by calling - * {@link #getReplyString getReplyString } or - * {@link #getReplyStrings getReplyStrings }. - * <p> - * @param command The text representation of the SMTP command to send. - * @param args The arguments to the SMTP command. If this parameter is - * set to null, then the command is sent with no argument. - * @return The integer value of the SMTP reply code returned by the server - * in response to the command. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int sendCommand(String command, String args) throws IOException - { - return __sendCommand(command, args, true); - } - - - /*** - * Sends an SMTP command to the server, waits for a reply and returns the - * numerical response code. After invocation, for more detailed - * information, the actual reply text can be accessed by calling - * {@link #getReplyString getReplyString } or - * {@link #getReplyStrings getReplyStrings }. - * <p> - * @param command The SMTPCommand constant corresponding to the SMTP command - * to send. - * @param args The arguments to the SMTP command. If this parameter is - * set to null, then the command is sent with no argument. - * @return The integer value of the SMTP reply code returned by the server - * in response to the command. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int sendCommand(int command, String args) throws IOException - { - return sendCommand(SMTPCommand.getCommand(command), args); - } - - - /*** - * Sends an SMTP command with no arguments to the server, waits for a - * reply and returns the numerical response code. After invocation, for - * more detailed information, the actual reply text can be accessed by - * calling {@link #getReplyString getReplyString } or - * {@link #getReplyStrings getReplyStrings }. - * <p> - * @param command The text representation of the SMTP command to send. - * @return The integer value of the SMTP reply code returned by the server - * in response to the command. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int sendCommand(String command) throws IOException - { - return sendCommand(command, null); - } - - - /*** - * Sends an SMTP command with no arguments to the server, waits for a - * reply and returns the numerical response code. After invocation, for - * more detailed information, the actual reply text can be accessed by - * calling {@link #getReplyString getReplyString } or - * {@link #getReplyStrings getReplyStrings }. - * <p> - * @param command The SMTPCommand constant corresponding to the SMTP command - * to send. - * @return The integer value of the SMTP reply code returned by the server - * in response to the command. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int sendCommand(int command) throws IOException - { - return sendCommand(command, null); - } - - - /*** - * Returns the integer value of the reply code of the last SMTP reply. - * You will usually only use this method after you connect to the - * SMTP server to check that the connection was successful since - * <code> connect </code> is of type void. + /** + * Returns the integer value of the reply code of the last SMTP reply. You will usually only use this method after you connect to the SMTP server to check + * that the connection was successful since <code> connect </code> is of type void. * <p> + * * @return The integer value of the reply code of the last SMTP reply. - ***/ - public int getReplyCode() - { - return _replyCode; - } - - /*** - * Fetches a reply from the SMTP server and returns the integer reply - * code. After calling this method, the actual reply text can be accessed - * from either calling {@link #getReplyString getReplyString } or - * {@link #getReplyStrings getReplyStrings }. Only use this - * method if you are implementing your own SMTP client or if you need to - * fetch a secondary response from the SMTP server. - * <p> - * @return The integer value of the reply code of the fetched SMTP reply. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while receiving the - * server reply. - ***/ - public int getReply() throws IOException - { - __getReply(); - return _replyCode; - } - - - /*** - * Returns the lines of text from the last SMTP server response as an array - * of strings, one entry per line. The end of line markers of each are - * stripped from each line. - * <p> - * @return The lines of text from the last SMTP response as an array. - ***/ - public String[] getReplyStrings() - { - return _replyLines.toArray(new String[_replyLines.size()]); + */ + public int getReplyCode() { + return replyCode; } - /*** - * Returns the entire text of the last SMTP server response exactly - * as it was received, including all end of line markers in NETASCII - * format. + /** + * Returns the entire text of the last SMTP server response exactly as it was received, including all end of line markers in NETASCII format. * <p> + * * @return The entire text from the last SMTP response as a String. - ***/ - public String getReplyString() - { - StringBuilder buffer; + */ + public String getReplyString() { + final StringBuilder buffer; - if (!_newReplyString) { - return _replyString; + if (!newReplyString) { + return replyString; } buffer = new StringBuilder(); - for (String line : _replyLines) - { + for (final String line : replyLines) { buffer.append(line); buffer.append(SocketClient.NETASCII_EOL); } - _newReplyString = false; + newReplyString = false; - return (_replyString = buffer.toString()); + replyString = buffer.toString(); + return replyString; } + /** + * Returns the lines of text from the last SMTP server response as an array of strings, one entry per line. The end of line markers of each are stripped + * from each line. + * <p> + * + * @return The lines of text from the last SMTP response as an array. + */ + public String[] getReplyStrings() { + return replyLines.toArray(NetConstants.EMPTY_STRING_ARRAY); + } - /*** - * A convenience method to send the SMTP HELO command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the SMTP HELO command to the server, receive the reply, and return the reply code. * <p> + * * @param hostname The hostname of the sender. * @return The reply code received from the server. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int helo(String hostname) throws IOException - { + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int helo(final String hostname) throws IOException { return sendCommand(SMTPCommand.HELO, hostname); } - - /*** - * A convenience method to send the SMTP MAIL command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the SMTP HELP command to the server, receive the reply, and return the reply code. * <p> - * @param reversePath The reverese path. + * * @return The reply code received from the server. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int mail(String reversePath) throws IOException - { - return __sendCommand(SMTPCommand.MAIL, reversePath, false); + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int help() throws IOException { + return sendCommand(SMTPCommand.HELP); } - - /*** - * A convenience method to send the SMTP RCPT command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the SMTP HELP command to the server, receive the reply, and return the reply code. * <p> - * @param forwardPath The forward path. + * + * @param command The command name on which to request help. * @return The reply code received from the server. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int rcpt(String forwardPath) throws IOException - { - return __sendCommand(SMTPCommand.RCPT, forwardPath, false); + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int help(final String command) throws IOException { + return sendCommand(SMTPCommand.HELP, command); } - - /*** - * A convenience method to send the SMTP DATA command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the SMTP MAIL command to the server, receive the reply, and return the reply code. * <p> + * + * @param reversePath The reverese path. * @return The reply code received from the server. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int data() throws IOException - { - return sendCommand(SMTPCommand.DATA); + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int mail(final String reversePath) throws IOException { + return sendCommand(SMTPCommand.MAIL, reversePath, false); } - - /*** - * A convenience method to send the SMTP SEND command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the SMTP NOOP command to the server, receive the reply, and return the reply code. * <p> - * @param reversePath The reverese path. + * * @return The reply code received from the server. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int send(String reversePath) throws IOException - { - return sendCommand(SMTPCommand.SEND, reversePath); + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int noop() throws IOException { + return sendCommand(SMTPCommand.NOOP); } - - /*** - * A convenience method to send the SMTP SOML command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the SMTP QUIT command to the server, receive the reply, and return the reply code. * <p> - * @param reversePath The reverese path. + * * @return The reply code received from the server. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int soml(String reversePath) throws IOException - { - return sendCommand(SMTPCommand.SOML, reversePath); + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int quit() throws IOException { + return sendCommand(SMTPCommand.QUIT); } - - /*** - * A convenience method to send the SMTP SAML command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the SMTP RCPT command to the server, receive the reply, and return the reply code. * <p> - * @param reversePath The reverese path. + * + * @param forwardPath The forward path. * @return The reply code received from the server. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int saml(String reversePath) throws IOException - { - return sendCommand(SMTPCommand.SAML, reversePath); + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int rcpt(final String forwardPath) throws IOException { + return sendCommand(SMTPCommand.RCPT, forwardPath, false); } + /** + * Removes a ProtocolCommandListener. + * + * Delegates this incorrectly named method - removeProtocolCommandistener (note the missing "L")- to the correct method + * {@link SocketClient#removeProtocolCommandListener} + * + * @param listener The ProtocolCommandListener to remove + */ + public void removeProtocolCommandistener(final org.apache.commons.net.ProtocolCommandListener listener) { + removeProtocolCommandListener(listener); + } - /*** - * A convenience method to send the SMTP RSET command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the SMTP RSET command to the server, receive the reply, and return the reply code. * <p> + * * @return The reply code received from the server. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int rset() throws IOException - { + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int rset() throws IOException { return sendCommand(SMTPCommand.RSET); } - - /*** - * A convenience method to send the SMTP VRFY command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the SMTP SAML command to the server, receive the reply, and return the reply code. * <p> - * @param user The user address to verify. + * + * @param reversePath The reverese path. * @return The reply code received from the server. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int vrfy(String user) throws IOException - { - return sendCommand(SMTPCommand.VRFY, user); + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int saml(final String reversePath) throws IOException { + return sendCommand(SMTPCommand.SAML, reversePath); } - - /*** - * A convenience method to send the SMTP VRFY command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the SMTP SEND command to the server, receive the reply, and return the reply code. * <p> - * @param name The name to expand. + * + * @param reversePath The reverese path. * @return The reply code received from the server. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int expn(String name) throws IOException - { - return sendCommand(SMTPCommand.EXPN, name); + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int send(final String reversePath) throws IOException { + return sendCommand(SMTPCommand.SEND, reversePath); } - /*** - * A convenience method to send the SMTP HELP command to the server, - * receive the reply, and return the reply code. + /** + * Sends an SMTP command with no arguments to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed + * information, the actual reply text can be accessed by calling {@link #getReplyString getReplyString } or {@link #getReplyStrings getReplyStrings }. * <p> - * @return The reply code received from the server. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int help() throws IOException - { - return sendCommand(SMTPCommand.HELP); + * + * @param command The SMTPCommand constant corresponding to the SMTP command to send. + * @return The integer value of the SMTP reply code returned by the server in response to the command. + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int sendCommand(final int command) throws IOException { + return sendCommand(command, null); } - /*** - * A convenience method to send the SMTP HELP command to the server, - * receive the reply, and return the reply code. + /** + * Sends an SMTP command to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed information, the + * actual reply text can be accessed by calling {@link #getReplyString getReplyString } or {@link #getReplyStrings getReplyStrings }. * <p> - * @param command The command name on which to request help. - * @return The reply code received from the server. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int help(String command) throws IOException - { - return sendCommand(SMTPCommand.HELP, command); + * + * @param command The SMTPCommand constant corresponding to the SMTP command to send. + * @param args The arguments to the SMTP command. If this parameter is set to null, then the command is sent with no argument. + * @return The integer value of the SMTP reply code returned by the server in response to the command. + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int sendCommand(final int command, final String args) throws IOException { + return sendCommand(SMTPCommand.getCommand(command), args); } - /*** - * A convenience method to send the SMTP NOOP command to the server, - * receive the reply, and return the reply code. - * <p> - * @return The reply code received from the server. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int noop() throws IOException - { - return sendCommand(SMTPCommand.NOOP); + /** + * + * @param command the command to send (as an int defined in {@link SMPTCommand}) + * @param args the command arguments, may be {@code null} + * @param includeSpace if {@code true}, add a space between the command and its arguments + * @return the reply code + * @throws IOException + */ + private int sendCommand(final int command, final String args, final boolean includeSpace) throws IOException { + return sendCommand(SMTPCommand.getCommand(command), args, includeSpace); } + /** + * Sends an SMTP command with no arguments to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed + * information, the actual reply text can be accessed by calling {@link #getReplyString getReplyString } or {@link #getReplyStrings getReplyStrings }. + * <p> + * + * @param command The text representation of the SMTP command to send. + * @return The integer value of the SMTP reply code returned by the server in response to the command. + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int sendCommand(final String command) throws IOException { + return sendCommand(command, null); + } - /*** - * A convenience method to send the SMTP TURN command to the server, - * receive the reply, and return the reply code. + /** + * Sends an SMTP command to the server, waits for a reply and returns the numerical response code. After invocation, for more detailed information, the + * actual reply text can be accessed by calling {@link #getReplyString getReplyString } or {@link #getReplyStrings getReplyStrings }. * <p> - * @return The reply code received from the server. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int turn() throws IOException - { - return sendCommand(SMTPCommand.TURN); + * + * @param command The text representation of the SMTP command to send. + * @param args The arguments to the SMTP command. If this parameter is set to null, then the command is sent with no argument. + * @return The integer value of the SMTP reply code returned by the server in response to the command. + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int sendCommand(final String command, final String args) throws IOException { + return sendCommand(command, args, true); } + /** + * Send a command to the server. May also be used to send text data. + * + * @param command the command to send (as a plain String) + * @param args the command arguments, may be {@code null} + * @param includeSpace if {@code true}, add a space between the command and its arguments + * @return the reply code + * @throws IOException + */ + private int sendCommand(final String command, final String args, final boolean includeSpace) throws IOException { + final StringBuilder __commandBuffer = new StringBuilder(); + __commandBuffer.append(command); + + if (args != null) { + if (includeSpace) { + __commandBuffer.append(' '); + } + __commandBuffer.append(args); + } + + __commandBuffer.append(SocketClient.NETASCII_EOL); + + final String message = __commandBuffer.toString(); + writer.write(message); + writer.flush(); + + fireCommandSent(command, message); + + return getReply(); + } - /*** - * A convenience method to send the SMTP QUIT command to the server, - * receive the reply, and return the reply code. + /** + * A convenience method to send the SMTP SOML command to the server, receive the reply, and return the reply code. * <p> + * + * @param reversePath The reverese path. * @return The reply code received from the server. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending the - * command or receiving the server reply. - ***/ - public int quit() throws IOException - { - return sendCommand(SMTPCommand.QUIT); + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. + */ + public int soml(final String reversePath) throws IOException { + return sendCommand(SMTPCommand.SOML, reversePath); } /** - * Removes a ProtocolCommandListener. + * A convenience method to send the SMTP TURN command to the server, receive the reply, and return the reply code. + * <p> * - * Delegates this incorrectly named method - removeProtocolCommandistener (note the missing "L")- to - * the correct method {@link SocketClient#removeProtocolCommandListener} - * @param listener The ProtocolCommandListener to remove + * @return The reply code received from the server. + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - public void removeProtocolCommandistener(org.apache.commons.net.ProtocolCommandListener listener){ - removeProtocolCommandListener(listener); + public int turn() throws IOException { + return sendCommand(SMTPCommand.TURN); } /** - * Provide command support to super-class + * A convenience method to send the SMTP VRFY command to the server, receive the reply, and return the reply code. + * <p> + * + * @param user The user address to verify. + * @return The reply code received from the server. + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending the command or receiving the server reply. */ - @Override - protected ProtocolCommandSupport getCommandSupport() { - return _commandSupport_; + public int vrfy(final String user) throws IOException { + return sendCommand(SMTPCommand.VRFY, user); } } - -/* Emacs configuration - * Local variables: ** - * mode: java ** - * c-basic-offset: 4 ** - * indent-tabs-mode: nil ** - * End: ** - */ diff --git a/src/main/java/org/apache/commons/net/smtp/SMTPClient.java b/src/main/java/org/apache/commons/net/smtp/SMTPClient.java index 606643f..e213d74 100644 --- a/src/main/java/org/apache/commons/net/smtp/SMTPClient.java +++ b/src/main/java/org/apache/commons/net/smtp/SMTPClient.java @@ -23,19 +23,13 @@ import java.net.InetAddress; import org.apache.commons.net.io.DotTerminatedMessageWriter; -/*** - * SMTPClient encapsulates all the functionality necessary to send files - * through an SMTP server. This class takes care of all - * low level details of interacting with an SMTP server and provides - * a convenient higher level interface. As with all classes derived - * from {@link org.apache.commons.net.SocketClient}, - * you must first connect to the server with - * {@link org.apache.commons.net.SocketClient#connect connect } - * before doing anything, and finally - * {@link org.apache.commons.net.SocketClient#disconnect disconnect } - * after you're completely finished interacting with the server. - * Then you need to check the SMTP reply code to see if the connection - * was successful. For example: +/** + * SMTPClient encapsulates all the functionality necessary to send files through an SMTP server. This class takes care of all low level details of interacting + * with an SMTP server and provides a convenient higher level interface. As with all classes derived from {@link org.apache.commons.net.SocketClient}, you must + * first connect to the server with {@link org.apache.commons.net.SocketClient#connect connect } before doing anything, and finally + * {@link org.apache.commons.net.SocketClient#disconnect disconnect } after you're completely finished interacting with the server. Then you need to check the + * SMTP reply code to see if the connection was successful. For example: + * * <pre> * try { * int reply; @@ -68,154 +62,162 @@ import org.apache.commons.net.io.DotTerminatedMessageWriter; * } * </pre> * <p> - * Immediately after connecting is the only real time you need to check the - * reply code (because connect is of type void). The convention for all the - * SMTP command methods in SMTPClient is such that they either return a - * boolean value or some other value. - * The boolean methods return true on a successful completion reply from - * the SMTP server and false on a reply resulting in an error condition or - * failure. The methods returning a value other than boolean return a value - * containing the higher level data produced by the SMTP command, or null if a - * reply resulted in an error condition or failure. If you want to access - * the exact SMTP reply code causing a success or failure, you must call - * {@link org.apache.commons.net.smtp.SMTP#getReplyCode getReplyCode } after - * a success or failure. + * Immediately after connecting is the only real time you need to check the reply code (because connect is of type void). The convention for all the SMTP + * command methods in SMTPClient is such that they either return a boolean value or some other value. The boolean methods return true on a successful completion + * reply from the SMTP server and false on a reply resulting in an error condition or failure. The methods returning a value other than boolean return a value + * containing the higher level data produced by the SMTP command, or null if a reply resulted in an error condition or failure. If you want to access the exact + * SMTP reply code causing a success or failure, you must call {@link org.apache.commons.net.smtp.SMTP#getReplyCode getReplyCode } after a success or failure. * <p> - * You should keep in mind that the SMTP server may choose to prematurely - * close a connection for various reasons. The SMTPClient class will detect a - * premature SMTP server connection closing when it receives a - * {@link org.apache.commons.net.smtp.SMTPReply#SERVICE_NOT_AVAILABLE SMTPReply.SERVICE_NOT_AVAILABLE } - * response to a command. - * When that occurs, the method encountering that reply will throw - * an {@link org.apache.commons.net.smtp.SMTPConnectionClosedException} - * . - * <code>SMTPConectionClosedException</code> - * is a subclass of <code> IOException </code> and therefore need not be - * caught separately, but if you are going to catch it separately, its - * catch block must appear before the more general <code> IOException </code> - * catch block. When you encounter an - * {@link org.apache.commons.net.smtp.SMTPConnectionClosedException} - * , you must disconnect the connection with - * {@link #disconnect disconnect() } to properly clean up the - * system resources used by SMTPClient. Before disconnecting, you may check - * the last reply code and text with - * {@link org.apache.commons.net.smtp.SMTP#getReplyCode getReplyCode }, - * {@link org.apache.commons.net.smtp.SMTP#getReplyString getReplyString }, - * and + * You should keep in mind that the SMTP server may choose to prematurely close a connection for various reasons. The SMTPClient class will detect a premature + * SMTP server connection closing when it receives a {@link org.apache.commons.net.smtp.SMTPReply#SERVICE_NOT_AVAILABLE SMTPReply.SERVICE_NOT_AVAILABLE } + * response to a command. When that occurs, the method encountering that reply will throw an {@link org.apache.commons.net.smtp.SMTPConnectionClosedException} . + * <code>SMTPConectionClosedException</code> is a subclass of <code> IOException </code> and therefore need not be caught separately, but if you are going to + * catch it separately, its catch block must appear before the more general <code> IOException </code> catch block. When you encounter an + * {@link org.apache.commons.net.smtp.SMTPConnectionClosedException} , you must disconnect the connection with {@link #disconnect disconnect() } to properly + * clean up the system resources used by SMTPClient. Before disconnecting, you may check the last reply code and text with + * {@link org.apache.commons.net.smtp.SMTP#getReplyCode getReplyCode }, {@link org.apache.commons.net.smtp.SMTP#getReplyString getReplyString }, and * {@link org.apache.commons.net.smtp.SMTP#getReplyStrings getReplyStrings}. * <p> - * Rather than list it separately for each method, we mention here that - * every method communicating with the server and throwing an IOException - * can also throw a - * {@link org.apache.commons.net.MalformedServerReplyException} - * , which is a subclass - * of IOException. A MalformedServerReplyException will be thrown when - * the reply received from the server deviates enough from the protocol - * specification that it cannot be interpreted in a useful manner despite - * attempts to be as lenient as possible. + * Rather than list it separately for each method, we mention here that every method communicating with the server and throwing an IOException can also throw a + * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the + * reply received from the server deviates enough from the protocol specification that it cannot be interpreted in a useful manner despite attempts to be as + * lenient as possible. * * @see SMTP * @see SimpleSMTPHeader * @see RelayPath * @see SMTPConnectionClosedException * @see org.apache.commons.net.MalformedServerReplyException - ***/ + */ -public class SMTPClient extends SMTP -{ +public class SMTPClient extends SMTP { /** - * Default SMTPClient constructor. Creates a new SMTPClient instance. + * Default SMTPClient constructor. Creates a new SMTPClient instance. */ - public SMTPClient() { } + public SMTPClient() { + } /** * Overloaded constructor that takes an encoding specification + * * @param encoding The encoding to use * @since 2.0 */ - public SMTPClient(String encoding) { + public SMTPClient(final String encoding) { super(encoding); } + /** + * Add a recipient for a message using the SMTP RCPT command, specifying a forward relay path. The sender must be set first before any recipients may be + * specified, otherwise the mail server will reject your commands. + * <p> + * + * @param path The forward relay path pointing to the recipient. + * @return True if successfully completed, false if not. + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean addRecipient(final RelayPath path) throws IOException { + return SMTPReply.isPositiveCompletion(rcpt(path.toString())); + } - /*** - * At least one SMTPClient method ({@link #sendMessageData sendMessageData }) - * does not complete the entire sequence of SMTP commands to complete a - * transaction. These types of commands require some action by the - * programmer after the reception of a positive intermediate command. - * After the programmer's code completes its actions, it must call this - * method to receive the completion reply from the server and verify the - * success of the entire transaction. + /** + * Add a recipient for a message using the SMTP RCPT command, the recipient's email address. The sender must be set first before any recipients may be + * specified, otherwise the mail server will reject your commands. + * <p> + * + * @param address The recipient's email address. + * @return True if successfully completed, false if not. + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean addRecipient(final String address) throws IOException { + return SMTPReply.isPositiveCompletion(rcpt("<" + address + ">")); + } + + /** + * At least one SMTPClient method ({@link #sendMessageData sendMessageData }) does not complete the entire sequence of SMTP commands to complete a + * transaction. These types of commands require some action by the programmer after the reception of a positive intermediate command. After the programmer's + * code completes its actions, it must call this method to receive the completion reply from the server and verify the success of the entire transaction. * <p> * For example, + * * <pre> * writer = client.sendMessageData(); - * if(writer == null) // failure - * return false; - * header = - * new SimpleSMTPHeader("foobar@foo.com", "foo@foobar.com", "Re: Foo"); + * if (writer == null) // failure + * return false; + * header = new SimpleSMTPHeader("foobar@foo.com", "foo@foobar.com", "Re: Foo"); * writer.write(header.toString()); * writer.write("This is just a test"); * writer.close(); - * if(!client.completePendingCommand()) // failure - * return false; + * if (!client.completePendingCommand()) // failure + * return false; * </pre> * <p> + * * @return True if successfully completed, false if not. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean completePendingCommand() throws IOException - { + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean completePendingCommand() throws IOException { return SMTPReply.isPositiveCompletion(getReply()); } - - /*** - * Login to the SMTP server by sending the HELO command with the - * given hostname as an argument. Before performing any mail commands, - * you must first login. + /** + * Fetches the system help information from the server and returns the full string. * <p> - * @param hostname The hostname with which to greet the SMTP server. - * @return True if successfully completed, false if not. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean login(String hostname) throws IOException - { - return SMTPReply.isPositiveCompletion(helo(hostname)); + * + * @return The system help string obtained from the server. null if the information could not be obtained. + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public String listHelp() throws IOException { + if (SMTPReply.isPositiveCompletion(help())) { + return getReplyString(); + } + return null; } + /** + * Fetches the help information for a given command from the server and returns the full string. + * <p> + * + * @param command The command on which to ask for help. + * @return The command help string obtained from the server. null if the information could not be obtained. + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public String listHelp(final String command) throws IOException { + if (SMTPReply.isPositiveCompletion(help(command))) { + return getReplyString(); + } + return null; + } - /*** - * Login to the SMTP server by sending the HELO command with the - * client hostname as an argument. Before performing any mail commands, - * you must first login. + /** + * Login to the SMTP server by sending the HELO command with the client hostname as an argument. Before performing any mail commands, you must first login. * <p> + * * @return True if successfully completed, false if not. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean login() throws IOException - { - String name; - InetAddress host; + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean login() throws IOException { + final String name; + final InetAddress host; host = getLocalAddress(); name = host.getHostName(); @@ -227,207 +229,138 @@ public class SMTPClient extends SMTP return SMTPReply.isPositiveCompletion(helo(name)); } - - /*** - * Set the sender of a message using the SMTP MAIL command, specifying - * a reverse relay path. The sender must be set first before any - * recipients may be specified, otherwise the mail server will reject - * your commands. - * <p> - * @param path The reverse relay path pointing back to the sender. - * @return True if successfully completed, false if not. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean setSender(RelayPath path) throws IOException - { - return SMTPReply.isPositiveCompletion(mail(path.toString())); - } - - - /*** - * Set the sender of a message using the SMTP MAIL command, specifying - * the sender's email address. The sender must be set first before any - * recipients may be specified, otherwise the mail server will reject - * your commands. + /** + * Login to the SMTP server by sending the HELO command with the given hostname as an argument. Before performing any mail commands, you must first login. * <p> - * @param address The sender's email address. + * + * @param hostname The hostname with which to greet the SMTP server. * @return True if successfully completed, false if not. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean setSender(String address) throws IOException - { - return SMTPReply.isPositiveCompletion(mail("<" + address + ">")); + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean login(final String hostname) throws IOException { + return SMTPReply.isPositiveCompletion(helo(hostname)); } - - /*** - * Add a recipient for a message using the SMTP RCPT command, specifying - * a forward relay path. The sender must be set first before any - * recipients may be specified, otherwise the mail server will reject - * your commands. + /** + * Logout of the SMTP server by sending the QUIT command. * <p> - * @param path The forward relay path pointing to the recipient. + * * @return True if successfully completed, false if not. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean addRecipient(RelayPath path) throws IOException - { - return SMTPReply.isPositiveCompletion(rcpt(path.toString())); + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean logout() throws IOException { + return SMTPReply.isPositiveCompletion(quit()); } - - /*** - * Add a recipient for a message using the SMTP RCPT command, the - * recipient's email address. The sender must be set first before any - * recipients may be specified, otherwise the mail server will reject - * your commands. + /** + * Aborts the current mail transaction, resetting all server stored sender, recipient, and mail data, cleaing all buffers and tables. * <p> - * @param address The recipient's email address. + * * @return True if successfully completed, false if not. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean addRecipient(String address) throws IOException - { - return SMTPReply.isPositiveCompletion(rcpt("<" + address + ">")); + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean reset() throws IOException { + return SMTPReply.isPositiveCompletion(rset()); } - - - /*** - * Send the SMTP DATA command in preparation to send an email message. - * This method returns a DotTerminatedMessageWriter instance to which - * the message can be written. Null is returned if the DATA command - * fails. + /** + * Send the SMTP DATA command in preparation to send an email message. This method returns a DotTerminatedMessageWriter instance to which the message can be + * written. Null is returned if the DATA command fails. * <p> - * You must not issue any commands to the SMTP server (i.e., call any - * (other methods) until you finish writing to the returned Writer - * instance and close it. The SMTP protocol uses the same stream for - * issuing commands as it does for returning results. Therefore the - * returned Writer actually writes directly to the SMTP connection. - * After you close the writer, you can execute new commands. If you - * do not follow these requirements your program will not work properly. + * You must not issue any commands to the SMTP server (i.e., call any (other methods) until you finish writing to the returned Writer instance and close it. + * The SMTP protocol uses the same stream for issuing commands as it does for returning results. Therefore the returned Writer actually writes directly to + * the SMTP connection. After you close the writer, you can execute new commands. If you do not follow these requirements your program will not work + * properly. * <p> - * You can use the provided - * {@link org.apache.commons.net.smtp.SimpleSMTPHeader} - * class to construct a bare minimum header. - * To construct more complicated headers you should - * refer to RFC 5322. When the Java Mail API is finalized, you will be - * able to use it to compose fully compliant Internet text messages. - * The DotTerminatedMessageWriter takes care of doubling line-leading - * dots and ending the message with a single dot upon closing, so all - * you have to worry about is writing the header and the message. + * You can use the provided {@link org.apache.commons.net.smtp.SimpleSMTPHeader} class to construct a bare minimum header. To construct more complicated + * headers you should refer to RFC 5322. When the Java Mail API is finalized, you will be able to use it to compose fully compliant Internet text messages. + * The DotTerminatedMessageWriter takes care of doubling line-leading dots and ending the message with a single dot upon closing, so all you have to worry + * about is writing the header and the message. * <p> - * Upon closing the returned Writer, you need to call - * {@link #completePendingCommand completePendingCommand() } - * to finalize the transaction and verify its success or failure from - * the server reply. + * Upon closing the returned Writer, you need to call {@link #completePendingCommand completePendingCommand() } to finalize the transaction and verify its + * success or failure from the server reply. * <p> - * @return A DotTerminatedMessageWriter to which the message (including - * header) can be written. Returns null if the command fails. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. + * + * @return A DotTerminatedMessageWriter to which the message (including header) can be written. Returns null if the command fails. + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. * @see #sendShortMessageData(String) - ***/ - public Writer sendMessageData() throws IOException - { + */ + public Writer sendMessageData() throws IOException { if (!SMTPReply.isPositiveIntermediate(data())) { return null; } - return new DotTerminatedMessageWriter(_writer); + return new DotTerminatedMessageWriter(writer); } + /** + * Sends a NOOP command to the SMTP server. This is useful for preventing server timeouts. + * <p> + * + * @return True if successfully completed, false if not. + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean sendNoOp() throws IOException { + return SMTPReply.isPositiveCompletion(noop()); + } - /*** - * A convenience method for sending short messages. This method fetches - * the Writer returned by {@link #sendMessageData sendMessageData() } - * and writes the specified String to it. After writing the message, - * this method calls {@link #completePendingCommand completePendingCommand() } - * to finalize the transaction and returns - * its success or failure. + /** + * A convenience method for sending short messages. This method fetches the Writer returned by {@link #sendMessageData sendMessageData() } and writes the + * specified String to it. After writing the message, this method calls {@link #completePendingCommand completePendingCommand() } to finalize the + * transaction and returns its success or failure. * <p> - * @param message The short email message to send. - * This must include the headers and the body, but not the trailing "." + * + * @param message The short email message to send. This must include the headers and the body, but not the trailing "." * @return True if successfully completed, false if not. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean sendShortMessageData(String message) throws IOException - { - Writer writer; - - writer = sendMessageData(); - - if (writer == null) { - return false; - } + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean sendShortMessageData(final String message) throws IOException { + try (final Writer writer = sendMessageData()) { + + if (writer == null) { + return false; + } - writer.write(message); - writer.close(); + writer.write(message); + } return completePendingCommand(); } - - /*** - * A convenience method for a sending short email without having to - * explicitly set the sender and recipient(s). This method - * sets the sender and recipient using - * {@link #setSender setSender } and - * {@link #addRecipient addRecipient }, and then sends the - * message using {@link #sendShortMessageData sendShortMessageData }. + /** + * A convenience method for a sending short email without having to explicitly set the sender and recipient(s). This method sets the sender and recipient + * using {@link #setSender setSender } and {@link #addRecipient addRecipient }, and then sends the message using {@link #sendShortMessageData + * sendShortMessageData }. * <p> - * @param sender The email address of the sender. - * @param recipient The email address of the recipient. - * @param message The short email message to send. - * This must include the headers and the body, but not the trailing "." + * + * @param sender The email address of the sender. + * @param recipient The email address of the recipient. + * @param message The short email message to send. This must include the headers and the body, but not the trailing "." * @return True if successfully completed, false if not. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean sendSimpleMessage(String sender, String recipient, - String message) - throws IOException - { + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean sendSimpleMessage(final String sender, final String recipient, final String message) throws IOException { if (!setSender(sender)) { return false; } @@ -439,39 +372,25 @@ public class SMTPClient extends SMTP return sendShortMessageData(message); } - - - /*** - * A convenience method for a sending short email without having to - * explicitly set the sender and recipient(s). This method - * sets the sender and recipients using - * {@link #setSender(String) setSender} and - * {@link #addRecipient(String) addRecipient}, and then sends the - * message using {@link #sendShortMessageData(String) sendShortMessageData}. + /** + * A convenience method for a sending short email without having to explicitly set the sender and recipient(s). This method sets the sender and recipients + * using {@link #setSender(String) setSender} and {@link #addRecipient(String) addRecipient}, and then sends the message using + * {@link #sendShortMessageData(String) sendShortMessageData}. * <p> - * Note that the method ignores failures when calling - * {@link #addRecipient(String) addRecipient} so long as - * at least one call succeeds. If no recipients can be successfully - * added then the method will fail (and does not attempt to - * send the message) + * Note that the method ignores failures when calling {@link #addRecipient(String) addRecipient} so long as at least one call succeeds. If no recipients can + * be successfully added then the method will fail (and does not attempt to send the message) * <p> - * @param sender The email address of the sender. - * @param recipients An array of recipient email addresses. - * @param message The short email message to send. - * This must include the headers and the body, but not the trailing "." + * + * @param sender The email address of the sender. + * @param recipients An array of recipient email addresses. + * @param message The short email message to send. This must include the headers and the body, but not the trailing "." * @return True if successfully completed, false if not. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean sendSimpleMessage(String sender, String[] recipients, - String message) - throws IOException - { + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean sendSimpleMessage(final String sender, final String[] recipients, final String message) throws IOException { boolean oneSuccess = false; int count; @@ -479,8 +398,7 @@ public class SMTPClient extends SMTP return false; } - for (count = 0; count < recipients.length; count++) - { + for (count = 0; count < recipients.length; count++) { if (addRecipient(recipients[count])) { oneSuccess = true; } @@ -493,133 +411,55 @@ public class SMTPClient extends SMTP return sendShortMessageData(message); } - - /*** - * Logout of the SMTP server by sending the QUIT command. + /** + * Set the sender of a message using the SMTP MAIL command, specifying a reverse relay path. The sender must be set first before any recipients may be + * specified, otherwise the mail server will reject your commands. * <p> + * + * @param path The reverse relay path pointing back to the sender. * @return True if successfully completed, false if not. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean logout() throws IOException - { - return SMTPReply.isPositiveCompletion(quit()); + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean setSender(final RelayPath path) throws IOException { + return SMTPReply.isPositiveCompletion(mail(path.toString())); } - - - /*** - * Aborts the current mail transaction, resetting all server stored - * sender, recipient, and mail data, cleaing all buffers and tables. + /** + * Set the sender of a message using the SMTP MAIL command, specifying the sender's email address. The sender must be set first before any recipients may be + * specified, otherwise the mail server will reject your commands. * <p> + * + * @param address The sender's email address. * @return True if successfully completed, false if not. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean reset() throws IOException - { - return SMTPReply.isPositiveCompletion(rset()); + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean setSender(final String address) throws IOException { + return SMTPReply.isPositiveCompletion(mail("<" + address + ">")); } - - /*** - * Verify that a username or email address is valid, i.e., that mail - * can be delivered to that mailbox on the server. + /** + * Verify that a username or email address is valid, i.e., that mail can be delivered to that mailbox on the server. * <p> - * @param username The username or email address to validate. + * + * @param username The username or email address to validate. * @return True if the username is valid, false if not. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean verify(String username) throws IOException - { - int result; + * @throws SMTPConnectionClosedException If the SMTP server prematurely closes the connection as a result of the client being idle or some other reason + * causing the server to send SMTP reply code 421. This exception may be caught either as an IOException or + * independently as itself. + * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server. + */ + public boolean verify(final String username) throws IOException { + final int result; result = vrfy(username); - return (result == SMTPReply.ACTION_OK || - result == SMTPReply.USER_NOT_LOCAL_WILL_FORWARD); - } - - - /*** - * Fetches the system help information from the server and returns the - * full string. - * <p> - * @return The system help string obtained from the server. null if the - * information could not be obtained. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public String listHelp() throws IOException - { - if (SMTPReply.isPositiveCompletion(help())) { - return getReplyString(); - } - return null; - } - - - /*** - * Fetches the help information for a given command from the server and - * returns the full string. - * <p> - * @param command The command on which to ask for help. - * @return The command help string obtained from the server. null if the - * information could not be obtained. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public String listHelp(String command) throws IOException - { - if (SMTPReply.isPositiveCompletion(help(command))) { - return getReplyString(); - } - return null; - } - - - /*** - * Sends a NOOP command to the SMTP server. This is useful for preventing - * server timeouts. - * <p> - * @return True if successfully completed, false if not. - * @throws SMTPConnectionClosedException - * If the SMTP server prematurely closes the connection as a result - * of the client being idle or some other reason causing the server - * to send SMTP reply code 421. This exception may be caught either - * as an IOException or independently as itself. - * @throws IOException If an I/O error occurs while either sending a - * command to the server or receiving a reply from the server. - ***/ - public boolean sendNoOp() throws IOException - { - return SMTPReply.isPositiveCompletion(noop()); + return result == SMTPReply.ACTION_OK || result == SMTPReply.USER_NOT_LOCAL_WILL_FORWARD; } } diff --git a/src/main/java/org/apache/commons/net/smtp/SMTPCommand.java b/src/main/java/org/apache/commons/net/smtp/SMTPCommand.java index c599336..2addbb4 100644 --- a/src/main/java/org/apache/commons/net/smtp/SMTPCommand.java +++ b/src/main/java/org/apache/commons/net/smtp/SMTPCommand.java @@ -18,17 +18,12 @@ package org.apache.commons.net.smtp; /** - * SMTPCommand stores a set of constants for SMTP command codes. To interpret - * the meaning of the codes, familiarity with RFC 821 is assumed. - * The mnemonic constant names are transcriptions from the code descriptions - * of RFC 821. For those who think in terms of the actual SMTP commands, - * a set of constants such as {@link #HELO HELO } are provided - * where the constant name is the same as the SMTP command. + * SMTPCommand stores a set of constants for SMTP command codes. To interpret the meaning of the codes, familiarity with RFC 821 is assumed. The mnemonic + * constant names are transcriptions from the code descriptions of RFC 821. For those who think in terms of the actual SMTP commands, a set of constants such as + * {@link #HELO HELO } are provided where the constant name is the same as the SMTP command. */ -public final class SMTPCommand -{ - +public final class SMTPCommand { public static final int HELO = 0; public static final int MAIL = 1; @@ -47,17 +42,19 @@ public final class SMTPCommand /** * The authorization command + * * @since 3.0 */ - public static final int AUTH = 14 ; + public static final int AUTH = 14; /** * The extended hello command + * * @since 3.0 */ - public static final int EHLO = 15 ; + public static final int EHLO = 15; - private static final int _NEXT_ = EHLO + 1; // update as necessary when adding new entries + private static final int NEXT = EHLO + 1; // update as necessary when adding new entries public static final int HELLO = HELO; public static final int LOGIN = HELO; @@ -76,34 +73,28 @@ public final class SMTPCommand // public static final int QUIT = QUIT; public static final int LOGOUT = QUIT; - // Cannot be instantiated - private SMTPCommand() - {} - - private static final String[] _commands = { - "HELO", "MAIL FROM:", "RCPT TO:", "DATA", "SEND FROM:", "SOML FROM:", - "SAML FROM:", "RSET", "VRFY", "EXPN", "HELP", "NOOP", "TURN", "QUIT", - "AUTH", "EHLO" - }; - + private static final String[] commands = { "HELO", "MAIL FROM:", "RCPT TO:", "DATA", "SEND FROM:", "SOML FROM:", "SAML FROM:", "RSET", "VRFY", "EXPN", + "HELP", "NOOP", "TURN", "QUIT", "AUTH", "EHLO" }; static { - if (_commands.length != _NEXT_) { + if (commands.length != NEXT) { throw new RuntimeException("Error in array definition"); } } - /*** - * Retrieve the SMTP protocol command string corresponding to a specified - * command code. + /** + * Retrieve the SMTP protocol command string corresponding to a specified command code. * <p> + * * @param command The command code. - * @return The SMTP protcol command string corresponding to a specified - * command code. - ***/ - public static final String getCommand(int command) - { - return _commands[command]; + * @return The SMTP protcol command string corresponding to a specified command code. + */ + public static String getCommand(final int command) { + return commands[command]; + } + + // Cannot be instantiated + private SMTPCommand() { } } diff --git a/src/main/java/org/apache/commons/net/smtp/SMTPConnectionClosedException.java b/src/main/java/org/apache/commons/net/smtp/SMTPConnectionClosedException.java index 0183391..04745ca 100644 --- a/src/main/java/org/apache/commons/net/smtp/SMTPConnectionClosedException.java +++ b/src/main/java/org/apache/commons/net/smtp/SMTPConnectionClosedException.java @@ -19,38 +19,30 @@ package org.apache.commons.net.smtp; import java.io.IOException; -/*** - * SMTPConnectionClosedException is used to indicate the premature or - * unexpected closing of an SMTP connection resulting from a - * {@link org.apache.commons.net.smtp.SMTPReply#SERVICE_NOT_AVAILABLE SMTPReply.SERVICE_NOT_AVAILABLE } - * response (SMTP reply code 421) to a - * failed SMTP command. This exception is derived from IOException and - * therefore may be caught either as an IOException or specifically as an - * SMTPConnectionClosedException. +/** + * SMTPConnectionClosedException is used to indicate the premature or unexpected closing of an SMTP connection resulting from a + * {@link org.apache.commons.net.smtp.SMTPReply#SERVICE_NOT_AVAILABLE SMTPReply.SERVICE_NOT_AVAILABLE } response (SMTP reply code 421) to a failed SMTP command. + * This exception is derived from IOException and therefore may be caught either as an IOException or specifically as an SMTPConnectionClosedException. * * * @see SMTP * @see SMTPClient - ***/ + */ -public final class SMTPConnectionClosedException extends IOException -{ +public final class SMTPConnectionClosedException extends IOException { private static final long serialVersionUID = 626520434326660627L; - /*** Constructs a SMTPConnectionClosedException with no message ***/ - public SMTPConnectionClosedException() - { - super(); + /** Constructs a SMTPConnectionClosedException with no message */ + public SMTPConnectionClosedException() { } - /*** + /** * Constructs a SMTPConnectionClosedException with a specified message. * - * @param message The message explaining the reason for the exception. - ***/ - public SMTPConnectionClosedException(String message) - { + * @param message The message explaining the reason for the exception. + */ + public SMTPConnectionClosedException(final String message) { super(message); } diff --git a/src/main/java/org/apache/commons/net/smtp/SMTPReply.java b/src/main/java/org/apache/commons/net/smtp/SMTPReply.java index 3d69479..cfd8eba 100644 --- a/src/main/java/org/apache/commons/net/smtp/SMTPReply.java +++ b/src/main/java/org/apache/commons/net/smtp/SMTPReply.java @@ -17,15 +17,12 @@ package org.apache.commons.net.smtp; -/*** - * SMTPReply stores a set of constants for SMTP reply codes. To interpret - * the meaning of the codes, familiarity with RFC 821 is assumed. - * The mnemonic constant names are transcriptions from the code descriptions - * of RFC 821. - ***/ +/** + * SMTPReply stores a set of constants for SMTP reply codes. To interpret the meaning of the codes, familiarity with RFC 821 is assumed. The mnemonic constant + * names are transcriptions from the code descriptions of RFC 821. + */ -public final class SMTPReply -{ +public final class SMTPReply { public static final int SYSTEM_STATUS = 211; public static final int HELP_MESSAGE = 214; @@ -49,91 +46,72 @@ public final class SMTPReply public static final int MAILBOX_NAME_NOT_ALLOWED = 553; public static final int TRANSACTION_FAILED = 554; - // Cannot be instantiated - private SMTPReply() - {} - - /*** - * Determine if a reply code is a positive preliminary response. All - * codes beginning with a 1 are positive preliminary responses. - * Postitive preliminary responses are used to indicate tentative success. - * No further commands can be issued to the SMTP server after a positive - * preliminary response until a follow up response is received from the - * server. - * <p> - * <b> Note: </b> <em> No SMTP commands defined in RFC 822 provide this - * type of reply. </em> + /** + * Determine if a reply code is a negative permanent response. All codes beginning with a 5 are negative permanent responses. The SMTP server will send a + * negative permanent response on the failure of a command that cannot be reattempted with success. * <p> - * @param reply The reply code to test. - * @return True if a reply code is a postive preliminary response, false - * if not. - ***/ - public static boolean isPositivePreliminary(int reply) - { - return (reply >= 100 && reply < 200); + * + * @param reply The reply code to test. + * @return True if a reply code is a negative permanent response, false if not. + */ + public static boolean isNegativePermanent(final int reply) { + return reply >= 500 && reply < 600; } - /*** - * Determine if a reply code is a positive completion response. All - * codes beginning with a 2 are positive completion responses. - * The SMTP server will send a positive completion response on the final - * successful completion of a command. + /** + * Determine if a reply code is a negative transient response. All codes beginning with a 4 are negative transient responses. The SMTP server will send a + * negative transient response on the failure of a command that can be reattempted with success. * <p> - * @param reply The reply code to test. - * @return True if a reply code is a postive completion response, false - * if not. - ***/ - public static boolean isPositiveCompletion(int reply) - { - return (reply >= 200 && reply < 300); + * + * @param reply The reply code to test. + * @return True if a reply code is a negative transient response, false if not. + */ + public static boolean isNegativeTransient(final int reply) { + return reply >= 400 && reply < 500; } - /*** - * Determine if a reply code is a positive intermediate response. All - * codes beginning with a 3 are positive intermediate responses. - * The SMTP server will send a positive intermediate response on the - * successful completion of one part of a multi-part sequence of - * commands. For example, after a successful DATA command, a positive - * intermediate response will be sent to indicate that the server is - * ready to receive the message data. + /** + * Determine if a reply code is a positive completion response. All codes beginning with a 2 are positive completion responses. The SMTP server will send a + * positive completion response on the final successful completion of a command. * <p> - * @param reply The reply code to test. - * @return True if a reply code is a postive intermediate response, false - * if not. - ***/ - public static boolean isPositiveIntermediate(int reply) - { - return (reply >= 300 && reply < 400); + * + * @param reply The reply code to test. + * @return True if a reply code is a positive completion response, false if not. + */ + public static boolean isPositiveCompletion(final int reply) { + return reply >= 200 && reply < 300; } - /*** - * Determine if a reply code is a negative transient response. All - * codes beginning with a 4 are negative transient responses. - * The SMTP server will send a negative transient response on the - * failure of a command that can be reattempted with success. + /** + * Determine if a reply code is a positive intermediate response. All codes beginning with a 3 are positive intermediate responses. The SMTP server will + * send a positive intermediate response on the successful completion of one part of a multi-part sequence of commands. For example, after a successful DATA + * command, a positive intermediate response will be sent to indicate that the server is ready to receive the message data. * <p> - * @param reply The reply code to test. - * @return True if a reply code is a negative transient response, false - * if not. - ***/ - public static boolean isNegativeTransient(int reply) - { - return (reply >= 400 && reply < 500); + * + * @param reply The reply code to test. + * @return True if a reply code is a positive intermediate response, false if not. + */ + public static boolean isPositiveIntermediate(final int reply) { + return reply >= 300 && reply < 400; } - /*** - * Determine if a reply code is a negative permanent response. All - * codes beginning with a 5 are negative permanent responses. - * The SMTP server will send a negative permanent response on the - * failure of a command that cannot be reattempted with success. + /** + * Determine if a reply code is a positive preliminary response. All codes beginning with a 1 are positive preliminary responses. Postitive preliminary + * responses are used to indicate tentative success. No further commands can be issued to the SMTP server after a positive preliminary response until a + * follow up response is received from the server. * <p> - * @param reply The reply code to test. - * @return True if a reply code is a negative permanent response, false - * if not. - ***/ - public static boolean isNegativePermanent(int reply) - { - return (reply >= 500 && reply < 600); + * <b> Note: </b> <em> No SMTP commands defined in RFC 822 provide this type of reply. </em> + * <p> + * + * @param reply The reply code to test. + * @return True if a reply code is a positive preliminary response, false if not. + */ + public static boolean isPositivePreliminary(final int reply) { + return reply >= 100 && reply < 200; + } + + // Cannot be instantiated + private SMTPReply() { } } diff --git a/src/main/java/org/apache/commons/net/smtp/SMTPSClient.java b/src/main/java/org/apache/commons/net/smtp/SMTPSClient.java index 8b29479..83127b2 100644 --- a/src/main/java/org/apache/commons/net/smtp/SMTPSClient.java +++ b/src/main/java/org/apache/commons/net/smtp/SMTPSClient.java @@ -35,139 +35,148 @@ import org.apache.commons.net.util.SSLContextUtils; import org.apache.commons.net.util.SSLSocketUtils; /** - * SMTP over SSL processing. Copied from FTPSClient.java and modified to suit SMTP. - * If implicit mode is selected (NOT the default), SSL/TLS negotiation starts right - * after the connection has been established. In explicit mode (the default), SSL/TLS - * negotiation starts when the user calls execTLS() and the server accepts the command. - * Implicit usage: - * SMTPSClient c = new SMTPSClient(true); - * c.connect("127.0.0.1", 465); + * SMTP over SSL processing. Copied from FTPSClient.java and modified to suit SMTP. If implicit mode is selected (NOT the default), SSL/TLS negotiation starts + * right after the connection has been established. In explicit mode (the default), SSL/TLS negotiation starts when the user calls execTLS() and the server + * accepts the command. Implicit usage: + * + * <pre> + * SMTPSClient c = new SMTPSClient(true); + * c.connect("127.0.0.1", 465); + * </pre> + * * Explicit usage: - * SMTPSClient c = new SMTPSClient(); - * c.connect("127.0.0.1", 25); - * if (c.execTLS()) { /rest of the commands here/ } * - * Warning: the hostname is not verified against the certificate by default, use - * {@link #setHostnameVerifier(HostnameVerifier)} or {@link #setEndpointCheckingEnabled(boolean)} - * (on Java 1.7+) to enable verification. + * <pre> + * SMTPSClient c = new SMTPSClient(); + * c.connect("127.0.0.1", 25); + * if (c.execTLS()) { + * // Rest of the commands here + * } + * </pre> + * + * <em>Warning</em>: the hostname is not verified against the certificate by default, use {@link #setHostnameVerifier(HostnameVerifier)} or + * {@link #setEndpointCheckingEnabled(boolean)} (on Java 1.7+) to enable verification. + * * @since 3.0 */ -public class SMTPSClient extends SMTPClient -{ +public class SMTPSClient extends SMTPClient { /** Default secure socket protocol name, like TLS */ private static final String DEFAULT_PROTOCOL = "TLS"; /** The security mode. True - Implicit Mode / False - Explicit Mode. */ + private final boolean isImplicit; /** The secure socket protocol to be used, like SSL/TLS. */ + private final String protocol; /** The context object. */ - private SSLContext context = null; - /** The cipher suites. SSLSockets have a default set of these anyway, - so no initialization required. */ - private String[] suites = null; + + private SSLContext context; + /** + * The cipher suites. SSLSockets have a default set of these anyway, so no initialization required. + */ + + private String[] suites; /** The protocol versions. */ - private String[] protocols = null; + + private String[] protocols; /** The {@link TrustManager} implementation, default null (i.e. use system managers). */ - private TrustManager trustManager = null; + private TrustManager trustManager; /** The {@link KeyManager}, default null (i.e. use system managers). */ - private KeyManager keyManager = null; // seems not to be required + private KeyManager keyManager; // seems not to be required /** The {@link HostnameVerifier} to use post-TLS, default null (i.e. no verification). */ - private HostnameVerifier hostnameVerifier = null; + private HostnameVerifier hostnameVerifier; /** Use Java 1.7+ HTTPS Endpoint Identification Algorithim. */ private boolean tlsEndpointChecking; /** - * Constructor for SMTPSClient, using {@link #DEFAULT_PROTOCOL} i.e. TLS - * Sets security mode to explicit (isImplicit = false). + * Constructor for SMTPSClient, using {@link #DEFAULT_PROTOCOL} i.e. TLS Sets security mode to explicit (isImplicit = false). */ - public SMTPSClient() - { + public SMTPSClient() { this(DEFAULT_PROTOCOL, false); } /** * Constructor for SMTPSClient, using {@link #DEFAULT_PROTOCOL} i.e. TLS + * * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit */ - public SMTPSClient(boolean implicit) - { + public SMTPSClient(final boolean implicit) { this(DEFAULT_PROTOCOL, implicit); } + /** + * Constructor for SMTPSClient, using {@link #DEFAULT_PROTOCOL} i.e. TLS + * + * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit + * @param ctx A pre-configured SSL Context. + */ + public SMTPSClient(final boolean implicit, final SSLContext ctx) { + isImplicit = implicit; + context = ctx; + protocol = DEFAULT_PROTOCOL; + } + + /** + * Constructor for SMTPSClient. + * + * @param context A pre-configured SSL Context. + * @see #SMTPSClient(boolean, SSLContext) + */ + public SMTPSClient(final SSLContext context) { + this(false, context); + } + /** * Constructor for SMTPSClient, using explicit security mode. + * * @param proto the protocol. */ - public SMTPSClient(String proto) - { + public SMTPSClient(final String proto) { this(proto, false); } /** * Constructor for SMTPSClient. - * @param proto the protocol. + * + * @param proto the protocol. * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit */ - public SMTPSClient(String proto, boolean implicit) - { + public SMTPSClient(final String proto, final boolean implicit) { protocol = proto; isImplicit = implicit; } /** * Constructor for SMTPSClient. - * @param proto the protocol. + * + * @param proto the protocol. * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit * @param encoding the encoding * @since 3.3 */ - public SMTPSClient(String proto, boolean implicit, String encoding) - { + public SMTPSClient(final String proto, final boolean implicit, final String encoding) { super(encoding); protocol = proto; isImplicit = implicit; } /** - * Constructor for SMTPSClient, using {@link #DEFAULT_PROTOCOL} i.e. TLS - * @param implicit The security mode, {@code true} for implicit, {@code false} for explicit - * @param ctx A pre-configured SSL Context. - */ - public SMTPSClient(boolean implicit, SSLContext ctx) - { - isImplicit = implicit; - context = ctx; - protocol = DEFAULT_PROTOCOL; - } - - /** - * Constructor for SMTPSClient. - * @param context A pre-configured SSL Context. - * @see #SMTPSClient(boolean, SSLContext) - */ - public SMTPSClient(SSLContext context) - { - this(false, context); - } - - /** - * Because there are so many connect() methods, - * the _connectAction_() method is provided as a means of performing - * some action immediately after establishing a connection, - * rather than reimplementing all of the connect() methods. + * Because there are so many connect() methods, the _connectAction_() method is provided as a means of performing some action immediately after establishing + * a connection, rather than reimplementing all of the connect() methods. + * * @throws IOException If it is thrown by _connectAction_(). * @see org.apache.commons.net.SocketClient#_connectAction_() */ @Override - protected void _connectAction_() throws IOException - { + protected void _connectAction_() throws IOException { // Implicit mode. if (isImplicit) { + applySocketAttributes(); performSSLNegotiation(); } super._connectAction_(); @@ -175,208 +184,189 @@ public class SMTPSClient extends SMTPClient } /** - * Performs a lazy init of the SSL context. - * @throws IOException When could not initialize the SSL context. + * The TLS command execution. + * + * @throws IOException If an I/O error occurs while sending the command or performing the negotiation. + * @return TRUE if the command and negotiation succeeded. */ - private void initSSLContext() throws IOException - { - if (context == null) - { - context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager()); + public boolean execTLS() throws IOException { + if (!SMTPReply.isPositiveCompletion(sendCommand("STARTTLS"))) { + return false; + // throw new SSLException(getReplyString()); } + performSSLNegotiation(); + return true; } /** - * SSL/TLS negotiation. Acquires an SSL socket of a - * connection and carries out handshake processing. - * @throws IOException If server negotiation fails. + * Returns the names of the cipher suites which could be enabled for use on this connection. When the underlying {@link java.net.Socket Socket} is not an + * {@link SSLSocket} instance, returns null. + * + * @return An array of cipher suite names, or <code>null</code>. */ - private void performSSLNegotiation() throws IOException - { - initSSLContext(); - - SSLSocketFactory ssf = context.getSocketFactory(); - String host = (_hostname_ != null) ? _hostname_ : getRemoteAddress().getHostAddress(); - int port = getRemotePort(); - SSLSocket socket = - (SSLSocket) ssf.createSocket(_socket_, host, port, true); - socket.setEnableSessionCreation(true); - socket.setUseClientMode(true); - - if (tlsEndpointChecking) { - SSLSocketUtils.enableEndpointNameVerification(socket); - } - if (protocols != null) { - socket.setEnabledProtocols(protocols); - } - if (suites != null) { - socket.setEnabledCipherSuites(suites); + public String[] getEnabledCipherSuites() { + if (_socket_ instanceof SSLSocket) { + return ((SSLSocket) _socket_).getEnabledCipherSuites(); } - socket.startHandshake(); - - // TODO the following setup appears to duplicate that in the super class methods - _socket_ = socket; - _input_ = socket.getInputStream(); - _output_ = socket.getOutputStream(); - _reader = new CRLFLineReader( - new InputStreamReader(_input_, encoding)); - _writer = new BufferedWriter( - new OutputStreamWriter(_output_, encoding)); + return null; + } - if (hostnameVerifier != null && !hostnameVerifier.verify(host, socket.getSession())) { - throw new SSLHandshakeException("Hostname doesn't match certificate"); + /** + * Returns the names of the protocol versions which are currently enabled for use on this connection. When the underlying {@link java.net.Socket Socket} is + * not an {@link SSLSocket} instance, returns null. + * + * @return An array of protocols, or <code>null</code>. + */ + public String[] getEnabledProtocols() { + if (_socket_ instanceof SSLSocket) { + return ((SSLSocket) _socket_).getEnabledProtocols(); } + return null; } /** - * Get the {@link KeyManager} instance. - * @return The current {@link KeyManager} instance. + * Get the currently configured {@link HostnameVerifier}. + * + * @return A HostnameVerifier instance. + * @since 3.4 */ - public KeyManager getKeyManager() - { - return keyManager; + public HostnameVerifier getHostnameVerifier() { + return hostnameVerifier; } /** - * Set a {@link KeyManager} to use. - * @param newKeyManager The KeyManager implementation to set. - * @see org.apache.commons.net.util.KeyManagerUtils + * Get the {@link KeyManager} instance. + * + * @return The current {@link KeyManager} instance. */ - public void setKeyManager(KeyManager newKeyManager) - { - keyManager = newKeyManager; + public KeyManager getKeyManager() { + return keyManager; } /** - * Controls which particular cipher suites are enabled for use on this - * connection. Called before server negotiation. - * @param cipherSuites The cipher suites. + * Get the currently configured {@link TrustManager}. + * + * @return A TrustManager instance. */ - public void setEnabledCipherSuites(String[] cipherSuites) - { - suites = new String[cipherSuites.length]; - System.arraycopy(cipherSuites, 0, suites, 0, cipherSuites.length); + public TrustManager getTrustManager() { + return trustManager; } /** - * Returns the names of the cipher suites which could be enabled - * for use on this connection. - * When the underlying {@link java.net.Socket Socket} is not an {@link SSLSocket} instance, returns null. - * @return An array of cipher suite names, or <code>null</code>. + * Performs a lazy init of the SSL context. + * + * @throws IOException When could not initialize the SSL context. */ - public String[] getEnabledCipherSuites() - { - if (_socket_ instanceof SSLSocket) - { - return ((SSLSocket)_socket_).getEnabledCipherSuites(); + private void initSSLContext() throws IOException { + if (context == null) { + context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager()); } - return null; } /** - * Controls which particular protocol versions are enabled for use on this - * connection. I perform setting before a server negotiation. - * @param protocolVersions The protocol versions. + * Return whether or not endpoint identification using the HTTPS algorithm on Java 1.7+ is enabled. The default behavior is for this to be disabled. + * + * @return True if enabled, false if not. + * @since 3.4 */ - public void setEnabledProtocols(String[] protocolVersions) - { - protocols = new String[protocolVersions.length]; - System.arraycopy(protocolVersions, 0, protocols, 0, protocolVersions.length); + public boolean isEndpointCheckingEnabled() { + return tlsEndpointChecking; } /** - * Returns the names of the protocol versions which are currently - * enabled for use on this connection. - * When the underlying {@link java.net.Socket Socket} is not an {@link SSLSocket} instance, returns null. - * @return An array of protocols, or <code>null</code>. + * SSL/TLS negotiation. Acquires an SSL socket of a connection and carries out handshake processing. + * + * @throws IOException If server negotiation fails. */ - public String[] getEnabledProtocols() - { - if (_socket_ instanceof SSLSocket) - { - return ((SSLSocket)_socket_).getEnabledProtocols(); + private void performSSLNegotiation() throws IOException { + initSSLContext(); + + final SSLSocketFactory ssf = context.getSocketFactory(); + final String host = _hostname_ != null ? _hostname_ : getRemoteAddress().getHostAddress(); + final int port = getRemotePort(); + final SSLSocket socket = (SSLSocket) ssf.createSocket(_socket_, host, port, true); + socket.setEnableSessionCreation(true); + socket.setUseClientMode(true); + + if (tlsEndpointChecking) { + SSLSocketUtils.enableEndpointNameVerification(socket); } - return null; - } + if (protocols != null) { + socket.setEnabledProtocols(protocols); + } + if (suites != null) { + socket.setEnabledCipherSuites(suites); + } + socket.startHandshake(); - /** - * The TLS command execution. - * @throws IOException If an I/O error occurs while sending - * the command or performing the negotiation. - * @return TRUE if the command and negotiation succeeded. - */ - public boolean execTLS() throws IOException - { - if (!SMTPReply.isPositiveCompletion(sendCommand("STARTTLS"))) - { - return false; - //throw new SSLException(getReplyString()); + // TODO the following setup appears to duplicate that in the super class methods + _socket_ = socket; + _input_ = socket.getInputStream(); + _output_ = socket.getOutputStream(); + reader = new CRLFLineReader(new InputStreamReader(_input_, encoding)); + writer = new BufferedWriter(new OutputStreamWriter(_output_, encoding)); + + if (hostnameVerifier != null && !hostnameVerifier.verify(host, socket.getSession())) { + throw new SSLHandshakeException("Hostname doesn't match certificate"); } - performSSLNegotiation(); - return true; } /** - * Get the currently configured {@link TrustManager}. - * @return A TrustManager instance. + * Controls which particular cipher suites are enabled for use on this connection. Called before server negotiation. + * + * @param cipherSuites The cipher suites. */ - public TrustManager getTrustManager() - { - return trustManager; + public void setEnabledCipherSuites(final String[] cipherSuites) { + suites = cipherSuites.clone(); } /** - * Override the default {@link TrustManager} to use. - * @param newTrustManager The TrustManager implementation to set. - * @see org.apache.commons.net.util.TrustManagerUtils + * Controls which particular protocol versions are enabled for use on this connection. I perform setting before a server negotiation. + * + * @param protocolVersions The protocol versions. */ - public void setTrustManager(TrustManager newTrustManager) - { - trustManager = newTrustManager; + public void setEnabledProtocols(final String[] protocolVersions) { + protocols = protocolVersions.clone(); } /** - * Get the currently configured {@link HostnameVerifier}. - * @return A HostnameVerifier instance. + * Automatic endpoint identification checking using the HTTPS algorithm is supported on Java 1.7+. The default behavior is for this to be disabled. + * + * @param enable Enable automatic endpoint identification checking using the HTTPS algorithm on Java 1.7+. * @since 3.4 */ - public HostnameVerifier getHostnameVerifier() - { - return hostnameVerifier; + public void setEndpointCheckingEnabled(final boolean enable) { + tlsEndpointChecking = enable; } /** * Override the default {@link HostnameVerifier} to use. + * * @param newHostnameVerifier The HostnameVerifier implementation to set or <code>null</code> to disable. * @since 3.4 */ - public void setHostnameVerifier(HostnameVerifier newHostnameVerifier) - { + public void setHostnameVerifier(final HostnameVerifier newHostnameVerifier) { hostnameVerifier = newHostnameVerifier; } /** - * Return whether or not endpoint identification using the HTTPS algorithm - * on Java 1.7+ is enabled. The default behaviour is for this to be disabled. + * Set a {@link KeyManager} to use. * - * @return True if enabled, false if not. - * @since 3.4 + * @param newKeyManager The KeyManager implementation to set. + * @see org.apache.commons.net.util.KeyManagerUtils */ - public boolean isEndpointCheckingEnabled() - { - return tlsEndpointChecking; + public void setKeyManager(final KeyManager newKeyManager) { + keyManager = newKeyManager; } /** - * Automatic endpoint identification checking using the HTTPS algorithm - * is supported on Java 1.7+. The default behaviour is for this to be disabled. + * Override the default {@link TrustManager} to use. * - * @param enable Enable automatic endpoint identification checking using the HTTPS algorithm on Java 1.7+. - * @since 3.4 + * @param newTrustManager The TrustManager implementation to set. + * @see org.apache.commons.net.util.TrustManagerUtils */ - public void setEndpointCheckingEnabled(boolean enable) - { - tlsEndpointChecking = enable; + public void setTrustManager(final TrustManager newTrustManager) { + trustManager = newTrustManager; } } diff --git a/src/main/java/org/apache/commons/net/smtp/SimpleSMTPHeader.java b/src/main/java/org/apache/commons/net/smtp/SimpleSMTPHeader.java index 56aac00..34be7e7 100644 --- a/src/main/java/org/apache/commons/net/smtp/SimpleSMTPHeader.java +++ b/src/main/java/org/apache/commons/net/smtp/SimpleSMTPHeader.java @@ -21,16 +21,13 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; -/*** - * This class is used to construct a bare minimum - * acceptable header for an email message. To construct more - * complicated headers you should refer to RFC 5322. When the - * Java Mail API is finalized, you will be - * able to use it to compose fully compliant Internet text messages. +/** + * This class is used to construct a bare minimum acceptable header for an email message. To construct more complicated headers you should refer to RFC 5322. + * When the Java Mail API is finalized, you will be able to use it to compose fully compliant Internet text messages. * <p> - * The main purpose of the class is to faciliatate the mail sending - * process, by relieving the programmer from having to explicitly format - * a simple message header. For example: + * The main purpose of the class is to faciliatate the mail sending process, by relieving the programmer from having to explicitly format a simple message + * header. For example: + * * <pre> * writer = client.sendMessageData(); * if(writer == null) // failure @@ -47,96 +44,83 @@ import java.util.Locale; * </pre> * * @see SMTPClient - ***/ - -public class SimpleSMTPHeader -{ - private final String __subject; - private final String __from; - private final String __to; - private final StringBuffer __headerFields; + */ + +public class SimpleSMTPHeader { + private final String subject; + private final String from; + private final String to; + private final StringBuffer headerFields; private boolean hasHeaderDate; - private StringBuffer __cc; + private StringBuffer cc; - /*** - * Creates a new SimpleSMTPHeader instance initialized with the given - * from, to, and subject header field values. + /** + * Creates a new SimpleSMTPHeader instance initialized with the given from, to, and subject header field values. * <p> - * @param from The value of the <code>From:</code> header field. This - * should be the sender's email address. - * Must not be null. - * @param to The value of the <code>To:</code> header field. This - * should be the recipient's email address. - * May be null - * @param subject The value of the <code>Subject:</code> header field. - * This should be the subject of the message. - * May be null - ***/ - public SimpleSMTPHeader(String from, String to, String subject) - { + * + * @param from The value of the <code>From:</code> header field. This should be the sender's email address. Must not be null. + * @param to The value of the <code>To:</code> header field. This should be the recipient's email address. May be null + * @param subject The value of the <code>Subject:</code> header field. This should be the subject of the message. May be null + */ + public SimpleSMTPHeader(final String from, final String to, final String subject) { if (from == null) { throw new IllegalArgumentException("From cannot be null"); } - __to = to; - __from = from; - __subject = subject; - __headerFields = new StringBuffer(); - __cc = null; - } - - /*** - * Adds an arbitrary header field with the given value to the article - * header. These headers will be written before the From, To, Subject, and - * Cc fields when the SimpleSMTPHeader is convertered to a string. - * An example use would be: - * <pre> - * header.addHeaderField("Organization", "Foobar, Inc."); - * </pre> - * <p> - * @param headerField The header field to add, not including the colon. - * @param value The value of the added header field. - ***/ - public void addHeaderField(String headerField, String value) - { - if (!hasHeaderDate && "Date".equals(headerField)) { - hasHeaderDate = true; - } - __headerFields.append(headerField); - __headerFields.append(": "); - __headerFields.append(value); - __headerFields.append('\n'); + this.to = to; + this.from = from; + this.subject = subject; + this.headerFields = new StringBuffer(); + this.cc = null; } - - /*** + /** * Add an email address to the CC (carbon copy or courtesy copy) list. * <p> + * * @param address The email address to add to the CC list. - ***/ - public void addCC(String address) - { - if (__cc == null) { - __cc = new StringBuffer(); + */ + public void addCC(final String address) { + if (cc == null) { + cc = new StringBuffer(); } else { - __cc.append(", "); + cc.append(", "); } - __cc.append(address); + cc.append(address); } + /** + * Adds an arbitrary header field with the given value to the article header. These headers will be written before the From, To, Subject, and Cc fields when + * the SimpleSMTPHeader is convertered to a string. An example use would be: + * + * <pre> + * header.addHeaderField("Organization", "Foobar, Inc."); + * </pre> + * <p> + * + * @param headerField The header field to add, not including the colon. + * @param value The value of the added header field. + */ + public void addHeaderField(final String headerField, final String value) { + if (!hasHeaderDate && "Date".equals(headerField)) { + hasHeaderDate = true; + } + headerFields.append(headerField); + headerFields.append(": "); + headerFields.append(value); + headerFields.append('\n'); + } - /*** - * Converts the SimpleSMTPHeader to a properly formatted header in - * the form of a String, including the blank line used to separate - * the header from the article body. The header fields CC and Subject - * are only included when they are non-null. + /** + * Converts the SimpleSMTPHeader to a properly formatted header in the form of a String, including the blank line used to separate the header from the + * article body. The header fields CC and Subject are only included when they are non-null. * <p> + * * @return The message header in the form of a String. - ***/ + */ @Override - public String toString() - { - StringBuilder header = new StringBuilder(); + public String toString() { + final StringBuilder header = new StringBuilder(); final String pattern = "EEE, dd MMM yyyy HH:mm:ss Z"; // Fri, 21 Nov 1997 09:55:06 -0600 final SimpleDateFormat format = new SimpleDateFormat(pattern, Locale.ENGLISH); @@ -144,24 +128,22 @@ public class SimpleSMTPHeader if (!hasHeaderDate) { addHeaderField("Date", format.format(new Date())); } - if (__headerFields.length() > 0) { - header.append(__headerFields.toString()); + if (headerFields.length() > 0) { + header.append(headerFields.toString()); } - header.append("From: ").append(__from).append("\n"); + header.append("From: ").append(from).append("\n"); - if (__to != null) { - header.append("To: ").append(__to).append("\n"); + if (to != null) { + header.append("To: ").append(to).append("\n"); } - if (__cc != null) - { - header.append("Cc: ").append(__cc.toString()).append("\n"); + if (cc != null) { + header.append("Cc: ").append(cc.toString()).append("\n"); } - if (__subject != null) - { - header.append("Subject: ").append(__subject).append("\n"); + if (subject != null) { + header.append("Subject: ").append(subject).append("\n"); } header.append('\n'); // end of headers; body follows @@ -169,6 +151,3 @@ public class SimpleSMTPHeader return header.toString(); } } - - - diff --git a/src/main/java/org/apache/commons/net/telnet/EchoOptionHandler.java b/src/main/java/org/apache/commons/net/telnet/EchoOptionHandler.java index a48f59c..7bd197f 100644 --- a/src/main/java/org/apache/commons/net/telnet/EchoOptionHandler.java +++ b/src/main/java/org/apache/commons/net/telnet/EchoOptionHandler.java @@ -17,36 +17,29 @@ package org.apache.commons.net.telnet; -/*** +/** * Implements the telnet echo option RFC 857. - ***/ -public class EchoOptionHandler extends TelnetOptionHandler -{ - /*** - * Constructor for the EchoOptionHandler. Allows defining desired - * initial setting for local/remote activation of this option and - * behaviour in case a local/remote activation request for this - * option is received. - * <p> - * @param initlocal - if set to true, a WILL is sent upon connection. - * @param initremote - if set to true, a DO is sent upon connection. - * @param acceptlocal - if set to true, any DO request is accepted. - * @param acceptremote - if set to true, any WILL request is accepted. - ***/ - public EchoOptionHandler(boolean initlocal, boolean initremote, - boolean acceptlocal, boolean acceptremote) - { - super(TelnetOption.ECHO, initlocal, initremote, - acceptlocal, acceptremote); + */ +public class EchoOptionHandler extends TelnetOptionHandler { + /** + * Constructor for the EchoOptionHandler. Initial and accept behavior flags are set to false + */ + public EchoOptionHandler() { + super(TelnetOption.ECHO, false, false, false, false); } - /*** - * Constructor for the EchoOptionHandler. Initial and accept - * behaviour flags are set to false - ***/ - public EchoOptionHandler() - { - super(TelnetOption.ECHO, false, false, false, false); + /** + * Constructor for the EchoOptionHandler. Allows defining desired initial setting for local/remote activation of this option and behavior in case a + * local/remote activation request for this option is received. + * <p> + * + * @param initlocal - if set to true, a WILL is sent upon connection. + * @param initremote - if set to true, a DO is sent upon connection. + * @param acceptlocal - if set to true, any DO request is accepted. + * @param acceptremote - if set to true, any WILL request is accepted. + */ + public EchoOptionHandler(final boolean initlocal, final boolean initremote, final boolean acceptlocal, final boolean acceptremote) { + super(TelnetOption.ECHO, initlocal, initremote, acceptlocal, acceptremote); } } diff --git a/src/main/java/org/apache/commons/net/telnet/InvalidTelnetOptionException.java b/src/main/java/org/apache/commons/net/telnet/InvalidTelnetOptionException.java index f595440..c3b3a63 100644 --- a/src/main/java/org/apache/commons/net/telnet/InvalidTelnetOptionException.java +++ b/src/main/java/org/apache/commons/net/telnet/InvalidTelnetOptionException.java @@ -17,46 +17,44 @@ package org.apache.commons.net.telnet; -/*** - * The InvalidTelnetOptionException is the exception that is - * thrown whenever a TelnetOptionHandler with an invlaid - * option code is registered in TelnetClient with addOptionHandler. - ***/ -public class InvalidTelnetOptionException extends Exception -{ +/** + * The InvalidTelnetOptionException is the exception that is thrown whenever a TelnetOptionHandler with an invlaid option code is registered in TelnetClient + * with addOptionHandler. + */ +public class InvalidTelnetOptionException extends Exception { private static final long serialVersionUID = -2516777155928793597L; - /*** + /** * Option code - ***/ + */ private final int optionCode; - /*** + /** * Error message - ***/ + */ private final String msg; - /*** + /** * Constructor for the exception. * <p> + * * @param message - Error message. * @param optcode - Option code. - ***/ - public InvalidTelnetOptionException(String message, int optcode) - { + */ + public InvalidTelnetOptionException(final String message, final int optcode) { optionCode = optcode; msg = message; } - /*** + /** * Gets the error message of ths exception. * <p> + * * @return the error message. - ***/ + */ @Override - public String getMessage() - { - return (msg + ": " + optionCode); + public String getMessage() { + return msg + ": " + optionCode; } } diff --git a/src/main/java/org/apache/commons/net/telnet/SimpleOptionHandler.java b/src/main/java/org/apache/commons/net/telnet/SimpleOptionHandler.java index 8a88e0f..6929b5b 100644 --- a/src/main/java/org/apache/commons/net/telnet/SimpleOptionHandler.java +++ b/src/main/java/org/apache/commons/net/telnet/SimpleOptionHandler.java @@ -17,43 +17,33 @@ package org.apache.commons.net.telnet; -/*** - * Simple option handler that can be used for options - * that don't require subnegotiation. - ***/ -public class SimpleOptionHandler extends TelnetOptionHandler -{ - /*** - * Constructor for the SimpleOptionHandler. Allows defining desired - * initial setting for local/remote activation of this option and - * behaviour in case a local/remote activation request for this - * option is received. +/** + * Simple option handler that can be used for options that don't require subnegotiation. + */ +public class SimpleOptionHandler extends TelnetOptionHandler { + /** + * Constructor for the SimpleOptionHandler. Initial and accept behavior flags are set to false * <p> + * * @param optcode - option code. - * @param initlocal - if set to true, a WILL is sent upon connection. - * @param initremote - if set to true, a DO is sent upon connection. - * @param acceptlocal - if set to true, any DO request is accepted. - * @param acceptremote - if set to true, any WILL request is accepted. - ***/ - public SimpleOptionHandler(int optcode, - boolean initlocal, - boolean initremote, - boolean acceptlocal, - boolean acceptremote) - { - super(optcode, initlocal, initremote, - acceptlocal, acceptremote); + */ + public SimpleOptionHandler(final int optcode) { + super(optcode, false, false, false, false); } - /*** - * Constructor for the SimpleOptionHandler. Initial and accept - * behaviour flags are set to false + /** + * Constructor for the SimpleOptionHandler. Allows defining desired initial setting for local/remote activation of this option and behavior in case a + * local/remote activation request for this option is received. * <p> - * @param optcode - option code. - ***/ - public SimpleOptionHandler(int optcode) - { - super(optcode, false, false, false, false); + * + * @param optcode - option code. + * @param initlocal - if set to true, a WILL is sent upon connection. + * @param initremote - if set to true, a DO is sent upon connection. + * @param acceptlocal - if set to true, any DO request is accepted. + * @param acceptremote - if set to true, any WILL request is accepted. + */ + public SimpleOptionHandler(final int optcode, final boolean initlocal, final boolean initremote, final boolean acceptlocal, final boolean acceptremote) { + super(optcode, initlocal, initremote, acceptlocal, acceptremote); } } diff --git a/src/main/java/org/apache/commons/net/telnet/SuppressGAOptionHandler.java b/src/main/java/org/apache/commons/net/telnet/SuppressGAOptionHandler.java index b353d48..17fb541 100644 --- a/src/main/java/org/apache/commons/net/telnet/SuppressGAOptionHandler.java +++ b/src/main/java/org/apache/commons/net/telnet/SuppressGAOptionHandler.java @@ -17,36 +17,29 @@ package org.apache.commons.net.telnet; -/*** +/** * Implements the telnet suppress go ahead option RFC 858. - ***/ -public class SuppressGAOptionHandler extends TelnetOptionHandler -{ - /*** - * Constructor for the SuppressGAOptionHandler. Allows defining desired - * initial setting for local/remote activation of this option and - * behaviour in case a local/remote activation request for this - * option is received. - * <p> - * @param initlocal - if set to true, a WILL is sent upon connection. - * @param initremote - if set to true, a DO is sent upon connection. - * @param acceptlocal - if set to true, any DO request is accepted. - * @param acceptremote - if set to true, any WILL request is accepted. - ***/ - public SuppressGAOptionHandler(boolean initlocal, boolean initremote, - boolean acceptlocal, boolean acceptremote) - { - super(TelnetOption.SUPPRESS_GO_AHEAD, initlocal, initremote, - acceptlocal, acceptremote); + */ +public class SuppressGAOptionHandler extends TelnetOptionHandler { + /** + * Constructor for the SuppressGAOptionHandler. Initial and accept behavior flags are set to false + */ + public SuppressGAOptionHandler() { + super(TelnetOption.SUPPRESS_GO_AHEAD, false, false, false, false); } - /*** - * Constructor for the SuppressGAOptionHandler. Initial and accept - * behaviour flags are set to false - ***/ - public SuppressGAOptionHandler() - { - super(TelnetOption.SUPPRESS_GO_AHEAD, false, false, false, false); + /** + * Constructor for the SuppressGAOptionHandler. Allows defining desired initial setting for local/remote activation of this option and behavior in case a + * local/remote activation request for this option is received. + * <p> + * + * @param initlocal - if set to true, a WILL is sent upon connection. + * @param initremote - if set to true, a DO is sent upon connection. + * @param acceptlocal - if set to true, any DO request is accepted. + * @param acceptremote - if set to true, any WILL request is accepted. + */ + public SuppressGAOptionHandler(final boolean initlocal, final boolean initremote, final boolean acceptlocal, final boolean acceptremote) { + super(TelnetOption.SUPPRESS_GO_AHEAD, initlocal, initremote, acceptlocal, acceptremote); } } diff --git a/src/main/java/org/apache/commons/net/telnet/Telnet.java b/src/main/java/org/apache/commons/net/telnet/Telnet.java index c66f891..2c10ddb 100644 --- a/src/main/java/org/apache/commons/net/telnet/Telnet.java +++ b/src/main/java/org/apache/commons/net/telnet/Telnet.java @@ -19,390 +19,342 @@ package org.apache.commons.net.telnet; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; -import java.io.OutputStream; import java.io.IOException; +import java.io.OutputStream; import java.util.Arrays; import org.apache.commons.net.SocketClient; -class Telnet extends SocketClient -{ - static final boolean debug = /*true;*/ false; +class Telnet extends SocketClient { + static final boolean debug = /* true; */ false; - static final boolean debugoptions = /*true;*/ false; + static final boolean debugoptions = /* true; */ false; - static final byte[] _COMMAND_DO = { - (byte)TelnetCommand.IAC, (byte)TelnetCommand.DO - }; + static final byte[] COMMAND_DO = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO }; - static final byte[] _COMMAND_DONT = { - (byte)TelnetCommand.IAC, (byte)TelnetCommand.DONT - }; + static final byte[] COMMAND_DONT = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.DONT }; - static final byte[] _COMMAND_WILL = { - (byte)TelnetCommand.IAC, (byte)TelnetCommand.WILL - }; + static final byte[] COMMAND_WILL = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.WILL }; - static final byte[] _COMMAND_WONT = { - (byte)TelnetCommand.IAC, (byte)TelnetCommand.WONT - }; + static final byte[] COMMAND_WONT = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT }; - static final byte[] _COMMAND_SB = { - (byte)TelnetCommand.IAC, (byte)TelnetCommand.SB - }; + static final byte[] COMMAND_SB = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.SB }; - static final byte[] _COMMAND_SE = { - (byte)TelnetCommand.IAC, (byte)TelnetCommand.SE - }; + static final byte[] COMMAND_SE = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.SE }; - static final int _WILL_MASK = 0x01, _DO_MASK = 0x02, - _REQUESTED_WILL_MASK = 0x04, _REQUESTED_DO_MASK = 0x08; + static final int WILL_MASK = 0x01; + static final int DO_MASK = 0x02; + static final int REQUESTED_WILL_MASK = 0x04; + static final int REQUESTED_DO_MASK = 0x08; /* public */ - static final int DEFAULT_PORT = 23; + static final int DEFAULT_PORT = 23; - int[] _doResponse, _willResponse, _options; - - /* TERMINAL-TYPE option (start)*/ - /*** + /* TERMINAL-TYPE option (start) */ + /** * Terminal type option - ***/ + */ protected static final int TERMINAL_TYPE = 24; - - /*** + /** * Send (for subnegotiation) - ***/ - protected static final int TERMINAL_TYPE_SEND = 1; - - /*** + */ + protected static final int TERMINAL_TYPE_SEND = 1; + /** * Is (for subnegotiation) - ***/ - protected static final int TERMINAL_TYPE_IS = 0; + */ + protected static final int TERMINAL_TYPE_IS = 0; - /*** + /** * Is sequence (for subnegotiation) - ***/ - static final byte[] _COMMAND_IS = { - (byte) TERMINAL_TYPE, (byte) TERMINAL_TYPE_IS - }; + */ + static final byte[] COMMAND_IS = { (byte) TERMINAL_TYPE, (byte) TERMINAL_TYPE_IS }; - /*** - * Terminal type - ***/ - private String terminalType = null; - /* TERMINAL-TYPE option (end)*/ + /* Code Section added for supporting AYT (start) */ + /** + * AYT sequence + */ + static final byte[] COMMAND_AYT = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.AYT }; - /* open TelnetOptionHandler functionality (start)*/ - /*** - * Array of option handlers - ***/ - private final TelnetOptionHandler optionHandlers[]; + private final int[] doResponse; - /* open TelnetOptionHandler functionality (end)*/ + private final int[] willResponse; - /* Code Section added for supporting AYT (start)*/ - /*** - * AYT sequence - ***/ - static final byte[] _COMMAND_AYT = { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.AYT - }; + private final int[] options; + + /** + * Terminal type + */ + private String terminalType; + /* TERMINAL-TYPE option (end) */ + + /* open TelnetOptionHandler functionality (end) */ - /*** + /* open TelnetOptionHandler functionality (start) */ + /** + * Array of option handlers + */ + private final TelnetOptionHandler[] optionHandlers; + + /** * monitor to wait for AYT - ***/ + */ private final Object aytMonitor = new Object(); - /*** + /** * flag for AYT - ***/ + */ private volatile boolean aytFlag = true; - /* Code Section added for supporting AYT (end)*/ + /* Code Section added for supporting AYT (end) */ - /*** + /** * The stream on which to spy - ***/ - private volatile OutputStream spyStream = null; + */ + private volatile OutputStream spyStream; - /*** + /** * The notification handler - ***/ - private TelnetNotificationHandler __notifhand = null; - /*** + */ + private TelnetNotificationHandler notifhand; + + /** * Empty Constructor - ***/ - Telnet() - { + */ + Telnet() { setDefaultPort(DEFAULT_PORT); - _doResponse = new int[TelnetOption.MAX_OPTION_VALUE + 1]; - _willResponse = new int[TelnetOption.MAX_OPTION_VALUE + 1]; - _options = new int[TelnetOption.MAX_OPTION_VALUE + 1]; - optionHandlers = - new TelnetOptionHandler[TelnetOption.MAX_OPTION_VALUE + 1]; + doResponse = new int[TelnetOption.MAX_OPTION_VALUE + 1]; + willResponse = new int[TelnetOption.MAX_OPTION_VALUE + 1]; + options = new int[TelnetOption.MAX_OPTION_VALUE + 1]; + optionHandlers = new TelnetOptionHandler[TelnetOption.MAX_OPTION_VALUE + 1]; } - /* TERMINAL-TYPE option (start)*/ - /*** + /* TERMINAL-TYPE option (start) */ + /** * This constructor lets you specify the terminal type. * * @param termtype - terminal type to be negotiated (ej. VT100) - ***/ - Telnet(String termtype) - { + */ + Telnet(final String termtype) { setDefaultPort(DEFAULT_PORT); - _doResponse = new int[TelnetOption.MAX_OPTION_VALUE + 1]; - _willResponse = new int[TelnetOption.MAX_OPTION_VALUE + 1]; - _options = new int[TelnetOption.MAX_OPTION_VALUE + 1]; + doResponse = new int[TelnetOption.MAX_OPTION_VALUE + 1]; + willResponse = new int[TelnetOption.MAX_OPTION_VALUE + 1]; + options = new int[TelnetOption.MAX_OPTION_VALUE + 1]; terminalType = termtype; - optionHandlers = - new TelnetOptionHandler[TelnetOption.MAX_OPTION_VALUE + 1]; + optionHandlers = new TelnetOptionHandler[TelnetOption.MAX_OPTION_VALUE + 1]; } - /* TERMINAL-TYPE option (end)*/ + /* TERMINAL-TYPE option (end) */ - /*** - * Looks for the state of the option. - * - * @return returns true if a will has been acknowledged + /** + * Called upon connection. * - * @param option - option code to be looked up. - ***/ - boolean _stateIsWill(int option) - { - return ((_options[option] & _WILL_MASK) != 0); - } + * @throws IOException - Exception in I/O. + */ + @Override + protected void _connectAction_() throws IOException { + /* (start). BUGFIX: clean the option info for each connection */ + for (int ii = 0; ii < TelnetOption.MAX_OPTION_VALUE + 1; ii++) { + doResponse[ii] = 0; + willResponse[ii] = 0; + options[ii] = 0; + if (optionHandlers[ii] != null) { + optionHandlers[ii].setDo(false); + optionHandlers[ii].setWill(false); + } + } + /* (end). BUGFIX: clean the option info for each connection */ - /*** - * Looks for the state of the option. - * - * @return returns true if a wont has been acknowledged - * - * @param option - option code to be looked up. - ***/ - boolean _stateIsWont(int option) - { - return !_stateIsWill(option); - } + super._connectAction_(); + _input_ = new BufferedInputStream(_input_); + _output_ = new BufferedOutputStream(_output_); - /*** - * Looks for the state of the option. - * - * @return returns true if a do has been acknowledged - * - * @param option - option code to be looked up. - ***/ - boolean _stateIsDo(int option) - { - return ((_options[option] & _DO_MASK) != 0); - } + /* open TelnetOptionHandler functionality (start) */ + for (int ii = 0; ii < TelnetOption.MAX_OPTION_VALUE + 1; ii++) { + if (optionHandlers[ii] != null) { + if (optionHandlers[ii].getInitLocal()) { + requestWill(optionHandlers[ii].getOptionCode()); + } - /*** - * Looks for the state of the option. - * - * @return returns true if a dont has been acknowledged - * - * @param option - option code to be looked up. - ***/ - boolean _stateIsDont(int option) - { - return !_stateIsDo(option); + if (optionHandlers[ii].getInitRemote()) { + requestDo(optionHandlers[ii].getOptionCode()); + } + } + } + /* open TelnetOptionHandler functionality (end) */ } - /*** - * Looks for the state of the option. - * - * @return returns true if a will has been reuqested + /* Code Section added for supporting spystreams (start) */ + /** + * Registers an OutputStream for spying what's going on in the Telnet session. * - * @param option - option code to be looked up. - ***/ - boolean _requestedWill(int option) - { - return ((_options[option] & _REQUESTED_WILL_MASK) != 0); + * @param spystream - OutputStream on which session activity will be echoed. + */ + void _registerSpyStream(final OutputStream spystream) { + spyStream = spystream; } - /*** - * Looks for the state of the option. - * - * @return returns true if a wont has been reuqested + /* Code Section added for supporting AYT (start) */ + /** + * Sends an Are You There sequence and waits for the result. * - * @param option - option code to be looked up. - ***/ - boolean _requestedWont(int option) - { - return !_requestedWill(option); - } + * @param timeout - Time to wait for a response (millis.) + * @throws IOException - Exception in I/O. + * @throws IllegalArgumentException - Illegal argument + * @throws InterruptedException - Interrupted during wait. + * @return true if AYT received a response, false otherwise + **/ + final boolean _sendAYT(final long timeout) throws IOException, IllegalArgumentException, InterruptedException { + boolean retValue = false; + synchronized (aytMonitor) { + synchronized (this) { + aytFlag = false; + _output_.write(COMMAND_AYT); + _output_.flush(); + } + aytMonitor.wait(timeout); + if (!aytFlag) { + aytFlag = true; + } else { + retValue = true; + } + } - /*** - * Looks for the state of the option. - * - * @return returns true if a do has been reuqested - * - * @param option - option code to be looked up. - ***/ - boolean _requestedDo(int option) - { - return ((_options[option] & _REQUESTED_DO_MASK) != 0); + return retValue; } + /* Code Section added for supporting AYT (end) */ - /*** - * Looks for the state of the option. - * - * @return returns true if a dont has been reuqested + /** + * Sends a command, automatically adds IAC prefix and flushes the output. * - * @param option - option code to be looked up. - ***/ - boolean _requestedDont(int option) - { - return !_requestedDo(option); + * @param cmd - command data to be sent + * @throws IOException - Exception in I/O. + * @since 3.0 + */ + final synchronized void _sendCommand(final byte cmd) throws IOException { + _output_.write(TelnetCommand.IAC); + _output_.write(cmd); + _output_.flush(); } - /*** - * Sets the state of the option. + /* open TelnetOptionHandler functionality (start) */ + /** + * Manages subnegotiation for Terminal Type. * - * @param option - option code to be set. - * @throws IOException - ***/ - void _setWill(int option) throws IOException - { - _options[option] |= _WILL_MASK; - - /* open TelnetOptionHandler functionality (start)*/ - if (_requestedWill(option)) - { - if (optionHandlers[option] != null) - { - optionHandlers[option].setWill(true); - - int subneg[] = - optionHandlers[option].startSubnegotiationLocal(); - - if (subneg != null) - { - _sendSubnegotiation(subneg); - } + * @param subn - subnegotiation data to be sent + * @throws IOException - Exception in I/O. + **/ + final synchronized void _sendSubnegotiation(final int[] subn) throws IOException { + if (debug) { + System.err.println("SEND SUBNEGOTIATION: "); + if (subn != null) { + System.err.println(Arrays.toString(subn)); } } - /* open TelnetOptionHandler functionality (end)*/ - } - - /*** - * Sets the state of the option. - * - * @param option - option code to be set. - * @throws IOException - ***/ - void _setDo(int option) throws IOException - { - _options[option] |= _DO_MASK; - - /* open TelnetOptionHandler functionality (start)*/ - if (_requestedDo(option)) - { - if (optionHandlers[option] != null) - { - optionHandlers[option].setDo(true); - - int subneg[] = - optionHandlers[option].startSubnegotiationRemote(); - - if (subneg != null) - { - _sendSubnegotiation(subneg); + if (subn != null) { + _output_.write(COMMAND_SB); + // Note _output_ is buffered, so might as well simplify by writing single bytes + for (final int element : subn) { + final byte b = (byte) element; + if (b == (byte) TelnetCommand.IAC) { // cast is necessary because IAC is outside the signed byte range + _output_.write(b); // double any IAC bytes } + _output_.write(b); } + _output_.write(COMMAND_SE); + + /* Code Section added for sending the negotiation ASAP (start) */ + _output_.flush(); + /* Code Section added for sending the negotiation ASAP (end) */ } - /* open TelnetOptionHandler functionality (end)*/ } + /* open TelnetOptionHandler functionality (end) */ - /*** - * Sets the state of the option. + /** + * Stops spying this Telnet. * - * @param option - option code to be set. - ***/ - void _setWantWill(int option) - { - _options[option] |= _REQUESTED_WILL_MASK; + */ + void _stopSpyStream() { + spyStream = null; } - /*** - * Sets the state of the option. + /** + * Registers a new TelnetOptionHandler for this telnet to use. * - * @param option - option code to be set. - ***/ - void _setWantDo(int option) - { - _options[option] |= _REQUESTED_DO_MASK; - } + * @param opthand - option handler to be registered. + * @throws InvalidTelnetOptionException - The option code is invalid. + * @throws IOException on error + **/ + void addOptionHandler(final TelnetOptionHandler opthand) throws InvalidTelnetOptionException, IOException { + final int optcode = opthand.getOptionCode(); + if (!TelnetOption.isValidOption(optcode)) { + throw new InvalidTelnetOptionException("Invalid Option Code", optcode); + } + if (optionHandlers[optcode] != null) { + throw new InvalidTelnetOptionException("Already registered option", optcode); + } + optionHandlers[optcode] = opthand; + if (isConnected()) { + if (opthand.getInitLocal()) { + requestWill(optcode); + } - /*** - * Sets the state of the option. - * - * @param option - option code to be set. - ***/ - void _setWont(int option) - { - _options[option] &= ~_WILL_MASK; - - /* open TelnetOptionHandler functionality (start)*/ - if (optionHandlers[option] != null) - { - optionHandlers[option].setWill(false); + if (opthand.getInitRemote()) { + requestDo(optcode); + } } - /* open TelnetOptionHandler functionality (end)*/ } - /*** - * Sets the state of the option. + /** + * Unregisters a TelnetOptionHandler. * - * @param option - option code to be set. - ***/ - void _setDont(int option) - { - _options[option] &= ~_DO_MASK; - - /* open TelnetOptionHandler functionality (start)*/ - if (optionHandlers[option] != null) - { - optionHandlers[option].setDo(false); + * @param optcode - Code of the option to be unregistered. + * @throws InvalidTelnetOptionException - The option code is invalid. + * @throws IOException on error + **/ + void deleteOptionHandler(final int optcode) throws InvalidTelnetOptionException, IOException { + if (!TelnetOption.isValidOption(optcode)) { + throw new InvalidTelnetOptionException("Invalid Option Code", optcode); } - /* open TelnetOptionHandler functionality (end)*/ - } + if (optionHandlers[optcode] == null) { + throw new InvalidTelnetOptionException("Unregistered option", optcode); + } + final TelnetOptionHandler opthand = optionHandlers[optcode]; + optionHandlers[optcode] = null; - /*** - * Sets the state of the option. - * - * @param option - option code to be set. - ***/ - void _setWantWont(int option) - { - _options[option] &= ~_REQUESTED_WILL_MASK; + if (opthand.getWill()) { + requestWont(optcode); + } + + if (opthand.getDo()) { + requestDont(optcode); + } } + /* open TelnetOptionHandler functionality (end) */ - /*** - * Sets the state of the option. - * - * @param option - option code to be set. - ***/ - void _setWantDont(int option) - { - _options[option] &= ~_REQUESTED_DO_MASK; + /* Code Section added for supporting AYT (start) */ + /** + * Processes the response of an AYT + */ + final synchronized void processAYTResponse() { + if (!aytFlag) { + synchronized (aytMonitor) { + aytFlag = true; + aytMonitor.notifyAll(); + } + } } + /* Code Section added for supporting AYT (end) */ /** * Processes a COMMAND. * * @param command - option code to be set. **/ - void _processCommand(int command) - { - if (debugoptions) - { + void processCommand(final int command) { + if (debugoptions) { System.err.println("RECEIVED COMMAND: " + command); } - if (__notifhand != null) - { - __notifhand.receivedNegotiation( - TelnetNotificationHandler.RECEIVED_COMMAND, command); + if (notifhand != null) { + notifhand.receivedNegotiation(TelnetNotificationHandler.RECEIVED_COMMAND, command); } } @@ -412,85 +364,54 @@ class Telnet extends SocketClient * @param option - option code to be set. * @throws IOException - Exception in I/O. **/ - void _processDo(int option) throws IOException - { - if (debugoptions) - { - System.err.println("RECEIVED DO: " - + TelnetOption.getOption(option)); + void processDo(final int option) throws IOException { + if (debugoptions) { + System.err.println("RECEIVED DO: " + TelnetOption.getOption(option)); } - if (__notifhand != null) - { - __notifhand.receivedNegotiation( - TelnetNotificationHandler.RECEIVED_DO, - option); + if (notifhand != null) { + notifhand.receivedNegotiation(TelnetNotificationHandler.RECEIVED_DO, option); } boolean acceptNewState = false; - - /* open TelnetOptionHandler functionality (start)*/ - if (optionHandlers[option] != null) - { + /* open TelnetOptionHandler functionality (start) */ + if (optionHandlers[option] != null) { acceptNewState = optionHandlers[option].getAcceptLocal(); + } else if (option == TERMINAL_TYPE && terminalType != null && !terminalType.isEmpty()) { + acceptNewState = true; } - else - { - /* open TelnetOptionHandler functionality (end)*/ - /* TERMINAL-TYPE option (start)*/ - if (option == TERMINAL_TYPE) - { - if ((terminalType != null) && (terminalType.length() > 0)) - { - acceptNewState = true; - } - } - /* TERMINAL-TYPE option (end)*/ - /* open TelnetOptionHandler functionality (start)*/ - } - /* open TelnetOptionHandler functionality (end)*/ - - if (_willResponse[option] > 0) - { - --_willResponse[option]; - if (_willResponse[option] > 0 && _stateIsWill(option)) - { - --_willResponse[option]; + /* TERMINAL-TYPE option (end) */ + /* open TelnetOptionHandler functionality (start) */ + + if (willResponse[option] > 0) { + --willResponse[option]; + if (willResponse[option] > 0 && stateIsWill(option)) { + --willResponse[option]; } } - if (_willResponse[option] == 0) - { - if (_requestedWont(option)) - { + if (willResponse[option] == 0) { + if (requestedWont(option)) { - switch (option) - { + switch (option) { default: break; } - - if (acceptNewState) - { - _setWantWill(option); - _sendWill(option); - } - else - { - ++_willResponse[option]; - _sendWont(option); + if (acceptNewState) { + setWantWill(option); + sendWill(option); + } else { + ++willResponse[option]; + sendWont(option); } - } - else - { + } else { // Other end has acknowledged option. - switch (option) - { + switch (option) { default: break; @@ -500,7 +421,7 @@ class Telnet extends SocketClient } } - _setWill(option); + setWill(option); } /** @@ -509,52 +430,72 @@ class Telnet extends SocketClient * @param option - option code to be set. * @throws IOException - Exception in I/O. **/ - void _processDont(int option) throws IOException - { - if (debugoptions) - { - System.err.println("RECEIVED DONT: " - + TelnetOption.getOption(option)); + void processDont(final int option) throws IOException { + if (debugoptions) { + System.err.println("RECEIVED DONT: " + TelnetOption.getOption(option)); } - if (__notifhand != null) - { - __notifhand.receivedNegotiation( - TelnetNotificationHandler.RECEIVED_DONT, - option); + if (notifhand != null) { + notifhand.receivedNegotiation(TelnetNotificationHandler.RECEIVED_DONT, option); } - if (_willResponse[option] > 0) - { - --_willResponse[option]; - if (_willResponse[option] > 0 && _stateIsWont(option)) - { - --_willResponse[option]; + if (willResponse[option] > 0) { + --willResponse[option]; + if (willResponse[option] > 0 && stateIsWont(option)) { + --willResponse[option]; } } - if (_willResponse[option] == 0 && _requestedWill(option)) - { + if (willResponse[option] == 0 && requestedWill(option)) { - switch (option) - { + switch (option) { default: break; } - /* FIX for a BUG in the negotiation (start)*/ - if ((_stateIsWill(option)) || (_requestedWill(option))) - { - _sendWont(option); + /* FIX for a BUG in the negotiation (start) */ + if (stateIsWill(option) || requestedWill(option)) { + sendWont(option); } - _setWantWont(option); - /* FIX for a BUG in the negotiation (end)*/ + setWantWont(option); + /* FIX for a BUG in the negotiation (end) */ } - _setWont(option); + setWont(option); } + /* TERMINAL-TYPE option (start) */ + /** + * Processes a suboption negotiation. + * + * @param suboption - subnegotiation data received + * @param suboptionLength - length of data received + * @throws IOException - Exception in I/O. + **/ + void processSuboption(final int[] suboption, final int suboptionLength) throws IOException { + if (debug) { + System.err.println("PROCESS SUBOPTION."); + } + + /* open TelnetOptionHandler functionality (start) */ + if (suboptionLength > 0) { + if (optionHandlers[suboption[0]] != null) { + final int[] responseSuboption = optionHandlers[suboption[0]].answerSubnegotiation(suboption, suboptionLength); + _sendSubnegotiation(responseSuboption); + } else if (suboptionLength > 1) { + if (debug) { + for (int ii = 0; ii < suboptionLength; ii++) { + System.err.println("SUB[" + ii + "]: " + suboption[ii]); + } + } + if (suboption[0] == TERMINAL_TYPE && suboption[1] == TERMINAL_TYPE_SEND) { + sendTerminalType(); + } + } + } + /* open TelnetOptionHandler functionality (end) */ + } /** * Processes a WILL request. @@ -562,64 +503,49 @@ class Telnet extends SocketClient * @param option - option code to be set. * @throws IOException - Exception in I/O. **/ - void _processWill(int option) throws IOException - { - if (debugoptions) - { - System.err.println("RECEIVED WILL: " - + TelnetOption.getOption(option)); + void processWill(final int option) throws IOException { + if (debugoptions) { + System.err.println("RECEIVED WILL: " + TelnetOption.getOption(option)); } - if (__notifhand != null) - { - __notifhand.receivedNegotiation( - TelnetNotificationHandler.RECEIVED_WILL, - option); + if (notifhand != null) { + notifhand.receivedNegotiation(TelnetNotificationHandler.RECEIVED_WILL, option); } boolean acceptNewState = false; - /* open TelnetOptionHandler functionality (start)*/ - if (optionHandlers[option] != null) - { + /* open TelnetOptionHandler functionality (start) */ + if (optionHandlers[option] != null) { acceptNewState = optionHandlers[option].getAcceptRemote(); } - /* open TelnetOptionHandler functionality (end)*/ - - if (_doResponse[option] > 0) - { - --_doResponse[option]; - if (_doResponse[option] > 0 && _stateIsDo(option)) - { - --_doResponse[option]; + /* open TelnetOptionHandler functionality (end) */ + + if (doResponse[option] > 0) { + --doResponse[option]; + if (doResponse[option] > 0 && stateIsDo(option)) { + --doResponse[option]; } } - if (_doResponse[option] == 0 && _requestedDont(option)) - { + if (doResponse[option] == 0 && requestedDont(option)) { - switch (option) - { + switch (option) { default: break; } - - if (acceptNewState) - { - _setWantDo(option); - _sendDo(option); - } - else - { - ++_doResponse[option]; - _sendDont(option); + if (acceptNewState) { + setWantDo(option); + sendDo(option); + } else { + ++doResponse[option]; + sendDont(option); } } - _setDo(option); + setDo(option); } /** @@ -628,638 +554,467 @@ class Telnet extends SocketClient * @param option - option code to be set. * @throws IOException - Exception in I/O. **/ - void _processWont(int option) throws IOException - { - if (debugoptions) - { - System.err.println("RECEIVED WONT: " - + TelnetOption.getOption(option)); + void processWont(final int option) throws IOException { + if (debugoptions) { + System.err.println("RECEIVED WONT: " + TelnetOption.getOption(option)); } - if (__notifhand != null) - { - __notifhand.receivedNegotiation( - TelnetNotificationHandler.RECEIVED_WONT, - option); + if (notifhand != null) { + notifhand.receivedNegotiation(TelnetNotificationHandler.RECEIVED_WONT, option); } - if (_doResponse[option] > 0) - { - --_doResponse[option]; - if (_doResponse[option] > 0 && _stateIsDont(option)) - { - --_doResponse[option]; + if (doResponse[option] > 0) { + --doResponse[option]; + if (doResponse[option] > 0 && stateIsDont(option)) { + --doResponse[option]; } } - if (_doResponse[option] == 0 && _requestedDo(option)) - { + if (doResponse[option] == 0 && requestedDo(option)) { - switch (option) - { + switch (option) { default: break; } - /* FIX for a BUG in the negotiation (start)*/ - if ((_stateIsDo(option)) || (_requestedDo(option))) - { - _sendDont(option); + /* FIX for a BUG in the negotiation (start) */ + if (stateIsDo(option) || requestedDo(option)) { + sendDont(option); } - _setWantDont(option); - /* FIX for a BUG in the negotiation (end)*/ + setWantDont(option); + /* FIX for a BUG in the negotiation (end) */ } - _setDont(option); + setDont(option); } - /* TERMINAL-TYPE option (start)*/ /** - * Processes a suboption negotiation. + * Registers a notification handler to which will be sent notifications of received telnet option negotiation commands. * - * @param suboption - subnegotiation data received - * @param suboptionLength - length of data received - * @throws IOException - Exception in I/O. - **/ - void _processSuboption(int suboption[], int suboptionLength) - throws IOException - { - if (debug) - { - System.err.println("PROCESS SUBOPTION."); - } - - /* open TelnetOptionHandler functionality (start)*/ - if (suboptionLength > 0) - { - if (optionHandlers[suboption[0]] != null) - { - int responseSuboption[] = - optionHandlers[suboption[0]].answerSubnegotiation(suboption, - suboptionLength); - _sendSubnegotiation(responseSuboption); - } - else - { - if (suboptionLength > 1) - { - if (debug) - { - for (int ii = 0; ii < suboptionLength; ii++) - { - System.err.println("SUB[" + ii + "]: " - + suboption[ii]); - } - } - if ((suboption[0] == TERMINAL_TYPE) - && (suboption[1] == TERMINAL_TYPE_SEND)) - { - _sendTerminalType(); - } - } - } - } - /* open TelnetOptionHandler functionality (end)*/ + * @param notifhand - TelnetNotificationHandler to be registered + */ + public void registerNotifHandler(final TelnetNotificationHandler notifhand) { + this.notifhand = notifhand; } - /*** - * Sends terminal type information. + /** + * Requests a DO. * + * @param option - Option code. * @throws IOException - Exception in I/O. - ***/ - final synchronized void _sendTerminalType() - throws IOException - { - if (debug) - { - System.err.println("SEND TERMINAL-TYPE: " + terminalType); - } - if (terminalType != null) - { - _output_.write(_COMMAND_SB); - _output_.write(_COMMAND_IS); - _output_.write(terminalType.getBytes(getCharset())); - _output_.write(_COMMAND_SE); - _output_.flush(); + **/ + final synchronized void requestDo(final int option) throws IOException { + if (doResponse[option] == 0 && stateIsDo(option) || requestedDo(option)) { + return; } + setWantDo(option); + ++doResponse[option]; + sendDo(option); } - /* TERMINAL-TYPE option (end)*/ - - /* open TelnetOptionHandler functionality (start)*/ /** - * Manages subnegotiation for Terminal Type. + * Requests a DONT. * - * @param subn - subnegotiation data to be sent + * @param option - Option code. * @throws IOException - Exception in I/O. **/ - final synchronized void _sendSubnegotiation(int subn[]) - throws IOException - { - if (debug) - { - System.err.println("SEND SUBNEGOTIATION: "); - if (subn != null) - { - System.err.println(Arrays.toString(subn)); - } - } - if (subn != null) - { - _output_.write(_COMMAND_SB); - // Note _output_ is buffered, so might as well simplify by writing single bytes - for (int element : subn) - { - byte b = (byte) element; - if (b == (byte) TelnetCommand.IAC) { // cast is necessary because IAC is outside the signed byte range - _output_.write(b); // double any IAC bytes - } - _output_.write(b); - } - _output_.write(_COMMAND_SE); - - /* Code Section added for sending the negotiation ASAP (start)*/ - _output_.flush(); - /* Code Section added for sending the negotiation ASAP (end)*/ + final synchronized void requestDont(final int option) throws IOException { + if (doResponse[option] == 0 && stateIsDont(option) || requestedDont(option)) { + return; } + setWantDont(option); + ++doResponse[option]; + sendDont(option); } - /* open TelnetOptionHandler functionality (end)*/ /** - * Sends a command, automatically adds IAC prefix and flushes the output. + * Looks for the state of the option. * - * @param cmd - command data to be sent - * @throws IOException - Exception in I/O. - * @since 3.0 + * @return returns true if a do has been reuqested + * + * @param option - option code to be looked up. */ - final synchronized void _sendCommand(byte cmd) throws IOException - { - _output_.write(TelnetCommand.IAC); - _output_.write(cmd); - _output_.flush(); + boolean requestedDo(final int option) { + return (options[option] & REQUESTED_DO_MASK) != 0; } - /* Code Section added for supporting AYT (start)*/ - /*** - * Processes the response of an AYT - ***/ - final synchronized void _processAYTResponse() - { - if (!aytFlag) - { - synchronized (aytMonitor) - { - aytFlag = true; - aytMonitor.notifyAll(); - } - } + /** + * Looks for the state of the option. + * + * @return returns true if a dont has been reuqested + * + * @param option - option code to be looked up. + */ + boolean requestedDont(final int option) { + return !requestedDo(option); } - /* Code Section added for supporting AYT (end)*/ - /*** - * Called upon connection. + /** + * Looks for the state of the option. * - * @throws IOException - Exception in I/O. - ***/ - @Override - protected void _connectAction_() throws IOException - { - /* (start). BUGFIX: clean the option info for each connection*/ - for (int ii = 0; ii < TelnetOption.MAX_OPTION_VALUE + 1; ii++) - { - _doResponse[ii] = 0; - _willResponse[ii] = 0; - _options[ii] = 0; - if (optionHandlers[ii] != null) - { - optionHandlers[ii].setDo(false); - optionHandlers[ii].setWill(false); - } - } - /* (end). BUGFIX: clean the option info for each connection*/ - - super._connectAction_(); - _input_ = new BufferedInputStream(_input_); - _output_ = new BufferedOutputStream(_output_); - - /* open TelnetOptionHandler functionality (start)*/ - for (int ii = 0; ii < TelnetOption.MAX_OPTION_VALUE + 1; ii++) - { - if (optionHandlers[ii] != null) - { - if (optionHandlers[ii].getInitLocal()) - { - _requestWill(optionHandlers[ii].getOptionCode()); - } + * @return returns true if a will has been reuqested + * + * @param option - option code to be looked up. + */ + boolean requestedWill(final int option) { + return (options[option] & REQUESTED_WILL_MASK) != 0; + } - if (optionHandlers[ii].getInitRemote()) - { - _requestDo(optionHandlers[ii].getOptionCode()); - } - } - } - /* open TelnetOptionHandler functionality (end)*/ + /** + * Looks for the state of the option. + * + * @return returns true if a wont has been reuqested + * + * @param option - option code to be looked up. + */ + boolean requestedWont(final int option) { + return !requestedWill(option); } /** - * Sends a DO. + * Requests a WILL. * * @param option - Option code. * @throws IOException - Exception in I/O. **/ - final synchronized void _sendDo(int option) - throws IOException - { - if (debug || debugoptions) - { - System.err.println("DO: " + TelnetOption.getOption(option)); + final synchronized void requestWill(final int option) throws IOException { + if (willResponse[option] == 0 && stateIsWill(option) || requestedWill(option)) { + return; } - _output_.write(_COMMAND_DO); - _output_.write(option); - - /* Code Section added for sending the negotiation ASAP (start)*/ - _output_.flush(); - /* Code Section added for sending the negotiation ASAP (end)*/ + setWantWill(option); + ++doResponse[option]; + sendWill(option); } + /* TERMINAL-TYPE option (end) */ + /** - * Requests a DO. + * Requests a WONT. * * @param option - Option code. * @throws IOException - Exception in I/O. **/ - final synchronized void _requestDo(int option) - throws IOException - { - if ((_doResponse[option] == 0 && _stateIsDo(option)) - || _requestedDo(option)) - { - return ; + final synchronized void requestWont(final int option) throws IOException { + if (willResponse[option] == 0 && stateIsWont(option) || requestedWont(option)) { + return; } - _setWantDo(option); - ++_doResponse[option]; - _sendDo(option); + setWantWont(option); + ++doResponse[option]; + sendWont(option); } /** - * Sends a DONT. + * Sends a byte. * - * @param option - Option code. + * @param b - byte to send * @throws IOException - Exception in I/O. **/ - final synchronized void _sendDont(int option) - throws IOException - { - if (debug || debugoptions) - { - System.err.println("DONT: " + TelnetOption.getOption(option)); - } - _output_.write(_COMMAND_DONT); - _output_.write(option); + final synchronized void sendByte(final int b) throws IOException { + _output_.write(b); + + /* Code Section added for supporting spystreams (start) */ + spyWrite(b); + /* Code Section added for supporting spystreams (end) */ - /* Code Section added for sending the negotiation ASAP (start)*/ - _output_.flush(); - /* Code Section added for sending the negotiation ASAP (end)*/ } /** - * Requests a DONT. + * Sends a DO. * * @param option - Option code. * @throws IOException - Exception in I/O. **/ - final synchronized void _requestDont(int option) - throws IOException - { - if ((_doResponse[option] == 0 && _stateIsDont(option)) - || _requestedDont(option)) - { - return ; + final synchronized void sendDo(final int option) throws IOException { + if (debug || debugoptions) { + System.err.println("DO: " + TelnetOption.getOption(option)); } - _setWantDont(option); - ++_doResponse[option]; - _sendDont(option); - } + _output_.write(COMMAND_DO); + _output_.write(option); + /* Code Section added for sending the negotiation ASAP (start) */ + _output_.flush(); + /* Code Section added for sending the negotiation ASAP (end) */ + } /** - * Sends a WILL. + * Sends a DONT. * * @param option - Option code. * @throws IOException - Exception in I/O. **/ - final synchronized void _sendWill(int option) - throws IOException - { - if (debug || debugoptions) - { - System.err.println("WILL: " + TelnetOption.getOption(option)); + final synchronized void sendDont(final int option) throws IOException { + if (debug || debugoptions) { + System.err.println("DONT: " + TelnetOption.getOption(option)); } - _output_.write(_COMMAND_WILL); + _output_.write(COMMAND_DONT); _output_.write(option); - /* Code Section added for sending the negotiation ASAP (start)*/ + /* Code Section added for sending the negotiation ASAP (start) */ _output_.flush(); - /* Code Section added for sending the negotiation ASAP (end)*/ + /* Code Section added for sending the negotiation ASAP (end) */ } /** - * Requests a WILL. + * Sends terminal type information. * - * @param option - Option code. * @throws IOException - Exception in I/O. - **/ - final synchronized void _requestWill(int option) - throws IOException - { - if ((_willResponse[option] == 0 && _stateIsWill(option)) - || _requestedWill(option)) - { - return ; + */ + final synchronized void sendTerminalType() throws IOException { + if (debug) { + System.err.println("SEND TERMINAL-TYPE: " + terminalType); + } + if (terminalType != null) { + _output_.write(COMMAND_SB); + _output_.write(COMMAND_IS); + _output_.write(terminalType.getBytes(getCharset())); + _output_.write(COMMAND_SE); + _output_.flush(); } - _setWantWill(option); - ++_doResponse[option]; - _sendWill(option); } /** - * Sends a WONT. + * Sends a WILL. * * @param option - Option code. * @throws IOException - Exception in I/O. **/ - final synchronized void _sendWont(int option) - throws IOException - { - if (debug || debugoptions) - { - System.err.println("WONT: " + TelnetOption.getOption(option)); + final synchronized void sendWill(final int option) throws IOException { + if (debug || debugoptions) { + System.err.println("WILL: " + TelnetOption.getOption(option)); } - _output_.write(_COMMAND_WONT); + _output_.write(COMMAND_WILL); _output_.write(option); - /* Code Section added for sending the negotiation ASAP (start)*/ + /* Code Section added for sending the negotiation ASAP (start) */ _output_.flush(); - /* Code Section added for sending the negotiation ASAP (end)*/ + /* Code Section added for sending the negotiation ASAP (end) */ } /** - * Requests a WONT. + * Sends a WONT. * * @param option - Option code. * @throws IOException - Exception in I/O. **/ - final synchronized void _requestWont(int option) - throws IOException - { - if ((_willResponse[option] == 0 && _stateIsWont(option)) - || _requestedWont(option)) - { - return ; + final synchronized void sendWont(final int option) throws IOException { + if (debug || debugoptions) { + System.err.println("WONT: " + TelnetOption.getOption(option)); } - _setWantWont(option); - ++_doResponse[option]; - _sendWont(option); + _output_.write(COMMAND_WONT); + _output_.write(option); + + /* Code Section added for sending the negotiation ASAP (start) */ + _output_.flush(); + /* Code Section added for sending the negotiation ASAP (end) */ } /** - * Sends a byte. + * Sets the state of the option. * - * @param b - byte to send - * @throws IOException - Exception in I/O. - **/ - final synchronized void _sendByte(int b) - throws IOException - { - _output_.write(b); + * @param option - option code to be set. + * @throws IOException + */ + void setDo(final int option) throws IOException { + options[option] |= DO_MASK; + + /* open TelnetOptionHandler functionality (start) */ + if (requestedDo(option) && (optionHandlers[option] != null)) { + optionHandlers[option].setDo(true); - /* Code Section added for supporting spystreams (start)*/ - _spyWrite(b); - /* Code Section added for supporting spystreams (end)*/ + final int[] subneg = optionHandlers[option].startSubnegotiationRemote(); + if (subneg != null) { + _sendSubnegotiation(subneg); + } + } + /* open TelnetOptionHandler functionality (end) */ } - /* Code Section added for supporting AYT (start)*/ /** - * Sends an Are You There sequence and waits for the result. + * Sets the state of the option. * - * @param timeout - Time to wait for a response (millis.) - * @throws IOException - Exception in I/O. - * @throws IllegalArgumentException - Illegal argument - * @throws InterruptedException - Interrupted during wait. - * @return true if AYT received a response, false otherwise - **/ - final boolean _sendAYT(long timeout) - throws IOException, IllegalArgumentException, InterruptedException - { - boolean retValue = false; - synchronized (aytMonitor) - { - synchronized (this) - { - aytFlag = false; - _output_.write(_COMMAND_AYT); - _output_.flush(); - } - aytMonitor.wait(timeout); - if (aytFlag == false) - { - retValue = false; - aytFlag = true; - } - else - { - retValue = true; - } + * @param option - option code to be set. + */ + void setDont(final int option) { + options[option] &= ~DO_MASK; + + /* open TelnetOptionHandler functionality (start) */ + if (optionHandlers[option] != null) { + optionHandlers[option].setDo(false); } + /* open TelnetOptionHandler functionality (end) */ + } - return (retValue); + /** + * Sets the state of the option. + * + * @param option - option code to be set. + */ + void setWantDo(final int option) { + options[option] |= REQUESTED_DO_MASK; } - /* Code Section added for supporting AYT (end)*/ - /* open TelnetOptionHandler functionality (start)*/ + /** + * Sets the state of the option. + * + * @param option - option code to be set. + */ + void setWantDont(final int option) { + options[option] &= ~REQUESTED_DO_MASK; + } /** - * Registers a new TelnetOptionHandler for this telnet to use. + * Sets the state of the option. * - * @param opthand - option handler to be registered. - * @throws InvalidTelnetOptionException - The option code is invalid. - * @throws IOException on error - **/ - void addOptionHandler(TelnetOptionHandler opthand) - throws InvalidTelnetOptionException, IOException - { - int optcode = opthand.getOptionCode(); - if (TelnetOption.isValidOption(optcode)) - { - if (optionHandlers[optcode] == null) - { - optionHandlers[optcode] = opthand; - if (isConnected()) - { - if (opthand.getInitLocal()) - { - _requestWill(optcode); - } + * @param option - option code to be set. + */ + void setWantWill(final int option) { + options[option] |= REQUESTED_WILL_MASK; + } - if (opthand.getInitRemote()) - { - _requestDo(optcode); - } - } - } - else - { - throw (new InvalidTelnetOptionException( - "Already registered option", optcode)); - } - } - else - { - throw (new InvalidTelnetOptionException( - "Invalid Option Code", optcode)); - } + /** + * Sets the state of the option. + * + * @param option - option code to be set. + */ + void setWantWont(final int option) { + options[option] &= ~REQUESTED_WILL_MASK; } /** - * Unregisters a TelnetOptionHandler. + * Sets the state of the option. * - * @param optcode - Code of the option to be unregistered. - * @throws InvalidTelnetOptionException - The option code is invalid. - * @throws IOException on error - **/ - void deleteOptionHandler(int optcode) - throws InvalidTelnetOptionException, IOException - { - if (TelnetOption.isValidOption(optcode)) - { - if (optionHandlers[optcode] == null) - { - throw (new InvalidTelnetOptionException( - "Unregistered option", optcode)); - } - else - { - TelnetOptionHandler opthand = optionHandlers[optcode]; - optionHandlers[optcode] = null; + * @param option - option code to be set. + * @throws IOException + */ + void setWill(final int option) throws IOException { + options[option] |= WILL_MASK; - if (opthand.getWill()) - { - _requestWont(optcode); - } + /* open TelnetOptionHandler functionality (start) */ + if (requestedWill(option) && (optionHandlers[option] != null)) { + optionHandlers[option].setWill(true); - if (opthand.getDo()) - { - _requestDont(optcode); - } + final int[] subneg = optionHandlers[option].startSubnegotiationLocal(); + + if (subneg != null) { + _sendSubnegotiation(subneg); } } - else - { - throw (new InvalidTelnetOptionException( - "Invalid Option Code", optcode)); - } + /* open TelnetOptionHandler functionality (end) */ } - /* open TelnetOptionHandler functionality (end)*/ - /* Code Section added for supporting spystreams (start)*/ - /*** - * Registers an OutputStream for spying what's going on in - * the Telnet session. - * - * @param spystream - OutputStream on which session activity - * will be echoed. - ***/ - void _registerSpyStream(OutputStream spystream) - { - spyStream = spystream; - } + /* open TelnetOptionHandler functionality (start) */ - /*** - * Stops spying this Telnet. + /** + * Sets the state of the option. * - ***/ - void _stopSpyStream() - { - spyStream = null; + * @param option - option code to be set. + */ + void setWont(final int option) { + options[option] &= ~WILL_MASK; + + /* open TelnetOptionHandler functionality (start) */ + if (optionHandlers[option] != null) { + optionHandlers[option].setWill(false); + } + /* open TelnetOptionHandler functionality (end) */ } - /*** + /** * Sends a read char on the spy stream. * * @param ch - character read from the session - ***/ - void _spyRead(int ch) - { - OutputStream spy = spyStream; - if (spy != null) - { - try - { + */ + void spyRead(final int ch) { + final OutputStream spy = spyStream; + if (spy != null) { + try { if (ch != '\r') // never write '\r' on its own { - if (ch == '\n') - { + if (ch == '\n') { spy.write('\r'); // add '\r' before '\n' } spy.write(ch); // write original character spy.flush(); } - } - catch (IOException e) - { + } catch (final IOException e) { spyStream = null; } } } - /*** + /** * Sends a written char on the spy stream. * * @param ch - character written to the session - ***/ - void _spyWrite(int ch) - { - if (!(_stateIsDo(TelnetOption.ECHO) - && _requestedDo(TelnetOption.ECHO))) - { - OutputStream spy = spyStream; - if (spy != null) - { - try - { + */ + void spyWrite(final int ch) { + if (!(stateIsDo(TelnetOption.ECHO) && requestedDo(TelnetOption.ECHO))) { + final OutputStream spy = spyStream; + if (spy != null) { + try { spy.write(ch); spy.flush(); - } - catch (IOException e) - { + } catch (final IOException e) { spyStream = null; } } } } - /* Code Section added for supporting spystreams (end)*/ + /* Code Section added for supporting spystreams (end) */ - /*** - * Registers a notification handler to which will be sent - * notifications of received telnet option negotiation commands. + /** + * Looks for the state of the option. * - * @param notifhand - TelnetNotificationHandler to be registered - ***/ - public void registerNotifHandler(TelnetNotificationHandler notifhand) - { - __notifhand = notifhand; + * @return returns true if a do has been acknowledged + * + * @param option - option code to be looked up. + */ + boolean stateIsDo(final int option) { + return (options[option] & DO_MASK) != 0; + } + + /** + * Looks for the state of the option. + * + * @return returns true if a dont has been acknowledged + * + * @param option - option code to be looked up. + */ + boolean stateIsDont(final int option) { + return !stateIsDo(option); } - /*** + /** + * Looks for the state of the option. + * + * @return returns true if a will has been acknowledged + * + * @param option - option code to be looked up. + */ + boolean stateIsWill(final int option) { + return (options[option] & WILL_MASK) != 0; + } + + /** + * Looks for the state of the option. + * + * @return returns true if a wont has been acknowledged + * + * @param option - option code to be looked up. + */ + boolean stateIsWont(final int option) { + return !stateIsWill(option); + } + + /** * Unregisters the current notification handler. * - ***/ - public void unregisterNotifHandler() - { - __notifhand = null; + */ + public void unregisterNotifHandler() { + this.notifhand = null; } } diff --git a/src/main/java/org/apache/commons/net/telnet/TelnetClient.java b/src/main/java/org/apache/commons/net/telnet/TelnetClient.java index 97a8221..bd19753 100644 --- a/src/main/java/org/apache/commons/net/telnet/TelnetClient.java +++ b/src/main/java/org/apache/commons/net/telnet/TelnetClient.java @@ -22,42 +22,39 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -/*** - * The TelnetClient class implements the simple network virtual - * terminal (NVT) for the Telnet protocol according to RFC 854. It - * does not implement any of the extra Telnet options because it - * is meant to be used within a Java program providing automated - * access to Telnet accessible resources. +/** + * The TelnetClient class implements the simple network virtual terminal (NVT) for the Telnet protocol according to RFC 854. It does not implement any of the + * extra Telnet options because it is meant to be used within a Java program providing automated access to Telnet accessible resources. * <p> - * The class can be used by first connecting to a server using the - * SocketClient - * {@link org.apache.commons.net.SocketClient#connect connect} - * method. Then an InputStream and OutputStream for sending and - * receiving data over the Telnet connection can be obtained by - * using the {@link #getInputStream getInputStream() } and - * {@link #getOutputStream getOutputStream() } methods. - * When you finish using the streams, you must call - * {@link #disconnect disconnect } rather than simply + * The class can be used by first connecting to a server using the SocketClient {@link org.apache.commons.net.SocketClient#connect connect} method. Then an + * InputStream and OutputStream for sending and receiving data over the Telnet connection can be obtained by using the {@link #getInputStream getInputStream() } + * and {@link #getOutputStream getOutputStream() } methods. When you finish using the streams, you must call {@link #disconnect disconnect } rather than simply * closing the streams. - ***/ + */ + +public class TelnetClient extends Telnet { + private static final int DEFAULT_MAX_SUBNEGOTIATION_LENGTH = 512; -public class TelnetClient extends Telnet -{ - private InputStream __input; - private OutputStream __output; + final int maxSubnegotiationLength; + private InputStream input; + private OutputStream output; protected boolean readerThread = true; private TelnetInputListener inputListener; - /*** + /** * Default TelnetClient constructor, sets terminal-type {@code VT100}. - ***/ - public TelnetClient() - { - /* TERMINAL-TYPE option (start)*/ - super ("VT100"); - /* TERMINAL-TYPE option (end)*/ - __input = null; - __output = null; + */ + public TelnetClient() { + this("VT100", DEFAULT_MAX_SUBNEGOTIATION_LENGTH); + } + + /** + * Construct an instance with the specified max subnegotiation length and the default terminal-type {@code VT100} + * + * @param maxSubnegotiationLength the size of the subnegotiation buffer + */ + public TelnetClient(final int maxSubnegotiationLength) { + this("VT100", maxSubnegotiationLength); } /** @@ -65,359 +62,325 @@ public class TelnetClient extends Telnet * * @param termtype the terminal type to use, e.g. {@code VT100} */ - /* TERMINAL-TYPE option (start)*/ - public TelnetClient(String termtype) - { - super (termtype); - __input = null; - __output = null; + public TelnetClient(final String termtype) { + this(termtype, DEFAULT_MAX_SUBNEGOTIATION_LENGTH); } - /* TERMINAL-TYPE option (end)*/ - void _flushOutputStream() throws IOException - { - _output_.flush(); - } - void _closeOutputStream() throws IOException - { - try { - _output_.close(); - } finally { - _output_ = null; - } + /** + * Construct an instance with the specified terminal type and max subnegotiation length + * + * @param termtype the terminal type to use, e.g. {@code VT100} + * @param maxSubnegotiationLength the size of the subnegotiation buffer + */ + public TelnetClient(final String termtype, final int maxSubnegotiationLength) { + /* TERMINAL-TYPE option (start) */ + super(termtype); + /* TERMINAL-TYPE option (end) */ + this.input = null; + this.output = null; + this.maxSubnegotiationLength = maxSubnegotiationLength; } - /*** + /** * Handles special connection requirements. * - * @throws IOException If an error occurs during connection setup. - ***/ + * @throws IOException If an error occurs during connection setup. + */ @Override - protected void _connectAction_() throws IOException - { + protected void _connectAction_() throws IOException { super._connectAction_(); - TelnetInputStream tmp = new TelnetInputStream(_input_, this, readerThread); - if(readerThread) - { - tmp._start(); + final TelnetInputStream tmp = new TelnetInputStream(_input_, this, readerThread); + if (readerThread) { + tmp.start(); } - // __input CANNOT refer to the TelnetInputStream. We run into + // __input CANNOT refer to the TelnetInputStream. We run into // blocking problems when some classes use TelnetInputStream, so // we wrap it with a BufferedInputStream which we know is safe. // This blocking behavior requires further investigation, but right // now it looks like classes like InputStreamReader are not implemented // in a safe manner. - __input = new BufferedInputStream(tmp); - __output = new TelnetOutputStream(this); + input = new BufferedInputStream(tmp); + output = new TelnetOutputStream(this); + } + + /** + * Registers a new TelnetOptionHandler for this telnet client to use. + * + * @param opthand - option handler to be registered. + * + * @throws InvalidTelnetOptionException on error + * @throws IOException on error + */ + @Override + public void addOptionHandler(final TelnetOptionHandler opthand) throws InvalidTelnetOptionException, IOException { + super.addOptionHandler(opthand); + } + /* open TelnetOptionHandler functionality (end) */ + + void closeOutputStream() throws IOException { + if (_output_ == null) { + return; + } + try { + _output_.close(); + } finally { + _output_ = null; + } } - /*** - * Disconnects the telnet session, closing the input and output streams - * as well as the socket. If you have references to the - * input and output streams of the telnet connection, you should not - * close them yourself, but rather call disconnect to properly close - * the connection. - ***/ + /** + * Unregisters a TelnetOptionHandler. + * + * @param optcode - Code of the option to be unregistered. + * + * @throws InvalidTelnetOptionException on error + * @throws IOException on error + */ + @Override + public void deleteOptionHandler(final int optcode) throws InvalidTelnetOptionException, IOException { + super.deleteOptionHandler(optcode); + } + + /** + * Disconnects the telnet session, closing the input and output streams as well as the socket. If you have references to the input and output streams of the + * telnet connection, you should not close them yourself, but rather call disconnect to properly close the connection. + */ @Override - public void disconnect() throws IOException - { + public void disconnect() throws IOException { try { - if (__input != null) { - __input.close(); + if (input != null) { + input.close(); } - if (__output != null) { - __output.close(); + if (output != null) { + output.close(); } } finally { // NET-594 - __output = null; - __input = null; + output = null; + input = null; super.disconnect(); } } - /*** - * Returns the telnet connection output stream. You should not close the - * stream when you finish with it. Rather, you should call - * {@link #disconnect disconnect }. - * - * @return The telnet connection output stream. - ***/ - public OutputStream getOutputStream() - { - return __output; + void flushOutputStream() throws IOException { + if (_output_ == null) { + throw new IOException("Stream closed"); + } + _output_.flush(); } - /*** - * Returns the telnet connection input stream. You should not close the - * stream when you finish with it. Rather, you should call - * {@link #disconnect disconnect }. + /** + * Returns the telnet connection input stream. You should not close the stream when you finish with it. Rather, you should call {@link #disconnect + * disconnect }. * * @return The telnet connection input stream. - ***/ - public InputStream getInputStream() - { - return __input; + */ + public InputStream getInputStream() { + return input; } - /*** + /** * Returns the state of the option on the local side. * * @param option - Option to be checked. * * @return The state of the option on the local side. - ***/ - public boolean getLocalOptionState(int option) - { - /* BUG (option active when not already acknowledged) (start)*/ - return (_stateIsWill(option) && _requestedWill(option)); - /* BUG (option active when not already acknowledged) (end)*/ + */ + public boolean getLocalOptionState(final int option) { + /* BUG (option active when not already acknowledged) (start) */ + return stateIsWill(option) && requestedWill(option); + /* BUG (option active when not already acknowledged) (end) */ + } + + /* Code Section added for supporting AYT (start) */ + + /** + * Returns the telnet connection output stream. You should not close the stream when you finish with it. Rather, you should call {@link #disconnect + * disconnect }. + * + * @return The telnet connection output stream. + */ + public OutputStream getOutputStream() { + return output; } - /*** + /** + * Gets the status of the reader thread. + * + * @return true if the reader thread is enabled, false otherwise + */ + public boolean getReaderThread() { + return readerThread; + } + + /** * Returns the state of the option on the remote side. * * @param option - Option to be checked. * * @return The state of the option on the remote side. - ***/ - public boolean getRemoteOptionState(int option) - { - /* BUG (option active when not already acknowledged) (start)*/ - return (_stateIsDo(option) && _requestedDo(option)); - /* BUG (option active when not already acknowledged) (end)*/ + */ + public boolean getRemoteOptionState(final int option) { + /* BUG (option active when not already acknowledged) (start) */ + return stateIsDo(option) && requestedDo(option); + /* BUG (option active when not already acknowledged) (end) */ } - /* open TelnetOptionHandler functionality (end)*/ + /* open TelnetOptionHandler functionality (end) */ - /* Code Section added for supporting AYT (start)*/ + /* open TelnetOptionHandler functionality (start) */ - /*** - * Sends an Are You There sequence and waits for the result. - * - * @param timeout - Time to wait for a response (millis.) - * - * @return true if AYT received a response, false otherwise - * - * @throws InterruptedException on error - * @throws IllegalArgumentException on error - * @throws IOException on error - ***/ - public boolean sendAYT(long timeout) - throws IOException, IllegalArgumentException, InterruptedException - { - return (_sendAYT(timeout)); + // Notify input listener + void notifyInputListener() { + final TelnetInputListener listener; + synchronized (this) { + listener = this.inputListener; + } + if (listener != null) { + listener.telnetInputAvailable(); + } } - /* Code Section added for supporting AYT (start)*/ - /*** - * Sends a protocol-specific subnegotiation message to the remote peer. - * {@link TelnetClient} will add the IAC SB & IAC SE framing bytes; - * the first byte in {@code message} should be the appropriate telnet - * option code. + /** + * Register a listener to be notified when new incoming data is available to be read on the {@link #getInputStream input stream}. Only one listener is + * supported at a time. * * <p> - * This method does not wait for any response. Subnegotiation messages - * sent by the remote end can be handled by registering an approrpriate - * {@link TelnetOptionHandler}. + * More precisely, notifications are issued whenever the number of bytes available for immediate reading (i.e., the value returned by + * {@link InputStream#available}) transitions from zero to non-zero. Note that (in general) multiple reads may be required to empty the buffer and reset + * this notification, because incoming bytes are being added to the internal buffer asynchronously. * </p> * - * @param message option code followed by subnegotiation payload - * @throws IllegalArgumentException if {@code message} has length zero - * @throws IOException if an I/O error occurs while writing the message - * @since 3.0 - ***/ - public void sendSubnegotiation(int[] message) - throws IOException, IllegalArgumentException - { - if (message.length < 1) { - throw new IllegalArgumentException("zero length message"); - } - _sendSubnegotiation(message); - } - - /*** - * Sends a command byte to the remote peer, adding the IAC prefix. - * * <p> - * This method does not wait for any response. Messages - * sent by the remote end can be handled by registering an approrpriate - * {@link TelnetOptionHandler}. + * Notifications are only supported when a {@link #setReaderThread reader thread} is enabled for the connection. * </p> * - * @param command the code for the command - * @throws IOException if an I/O error occurs while writing the message - * @throws IllegalArgumentException on error + * @param listener listener to be registered; replaces any previous * @since 3.0 - ***/ - public void sendCommand(byte command) - throws IOException, IllegalArgumentException - { - _sendCommand(command); - } - - /* open TelnetOptionHandler functionality (start)*/ - - /*** - * Registers a new TelnetOptionHandler for this telnet client to use. - * - * @param opthand - option handler to be registered. - * - * @throws InvalidTelnetOptionException on error - * @throws IOException on error - ***/ - @Override - public void addOptionHandler(TelnetOptionHandler opthand) - throws InvalidTelnetOptionException, IOException - { - super.addOptionHandler(opthand); + */ + public synchronized void registerInputListener(final TelnetInputListener listener) { + this.inputListener = listener; } - /* open TelnetOptionHandler functionality (end)*/ - /*** - * Unregisters a TelnetOptionHandler. - * - * @param optcode - Code of the option to be unregistered. + /** + * Registers a notification handler to which will be sent notifications of received telnet option negotiation commands. * - * @throws InvalidTelnetOptionException on error - * @throws IOException on error - ***/ + * @param notifhand - TelnetNotificationHandler to be registered + */ @Override - public void deleteOptionHandler(int optcode) - throws InvalidTelnetOptionException, IOException - { - super.deleteOptionHandler(optcode); + public void registerNotifHandler(final TelnetNotificationHandler notifhand) { + super.registerNotifHandler(notifhand); } - /* Code Section added for supporting spystreams (start)*/ - /*** - * Registers an OutputStream for spying what's going on in - * the TelnetClient session. + /* Code Section added for supporting spystreams (start) */ + /** + * Registers an OutputStream for spying what's going on in the TelnetClient session. * - * @param spystream - OutputStream on which session activity - * will be echoed. - ***/ - public void registerSpyStream(OutputStream spystream) - { + * @param spystream - OutputStream on which session activity will be echoed. + */ + public void registerSpyStream(final OutputStream spystream) { super._registerSpyStream(spystream); } - /*** - * Stops spying this TelnetClient. + /** + * Sends an Are You There sequence and waits for the result. * - ***/ - public void stopSpyStream() - { - super._stopSpyStream(); + * @param timeout - Time to wait for a response (millis.) + * + * @return true if AYT received a response, false otherwise + * + * @throws InterruptedException on error + * @throws IllegalArgumentException on error + * @throws IOException on error + */ + public boolean sendAYT(final long timeout) throws IOException, IllegalArgumentException, InterruptedException { + return _sendAYT(timeout); } - /* Code Section added for supporting spystreams (end)*/ + /* Code Section added for supporting AYT (start) */ - /*** - * Registers a notification handler to which will be sent - * notifications of received telnet option negotiation commands. + /** + * Sends a command byte to the remote peer, adding the IAC prefix. * - * @param notifhand - TelnetNotificationHandler to be registered - ***/ - @Override - public void registerNotifHandler(TelnetNotificationHandler notifhand) - { - super.registerNotifHandler(notifhand); + * <p> + * This method does not wait for any response. Messages sent by the remote end can be handled by registering an approrpriate {@link TelnetOptionHandler}. + * </p> + * + * @param command the code for the command + * @throws IOException if an I/O error occurs while writing the message + * @throws IllegalArgumentException on error + * @since 3.0 + */ + public void sendCommand(final byte command) throws IOException, IllegalArgumentException { + _sendCommand(command); } - /*** - * Unregisters the current notification handler. + /** + * Sends a protocol-specific subnegotiation message to the remote peer. {@link TelnetClient} will add the IAC SB & IAC SE framing bytes; the first byte + * in {@code message} should be the appropriate telnet option code. * - ***/ - @Override - public void unregisterNotifHandler() - { - super.unregisterNotifHandler(); + * <p> + * This method does not wait for any response. Subnegotiation messages sent by the remote end can be handled by registering an approrpriate + * {@link TelnetOptionHandler}. + * </p> + * + * @param message option code followed by subnegotiation payload + * @throws IllegalArgumentException if {@code message} has length zero + * @throws IOException if an I/O error occurs while writing the message + * @since 3.0 + */ + public void sendSubnegotiation(final int[] message) throws IOException, IllegalArgumentException { + if (message.length < 1) { + throw new IllegalArgumentException("zero length message"); + } + _sendSubnegotiation(message); } - /*** + /** * Sets the status of the reader thread. * * <p> - * When enabled, a seaparate internal reader thread is created for new - * connections to read incoming data as it arrives. This results in - * immediate handling of option negotiation, notifications, etc. - * (at least until the fixed-size internal buffer fills up). - * Otherwise, no thread is created an all negotiation and option - * handling is deferred until a read() is performed on the - * {@link #getInputStream input stream}. + * When enabled, a seaparate internal reader thread is created for new connections to read incoming data as it arrives. This results in immediate handling + * of option negotiation, notifications, etc. (at least until the fixed-size internal buffer fills up). Otherwise, no thread is created an all negotiation + * and option handling is deferred until a read() is performed on the {@link #getInputStream input stream}. * </p> * * <p> - * The reader thread must be enabled for {@link TelnetInputListener} - * support. + * The reader thread must be enabled for {@link TelnetInputListener} support. * </p> * * <p> - * When this method is invoked, the reader thread status will apply to all - * subsequent connections; the current connection (if any) is not affected. + * When this method is invoked, the reader thread status will apply to all subsequent connections; the current connection (if any) is not affected. * </p> * * @param flag true to enable the reader thread, false to disable * @see #registerInputListener - ***/ - public void setReaderThread(boolean flag) - { + */ + public void setReaderThread(final boolean flag) { readerThread = flag; } - /*** - * Gets the status of the reader thread. - * - * @return true if the reader thread is enabled, false otherwise - ***/ - public boolean getReaderThread() - { - return (readerThread); - } - - /*** - * Register a listener to be notified when new incoming data is - * available to be read on the {@link #getInputStream input stream}. - * Only one listener is supported at a time. - * - * <p> - * More precisely, notifications are issued whenever the number of - * bytes available for immediate reading (i.e., the value returned - * by {@link InputStream#available}) transitions from zero to non-zero. - * Note that (in general) multiple reads may be required to empty the - * buffer and reset this notification, because incoming bytes are being - * added to the internal buffer asynchronously. - * </p> - * - * <p> - * Notifications are only supported when a {@link #setReaderThread - * reader thread} is enabled for the connection. - * </p> + /** + * Stops spying this TelnetClient. * - * @param listener listener to be registered; replaces any previous - * @since 3.0 - ***/ - public synchronized void registerInputListener(TelnetInputListener listener) - { - this.inputListener = listener; + */ + public void stopSpyStream() { + super._stopSpyStream(); } + /* Code Section added for supporting spystreams (end) */ - /*** + /** * Unregisters the current {@link TelnetInputListener}, if any. * * @since 3.0 - ***/ - public synchronized void unregisterInputListener() - { + */ + public synchronized void unregisterInputListener() { this.inputListener = null; } - // Notify input listener - void notifyInputListener() { - TelnetInputListener listener; - synchronized (this) { - listener = this.inputListener; - } - if (listener != null) { - listener.telnetInputAvailable(); - } + /** + * Unregisters the current notification handler. + * + */ + @Override + public void unregisterNotifHandler() { + super.unregisterNotifHandler(); } } diff --git a/src/main/java/org/apache/commons/net/telnet/TelnetCommand.java b/src/main/java/org/apache/commons/net/telnet/TelnetCommand.java index 6e65f30..cd1ae81 100644 --- a/src/main/java/org/apache/commons/net/telnet/TelnetCommand.java +++ b/src/main/java/org/apache/commons/net/telnet/TelnetCommand.java @@ -18,114 +18,109 @@ package org.apache.commons.net.telnet; /** - * The TelnetCommand class cannot be instantiated and only serves as a - * storehouse for telnet command constants. + * The TelnetCommand class cannot be instantiated and only serves as a storehouse for telnet command constants. + * * @see org.apache.commons.net.telnet.Telnet * @see org.apache.commons.net.telnet.TelnetClient */ -public final class TelnetCommand -{ - /*** The maximum value a command code can have. This value is 255. ***/ +public final class TelnetCommand { + /** The maximum value a command code can have. This value is 255. */ public static final int MAX_COMMAND_VALUE = 255; - /*** Interpret As Command code. Value is 255 according to RFC 854. ***/ + /** Interpret As Command code. Value is 255 according to RFC 854. */ public static final int IAC = 255; - /*** Don't use option code. Value is 254 according to RFC 854. ***/ + /** Don't use option code. Value is 254 according to RFC 854. */ public static final int DONT = 254; - /*** Request to use option code. Value is 253 according to RFC 854. ***/ + /** Request to use option code. Value is 253 according to RFC 854. */ public static final int DO = 253; - /*** Refuse to use option code. Value is 252 according to RFC 854. ***/ + /** Refuse to use option code. Value is 252 according to RFC 854. */ public static final int WONT = 252; - /*** Agree to use option code. Value is 251 according to RFC 854. ***/ + /** Agree to use option code. Value is 251 according to RFC 854. */ public static final int WILL = 251; - /*** Start subnegotiation code. Value is 250 according to RFC 854. ***/ + /** Start subnegotiation code. Value is 250 according to RFC 854. */ public static final int SB = 250; - /*** Go Ahead code. Value is 249 according to RFC 854. ***/ + /** Go Ahead code. Value is 249 according to RFC 854. */ public static final int GA = 249; - /*** Erase Line code. Value is 248 according to RFC 854. ***/ + /** Erase Line code. Value is 248 according to RFC 854. */ public static final int EL = 248; - /*** Erase Character code. Value is 247 according to RFC 854. ***/ + /** Erase Character code. Value is 247 according to RFC 854. */ public static final int EC = 247; - /*** Are You There code. Value is 246 according to RFC 854. ***/ + /** Are You There code. Value is 246 according to RFC 854. */ public static final int AYT = 246; - /*** Abort Output code. Value is 245 according to RFC 854. ***/ + /** Abort Output code. Value is 245 according to RFC 854. */ public static final int AO = 245; - /*** Interrupt Process code. Value is 244 according to RFC 854. ***/ + /** Interrupt Process code. Value is 244 according to RFC 854. */ public static final int IP = 244; - /*** Break code. Value is 243 according to RFC 854. ***/ + /** Break code. Value is 243 according to RFC 854. */ public static final int BREAK = 243; - /*** Data mark code. Value is 242 according to RFC 854. ***/ + /** Data mark code. Value is 242 according to RFC 854. */ public static final int DM = 242; - /*** No Operation code. Value is 241 according to RFC 854. ***/ + /** No Operation code. Value is 241 according to RFC 854. */ public static final int NOP = 241; - /*** End subnegotiation code. Value is 240 according to RFC 854. ***/ + /** End subnegotiation code. Value is 240 according to RFC 854. */ public static final int SE = 240; - /*** End of record code. Value is 239. ***/ + /** End of record code. Value is 239. */ public static final int EOR = 239; - /*** Abort code. Value is 238. ***/ + /** Abort code. Value is 238. */ public static final int ABORT = 238; - /*** Suspend process code. Value is 237. ***/ + /** Suspend process code. Value is 237. */ public static final int SUSP = 237; - /*** End of file code. Value is 236. ***/ + /** End of file code. Value is 236. */ public static final int EOF = 236; - /*** Synchronize code. Value is 242. ***/ + /** Synchronize code. Value is 242. */ public static final int SYNCH = 242; - /*** String representations of commands. ***/ - private static final String __commandString[] = { - "IAC", "DONT", "DO", "WONT", "WILL", "SB", "GA", "EL", "EC", "AYT", - "AO", "IP", "BRK", "DMARK", "NOP", "SE", "EOR", "ABORT", "SUSP", "EOF" - }; + /** String representations of commands. */ + private static final String commandString[] = { "IAC", "DONT", "DO", "WONT", "WILL", "SB", "GA", "EL", "EC", "AYT", "AO", "IP", "BRK", "DMARK", "NOP", "SE", + "EOR", "ABORT", "SUSP", "EOF" }; - private static final int __FIRST_COMMAND = IAC; - private static final int __LAST_COMMAND = EOF; + private static final int FIRST_COMMAND = IAC; + private static final int LAST_COMMAND = EOF; - /*** - * Returns the string representation of the telnet protocol command - * corresponding to the given command code. + /** + * Returns the string representation of the telnet protocol command corresponding to the given command code. * <p> + * * @param code The command code of the telnet protocol command. * @return The string representation of the telnet protocol command. - ***/ - public static final String getCommand(int code) - { - return __commandString[__FIRST_COMMAND - code]; + */ + public static String getCommand(final int code) { + return commandString[FIRST_COMMAND - code]; } - /*** - * Determines if a given command code is valid. Returns true if valid, - * false if not. + /** + * Determines if a given command code is valid. Returns true if valid, false if not. * <p> - * @param code The command code to test. + * + * @param code The command code to test. * @return True if the command code is valid, false if not. **/ - public static final boolean isValidCommand(int code) - { - return (code <= __FIRST_COMMAND && code >= __LAST_COMMAND); + public static boolean isValidCommand(final int code) { + return code <= FIRST_COMMAND && code >= LAST_COMMAND; } // Cannot be instantiated - private TelnetCommand() - { } + private TelnetCommand() { + } } diff --git a/src/main/java/org/apache/commons/net/telnet/TelnetInputListener.java b/src/main/java/org/apache/commons/net/telnet/TelnetInputListener.java index 34bb2b6..c643cd2 100644 --- a/src/main/java/org/apache/commons/net/telnet/TelnetInputListener.java +++ b/src/main/java/org/apache/commons/net/telnet/TelnetInputListener.java @@ -17,21 +17,18 @@ package org.apache.commons.net.telnet; -/*** - * Listener interface used for notification that incoming data is - * available to be read. +/** + * Listener interface used for notification that incoming data is available to be read. * * @see TelnetClient * @since 3.0 - ***/ -public interface TelnetInputListener -{ + */ +public interface TelnetInputListener { - /*** - * Callback method invoked when new incoming data is available on a - * {@link TelnetClient}'s {@link TelnetClient#getInputStream input stream}. + /** + * Callback method invoked when new incoming data is available on a {@link TelnetClient}'s {@link TelnetClient#getInputStream input stream}. * * @see TelnetClient#registerInputListener - ***/ - public void telnetInputAvailable(); + */ + void telnetInputAvailable(); } diff --git a/src/main/java/org/apache/commons/net/telnet/TelnetInputStream.java b/src/main/java/org/apache/commons/net/telnet/TelnetInputStream.java index c6dbcbf..0567afd 100644 --- a/src/main/java/org/apache/commons/net/telnet/TelnetInputStream.java +++ b/src/main/java/org/apache/commons/net/telnet/TelnetInputStream.java @@ -22,8 +22,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; -final class TelnetInputStream extends BufferedInputStream implements Runnable -{ +final class TelnetInputStream extends BufferedInputStream implements Runnable { /** End of file has been reached */ private static final int EOF = -1; @@ -31,100 +30,250 @@ final class TelnetInputStream extends BufferedInputStream implements Runnable private static final int WOULD_BLOCK = -2; // TODO should these be private enums? - static final int _STATE_DATA = 0, _STATE_IAC = 1, _STATE_WILL = 2, - _STATE_WONT = 3, _STATE_DO = 4, _STATE_DONT = 5, - _STATE_SB = 6, _STATE_SE = 7, _STATE_CR = 8, _STATE_IAC_SB = 9; - - private boolean __hasReachedEOF; // @GuardedBy("__queue") - private volatile boolean __isClosed; - private boolean __readIsWaiting; - private int __receiveState, __queueHead, __queueTail, __bytesAvailable; - private final int[] __queue; - private final TelnetClient __client; - private final Thread __thread; - private IOException __ioException; - - /* TERMINAL-TYPE option (start)*/ - private final int __suboption[] = new int[512]; - private int __suboption_count = 0; - /* TERMINAL-TYPE option (end)*/ - - private volatile boolean __threaded; - - TelnetInputStream(InputStream input, TelnetClient client, - boolean readerThread) - { + static final int STATE_DATA = 0, STATE_IAC = 1, STATE_WILL = 2, STATE_WONT = 3, STATE_DO = 4, STATE_DONT = 5, STATE_SB = 6, STATE_SE = 7, STATE_CR = 8, + STATE_IAC_SB = 9; + + private boolean hasReachedEOF; // @GuardedBy("queue") + private volatile boolean isClosed; + private boolean readIsWaiting; + private int receiveState, queueHead, queueTail, bytesAvailable; + private final int[] queue; + private final TelnetClient client; + private final Thread thread; + private IOException ioException; + + /* TERMINAL-TYPE option (start) */ + private final int suboption[]; + private int suboptionCount; + /* TERMINAL-TYPE option (end) */ + + private volatile boolean threaded; + + TelnetInputStream(final InputStream input, final TelnetClient client) { + this(input, client, true); + } + + TelnetInputStream(final InputStream input, final TelnetClient client, final boolean readerThread) { super(input); - __client = client; - __receiveState = _STATE_DATA; - __isClosed = true; - __hasReachedEOF = false; + this.client = client; + this.receiveState = STATE_DATA; + this.isClosed = true; + this.hasReachedEOF = false; // Make it 2049, because when full, one slot will go unused, and we // want a 2048 byte buffer just to have a round number (base 2 that is) - __queue = new int[2049]; - __queueHead = 0; - __queueTail = 0; - __bytesAvailable = 0; - __ioException = null; - __readIsWaiting = false; - __threaded = false; - if(readerThread) { - __thread = new Thread(this); + this.queue = new int[2049]; + this.queueHead = 0; + this.queueTail = 0; + this.suboption = new int[client.maxSubnegotiationLength]; + this.bytesAvailable = 0; + this.ioException = null; + this.readIsWaiting = false; + this.threaded = false; + if (readerThread) { + this.thread = new Thread(this); } else { - __thread = null; + this.thread = null; } } - TelnetInputStream(InputStream input, TelnetClient client) { - this(input, client, true); + @Override + public int available() throws IOException { + // Critical section because run() may change bytesAvailable + synchronized (queue) { + if (threaded) { // Must not call super.available when running threaded: NET-466 + return bytesAvailable; + } + return bytesAvailable + super.available(); + } } - void _start() - { - if(__thread == null) { - return; + // Cannot be synchronized. Will cause deadlock if run() is blocked + // in read because BufferedInputStream read() is synchronized. + @Override + public void close() throws IOException { + // Completely disregard the fact thread may still be running. + // We can't afford to block on this close by waiting for + // thread to terminate because few if any JVM's will actually + // interrupt a system read() from the interrupt() method. + super.close(); + + synchronized (queue) { + hasReachedEOF = true; + isClosed = true; + + if (thread != null && thread.isAlive()) { + thread.interrupt(); + } + + queue.notifyAll(); } - int priority; - __isClosed = false; - // TODO remove this - // Need to set a higher priority in case JVM does not use pre-emptive - // threads. This should prevent scheduler induced deadlock (rather than - // deadlock caused by a bug in this code). - priority = Thread.currentThread().getPriority() + 1; - if (priority > Thread.MAX_PRIORITY) { - priority = Thread.MAX_PRIORITY; + } + + /** Returns false. Mark is not supported. */ + @Override + public boolean markSupported() { + return false; + } + + // synchronized(client) critical sections are to protect against + // TelnetOutputStream writing through the telnet client at same time + // as a processDo/Will/etc. command invoked from TelnetInputStream + // tries to write. Returns true if buffer was previously empty. + private boolean processChar(final int ch) throws InterruptedException { + // Critical section because we're altering bytesAvailable, + // queueTail, and the contents of _queue. + final boolean bufferWasEmpty; + synchronized (queue) { + bufferWasEmpty = bytesAvailable == 0; + while (bytesAvailable >= queue.length - 1) { + // The queue is full. We need to wait before adding any more data to it. Hopefully the stream owner + // will consume some data soon! + if (!threaded) { + // We've been asked to add another character to the queue, but it is already full and there's + // no other thread to drain it. This should not have happened! + throw new IllegalStateException("Queue is full! Cannot process another character."); + } + queue.notify(); + try { + queue.wait(); + } catch (final InterruptedException e) { + throw e; + } + } + + // Need to do this in case we're not full, but block on a read + if (readIsWaiting && threaded) { + queue.notify(); + } + + queue[queueTail] = ch; + ++bytesAvailable; + + if (++queueTail >= queue.length) { + queueTail = 0; + } } - __thread.setPriority(priority); - __thread.setDaemon(true); - __thread.start(); - __threaded = true; // tell _processChar that we are running threaded + return bufferWasEmpty; } + @Override + public int read() throws IOException { + // Critical section because we're altering bytesAvailable, + // queueHead, and the contents of _queue in addition to + // testing value of hasReachedEOF. + synchronized (queue) { + + while (true) { + if (ioException != null) { + final IOException e; + e = ioException; + ioException = null; + throw e; + } + + if (bytesAvailable == 0) { + // Return EOF if at end of file + if (hasReachedEOF) { + return EOF; + } - // synchronized(__client) critical sections are to protect against + // Otherwise, we have to wait for queue to get something + if (threaded) { + queue.notify(); + try { + readIsWaiting = true; + queue.wait(); + readIsWaiting = false; + } catch (final InterruptedException e) { + throw new InterruptedIOException("Fatal thread interruption during read."); + } + } else { + // alreadyread = false; + readIsWaiting = true; + int ch; + boolean mayBlock = true; // block on the first read only + + do { + try { + if ((ch = read(mayBlock)) < 0) { // must be EOF + if (ch != WOULD_BLOCK) { + return ch; + } + } + } catch (final InterruptedIOException e) { + synchronized (queue) { + ioException = e; + queue.notifyAll(); + try { + queue.wait(100); + } catch (final InterruptedException interrupted) { + // Ignored + } + } + return EOF; + } + + try { + if (ch != WOULD_BLOCK) { + processChar(ch); + } + } catch (final InterruptedException e) { + if (isClosed) { + return EOF; + } + } + + // Reads should not block on subsequent iterations. Potentially, this could happen if the + // remaining buffered socket data consists entirely of Telnet command sequence and no "user" data. + mayBlock = false; + + } + // Continue reading as long as there is data available and the queue is not full. + while (super.available() > 0 && bytesAvailable < queue.length - 1); + + readIsWaiting = false; + } + continue; + } + final int ch; + + ch = queue[queueHead]; + + if (++queueHead >= queue.length) { + queueHead = 0; + } + + --bytesAvailable; + + // Need to explicitly notify() so available() works properly + if (bytesAvailable == 0 && threaded) { + queue.notify(); + } + + return ch; + } + } + } + + // synchronized(client) critical sections are to protect against // TelnetOutputStream writing through the telnet client at same time // as a processDo/Will/etc. command invoked from TelnetInputStream // tries to write. /** - * Get the next byte of data. - * IAC commands are processed internally and do not return data. + * Get the next byte of data. IAC commands are processed internally and do not return data. * * @param mayBlock true if method is allowed to block - * @return the next byte of data, - * or -1 (EOF) if end of stread reached, - * or -2 (WOULD_BLOCK) if mayBlock is false and there is no data available + * @return the next byte of data, or -1 (EOF) if end of stread reached, or -2 (WOULD_BLOCK) if mayBlock is false and there is no data available */ - private int __read(boolean mayBlock) throws IOException - { + private int read(final boolean mayBlock) throws IOException { int ch; - while (true) - { + while (true) { // If there is no more data AND we were told not to block, // just return WOULD_BLOCK (-2). (More efficient than exception.) - if(!mayBlock && super.available() == 0) { + if (!mayBlock && super.available() == 0) { return WOULD_BLOCK; } @@ -133,160 +282,145 @@ final class TelnetInputStream extends BufferedInputStream implements Runnable return EOF; } - ch = (ch & 0xff); + ch = ch & 0xff; - /* Code Section added for supporting AYT (start)*/ - synchronized (__client) - { - __client._processAYTResponse(); + /* Code Section added for supporting AYT (start) */ + synchronized (client) { + client.processAYTResponse(); } - /* Code Section added for supporting AYT (end)*/ + /* Code Section added for supporting AYT (end) */ - /* Code Section added for supporting spystreams (start)*/ - __client._spyRead(ch); - /* Code Section added for supporting spystreams (end)*/ + /* Code Section added for supporting spystreams (start) */ + client.spyRead(ch); + /* Code Section added for supporting spystreams (end) */ - switch (__receiveState) - { + switch (receiveState) { - case _STATE_CR: - if (ch == '\0') - { + case STATE_CR: + if (ch == '\0') { // Strip null continue; } // How do we handle newline after cr? - // else if (ch == '\n' && _requestedDont(TelnetOption.ECHO) && + // else if (ch == '\n' && _requestedDont(TelnetOption.ECHO) && // Handle as normal data by falling through to _STATE_DATA case //$FALL-THROUGH$ - case _STATE_DATA: - if (ch == TelnetCommand.IAC) - { - __receiveState = _STATE_IAC; + case STATE_DATA: + if (ch == TelnetCommand.IAC) { + receiveState = STATE_IAC; continue; } - - if (ch == '\r') - { - synchronized (__client) - { - if (__client._requestedDont(TelnetOption.BINARY)) { - __receiveState = _STATE_CR; + if (ch == '\r') { + synchronized (client) { + if (client.requestedDont(TelnetOption.BINARY)) { + receiveState = STATE_CR; } else { - __receiveState = _STATE_DATA; + receiveState = STATE_DATA; } } } else { - __receiveState = _STATE_DATA; + receiveState = STATE_DATA; } break; - case _STATE_IAC: - switch (ch) - { + case STATE_IAC: + switch (ch) { case TelnetCommand.WILL: - __receiveState = _STATE_WILL; + receiveState = STATE_WILL; continue; case TelnetCommand.WONT: - __receiveState = _STATE_WONT; + receiveState = STATE_WONT; continue; case TelnetCommand.DO: - __receiveState = _STATE_DO; + receiveState = STATE_DO; continue; case TelnetCommand.DONT: - __receiveState = _STATE_DONT; + receiveState = STATE_DONT; continue; - /* TERMINAL-TYPE option (start)*/ + /* TERMINAL-TYPE option (start) */ case TelnetCommand.SB: - __suboption_count = 0; - __receiveState = _STATE_SB; + suboptionCount = 0; + receiveState = STATE_SB; continue; - /* TERMINAL-TYPE option (end)*/ + /* TERMINAL-TYPE option (end) */ case TelnetCommand.IAC: - __receiveState = _STATE_DATA; + receiveState = STATE_DATA; break; // exit to enclosing switch to return IAC from read case TelnetCommand.SE: // unexpected byte! ignore it (don't send it as a command) - __receiveState = _STATE_DATA; + receiveState = STATE_DATA; continue; default: - __receiveState = _STATE_DATA; - __client._processCommand(ch); // Notify the user + receiveState = STATE_DATA; + client.processCommand(ch); // Notify the user continue; // move on the next char } break; // exit and return from read - case _STATE_WILL: - synchronized (__client) - { - __client._processWill(ch); - __client._flushOutputStream(); + case STATE_WILL: + synchronized (client) { + client.processWill(ch); + client.flushOutputStream(); } - __receiveState = _STATE_DATA; + receiveState = STATE_DATA; continue; - case _STATE_WONT: - synchronized (__client) - { - __client._processWont(ch); - __client._flushOutputStream(); + case STATE_WONT: + synchronized (client) { + client.processWont(ch); + client.flushOutputStream(); } - __receiveState = _STATE_DATA; + receiveState = STATE_DATA; continue; - case _STATE_DO: - synchronized (__client) - { - __client._processDo(ch); - __client._flushOutputStream(); + case STATE_DO: + synchronized (client) { + client.processDo(ch); + client.flushOutputStream(); } - __receiveState = _STATE_DATA; + receiveState = STATE_DATA; continue; - case _STATE_DONT: - synchronized (__client) - { - __client._processDont(ch); - __client._flushOutputStream(); + case STATE_DONT: + synchronized (client) { + client.processDont(ch); + client.flushOutputStream(); } - __receiveState = _STATE_DATA; + receiveState = STATE_DATA; continue; - /* TERMINAL-TYPE option (start)*/ - case _STATE_SB: - switch (ch) - { + /* TERMINAL-TYPE option (start) */ + case STATE_SB: + switch (ch) { case TelnetCommand.IAC: - __receiveState = _STATE_IAC_SB; + receiveState = STATE_IAC_SB; continue; default: // store suboption char - if (__suboption_count < __suboption.length) { - __suboption[__suboption_count++] = ch; + if (suboptionCount < suboption.length) { + suboption[suboptionCount++] = ch; } break; } - __receiveState = _STATE_SB; + receiveState = STATE_SB; continue; - case _STATE_IAC_SB: // IAC received during SB phase - switch (ch) - { + case STATE_IAC_SB: // IAC received during SB phase + switch (ch) { case TelnetCommand.SE: - synchronized (__client) - { - __client._processSuboption(__suboption, __suboption_count); - __client._flushOutputStream(); + synchronized (client) { + client.processSuboption(suboption, suboptionCount); + client.flushOutputStream(); } - __receiveState = _STATE_DATA; + receiveState = STATE_DATA; continue; case TelnetCommand.IAC: // De-dup the duplicated IAC - if (__suboption_count < __suboption.length) { - __suboption[__suboption_count++] = ch; + if (suboptionCount < suboption.length) { + suboption[suboptionCount++] = ch; } break; - default: // unexpected byte! ignore it + default: // unexpected byte! ignore it break; } - __receiveState = _STATE_SB; + receiveState = STATE_SB; continue; - /* TERMINAL-TYPE option (end)*/ + /* TERMINAL-TYPE option (end) */ } break; @@ -295,231 +429,43 @@ final class TelnetInputStream extends BufferedInputStream implements Runnable return ch; } - // synchronized(__client) critical sections are to protect against - // TelnetOutputStream writing through the telnet client at same time - // as a processDo/Will/etc. command invoked from TelnetInputStream - // tries to write. Returns true if buffer was previously empty. - private boolean __processChar(int ch) throws InterruptedException - { - // Critical section because we're altering __bytesAvailable, - // __queueTail, and the contents of _queue. - boolean bufferWasEmpty; - synchronized (__queue) - { - bufferWasEmpty = (__bytesAvailable == 0); - while (__bytesAvailable >= __queue.length - 1) - { - // The queue is full. We need to wait before adding any more data to it. Hopefully the stream owner - // will consume some data soon! - if(__threaded) - { - __queue.notify(); - try - { - __queue.wait(); - } - catch (InterruptedException e) - { - throw e; - } - } - else - { - // We've been asked to add another character to the queue, but it is already full and there's - // no other thread to drain it. This should not have happened! - throw new IllegalStateException("Queue is full! Cannot process another character."); - } - } - - // Need to do this in case we're not full, but block on a read - if (__readIsWaiting && __threaded) - { - __queue.notify(); - } - - __queue[__queueTail] = ch; - ++__bytesAvailable; - - if (++__queueTail >= __queue.length) { - __queueTail = 0; - } - } - return bufferWasEmpty; - } - - @Override - public int read() throws IOException - { - // Critical section because we're altering __bytesAvailable, - // __queueHead, and the contents of _queue in addition to - // testing value of __hasReachedEOF. - synchronized (__queue) - { - - while (true) - { - if (__ioException != null) - { - IOException e; - e = __ioException; - __ioException = null; - throw e; - } - - if (__bytesAvailable == 0) - { - // Return EOF if at end of file - if (__hasReachedEOF) { - return EOF; - } - - // Otherwise, we have to wait for queue to get something - if(__threaded) - { - __queue.notify(); - try - { - __readIsWaiting = true; - __queue.wait(); - __readIsWaiting = false; - } - catch (InterruptedException e) - { - throw new InterruptedIOException("Fatal thread interruption during read."); - } - } - else - { - //__alreadyread = false; - __readIsWaiting = true; - int ch; - boolean mayBlock = true; // block on the first read only - - do - { - try - { - if ((ch = __read(mayBlock)) < 0) { // must be EOF - if(ch != WOULD_BLOCK) { - return (ch); - } - } - } - catch (InterruptedIOException e) - { - synchronized (__queue) - { - __ioException = e; - __queue.notifyAll(); - try - { - __queue.wait(100); - } - catch (InterruptedException interrupted) - { - // Ignored - } - } - return EOF; - } - - - try - { - if(ch != WOULD_BLOCK) - { - __processChar(ch); - } - } - catch (InterruptedException e) - { - if (__isClosed) { - return EOF; - } - } - - // Reads should not block on subsequent iterations. Potentially, this could happen if the - // remaining buffered socket data consists entirely of Telnet command sequence and no "user" data. - mayBlock = false; - - } - // Continue reading as long as there is data available and the queue is not full. - while (super.available() > 0 && __bytesAvailable < __queue.length - 1); - - __readIsWaiting = false; - } - continue; - } - else - { - int ch; - - ch = __queue[__queueHead]; - - if (++__queueHead >= __queue.length) { - __queueHead = 0; - } - - --__bytesAvailable; - - // Need to explicitly notify() so available() works properly - if(__bytesAvailable == 0 && __threaded) { - __queue.notify(); - } - - return ch; - } - } - } - } - - - /*** - * Reads the next number of bytes from the stream into an array and - * returns the number of bytes read. Returns -1 if the end of the - * stream has been reached. + /** + * Reads the next number of bytes from the stream into an array and returns the number of bytes read. Returns -1 if the end of the stream has been reached. * <p> - * @param buffer The byte array in which to store the data. - * @return The number of bytes read. Returns -1 if the - * end of the message has been reached. - * @throws IOException If an error occurs in reading the underlying - * stream. - ***/ + * + * @param buffer The byte array in which to store the data. + * @return The number of bytes read. Returns -1 if the end of the message has been reached. + * @throws IOException If an error occurs in reading the underlying stream. + */ @Override - public int read(byte buffer[]) throws IOException - { + public int read(final byte buffer[]) throws IOException { return read(buffer, 0, buffer.length); } - - /*** - * Reads the next number of bytes from the stream into an array and returns - * the number of bytes read. Returns -1 if the end of the - * message has been reached. The characters are stored in the array - * starting from the given offset and up to the length specified. + /** + * Reads the next number of bytes from the stream into an array and returns the number of bytes read. Returns -1 if the end of the message has been reached. + * The characters are stored in the array starting from the given offset and up to the length specified. * <p> + * * @param buffer The byte array in which to store the data. - * @param offset The offset into the array at which to start storing data. - * @param length The number of bytes to read. - * @return The number of bytes read. Returns -1 if the - * end of the stream has been reached. - * @throws IOException If an error occurs while reading the underlying - * stream. - ***/ + * @param offset The offset into the array at which to start storing data. + * @param length The number of bytes to read. + * @return The number of bytes read. Returns -1 if the end of the stream has been reached. + * @throws IOException If an error occurs while reading the underlying stream. + */ @Override - public int read(byte buffer[], int offset, int length) throws IOException - { - int ch, off; + public int read(final byte buffer[], int offset, int length) throws IOException { + int ch; + final int off; if (length < 1) { return 0; } - // Critical section because run() may change __bytesAvailable - synchronized (__queue) - { - if (length > __bytesAvailable) { - length = __bytesAvailable; + // Critical section because run() may change bytesAvailable + synchronized (queue) { + if (length > bytesAvailable) { + length = bytesAvailable; } } @@ -529,102 +475,40 @@ final class TelnetInputStream extends BufferedInputStream implements Runnable off = offset; - do - { - buffer[offset++] = (byte)ch; - } - while (--length > 0 && (ch = read()) != EOF); + do { + buffer[offset++] = (byte) ch; + } while (--length > 0 && (ch = read()) != EOF); - //__client._spyRead(buffer, off, offset - off); - return (offset - off); + // client._spyRead(buffer, off, offset - off); + return offset - off; } - - /*** Returns false. Mark is not supported. ***/ @Override - public boolean markSupported() - { - return false; - } - - @Override - public int available() throws IOException - { - // Critical section because run() may change __bytesAvailable - synchronized (__queue) - { - if (__threaded) { // Must not call super.available when running threaded: NET-466 - return __bytesAvailable; - } else { - return __bytesAvailable + super.available(); - } - } - } - - - // Cannot be synchronized. Will cause deadlock if run() is blocked - // in read because BufferedInputStream read() is synchronized. - @Override - public void close() throws IOException - { - // Completely disregard the fact thread may still be running. - // We can't afford to block on this close by waiting for - // thread to terminate because few if any JVM's will actually - // interrupt a system read() from the interrupt() method. - super.close(); - - synchronized (__queue) - { - __hasReachedEOF = true; - __isClosed = true; - - if (__thread != null && __thread.isAlive()) - { - __thread.interrupt(); - } - - __queue.notifyAll(); - } - - } - - @Override - public void run() - { + public void run() { int ch; - try - { -_outerLoop: - while (!__isClosed) - { - try - { - if ((ch = __read(true)) < 0) { + try { + _outerLoop: while (!isClosed) { + try { + if ((ch = read(true)) < 0) { break; } - } - catch (InterruptedIOException e) - { - synchronized (__queue) - { - __ioException = e; - __queue.notifyAll(); - try - { - __queue.wait(100); - } - catch (InterruptedException interrupted) - { - if (__isClosed) { + } catch (final InterruptedIOException e) { + synchronized (queue) { + ioException = e; + queue.notifyAll(); + try { + queue.wait(100); + } catch (final InterruptedException interrupted) { + if (isClosed) { break _outerLoop; } } continue; } - } catch(RuntimeException re) { + } catch (final RuntimeException re) { // We treat any runtime exceptions as though the - // stream has been closed. We close the + // stream has been closed. We close the // underlying stream just to be sure. super.close(); // Breaking the loop has the effect of setting @@ -634,47 +518,53 @@ _outerLoop: // Process new character boolean notify = false; - try - { - notify = __processChar(ch); - } - catch (InterruptedException e) - { - if (__isClosed) { + try { + notify = processChar(ch); + } catch (final InterruptedException e) { + if (isClosed) { break _outerLoop; } } // Notify input listener if buffer was previously empty if (notify) { - __client.notifyInputListener(); + client.notifyInputListener(); } } - } - catch (IOException ioe) - { - synchronized (__queue) - { - __ioException = ioe; + } catch (final IOException ioe) { + synchronized (queue) { + ioException = ioe; } - __client.notifyInputListener(); + client.notifyInputListener(); } - synchronized (__queue) - { - __isClosed = true; // Possibly redundant - __hasReachedEOF = true; - __queue.notify(); + synchronized (queue) { + isClosed = true; // Possibly redundant + hasReachedEOF = true; + queue.notify(); } - __threaded = false; + threaded = false; } -} -/* Emacs configuration - * Local variables: ** - * mode: java ** - * c-basic-offset: 4 ** - * indent-tabs-mode: nil ** - * End: ** - */ + void start() { + if (thread == null) { + return; + } + + int priority; + isClosed = false; + // TODO remove this + // Need to set a higher priority in case JVM does not use pre-emptive + // threads. This should prevent scheduler induced deadlock (rather than + // deadlock caused by a bug in this code). + priority = Thread.currentThread().getPriority() + 1; + if (priority > Thread.MAX_PRIORITY) { + priority = Thread.MAX_PRIORITY; + } + thread.setPriority(priority); + thread.setDaemon(true); + thread.start(); + threaded = true; // tell _processChar that we are running threaded + } +} diff --git a/src/main/java/org/apache/commons/net/telnet/TelnetNotificationHandler.java b/src/main/java/org/apache/commons/net/telnet/TelnetNotificationHandler.java index 19446dd..3898916 100644 --- a/src/main/java/org/apache/commons/net/telnet/TelnetNotificationHandler.java +++ b/src/main/java/org/apache/commons/net/telnet/TelnetNotificationHandler.java @@ -17,52 +17,47 @@ package org.apache.commons.net.telnet; -/*** - * The TelnetNotificationHandler interface can be used to handle - * notification of options negotiation commands received on a telnet - * session. +/** + * The TelnetNotificationHandler interface can be used to handle notification of options negotiation commands received on a telnet session. * <p> - * The user can implement this interface and register a - * TelnetNotificationHandler by using the registerNotificationHandler() - * of TelnetClient to be notified of option negotiation commands. - ***/ + * The user can implement this interface and register a TelnetNotificationHandler by using the registerNotificationHandler() of TelnetClient to be notified of + * option negotiation commands. + */ -public interface TelnetNotificationHandler -{ - /*** +public interface TelnetNotificationHandler { + /** * The remote party sent a DO command. - ***/ - public static final int RECEIVED_DO = 1; + */ + int RECEIVED_DO = 1; - /*** + /** * The remote party sent a DONT command. - ***/ - public static final int RECEIVED_DONT = 2; + */ + int RECEIVED_DONT = 2; - /*** + /** * The remote party sent a WILL command. - ***/ - public static final int RECEIVED_WILL = 3; + */ + int RECEIVED_WILL = 3; - /*** + /** * The remote party sent a WONT command. - ***/ - public static final int RECEIVED_WONT = 4; + */ + int RECEIVED_WONT = 4; - /*** + /** * The remote party sent a COMMAND. + * * @since 2.2 - ***/ - public static final int RECEIVED_COMMAND = 5; + */ + int RECEIVED_COMMAND = 5; - /*** - * Callback method called when TelnetClient receives an - * command or option negotiation command + /** + * Callback method called when TelnetClient receives an command or option negotiation command * - * @param negotiation_code - type of (negotiation) command received - * (RECEIVED_DO, RECEIVED_DONT, RECEIVED_WILL, RECEIVED_WONT, RECEIVED_COMMAND) + * @param negotiation_code - type of (negotiation) command received (RECEIVED_DO, RECEIVED_DONT, RECEIVED_WILL, RECEIVED_WONT, RECEIVED_COMMAND) * - * @param option_code - code of the option negotiated, or the command code itself (e.g. NOP). - ***/ - public void receivedNegotiation(int negotiation_code, int option_code); + * @param option_code - code of the option negotiated, or the command code itself (e.g. NOP). + */ + void receivedNegotiation(int negotiation_code, int option_code); } diff --git a/src/main/java/org/apache/commons/net/telnet/TelnetOption.java b/src/main/java/org/apache/commons/net/telnet/TelnetOption.java index 5fa7d5d..5fc64df 100644 --- a/src/main/java/org/apache/commons/net/telnet/TelnetOption.java +++ b/src/main/java/org/apache/commons/net/telnet/TelnetOption.java @@ -17,20 +17,18 @@ package org.apache.commons.net.telnet; -/*** - * The TelnetOption class cannot be instantiated and only serves as a - * storehouse for telnet option constants. +/** + * The TelnetOption class cannot be instantiated and only serves as a storehouse for telnet option constants. * <p> * Details regarding Telnet option specification can be found in RFC 855. * * * @see org.apache.commons.net.telnet.Telnet * @see org.apache.commons.net.telnet.TelnetClient - ***/ + */ -public class TelnetOption -{ - /*** The maximum value an option code can have. This value is 255. ***/ +public class TelnetOption { + /** The maximum value an option code can have. This value is 255. */ public static final int MAX_OPTION_VALUE = 255; public static final int BINARY = 0; @@ -116,78 +114,45 @@ public class TelnetOption public static final int EXTENDED_OPTIONS_LIST = 255; @SuppressWarnings("unused") - private static final int __FIRST_OPTION = BINARY; - private static final int __LAST_OPTION = EXTENDED_OPTIONS_LIST; - - private static final String __optionString[] = { - "BINARY", "ECHO", "RCP", "SUPPRESS GO AHEAD", "NAME", "STATUS", - "TIMING MARK", "RCTE", "NAOL", "NAOP", "NAOCRD", "NAOHTS", "NAOHTD", - "NAOFFD", "NAOVTS", "NAOVTD", "NAOLFD", "EXTEND ASCII", "LOGOUT", - "BYTE MACRO", "DATA ENTRY TERMINAL", "SUPDUP", "SUPDUP OUTPUT", - "SEND LOCATION", "TERMINAL TYPE", "END OF RECORD", "TACACS UID", - "OUTPUT MARKING", "TTYLOC", "3270 REGIME", "X.3 PAD", "NAWS", "TSPEED", - "LFLOW", "LINEMODE", "XDISPLOC", "OLD-ENVIRON", "AUTHENTICATION", - "ENCRYPT", "NEW-ENVIRON", "TN3270E", "XAUTH", "CHARSET", "RSP", - "Com Port Control", "Suppress Local Echo", "Start TLS", - "KERMIT", "SEND-URL", "FORWARD_X", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "TELOPT PRAGMA LOGON", "TELOPT SSPI LOGON", - "TELOPT PRAGMA HEARTBEAT", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", "", "", "", "", - "Extended-Options-List" - }; - - - /*** - * Returns the string representation of the telnet protocol option - * corresponding to the given option code. + private static final int FIRST_OPTION = BINARY; + private static final int LAST_OPTION = EXTENDED_OPTIONS_LIST; + + private static final String optionString[] = { "BINARY", "ECHO", "RCP", "SUPPRESS GO AHEAD", "NAME", "STATUS", "TIMING MARK", "RCTE", "NAOL", "NAOP", + "NAOCRD", "NAOHTS", "NAOHTD", "NAOFFD", "NAOVTS", "NAOVTD", "NAOLFD", "EXTEND ASCII", "LOGOUT", "BYTE MACRO", "DATA ENTRY TERMINAL", "SUPDUP", + "SUPDUP OUTPUT", "SEND LOCATION", "TERMINAL TYPE", "END OF RECORD", "TACACS UID", "OUTPUT MARKING", "TTYLOC", "3270 REGIME", "X.3 PAD", "NAWS", + "TSPEED", "LFLOW", "LINEMODE", "XDISPLOC", "OLD-ENVIRON", "AUTHENTICATION", "ENCRYPT", "NEW-ENVIRON", "TN3270E", "XAUTH", "CHARSET", "RSP", + "Com Port Control", "Suppress Local Echo", "Start TLS", "KERMIT", "SEND-URL", "FORWARD_X", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "TELOPT PRAGMA LOGON", "TELOPT SSPI LOGON", "TELOPT PRAGMA HEARTBEAT", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "Extended-Options-List" }; + + /** + * Returns the string representation of the telnet protocol option corresponding to the given option code. * * @param code The option code of the telnet protocol option * @return The string representation of the telnet protocol option. - ***/ - public static final String getOption(int code) - { - if(__optionString[code].length() == 0) - { + */ + public static final String getOption(final int code) { + if (optionString[code].isEmpty()) { return "UNASSIGNED"; } - else - { - return __optionString[code]; - } + return optionString[code]; } - - /*** - * Determines if a given option code is valid. Returns true if valid, - * false if not. + /** + * Determines if a given option code is valid. Returns true if valid, false if not. * - * @param code The option code to test. + * @param code The option code to test. * @return True if the option code is valid, false if not. **/ - public static final boolean isValidOption(int code) - { - return (code <= __LAST_OPTION); + public static final boolean isValidOption(final int code) { + return code <= LAST_OPTION; } // Cannot be instantiated - private TelnetOption() - { } + private TelnetOption() { + } } diff --git a/src/main/java/org/apache/commons/net/telnet/TelnetOptionHandler.java b/src/main/java/org/apache/commons/net/telnet/TelnetOptionHandler.java index 14b4f01..516642d 100644 --- a/src/main/java/org/apache/commons/net/telnet/TelnetOptionHandler.java +++ b/src/main/java/org/apache/commons/net/telnet/TelnetOptionHandler.java @@ -17,69 +17,59 @@ package org.apache.commons.net.telnet; -/*** - * The TelnetOptionHandler class is the base class to be used - * for implementing handlers for telnet options. +/** + * The TelnetOptionHandler class is the base class to be used for implementing handlers for telnet options. * <p> - * TelnetOptionHandler implements basic option handling - * functionality and defines abstract methods that must be - * implemented to define subnegotiation behaviour. - ***/ -public abstract class TelnetOptionHandler -{ - /*** + * TelnetOptionHandler implements basic option handling functionality and defines abstract methods that must be implemented to define subnegotiation behavior. + */ +public abstract class TelnetOptionHandler { + /** * Option code - ***/ + */ private int optionCode = -1; - /*** + /** * true if the option should be activated on the local side - ***/ - private boolean initialLocal = false; + */ + private boolean initialLocal; - /*** + /** * true if the option should be activated on the remote side - ***/ - private boolean initialRemote = false; + */ + private boolean initialRemote; - /*** + /** * true if the option should be accepted on the local side - ***/ - private boolean acceptLocal = false; + */ + private boolean acceptLocal; - /*** + /** * true if the option should be accepted on the remote side - ***/ - private boolean acceptRemote = false; + */ + private boolean acceptRemote; - /*** + /** * true if the option is active on the local side - ***/ - private boolean doFlag = false; + */ + private boolean doFlag; - /*** + /** * true if the option is active on the remote side - ***/ - private boolean willFlag = false; - - /*** - * Constructor for the TelnetOptionHandler. Allows defining desired - * initial setting for local/remote activation of this option and - * behaviour in case a local/remote activation request for this - * option is received. - * <p> - * @param optcode - Option code. - * @param initlocal - if set to true, a WILL is sent upon connection. - * @param initremote - if set to true, a DO is sent upon connection. - * @param acceptlocal - if set to true, any DO request is accepted. + */ + private boolean willFlag; + + /** + * Constructor for the TelnetOptionHandler. Allows defining desired initial setting for local/remote activation of this option and behavior in case a + * local/remote activation request for this option is received. + * <p> + * + * @param optcode - Option code. + * @param initlocal - if set to true, a WILL is sent upon connection. + * @param initremote - if set to true, a DO is sent upon connection. + * @param acceptlocal - if set to true, any DO request is accepted. * @param acceptremote - if set to true, any WILL request is accepted. - ***/ - public TelnetOptionHandler(int optcode, - boolean initlocal, - boolean initremote, - boolean acceptlocal, - boolean acceptremote) - { + */ + public TelnetOptionHandler(final int optcode, final boolean initlocal, final boolean initremote, final boolean acceptlocal, final boolean acceptremote) { optionCode = optcode; initialLocal = initlocal; initialRemote = initremote; @@ -87,197 +77,175 @@ public abstract class TelnetOptionHandler acceptRemote = acceptremote; } - - /*** - * Returns the option code for this option. + /** + * Method called upon reception of a subnegotiation for this option coming from the other end. * <p> - * @return Option code. - ***/ - public int getOptionCode() - { - return (optionCode); + * This implementation returns null, and must be overridden by the actual TelnetOptionHandler to specify which response must be sent for the subnegotiation + * request. + * <p> + * + * @param suboptionData - the sequence received, without IAC SB & IAC SE + * @param suboptionLength - the length of data in suboption_data + * <p> + * @return response to be sent to the subnegotiation sequence. TelnetClient will add IAC SB & IAC SE. null means no response + */ + public int[] answerSubnegotiation(final int suboptionData[], final int suboptionLength) { + return null; } - /*** - * Returns a boolean indicating whether to accept a DO - * request coming from the other end. + /** + * Returns a boolean indicating whether to accept a DO request coming from the other end. * <p> + * * @return true if a DO request shall be accepted. - ***/ - public boolean getAcceptLocal() - { - return (acceptLocal); + */ + public boolean getAcceptLocal() { + return acceptLocal; } - /*** - * Returns a boolean indicating whether to accept a WILL - * request coming from the other end. + /** + * Returns a boolean indicating whether to accept a WILL request coming from the other end. * <p> + * * @return true if a WILL request shall be accepted. - ***/ - public boolean getAcceptRemote() - { - return (acceptRemote); - } - - /*** - * Set behaviour of the option for DO requests coming from - * the other end. - * <p> - * @param accept - if true, subsequent DO requests will be accepted. - ***/ - public void setAcceptLocal(boolean accept) - { - acceptLocal = accept; + */ + public boolean getAcceptRemote() { + return acceptRemote; } - /*** - * Set behaviour of the option for WILL requests coming from - * the other end. + /** + * Returns a boolean indicating whether a DO request sent to the other side has been acknowledged. * <p> - * @param accept - if true, subsequent WILL requests will be accepted. - ***/ - public void setAcceptRemote(boolean accept) - { - acceptRemote = accept; + * + * @return true if a DO sent to the other side has been acknowledged. + */ + boolean getDo() { + return doFlag; } - /*** - * Returns a boolean indicating whether to send a WILL request - * to the other end upon connection. + /** + * Returns a boolean indicating whether to send a WILL request to the other end upon connection. * <p> + * * @return true if a WILL request shall be sent upon connection. - ***/ - public boolean getInitLocal() - { - return (initialLocal); + */ + public boolean getInitLocal() { + return initialLocal; } - /*** - * Returns a boolean indicating whether to send a DO request - * to the other end upon connection. + /** + * Returns a boolean indicating whether to send a DO request to the other end upon connection. * <p> + * * @return true if a DO request shall be sent upon connection. - ***/ - public boolean getInitRemote() - { - return (initialRemote); + */ + public boolean getInitRemote() { + return initialRemote; } - /*** - * Tells this option whether to send a WILL request upon connection. + /** + * Returns the option code for this option. * <p> - * @param init - if true, a WILL request will be sent upon subsequent - * connections. - ***/ - public void setInitLocal(boolean init) - { - initialLocal = init; + * + * @return Option code. + */ + public int getOptionCode() { + return optionCode; } - /*** - * Tells this option whether to send a DO request upon connection. + /** + * Returns a boolean indicating whether a WILL request sent to the other side has been acknowledged. * <p> - * @param init - if true, a DO request will be sent upon subsequent - * connections. - ***/ - public void setInitRemote(boolean init) - { - initialRemote = init; + * + * @return true if a WILL sent to the other side has been acknowledged. + */ + boolean getWill() { + return willFlag; } - /*** - * Method called upon reception of a subnegotiation for this option - * coming from the other end. - * <p> - * This implementation returns null, and - * must be overridden by the actual TelnetOptionHandler to specify - * which response must be sent for the subnegotiation request. + /** + * Set behavior of the option for DO requests coming from the other end. * <p> - * @param suboptionData - the sequence received, without IAC SB & IAC SE - * @param suboptionLength - the length of data in suboption_data + * + * @param accept - if true, subsequent DO requests will be accepted. + */ + public void setAcceptLocal(final boolean accept) { + acceptLocal = accept; + } + + /** + * Set behavior of the option for WILL requests coming from the other end. * <p> - * @return response to be sent to the subnegotiation sequence. TelnetClient - * will add IAC SB & IAC SE. null means no response - ***/ - public int[] answerSubnegotiation(int suboptionData[], int suboptionLength) { - return null; + * + * @param accept - if true, subsequent WILL requests will be accepted. + */ + public void setAcceptRemote(final boolean accept) { + acceptRemote = accept; } - /*** - * This method is invoked whenever this option is acknowledged active on - * the local end (TelnetClient sent a WILL, remote side sent a DO). - * The method is used to specify a subnegotiation sequence that will be - * sent by TelnetClient when the option is activated. + /** + * Tells this option whether a DO request sent to the other side has been acknowledged (invoked by TelnetClient). * <p> - * This implementation returns null, and must be overriden by - * the actual TelnetOptionHandler to specify - * which response must be sent for the subnegotiation request. - * @return subnegotiation sequence to be sent by TelnetClient. TelnetClient - * will add IAC SB & IAC SE. null means no subnegotiation. - ***/ - public int[] startSubnegotiationLocal() { - return null; + * + * @param state - if true, a DO request has been acknowledged. + */ + void setDo(final boolean state) { + doFlag = state; } - /*** - * This method is invoked whenever this option is acknowledged active on - * the remote end (TelnetClient sent a DO, remote side sent a WILL). - * The method is used to specify a subnegotiation sequence that will be - * sent by TelnetClient when the option is activated. + /** + * Tells this option whether to send a WILL request upon connection. * <p> - * This implementation returns null, and must be overriden by - * the actual TelnetOptionHandler to specify - * which response must be sent for the subnegotiation request. - * @return subnegotiation sequence to be sent by TelnetClient. TelnetClient - * will add IAC SB & IAC SE. null means no subnegotiation. - ***/ - public int[] startSubnegotiationRemote() { - return null; + * + * @param init - if true, a WILL request will be sent upon subsequent connections. + */ + public void setInitLocal(final boolean init) { + initialLocal = init; } - /*** - * Returns a boolean indicating whether a WILL request sent to the other - * side has been acknowledged. + /** + * Tells this option whether to send a DO request upon connection. * <p> - * @return true if a WILL sent to the other side has been acknowledged. - ***/ - boolean getWill() - { - return willFlag; + * + * @param init - if true, a DO request will be sent upon subsequent connections. + */ + public void setInitRemote(final boolean init) { + initialRemote = init; } - /*** - * Tells this option whether a WILL request sent to the other - * side has been acknowledged (invoked by TelnetClient). + /** + * Tells this option whether a WILL request sent to the other side has been acknowledged (invoked by TelnetClient). * <p> + * * @param state - if true, a WILL request has been acknowledged. - ***/ - void setWill(boolean state) - { + */ + void setWill(final boolean state) { willFlag = state; } - /*** - * Returns a boolean indicating whether a DO request sent to the other - * side has been acknowledged. + /** + * This method is invoked whenever this option is acknowledged active on the local end (TelnetClient sent a WILL, remote side sent a DO). The method is used + * to specify a subnegotiation sequence that will be sent by TelnetClient when the option is activated. * <p> - * @return true if a DO sent to the other side has been acknowledged. - ***/ - boolean getDo() - { - return doFlag; + * This implementation returns null, and must be overriden by the actual TelnetOptionHandler to specify which response must be sent for the subnegotiation + * request. + * + * @return subnegotiation sequence to be sent by TelnetClient. TelnetClient will add IAC SB & IAC SE. null means no subnegotiation. + */ + public int[] startSubnegotiationLocal() { + return null; } - - /*** - * Tells this option whether a DO request sent to the other - * side has been acknowledged (invoked by TelnetClient). + /** + * This method is invoked whenever this option is acknowledged active on the remote end (TelnetClient sent a DO, remote side sent a WILL). The method is + * used to specify a subnegotiation sequence that will be sent by TelnetClient when the option is activated. * <p> - * @param state - if true, a DO request has been acknowledged. - ***/ - void setDo(boolean state) - { - doFlag = state; + * This implementation returns null, and must be overriden by the actual TelnetOptionHandler to specify which response must be sent for the subnegotiation + * request. + * + * @return subnegotiation sequence to be sent by TelnetClient. TelnetClient will add IAC SB & IAC SE. null means no subnegotiation. + */ + public int[] startSubnegotiationRemote() { + return null; } } diff --git a/src/main/java/org/apache/commons/net/telnet/TelnetOutputStream.java b/src/main/java/org/apache/commons/net/telnet/TelnetOutputStream.java index 19f4f13..571cab1 100644 --- a/src/main/java/org/apache/commons/net/telnet/TelnetOutputStream.java +++ b/src/main/java/org/apache/commons/net/telnet/TelnetOutputStream.java @@ -25,141 +25,121 @@ import java.io.OutputStream; * <p> * In binary mode, the only conversion is to double IAC. * <p> - * In ASCII mode, if convertCRtoCRLF is true (currently always true), any CR is converted to CRLF. - * IACs are doubled. - * Also a bare LF is converted to CRLF and a bare CR is converted to CR\0 + * In ASCII mode, if convertCRtoCRLF is true (currently always true), any CR is converted to CRLF. IACs are doubled. Also a bare LF is converted to CRLF and a + * bare CR is converted to CR\0 * <p> - ***/ - + */ -final class TelnetOutputStream extends OutputStream -{ - private final TelnetClient __client; +final class TelnetOutputStream extends OutputStream { + private final TelnetClient client; // TODO there does not appear to be any way to change this value - should it be a ctor parameter? - private final boolean __convertCRtoCRLF = true; - private boolean __lastWasCR = false; + private final boolean convertCRtoCRLF = true; + private boolean lastWasCR; + + TelnetOutputStream(final TelnetClient client) { + this.client = client; + } - TelnetOutputStream(TelnetClient client) - { - __client = client; + /** Closes the stream. */ + @Override + public void close() throws IOException { + client.closeOutputStream(); + } + + /** Flushes the stream. */ + @Override + public void flush() throws IOException { + client.flushOutputStream(); + } + + /** + * Writes a byte array to the stream. + * <p> + * + * @param buffer The byte array to write. + * @throws IOException If an error occurs while writing to the underlying stream. + */ + @Override + public void write(final byte buffer[]) throws IOException { + write(buffer, 0, buffer.length); } + /** + * Writes a number of bytes from a byte array to the stream starting from a given offset. + * <p> + * + * @param buffer The byte array to write. + * @param offset The offset into the array at which to start copying data. + * @param length The number of bytes to write. + * @throws IOException If an error occurs while writing to the underlying stream. + */ + @Override + public void write(final byte buffer[], int offset, int length) throws IOException { + synchronized (client) { + while (length-- > 0) { + write(buffer[offset++]); + } + } + } - /*** + /** * Writes a byte to the stream. * <p> + * * @param ch The byte to write. - * @throws IOException If an error occurs while writing to the underlying - * stream. - ***/ + * @throws IOException If an error occurs while writing to the underlying stream. + */ @Override - public void write(int ch) throws IOException - { + public void write(int ch) throws IOException { - synchronized (__client) - { + synchronized (client) { ch &= 0xff; - if (__client._requestedWont(TelnetOption.BINARY)) // i.e. ASCII + if (client.requestedWont(TelnetOption.BINARY)) // i.e. ASCII { - if (__lastWasCR) - { - if (__convertCRtoCRLF) - { - __client._sendByte('\n'); + if (lastWasCR) { + if (convertCRtoCRLF) { + client.sendByte('\n'); if (ch == '\n') // i.e. was CRLF anyway { - __lastWasCR = false; - return ; + lastWasCR = false; + return; } } // __convertCRtoCRLF - else if (ch != '\n') - { - __client._sendByte('\0'); // RFC854 requires CR NUL for bare CR + else if (ch != '\n') { + client.sendByte('\0'); // RFC854 requires CR NUL for bare CR } } - switch (ch) - { + switch (ch) { case '\r': - __client._sendByte('\r'); - __lastWasCR = true; + client.sendByte('\r'); + lastWasCR = true; break; case '\n': - if (!__lastWasCR) { // convert LF to CRLF - __client._sendByte('\r'); + if (!lastWasCR) { // convert LF to CRLF + client.sendByte('\r'); } - __client._sendByte(ch); - __lastWasCR = false; + client.sendByte(ch); + lastWasCR = false; break; case TelnetCommand.IAC: - __client._sendByte(TelnetCommand.IAC); - __client._sendByte(TelnetCommand.IAC); - __lastWasCR = false; + client.sendByte(TelnetCommand.IAC); + client.sendByte(TelnetCommand.IAC); + lastWasCR = false; break; default: - __client._sendByte(ch); - __lastWasCR = false; + client.sendByte(ch); + lastWasCR = false; break; } } // end ASCII - else if (ch == TelnetCommand.IAC) - { - __client._sendByte(ch); - __client._sendByte(TelnetCommand.IAC); + else if (ch == TelnetCommand.IAC) { + client.sendByte(ch); + client.sendByte(TelnetCommand.IAC); } else { - __client._sendByte(ch); + client.sendByte(ch); } } } - - - /*** - * Writes a byte array to the stream. - * <p> - * @param buffer The byte array to write. - * @throws IOException If an error occurs while writing to the underlying - * stream. - ***/ - @Override - public void write(byte buffer[]) throws IOException - { - write(buffer, 0, buffer.length); - } - - - /*** - * Writes a number of bytes from a byte array to the stream starting from - * a given offset. - * <p> - * @param buffer The byte array to write. - * @param offset The offset into the array at which to start copying data. - * @param length The number of bytes to write. - * @throws IOException If an error occurs while writing to the underlying - * stream. - ***/ - @Override - public void write(byte buffer[], int offset, int length) throws IOException - { - synchronized (__client) - { - while (length-- > 0) { - write(buffer[offset++]); - } - } - } - - /*** Flushes the stream. ***/ - @Override - public void flush() throws IOException - { - __client._flushOutputStream(); - } - - /*** Closes the stream. ***/ - @Override - public void close() throws IOException - { - __client._closeOutputStream(); - } } diff --git a/src/main/java/org/apache/commons/net/telnet/TerminalTypeOptionHandler.java b/src/main/java/org/apache/commons/net/telnet/TerminalTypeOptionHandler.java index 49e0fa3..c6d63f9 100644 --- a/src/main/java/org/apache/commons/net/telnet/TerminalTypeOptionHandler.java +++ b/src/main/java/org/apache/commons/net/telnet/TerminalTypeOptionHandler.java @@ -17,90 +17,77 @@ package org.apache.commons.net.telnet; -/*** +/** * Implements the telnet terminal type option RFC 1091. - ***/ -public class TerminalTypeOptionHandler extends TelnetOptionHandler -{ - /*** - * Terminal type - ***/ - private final String termType; - - /*** + */ +public class TerminalTypeOptionHandler extends TelnetOptionHandler { + /** * Terminal type option - ***/ + */ protected static final int TERMINAL_TYPE = 24; - /*** + /** * Send (for subnegotiation) - ***/ - protected static final int TERMINAL_TYPE_SEND = 1; + */ + protected static final int TERMINAL_TYPE_SEND = 1; - /*** + /** * Is (for subnegotiation) - ***/ - protected static final int TERMINAL_TYPE_IS = 0; + */ + protected static final int TERMINAL_TYPE_IS = 0; + + /** + * Terminal type + */ + private final String termType; - /*** - * Constructor for the TerminalTypeOptionHandler. Allows defining desired - * initial setting for local/remote activation of this option and - * behaviour in case a local/remote activation request for this - * option is received. + /** + * Constructor for the TerminalTypeOptionHandler. Initial and accept behavior flags are set to false * <p> + * * @param termtype - terminal type that will be negotiated. - * @param initlocal - if set to true, a WILL is sent upon connection. - * @param initremote - if set to true, a DO is sent upon connection. - * @param acceptlocal - if set to true, any DO request is accepted. - * @param acceptremote - if set to true, any WILL request is accepted. - ***/ - public TerminalTypeOptionHandler(String termtype, - boolean initlocal, - boolean initremote, - boolean acceptlocal, - boolean acceptremote) - { - super(TelnetOption.TERMINAL_TYPE, initlocal, initremote, - acceptlocal, acceptremote); + */ + public TerminalTypeOptionHandler(final String termtype) { + super(TelnetOption.TERMINAL_TYPE, false, false, false, false); termType = termtype; } - /*** - * Constructor for the TerminalTypeOptionHandler. Initial and accept - * behaviour flags are set to false + /** + * Constructor for the TerminalTypeOptionHandler. Allows defining desired initial setting for local/remote activation of this option and behavior in case a + * local/remote activation request for this option is received. * <p> - * @param termtype - terminal type that will be negotiated. - ***/ - public TerminalTypeOptionHandler(String termtype) - { - super(TelnetOption.TERMINAL_TYPE, false, false, false, false); + * + * @param termtype - terminal type that will be negotiated. + * @param initlocal - if set to true, a WILL is sent upon connection. + * @param initremote - if set to true, a DO is sent upon connection. + * @param acceptlocal - if set to true, any DO request is accepted. + * @param acceptremote - if set to true, any WILL request is accepted. + */ + public TerminalTypeOptionHandler(final String termtype, final boolean initlocal, final boolean initremote, final boolean acceptlocal, + final boolean acceptremote) { + super(TelnetOption.TERMINAL_TYPE, initlocal, initremote, acceptlocal, acceptremote); termType = termtype; } - /*** + /** * Implements the abstract method of TelnetOptionHandler. * <p> - * @param suboptionData - the sequence received, without IAC SB & IAC SE + * + * @param suboptionData - the sequence received, without IAC SB & IAC SE * @param suboptionLength - the length of data in suboption_data - * <p> + * <p> * @return terminal type information - ***/ + */ @Override - public int[] answerSubnegotiation(int suboptionData[], int suboptionLength) - { - if ((suboptionData != null) && (suboptionLength > 1) - && (termType != null)) - { - if ((suboptionData[0] == TERMINAL_TYPE) - && (suboptionData[1] == TERMINAL_TYPE_SEND)) - { - int response[] = new int[termType.length() + 2]; + public int[] answerSubnegotiation(final int suboptionData[], final int suboptionLength) { + if ((suboptionData != null) && (suboptionLength > 1) && (termType != null)) { + if ((suboptionData[0] == TERMINAL_TYPE) && (suboptionData[1] == TERMINAL_TYPE_SEND)) { + final int response[] = new int[termType.length() + 2]; response[0] = TERMINAL_TYPE; response[1] = TERMINAL_TYPE_IS; - for (int ii = 0; ii < termType.length(); ii++) - { + for (int ii = 0; ii < termType.length(); ii++) { response[ii + 2] = termType.charAt(ii); } diff --git a/src/main/java/org/apache/commons/net/telnet/WindowSizeOptionHandler.java b/src/main/java/org/apache/commons/net/telnet/WindowSizeOptionHandler.java index e1ba769..4836ce6 100644 --- a/src/main/java/org/apache/commons/net/telnet/WindowSizeOptionHandler.java +++ b/src/main/java/org/apache/commons/net/telnet/WindowSizeOptionHandler.java @@ -17,140 +17,113 @@ package org.apache.commons.net.telnet; -/*** +/** * Implements the telnet window size option RFC 1073. - * @version $Id: WindowSizeOptionHandler.java 1697293 2015-08-24 01:01:00Z sebb $ + * * @since 2.0 - ***/ -public class WindowSizeOptionHandler extends TelnetOptionHandler -{ - /*** + */ +public class WindowSizeOptionHandler extends TelnetOptionHandler { + /** + * Window size option + */ + protected static final int WINDOW_SIZE = 31; + + /** * Horizontal Size - ***/ - private int m_nWidth = 80; + */ + private int width = 80; - /*** + /** * Vertical Size - ***/ - private int m_nHeight = 24; + */ + private int height = 24; - /*** - * Window size option - ***/ - protected static final int WINDOW_SIZE = 31; - - /*** - * Constructor for the WindowSizeOptionHandler. Allows defining desired - * initial setting for local/remote activation of this option and - * behaviour in case a local/remote activation request for this - * option is received. + /** + * Constructor for the WindowSizeOptionHandler. Initial and accept behavior flags are set to false * <p> - * @param nWidth - Window width. + * + * @param nWidth - Window width. * @param nHeight - Window Height - * @param initlocal - if set to true, a WILL is sent upon connection. - * @param initremote - if set to true, a DO is sent upon connection. - * @param acceptlocal - if set to true, any DO request is accepted. - * @param acceptremote - if set to true, any WILL request is accepted. - ***/ - public WindowSizeOptionHandler( - int nWidth, - int nHeight, - boolean initlocal, - boolean initremote, - boolean acceptlocal, - boolean acceptremote - ) { - super ( - TelnetOption.WINDOW_SIZE, - initlocal, - initremote, - acceptlocal, - acceptremote - ); - - m_nWidth = nWidth; - m_nHeight = nHeight; + */ + public WindowSizeOptionHandler(final int nWidth, final int nHeight) { + super(TelnetOption.WINDOW_SIZE, false, false, false, false); + + width = nWidth; + height = nHeight; } - /*** - * Constructor for the WindowSizeOptionHandler. Initial and accept - * behaviour flags are set to false + /** + * Constructor for the WindowSizeOptionHandler. Allows defining desired initial setting for local/remote activation of this option and behavior in case a + * local/remote activation request for this option is received. * <p> - * @param nWidth - Window width. - * @param nHeight - Window Height - ***/ - public WindowSizeOptionHandler( - int nWidth, - int nHeight - ) { - super ( - TelnetOption.WINDOW_SIZE, - false, - false, - false, - false - ); - - m_nWidth = nWidth; - m_nHeight = nHeight; + * + * @param nWidth - Window width. + * @param nHeight - Window Height + * @param initlocal - if set to true, a WILL is sent upon connection. + * @param initremote - if set to true, a DO is sent upon connection. + * @param acceptlocal - if set to true, any DO request is accepted. + * @param acceptremote - if set to true, any WILL request is accepted. + */ + public WindowSizeOptionHandler(final int nWidth, final int nHeight, final boolean initlocal, final boolean initremote, final boolean acceptlocal, + final boolean acceptremote) { + super(TelnetOption.WINDOW_SIZE, initlocal, initremote, acceptlocal, acceptremote); + + width = nWidth; + height = nHeight; } - /*** - * Implements the abstract method of TelnetOptionHandler. - * This will send the client Height and Width to the server. + /** + * Implements the abstract method of TelnetOptionHandler. This will send the client Height and Width to the server. * <p> + * * @return array to send to remote system - ***/ + */ @Override - public int[] startSubnegotiationLocal() - { - int nCompoundWindowSize = m_nWidth * 0x10000 + m_nHeight; + public int[] startSubnegotiationLocal() { + final int nCompoundWindowSize = width * 0x10000 + height; int nResponseSize = 5; int nIndex; int nShift; int nTurnedOnBits; - if ((m_nWidth % 0x100) == 0xFF) { + if (width % 0x100 == 0xFF) { nResponseSize += 1; } - if ((m_nWidth / 0x100) == 0xFF) { + if (width / 0x100 == 0xFF) { nResponseSize += 1; } - if ((m_nHeight % 0x100) == 0xFF) { + if (height % 0x100 == 0xFF) { nResponseSize += 1; } - if ((m_nHeight / 0x100) == 0xFF) { + if (height / 0x100 == 0xFF) { nResponseSize += 1; } // // allocate response array // - int response[] = new int[nResponseSize]; + final int response[] = new int[nResponseSize]; // // Build response array. // --------------------- // 1. put option name. // 2. loop through Window size and fill the values, - // 3. duplicate 'ff' if needed. + // 3. duplicate 'ff' if needed. // - response[0] = WINDOW_SIZE; // 1 // + response[0] = WINDOW_SIZE; // 1 // - for ( // 2 // - nIndex=1, nShift = 24; - nIndex < nResponseSize; - nIndex++, nShift -=8 - ) { + for ( // 2 // + nIndex = 1, nShift = 24; nIndex < nResponseSize; nIndex++, nShift -= 8) { nTurnedOnBits = 0xFF; nTurnedOnBits <<= nShift; response[nIndex] = (nCompoundWindowSize & nTurnedOnBits) >>> nShift; - if (response[nIndex] == 0xff) { // 3 // + if (response[nIndex] == 0xff) { // 3 // nIndex++; response[nIndex] = 0xff; } diff --git a/src/main/java/org/apache/commons/net/tftp/TFTP.java b/src/main/java/org/apache/commons/net/tftp/TFTP.java index b7ca919..ecb3444 100644 --- a/src/main/java/org/apache/commons/net/tftp/TFTP.java +++ b/src/main/java/org/apache/commons/net/tftp/TFTP.java @@ -24,295 +24,232 @@ import java.net.SocketException; import org.apache.commons.net.DatagramSocketClient; -/*** - * The TFTP class exposes a set of methods to allow you to deal with the TFTP - * protocol directly, in case you want to write your own TFTP client or - * server. However, almost every user should only be concerend with - * the {@link org.apache.commons.net.DatagramSocketClient#open open() }, - * and {@link org.apache.commons.net.DatagramSocketClient#close close() }, - * methods. Additionally,the a - * {@link org.apache.commons.net.DatagramSocketClient#setDefaultTimeout setDefaultTimeout() } - * method may be of importance for performance tuning. +/** + * The TFTP class exposes a set of methods to allow you to deal with the TFTP protocol directly, in case you want to write your own TFTP client or server. + * However, almost every user should only be concerend with the {@link org.apache.commons.net.DatagramSocketClient#open open() }, and + * {@link org.apache.commons.net.DatagramSocketClient#close close() }, methods. Additionally,the a + * {@link org.apache.commons.net.DatagramSocketClient#setDefaultTimeout setDefaultTimeout() } method may be of importance for performance tuning. * <p> - * Details regarding the TFTP protocol and the format of TFTP packets can - * be found in RFC 783. But the point of these classes is to keep you - * from having to worry about the internals. + * Details regarding the TFTP protocol and the format of TFTP packets can be found in RFC 783. But the point of these classes is to keep you from having to + * worry about the internals. * * * @see org.apache.commons.net.DatagramSocketClient * @see TFTPPacket * @see TFTPPacketException * @see TFTPClient - ***/ + */ -public class TFTP extends DatagramSocketClient -{ - /*** - * The ascii transfer mode. Its value is 0 and equivalent to NETASCII_MODE - ***/ +public class TFTP extends DatagramSocketClient { + /** + * The ascii transfer mode. Its value is 0 and equivalent to NETASCII_MODE + */ public static final int ASCII_MODE = 0; - /*** - * The netascii transfer mode. Its value is 0. - ***/ + /** + * The netascii transfer mode. Its value is 0. + */ public static final int NETASCII_MODE = 0; - /*** - * The binary transfer mode. Its value is 1 and equivalent to OCTET_MODE. - ***/ + /** + * The binary transfer mode. Its value is 1 and equivalent to OCTET_MODE. + */ public static final int BINARY_MODE = 1; - /*** - * The image transfer mode. Its value is 1 and equivalent to OCTET_MODE. - ***/ + /** + * The image transfer mode. Its value is 1 and equivalent to OCTET_MODE. + */ public static final int IMAGE_MODE = 1; - /*** - * The octet transfer mode. Its value is 1. - ***/ + /** + * The octet transfer mode. Its value is 1. + */ public static final int OCTET_MODE = 1; - /*** - * The default number of milliseconds to wait to receive a datagram - * before timing out. The default is 5000 milliseconds (5 seconds). - ***/ + /** + * The default number of milliseconds to wait to receive a datagram before timing out. The default is 5000 milliseconds (5 seconds). + */ public static final int DEFAULT_TIMEOUT = 5000; - /*** + /** * The default TFTP port according to RFC 783 is 69. - ***/ + */ public static final int DEFAULT_PORT = 69; - /*** - * The size to use for TFTP packet buffers. Its 4 plus the - * TFTPPacket.SEGMENT_SIZE, i.e. 516. - ***/ + /** + * The size to use for TFTP packet buffers. Its 4 plus the TFTPPacket.SEGMENT_SIZE, i.e. 516. + */ static final int PACKET_SIZE = TFTPPacket.SEGMENT_SIZE + 4; - /*** A buffer used to accelerate receives in bufferedReceive() ***/ - private byte[] __receiveBuffer; + /** + * Returns the TFTP string representation of a TFTP transfer mode. Will throw an ArrayIndexOutOfBoundsException if an invalid transfer mode is specified. + * + * @param mode The TFTP transfer mode. One of the MODE constants. + * @return The TFTP string representation of the TFTP transfer mode. + */ + public static final String getModeName(final int mode) { + return TFTPRequestPacket.modeStrings[mode]; + } - /*** A datagram used to minimize memory allocation in bufferedReceive() ***/ - private DatagramPacket __receiveDatagram; + /** A buffer used to accelerate receives in bufferedReceive() */ + private byte[] receiveBuffer; - /*** A datagram used to minimize memory allocation in bufferedSend() ***/ - private DatagramPacket __sendDatagram; + /** A datagram used to minimize memory allocation in bufferedReceive() */ + private DatagramPacket receiveDatagram; - /*** - * A buffer used to accelerate sends in bufferedSend(). - * It is left package visible so that TFTPClient may be slightly more - * efficient during file sends. It saves the creation of an - * additional buffer and prevents a buffer copy in _newDataPcket(). - ***/ - byte[] _sendBuffer; + /** A datagram used to minimize memory allocation in bufferedSend() */ + private DatagramPacket sendDatagram; + /** + * A buffer used to accelerate sends in bufferedSend(). It is left package visible so that TFTPClient may be slightly more efficient during file sends. It + * saves the creation of an additional buffer and prevents a buffer copy in _newDataPcket(). + */ + byte[] sendBuffer; + + /** + * Creates a TFTP instance with a default timeout of DEFAULT_TIMEOUT, a null socket, and buffered operations disabled. + */ + public TFTP() { + setDefaultTimeout(DEFAULT_TIMEOUT); + receiveBuffer = null; + receiveDatagram = null; + } + + /** + * Initializes the internal buffers. Buffers are used by {@link #bufferedSend bufferedSend() } and {@link #bufferedReceive bufferedReceive() }. This method + * must be called before calling either one of those two methods. When you finish using buffered operations, you must call {@link #endBufferedOps + * endBufferedOps() }. + */ + public final void beginBufferedOps() { + receiveBuffer = new byte[PACKET_SIZE]; + receiveDatagram = new DatagramPacket(receiveBuffer, receiveBuffer.length); + sendBuffer = new byte[PACKET_SIZE]; + sendDatagram = new DatagramPacket(sendBuffer, sendBuffer.length); + } - /*** - * Returns the TFTP string representation of a TFTP transfer mode. - * Will throw an ArrayIndexOutOfBoundsException if an invalid transfer - * mode is specified. + /** + * This is a special method to perform a more efficient packet receive. It should only be used after calling {@link #beginBufferedOps beginBufferedOps() }. + * beginBufferedOps() initializes a set of buffers used internally that prevent the new allocation of a DatagramPacket and byte array for each send and + * receive. To use these buffers you must call the bufferedReceive() and bufferedSend() methods instead of send() and receive(). You must also be certain + * that you don't manipulate the resulting packet in such a way that it interferes with future buffered operations. For example, a TFTPDataPacket received + * with bufferedReceive() will have a reference to the internal byte buffer. You must finish using this data before calling bufferedReceive() again, or else + * the data will be overwritten by the the call. * - * @param mode The TFTP transfer mode. One of the MODE constants. - * @return The TFTP string representation of the TFTP transfer mode. - ***/ - public static final String getModeName(int mode) - { - return TFTPRequestPacket._modeStrings[mode]; + * @return The TFTPPacket received. + * @throws InterruptedIOException If a socket timeout occurs. The Java documentation claims an InterruptedIOException is thrown on a DatagramSocket timeout, + * but in practice we find a SocketException is thrown. You should catch both to be safe. + * @throws SocketException If a socket timeout occurs. The Java documentation claims an InterruptedIOException is thrown on a DatagramSocket timeout, + * but in practice we find a SocketException is thrown. You should catch both to be safe. + * @throws IOException If some other I/O error occurs. + * @throws TFTPPacketException If an invalid TFTP packet is received. + */ + public final TFTPPacket bufferedReceive() throws IOException, InterruptedIOException, SocketException, TFTPPacketException { + receiveDatagram.setData(receiveBuffer); + receiveDatagram.setLength(receiveBuffer.length); + _socket_.receive(receiveDatagram); + + final TFTPPacket newTFTPPacket = TFTPPacket.newTFTPPacket(receiveDatagram); + trace("<", newTFTPPacket); + return newTFTPPacket; } - /*** - * Creates a TFTP instance with a default timeout of DEFAULT_TIMEOUT, - * a null socket, and buffered operations disabled. - ***/ - public TFTP() - { - setDefaultTimeout(DEFAULT_TIMEOUT); - __receiveBuffer = null; - __receiveDatagram = null; + /** + * This is a special method to perform a more efficient packet send. It should only be used after calling {@link #beginBufferedOps beginBufferedOps() }. + * beginBufferedOps() initializes a set of buffers used internally that prevent the new allocation of a DatagramPacket and byte array for each send and + * receive. To use these buffers you must call the bufferedReceive() and bufferedSend() methods instead of send() and receive(). You must also be certain + * that you don't manipulate the resulting packet in such a way that it interferes with future buffered operations. For example, a TFTPDataPacket received + * with bufferedReceive() will have a reference to the internal byte buffer. You must finish using this data before calling bufferedReceive() again, or else + * the data will be overwritten by the the call. + * + * @param packet The TFTP packet to send. + * @throws IOException If some I/O error occurs. + */ + public final void bufferedSend(final TFTPPacket packet) throws IOException { + trace(">", packet); + _socket_.send(packet.newDatagram(sendDatagram, sendBuffer)); } - /*** - * This method synchronizes a connection by discarding all packets that - * may be in the local socket buffer. This method need only be called - * when you implement your own TFTP client or server. + /** + * This method synchronizes a connection by discarding all packets that may be in the local socket buffer. This method need only be called when you + * implement your own TFTP client or server. * * @throws IOException if an I/O error occurs. - ***/ - public final void discardPackets() throws IOException - { - int to; - DatagramPacket datagram; + */ + public final void discardPackets() throws IOException { + final int to; + final DatagramPacket datagram; datagram = new DatagramPacket(new byte[PACKET_SIZE], PACKET_SIZE); to = getSoTimeout(); setSoTimeout(1); - try - { + try { while (true) { _socket_.receive(datagram); } - } - catch (SocketException e) - { - // Do nothing. We timed out so we hope we're caught up. - } - catch (InterruptedIOException e) - { - // Do nothing. We timed out so we hope we're caught up. + } catch (final SocketException | InterruptedIOException e) { + // Do nothing. We timed out so we hope we're caught up. } setSoTimeout(to); } - - /*** - * This is a special method to perform a more efficient packet receive. - * It should only be used after calling - * {@link #beginBufferedOps beginBufferedOps() }. beginBufferedOps() - * initializes a set of buffers used internally that prevent the new - * allocation of a DatagramPacket and byte array for each send and receive. - * To use these buffers you must call the bufferedReceive() and - * bufferedSend() methods instead of send() and receive(). You must - * also be certain that you don't manipulate the resulting packet in - * such a way that it interferes with future buffered operations. - * For example, a TFTPDataPacket received with bufferedReceive() will - * have a reference to the internal byte buffer. You must finish using - * this data before calling bufferedReceive() again, or else the data - * will be overwritten by the the call. - * - * @return The TFTPPacket received. - * @throws InterruptedIOException If a socket timeout occurs. The - * Java documentation claims an InterruptedIOException is thrown - * on a DatagramSocket timeout, but in practice we find a - * SocketException is thrown. You should catch both to be safe. - * @throws SocketException If a socket timeout occurs. The - * Java documentation claims an InterruptedIOException is thrown - * on a DatagramSocket timeout, but in practice we find a - * SocketException is thrown. You should catch both to be safe. - * @throws IOException If some other I/O error occurs. - * @throws TFTPPacketException If an invalid TFTP packet is received. - ***/ - public final TFTPPacket bufferedReceive() throws IOException, - InterruptedIOException, SocketException, TFTPPacketException - { - __receiveDatagram.setData(__receiveBuffer); - __receiveDatagram.setLength(__receiveBuffer.length); - _socket_.receive(__receiveDatagram); - - TFTPPacket newTFTPPacket = TFTPPacket.newTFTPPacket(__receiveDatagram); - trace("<", newTFTPPacket); - return newTFTPPacket; - } - - /*** - * This is a special method to perform a more efficient packet send. - * It should only be used after calling - * {@link #beginBufferedOps beginBufferedOps() }. beginBufferedOps() - * initializes a set of buffers used internally that prevent the new - * allocation of a DatagramPacket and byte array for each send and receive. - * To use these buffers you must call the bufferedReceive() and - * bufferedSend() methods instead of send() and receive(). You must - * also be certain that you don't manipulate the resulting packet in - * such a way that it interferes with future buffered operations. - * For example, a TFTPDataPacket received with bufferedReceive() will - * have a reference to the internal byte buffer. You must finish using - * this data before calling bufferedReceive() again, or else the data - * will be overwritten by the the call. - * - * @param packet The TFTP packet to send. - * @throws IOException If some I/O error occurs. - ***/ - public final void bufferedSend(TFTPPacket packet) throws IOException - { - trace(">", packet); - _socket_.send(packet._newDatagram(__sendDatagram, _sendBuffer)); - } - - - /*** - * Initializes the internal buffers. Buffers are used by - * {@link #bufferedSend bufferedSend() } and - * {@link #bufferedReceive bufferedReceive() }. This - * method must be called before calling either one of those two - * methods. When you finish using buffered operations, you must - * call {@link #endBufferedOps endBufferedOps() }. - ***/ - public final void beginBufferedOps() - { - __receiveBuffer = new byte[PACKET_SIZE]; - __receiveDatagram = - new DatagramPacket(__receiveBuffer, __receiveBuffer.length); - _sendBuffer = new byte[PACKET_SIZE]; - __sendDatagram = - new DatagramPacket(_sendBuffer, _sendBuffer.length); - } - - /*** + /** * Releases the resources used to perform buffered sends and receives. - ***/ - public final void endBufferedOps() - { - __receiveBuffer = null; - __receiveDatagram = null; - _sendBuffer = null; - __sendDatagram = null; - } - - - /*** - * Sends a TFTP packet to its destination. - * - * @param packet The TFTP packet to send. - * @throws IOException If some I/O error occurs. - ***/ - public final void send(TFTPPacket packet) throws IOException - { - trace(">", packet); - _socket_.send(packet.newDatagram()); + */ + public final void endBufferedOps() { + receiveBuffer = null; + receiveDatagram = null; + sendBuffer = null; + sendDatagram = null; } - - /*** + /** * Receives a TFTPPacket. * * @return The TFTPPacket received. - * @throws InterruptedIOException If a socket timeout occurs. The - * Java documentation claims an InterruptedIOException is thrown - * on a DatagramSocket timeout, but in practice we find a - * SocketException is thrown. You should catch both to be safe. - * @throws SocketException If a socket timeout occurs. The - * Java documentation claims an InterruptedIOException is thrown - * on a DatagramSocket timeout, but in practice we find a - * SocketException is thrown. You should catch both to be safe. - * @throws IOException If some other I/O error occurs. - * @throws TFTPPacketException If an invalid TFTP packet is received. - ***/ - public final TFTPPacket receive() throws IOException, InterruptedIOException, - SocketException, TFTPPacketException - { - DatagramPacket packet; + * @throws InterruptedIOException If a socket timeout occurs. The Java documentation claims an InterruptedIOException is thrown on a DatagramSocket timeout, + * but in practice we find a SocketException is thrown. You should catch both to be safe. + * @throws SocketException If a socket timeout occurs. The Java documentation claims an InterruptedIOException is thrown on a DatagramSocket timeout, + * but in practice we find a SocketException is thrown. You should catch both to be safe. + * @throws IOException If some other I/O error occurs. + * @throws TFTPPacketException If an invalid TFTP packet is received. + */ + public final TFTPPacket receive() throws IOException, InterruptedIOException, SocketException, TFTPPacketException { + final DatagramPacket packet; packet = new DatagramPacket(new byte[PACKET_SIZE], PACKET_SIZE); _socket_.receive(packet); - TFTPPacket newTFTPPacket = TFTPPacket.newTFTPPacket(packet); + final TFTPPacket newTFTPPacket = TFTPPacket.newTFTPPacket(packet); trace("<", newTFTPPacket); return newTFTPPacket; } + /** + * Sends a TFTP packet to its destination. + * + * @param packet The TFTP packet to send. + * @throws IOException If some I/O error occurs. + */ + public final void send(final TFTPPacket packet) throws IOException { + trace(">", packet); + _socket_.send(packet.newDatagram()); + } + /** * Trace facility; this implementation does nothing. * <p> * Override it to trace the data, for example:<br> * {@code System.out.println(direction + " " + packet.toString());} - * @param direction ">" or "<" - * @param packet the packet to be sent or that has been received + * + * @param direction {@code >} or {@code <} + * @param packet the packet to be sent or that has been received respectively * @since 3.6 */ - protected void trace(String direction, TFTPPacket packet) { + protected void trace(final String direction, final TFTPPacket packet) { } } diff --git a/src/main/java/org/apache/commons/net/tftp/TFTPAckPacket.java b/src/main/java/org/apache/commons/net/tftp/TFTPAckPacket.java index e8cc4ed..ddce2e0 100644 --- a/src/main/java/org/apache/commons/net/tftp/TFTPAckPacket.java +++ b/src/main/java/org/apache/commons/net/tftp/TFTPAckPacket.java @@ -20,60 +20,34 @@ package org.apache.commons.net.tftp; import java.net.DatagramPacket; import java.net.InetAddress; -/*** - * A final class derived from TFTPPacket definiing the TFTP Acknowledgement - * packet type. +/** + * A final class derived from TFTPPacket definiing the TFTP Acknowledgement packet type. * <p> - * Details regarding the TFTP protocol and the format of TFTP packets can - * be found in RFC 783. But the point of these classes is to keep you - * from having to worry about the internals. Additionally, only very - * few people should have to care about any of the TFTPPacket classes - * or derived classes. Almost all users should only be concerned with the - * {@link org.apache.commons.net.tftp.TFTPClient} class - * {@link org.apache.commons.net.tftp.TFTPClient#receiveFile receiveFile()} - * and - * {@link org.apache.commons.net.tftp.TFTPClient#sendFile sendFile()} - * methods. + * Details regarding the TFTP protocol and the format of TFTP packets can be found in RFC 783. But the point of these classes is to keep you from having to + * worry about the internals. Additionally, only very few people should have to care about any of the TFTPPacket classes or derived classes. Almost all users + * should only be concerned with the {@link org.apache.commons.net.tftp.TFTPClient} class {@link org.apache.commons.net.tftp.TFTPClient#receiveFile + * receiveFile()} and {@link org.apache.commons.net.tftp.TFTPClient#sendFile sendFile()} methods. * * * @see TFTPPacket * @see TFTPPacketException * @see TFTP - ***/ - -public final class TFTPAckPacket extends TFTPPacket -{ - /*** The block number being acknowledged by the packet. ***/ - int _blockNumber; + */ - /*** - * Creates an acknowledgment packet to be sent to a host at a given port - * acknowledging receipt of a block. - * - * @param destination The host to which the packet is going to be sent. - * @param port The port to which the packet is going to be sent. - * @param blockNumber The block number being acknowledged. - ***/ - public TFTPAckPacket(InetAddress destination, int port, int blockNumber) - { - super(TFTPPacket.ACKNOWLEDGEMENT, destination, port); - _blockNumber = blockNumber; - } +public final class TFTPAckPacket extends TFTPPacket { + /** The block number being acknowledged by the packet. */ + int blockNumber; - /*** - * Creates an acknowledgement packet based from a received - * datagram. Assumes the datagram is at least length 4, else an - * ArrayIndexOutOfBoundsException may be thrown. + /** + * Creates an acknowledgement packet based from a received datagram. Assumes the datagram is at least length 4, else an ArrayIndexOutOfBoundsException may + * be thrown. * - * @param datagram The datagram containing the received acknowledgement. - * @throws TFTPPacketException If the datagram isn't a valid TFTP - * acknowledgement packet. - ***/ - TFTPAckPacket(DatagramPacket datagram) throws TFTPPacketException - { - super(TFTPPacket.ACKNOWLEDGEMENT, datagram.getAddress(), - datagram.getPort()); - byte[] data; + * @param datagram The datagram containing the received acknowledgement. + * @throws TFTPPacketException If the datagram isn't a valid TFTP acknowledgement packet. + */ + TFTPAckPacket(final DatagramPacket datagram) throws TFTPPacketException { + super(TFTPPacket.ACKNOWLEDGEMENT, datagram.getAddress(), datagram.getPort()); + final byte[] data; data = datagram.getData(); @@ -81,91 +55,89 @@ public final class TFTPAckPacket extends TFTPPacket throw new TFTPPacketException("TFTP operator code does not match type."); } - _blockNumber = (((data[2] & 0xff) << 8) | (data[3] & 0xff)); + this.blockNumber = (((data[2] & 0xff) << 8) | (data[3] & 0xff)); } - /*** - * This is a method only available within the package for - * implementing efficient datagram transport by elminating buffering. - * It takes a datagram as an argument, and a byte buffer in which - * to store the raw datagram data. Inside the method, the data - * is set as the datagram's data and the datagram returned. + /** + * Creates an acknowledgment packet to be sent to a host at a given port acknowledging receipt of a block. * - * @param datagram The datagram to create. - * @param data The buffer to store the packet and to use in the datagram. - * @return The datagram argument. - ***/ - @Override - DatagramPacket _newDatagram(DatagramPacket datagram, byte[] data) - { - data[0] = 0; - data[1] = (byte)_type; - data[2] = (byte)((_blockNumber & 0xffff) >> 8); - data[3] = (byte)(_blockNumber & 0xff); - - datagram.setAddress(_address); - datagram.setPort(_port); - datagram.setData(data); - datagram.setLength(4); - - return datagram; + * @param destination The host to which the packet is going to be sent. + * @param port The port to which the packet is going to be sent. + * @param blockNumber The block number being acknowledged. + */ + public TFTPAckPacket(final InetAddress destination, final int port, final int blockNumber) { + super(TFTPPacket.ACKNOWLEDGEMENT, destination, port); + this.blockNumber = blockNumber; } + /** + * Returns the block number of the acknowledgement. + * + * @return The block number of the acknowledgement. + */ + public int getBlockNumber() { + return blockNumber; + } - /*** - * Creates a UDP datagram containing all the TFTP - * acknowledgement packet data in the proper format. - * This is a method exposed to the programmer in case he - * wants to implement his own TFTP client instead of using - * the {@link org.apache.commons.net.tftp.TFTPClient} - * class. Under normal circumstances, you should not have a need to call this - * method. + /** + * Creates a UDP datagram containing all the TFTP acknowledgement packet data in the proper format. This is a method exposed to the programmer in case he + * wants to implement his own TFTP client instead of using the {@link org.apache.commons.net.tftp.TFTPClient} class. Under normal circumstances, you should + * not have a need to call this method. * * @return A UDP datagram containing the TFTP acknowledgement packet. - ***/ + */ @Override - public DatagramPacket newDatagram() - { - byte[] data; + public DatagramPacket newDatagram() { + final byte[] data; data = new byte[4]; data[0] = 0; - data[1] = (byte)_type; - data[2] = (byte)((_blockNumber & 0xffff) >> 8); - data[3] = (byte)(_blockNumber & 0xff); + data[1] = (byte) type; + data[2] = (byte) ((blockNumber & 0xffff) >> 8); + data[3] = (byte) (blockNumber & 0xff); - return new DatagramPacket(data, data.length, _address, _port); + return new DatagramPacket(data, data.length, address, port); } - - /*** - * Returns the block number of the acknowledgement. + /** + * This is a method only available within the package for implementing efficient datagram transport by elminating buffering. It takes a datagram as an + * argument, and a byte buffer in which to store the raw datagram data. Inside the method, the data is set as the datagram's data and the datagram returned. * - * @return The block number of the acknowledgement. - ***/ - public int getBlockNumber() - { - return _blockNumber; - } + * @param datagram The datagram to create. + * @param data The buffer to store the packet and to use in the datagram. + * @return The datagram argument. + */ + @Override + DatagramPacket newDatagram(final DatagramPacket datagram, final byte[] data) { + data[0] = 0; + data[1] = (byte) type; + data[2] = (byte) ((blockNumber & 0xffff) >> 8); + data[3] = (byte) (blockNumber & 0xff); + datagram.setAddress(address); + datagram.setPort(port); + datagram.setData(data); + datagram.setLength(4); - /*** + return datagram; + } + + /** * Sets the block number of the acknowledgement. * * @param blockNumber the number to set - ***/ - public void setBlockNumber(int blockNumber) - { - _blockNumber = blockNumber; + */ + public void setBlockNumber(final int blockNumber) { + this.blockNumber = blockNumber; } /** * For debugging + * * @since 3.6 */ @Override public String toString() { - return super.toString() + " ACK " + _blockNumber; + return super.toString() + " ACK " + blockNumber; } } - diff --git a/src/main/java/org/apache/commons/net/tftp/TFTPClient.java b/src/main/java/org/apache/commons/net/tftp/TFTPClient.java index 3ea5541..5011b97 100644 --- a/src/main/java/org/apache/commons/net/tftp/TFTPClient.java +++ b/src/main/java/org/apache/commons/net/tftp/TFTPClient.java @@ -24,94 +24,59 @@ import java.io.OutputStream; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; + import org.apache.commons.net.io.FromNetASCIIOutputStream; import org.apache.commons.net.io.ToNetASCIIInputStream; -/*** - * The TFTPClient class encapsulates all the aspects of the TFTP protocol - * necessary to receive and send files through TFTP. It is derived from - * the {@link org.apache.commons.net.tftp.TFTP} because - * it is more convenient than using aggregation, and as a result exposes - * the same set of methods to allow you to deal with the TFTP protocol - * directly. However, almost every user should only be concerend with the - * the {@link org.apache.commons.net.DatagramSocketClient#open open() }, - * {@link org.apache.commons.net.DatagramSocketClient#close close() }, - * {@link #sendFile sendFile() }, and - * {@link #receiveFile receiveFile() } methods. Additionally, the - * {@link #setMaxTimeouts setMaxTimeouts() } and - * {@link org.apache.commons.net.DatagramSocketClient#setDefaultTimeout setDefaultTimeout() } - * methods may be of importance for performance - * tuning. +/** + * The TFTPClient class encapsulates all the aspects of the TFTP protocol necessary to receive and send files through TFTP. It is derived from the + * {@link org.apache.commons.net.tftp.TFTP} because it is more convenient than using aggregation, and as a result exposes the same set of methods to allow you + * to deal with the TFTP protocol directly. However, almost every user should only be concerend with the the + * {@link org.apache.commons.net.DatagramSocketClient#open open() }, {@link org.apache.commons.net.DatagramSocketClient#close close() }, {@link #sendFile + * sendFile() }, and {@link #receiveFile receiveFile() } methods. Additionally, the {@link #setMaxTimeouts setMaxTimeouts() } and + * {@link org.apache.commons.net.DatagramSocketClient#setDefaultTimeout setDefaultTimeout() } methods may be of importance for performance tuning. * <p> - * Details regarding the TFTP protocol and the format of TFTP packets can - * be found in RFC 783. But the point of these classes is to keep you - * from having to worry about the internals. + * Details regarding the TFTP protocol and the format of TFTP packets can be found in RFC 783. But the point of these classes is to keep you from having to + * worry about the internals. * * * @see TFTP * @see TFTPPacket * @see TFTPPacketException - ***/ - -public class TFTPClient extends TFTP -{ - /*** - * The default number of times a receive attempt is allowed to timeout - * before ending attempts to retry the receive and failing. The default - * is 5 timeouts. - ***/ - public static final int DEFAULT_MAX_TIMEOUTS = 5; + */ - /*** The maximum number of timeouts allowed before failing. ***/ - private int __maxTimeouts; +public class TFTPClient extends TFTP { + /** + * The default number of times a receive attempt is allowed to timeout before ending attempts to retry the receive and failing. The default is 5 timeouts. + */ + public static final int DEFAULT_MAX_TIMEOUTS = 5; - /*** The number of bytes received in the ongoing download. ***/ - private long totalBytesReceived = 0; + /** The maximum number of timeouts allowed before failing. */ + private int maxTimeouts; - /*** The number of bytes sent in the ongoing upload. ***/ - private long totalBytesSent = 0; + /** The number of bytes received in the ongoing download. */ + private long totalBytesReceived; - /*** - * Creates a TFTPClient instance with a default timeout of DEFAULT_TIMEOUT, - * maximum timeouts value of DEFAULT_MAX_TIMEOUTS, a null socket, - * and buffered operations disabled. - ***/ - public TFTPClient() - { - __maxTimeouts = DEFAULT_MAX_TIMEOUTS; - } + /** The number of bytes sent in the ongoing upload. */ + private long totalBytesSent; - /*** - * Sets the maximum number of times a receive attempt is allowed to - * timeout during a receiveFile() or sendFile() operation before ending - * attempts to retry the receive and failing. - * The default is DEFAULT_MAX_TIMEOUTS. - * - * @param numTimeouts The maximum number of timeouts to allow. Values - * less than 1 should not be used, but if they are, they are - * treated as 1. - ***/ - public void setMaxTimeouts(int numTimeouts) - { - if (numTimeouts < 1) { - __maxTimeouts = 1; - } else { - __maxTimeouts = numTimeouts; - } + /** + * Creates a TFTPClient instance with a default timeout of DEFAULT_TIMEOUT, maximum timeouts value of DEFAULT_MAX_TIMEOUTS, a null socket, and buffered + * operations disabled. + */ + public TFTPClient() { + maxTimeouts = DEFAULT_MAX_TIMEOUTS; } - /*** - * Returns the maximum number of times a receive attempt is allowed to - * timeout before ending attempts to retry the receive and failing. + /** + * Returns the maximum number of times a receive attempt is allowed to timeout before ending attempts to retry the receive and failing. * * @return The maximum number of timeouts allowed. - ***/ - public int getMaxTimeouts() - { - return __maxTimeouts; + */ + public int getMaxTimeouts() { + return maxTimeouts; } - /** * @return The number of bytes received in the ongoing download */ @@ -126,26 +91,34 @@ public class TFTPClient extends TFTP return totalBytesSent; } - /*** - * Requests a named file from a remote host, writes the - * file to an OutputStream, closes the connection, and returns the number - * of bytes read. A local UDP socket must first be created by - * {@link org.apache.commons.net.DatagramSocketClient#open open()} before - * invoking this method. This method will not close the OutputStream - * containing the file; you must close it after the method invocation. + /** + * Same as calling receiveFile(fileName, mode, output, host, TFTP.DEFAULT_PORT). + * + * @param fileName The name of the file to receive. + * @param mode The TFTP mode of the transfer (one of the MODE constants). + * @param output The OutputStream to which the file should be written. + * @param host The remote host serving the file. + * @return number of bytes read + * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message. + */ + public int receiveFile(final String fileName, final int mode, final OutputStream output, final InetAddress host) throws IOException { + return receiveFile(fileName, mode, output, host, DEFAULT_PORT); + } + + /** + * Requests a named file from a remote host, writes the file to an OutputStream, closes the connection, and returns the number of bytes read. A local UDP + * socket must first be created by {@link org.apache.commons.net.DatagramSocketClient#open open()} before invoking this method. This method will not close + * the OutputStream containing the file; you must close it after the method invocation. * - * @param filename The name of the file to receive. - * @param mode The TFTP mode of the transfer (one of the MODE constants). - * @param output The OutputStream to which the file should be written. - * @param host The remote host serving the file. - * @param port The port number of the remote TFTP server. + * @param fileName The name of the file to receive. + * @param mode The TFTP mode of the transfer (one of the MODE constants). + * @param output The OutputStream to which the file should be written. + * @param host The remote host serving the file. + * @param port The port number of the remote TFTP server. * @return number of bytes read - * @throws IOException If an I/O error occurs. The nature of the - * error will be reported in the message. - ***/ - public int receiveFile(String filename, int mode, OutputStream output, - InetAddress host, int port) throws IOException - { + * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message. + */ + public int receiveFile(final String fileName, final int mode, OutputStream output, InetAddress host, final int port) throws IOException { int bytesRead = 0; int lastBlock = 0; int block = 1; @@ -158,8 +131,8 @@ public class TFTPClient extends TFTP output = new FromNetASCIIOutputStream(output); } - TFTPPacket sent = new TFTPReadRequestPacket(host, port, filename, mode); - TFTPAckPacket ack = new TFTPAckPacket(host, port, 0); + TFTPPacket sent = new TFTPReadRequestPacket(host, port, fileName, mode); + final TFTPAckPacket ack = new TFTPAckPacket(host, port, 0); beginBufferedOps(); @@ -171,7 +144,7 @@ public class TFTPClient extends TFTP int timeouts = 0; do { // until successful response try { - TFTPPacket received = bufferedReceive(); + final TFTPPacket received = bufferedReceive(); // The first time we receive we get the port number and // answering host address (for hosts with multiple IPs) final int recdPort = received.getPort(); @@ -179,16 +152,13 @@ public class TFTPClient extends TFTP if (justStarted) { justStarted = false; if (recdPort == port) { // must not use the control port here - TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, - recdPort, TFTPErrorPacket.UNKNOWN_TID, - "INCORRECT SOURCE PORT"); + final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, recdPort, TFTPErrorPacket.UNKNOWN_TID, "INCORRECT SOURCE PORT"); bufferedSend(error); - throw new IOException("Incorrect source port ("+recdPort+") in request reply."); + throw new IOException("Incorrect source port (" + recdPort + ") in request reply."); } hostPort = recdPort; ack.setPort(hostPort); - if(!host.equals(recdAddress)) - { + if (!host.equals(recdAddress)) { host = recdAddress; ack.setAddress(host); sent.setAddress(host); @@ -200,21 +170,18 @@ public class TFTPClient extends TFTP switch (received.getType()) { case TFTPPacket.ERROR: - TFTPErrorPacket error = (TFTPErrorPacket)received; - throw new IOException("Error code " + error.getError() + - " received: " + error.getMessage()); + TFTPErrorPacket error = (TFTPErrorPacket) received; + throw new IOException("Error code " + error.getError() + " received: " + error.getMessage()); case TFTPPacket.DATA: - TFTPDataPacket data = (TFTPDataPacket)received; + final TFTPDataPacket data = (TFTPDataPacket) received; dataLength = data.getDataLength(); lastBlock = data.getBlockNumber(); if (lastBlock == block) { // is the next block number? try { output.write(data.getData(), data.getDataOffset(), dataLength); - } catch (IOException e) { - error = new TFTPErrorPacket(host, hostPort, - TFTPErrorPacket.OUT_OF_SPACE, - "File write failed."); + } catch (final IOException e) { + error = new TFTPErrorPacket(host, hostPort, TFTPErrorPacket.OUT_OF_SPACE, "File write failed."); bufferedSend(error); throw e; } @@ -226,7 +193,7 @@ public class TFTPClient extends TFTP wantReply = false; // got the next block, drop out to ack it } else { // unexpected block number discardPackets(); - if (lastBlock == (block == 0 ? 65535 : (block - 1))) { + if (lastBlock == (block == 0 ? 65535 : block - 1)) { wantReply = false; // Resend last acknowledgemen } } @@ -236,23 +203,17 @@ public class TFTPClient extends TFTP throw new IOException("Received unexpected packet type (" + received.getType() + ")"); } } else { // incorrect host or TID - TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, recdPort, - TFTPErrorPacket.UNKNOWN_TID, - "Unexpected host or port."); + final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, recdPort, TFTPErrorPacket.UNKNOWN_TID, "Unexpected host or port."); bufferedSend(error); } - } catch (SocketException e) { - if (++timeouts >= __maxTimeouts) { + } catch (final SocketException | InterruptedIOException e) { + if (++timeouts >= maxTimeouts) { throw new IOException("Connection timed out."); } - } catch (InterruptedIOException e) { - if (++timeouts >= __maxTimeouts) { - throw new IOException("Connection timed out."); - } - } catch (TFTPPacketException e) { + } catch (final TFTPPacketException e) { throw new IOException("Bad packet: " + e.getMessage()); } - } while(wantReply); // waiting for response + } while (wantReply); // waiting for response ack.setBlockNumber(lastBlock); sent = ack; @@ -266,93 +227,67 @@ public class TFTPClient extends TFTP return bytesRead; } - - /*** - * Requests a named file from a remote host, writes the - * file to an OutputStream, closes the connection, and returns the number - * of bytes read. A local UDP socket must first be created by - * {@link org.apache.commons.net.DatagramSocketClient#open open()} before - * invoking this method. This method will not close the OutputStream - * containing the file; you must close it after the method invocation. + /** + * Same as calling receiveFile(fileName, mode, output, hostname, TFTP.DEFAULT_PORT). * - * @param filename The name of the file to receive. + * @param fileName The name of the file to receive. * @param mode The TFTP mode of the transfer (one of the MODE constants). * @param output The OutputStream to which the file should be written. * @param hostname The name of the remote host serving the file. - * @param port The port number of the remote TFTP server. * @return number of bytes read - * @throws IOException If an I/O error occurs. The nature of the - * error will be reported in the message. - * @throws UnknownHostException If the hostname cannot be resolved. - ***/ - public int receiveFile(String filename, int mode, OutputStream output, - String hostname, int port) - throws UnknownHostException, IOException - { - return receiveFile(filename, mode, output, InetAddress.getByName(hostname), - port); + * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message. + * @throws UnknownHostException If the hostname cannot be resolved. + */ + public int receiveFile(final String fileName, final int mode, final OutputStream output, final String hostname) throws UnknownHostException, IOException { + return receiveFile(fileName, mode, output, InetAddress.getByName(hostname), DEFAULT_PORT); } - - /*** - * Same as calling receiveFile(filename, mode, output, host, TFTP.DEFAULT_PORT). + /** + * Requests a named file from a remote host, writes the file to an OutputStream, closes the connection, and returns the number of bytes read. A local UDP + * socket must first be created by {@link org.apache.commons.net.DatagramSocketClient#open open()} before invoking this method. This method will not close + * the OutputStream containing the file; you must close it after the method invocation. * - * @param filename The name of the file to receive. + * @param fileName The name of the file to receive. * @param mode The TFTP mode of the transfer (one of the MODE constants). * @param output The OutputStream to which the file should be written. - * @param host The remote host serving the file. + * @param hostname The name of the remote host serving the file. + * @param port The port number of the remote TFTP server. * @return number of bytes read - * @throws IOException If an I/O error occurs. The nature of the - * error will be reported in the message. - ***/ - public int receiveFile(String filename, int mode, OutputStream output, - InetAddress host) - throws IOException - { - return receiveFile(filename, mode, output, host, DEFAULT_PORT); + * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message. + * @throws UnknownHostException If the hostname cannot be resolved. + */ + public int receiveFile(final String fileName, final int mode, final OutputStream output, final String hostname, final int port) + throws UnknownHostException, IOException { + return receiveFile(fileName, mode, output, InetAddress.getByName(hostname), port); } - /*** - * Same as calling receiveFile(filename, mode, output, hostname, TFTP.DEFAULT_PORT). + /** + * Same as calling sendFile(fileName, mode, input, host, TFTP.DEFAULT_PORT). * - * @param filename The name of the file to receive. + * @param fileName The name the remote server should use when creating the file on its file system. * @param mode The TFTP mode of the transfer (one of the MODE constants). - * @param output The OutputStream to which the file should be written. - * @param hostname The name of the remote host serving the file. - * @return number of bytes read - * @throws IOException If an I/O error occurs. The nature of the - * error will be reported in the message. - * @throws UnknownHostException If the hostname cannot be resolved. - ***/ - public int receiveFile(String filename, int mode, OutputStream output, - String hostname) - throws UnknownHostException, IOException - { - return receiveFile(filename, mode, output, InetAddress.getByName(hostname), - DEFAULT_PORT); + * @param input the input stream containing the data to be sent + * @param host The name of the remote host receiving the file. + * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message. + * @throws UnknownHostException If the hostname cannot be resolved. + */ + public void sendFile(final String fileName, final int mode, final InputStream input, final InetAddress host) throws IOException { + sendFile(fileName, mode, input, host, DEFAULT_PORT); } - - /*** - * Requests to send a file to a remote host, reads the file from an - * InputStream, sends the file to the remote host, and closes the - * connection. A local UDP socket must first be created by - * {@link org.apache.commons.net.DatagramSocketClient#open open()} before - * invoking this method. This method will not close the InputStream - * containing the file; you must close it after the method invocation. + /** + * Requests to send a file to a remote host, reads the file from an InputStream, sends the file to the remote host, and closes the connection. A local UDP + * socket must first be created by {@link org.apache.commons.net.DatagramSocketClient#open open()} before invoking this method. This method will not close + * the InputStream containing the file; you must close it after the method invocation. * - * @param filename The name the remote server should use when creating - * the file on its file system. + * @param fileName The name the remote server should use when creating the file on its file system. * @param mode The TFTP mode of the transfer (one of the MODE constants). - * @param input the input stream containing the data to be sent + * @param input the input stream containing the data to be sent * @param host The remote host receiving the file. * @param port The port number of the remote TFTP server. - * @throws IOException If an I/O error occurs. The nature of the - * error will be reported in the message. - ***/ - public void sendFile(String filename, int mode, InputStream input, - InetAddress host, int port) throws IOException - { + * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message. + */ + public void sendFile(final String fileName, final int mode, InputStream input, InetAddress host, final int port) throws IOException { int block = 0; int hostPort = 0; boolean justStarted = true; @@ -364,21 +299,21 @@ public class TFTPClient extends TFTP input = new ToNetASCIIInputStream(input); } - TFTPPacket sent = new TFTPWriteRequestPacket(host, port, filename, mode); - TFTPDataPacket data = new TFTPDataPacket(host, port, 0, _sendBuffer, 4, 0); + TFTPPacket sent = new TFTPWriteRequestPacket(host, port, fileName, mode); + final TFTPDataPacket data = new TFTPDataPacket(host, port, 0, sendBuffer, 4, 0); beginBufferedOps(); try { do { // until eof - // first time: block is 0, lastBlock is 0, send a request packet. - // subsequent: block is integer starting at 1, send data packet. + // first time: block is 0, lastBlock is 0, send a request packet. + // subsequent: block is integer starting at 1, send data packet. bufferedSend(sent); boolean wantReply = true; int timeouts = 0; do { try { - TFTPPacket received = bufferedReceive(); + final TFTPPacket received = bufferedReceive(); final InetAddress recdAddress = received.getAddress(); final int recdPort = received.getPort(); // The first time we receive we get the port number and @@ -386,11 +321,9 @@ public class TFTPClient extends TFTP if (justStarted) { justStarted = false; if (recdPort == port) { // must not use the control port here - TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, - recdPort, TFTPErrorPacket.UNKNOWN_TID, - "INCORRECT SOURCE PORT"); + final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, recdPort, TFTPErrorPacket.UNKNOWN_TID, "INCORRECT SOURCE PORT"); bufferedSend(error); - throw new IOException("Incorrect source port ("+recdPort+") in request reply."); + throw new IOException("Incorrect source port (" + recdPort + ") in request reply."); } hostPort = recdPort; data.setPort(hostPort); @@ -406,12 +339,11 @@ public class TFTPClient extends TFTP switch (received.getType()) { case TFTPPacket.ERROR: - TFTPErrorPacket error = (TFTPErrorPacket)received; - throw new IOException("Error code " + error.getError() + - " received: " + error.getMessage()); + final TFTPErrorPacket error = (TFTPErrorPacket) received; + throw new IOException("Error code " + error.getError() + " received: " + error.getMessage()); case TFTPPacket.ACKNOWLEDGEMENT: - int lastBlock = ((TFTPAckPacket)received).getBlockNumber(); + final int lastBlock = ((TFTPAckPacket) received).getBlockNumber(); if (lastBlock == block) { ++block; @@ -428,25 +360,18 @@ public class TFTPClient extends TFTP throw new IOException("Received unexpected packet type."); } } else { // wrong host or TID; send error - TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, - recdPort, - TFTPErrorPacket.UNKNOWN_TID, - "Unexpected host or port."); + final TFTPErrorPacket error = new TFTPErrorPacket(recdAddress, recdPort, TFTPErrorPacket.UNKNOWN_TID, "Unexpected host or port."); bufferedSend(error); } - } catch (SocketException e) { - if (++timeouts >= __maxTimeouts) { + } catch (final SocketException | InterruptedIOException e) { + if (++timeouts >= maxTimeouts) { throw new IOException("Connection timed out."); } - } catch (InterruptedIOException e) { - if (++timeouts >= __maxTimeouts) { - throw new IOException("Connection timed out."); - } - } catch (TFTPPacketException e) { + } catch (final TFTPPacketException e) { throw new IOException("Bad packet: " + e.getMessage()); } // retry until a good ack - } while(wantReply); + } while (wantReply); if (lastAckWait) { break; // we were waiting for this; now all done @@ -456,18 +381,17 @@ public class TFTPClient extends TFTP int offset = 4; int totalThisPacket = 0; int bytesRead = 0; - while (dataLength > 0 && - (bytesRead = input.read(_sendBuffer, offset, dataLength)) > 0) { + while (dataLength > 0 && (bytesRead = input.read(sendBuffer, offset, dataLength)) > 0) { offset += bytesRead; dataLength -= bytesRead; totalThisPacket += bytesRead; } - if( totalThisPacket < TFTPPacket.SEGMENT_SIZE ) { + if (totalThisPacket < TFTPPacket.SEGMENT_SIZE) { /* this will be our last packet -- send, wait for ack, stop */ lastAckWait = true; } data.setBlockNumber(block); - data.setData(_sendBuffer, 4, totalThisPacket); + data.setData(sendBuffer, 4, totalThisPacket); sent = data; totalBytesSent += totalThisPacket; } while (true); // loops until after lastAckWait is set @@ -476,69 +400,45 @@ public class TFTPClient extends TFTP } } - - /*** - * Requests to send a file to a remote host, reads the file from an - * InputStream, sends the file to the remote host, and closes the - * connection. A local UDP socket must first be created by - * {@link org.apache.commons.net.DatagramSocketClient#open open()} before - * invoking this method. This method will not close the InputStream - * containing the file; you must close it after the method invocation. + /** + * Same as calling sendFile(fileName, mode, input, hostname, TFTP.DEFAULT_PORT). * - * @param filename The name the remote server should use when creating - * the file on its file system. + * @param fileName The name the remote server should use when creating the file on its file system. * @param mode The TFTP mode of the transfer (one of the MODE constants). - * @param input the input stream containing the data to be sent + * @param input the input stream containing the data to be sent * @param hostname The name of the remote host receiving the file. - * @param port The port number of the remote TFTP server. - * @throws IOException If an I/O error occurs. The nature of the - * error will be reported in the message. - * @throws UnknownHostException If the hostname cannot be resolved. - ***/ - public void sendFile(String filename, int mode, InputStream input, - String hostname, int port) - throws UnknownHostException, IOException - { - sendFile(filename, mode, input, InetAddress.getByName(hostname), port); + * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message. + * @throws UnknownHostException If the hostname cannot be resolved. + */ + public void sendFile(final String fileName, final int mode, final InputStream input, final String hostname) throws UnknownHostException, IOException { + sendFile(fileName, mode, input, InetAddress.getByName(hostname), DEFAULT_PORT); } - - /*** - * Same as calling sendFile(filename, mode, input, host, TFTP.DEFAULT_PORT). + /** + * Requests to send a file to a remote host, reads the file from an InputStream, sends the file to the remote host, and closes the connection. A local UDP + * socket must first be created by {@link org.apache.commons.net.DatagramSocketClient#open open()} before invoking this method. This method will not close + * the InputStream containing the file; you must close it after the method invocation. * - * @param filename The name the remote server should use when creating - * the file on its file system. + * @param fileName The name the remote server should use when creating the file on its file system. * @param mode The TFTP mode of the transfer (one of the MODE constants). - * @param input the input stream containing the data to be sent - * @param host The name of the remote host receiving the file. - * @throws IOException If an I/O error occurs. The nature of the - * error will be reported in the message. - * @throws UnknownHostException If the hostname cannot be resolved. - ***/ - public void sendFile(String filename, int mode, InputStream input, - InetAddress host) - throws IOException - { - sendFile(filename, mode, input, host, DEFAULT_PORT); + * @param input the input stream containing the data to be sent + * @param hostname The name of the remote host receiving the file. + * @param port The port number of the remote TFTP server. + * @throws IOException If an I/O error occurs. The nature of the error will be reported in the message. + * @throws UnknownHostException If the hostname cannot be resolved. + */ + public void sendFile(final String fileName, final int mode, final InputStream input, final String hostname, final int port) + throws UnknownHostException, IOException { + sendFile(fileName, mode, input, InetAddress.getByName(hostname), port); } - /*** - * Same as calling sendFile(filename, mode, input, hostname, TFTP.DEFAULT_PORT). + /** + * Sets the maximum number of times a receive attempt is allowed to timeout during a receiveFile() or sendFile() operation before ending attempts to retry + * the receive and failing. The default is DEFAULT_MAX_TIMEOUTS. * - * @param filename The name the remote server should use when creating - * the file on its file system. - * @param mode The TFTP mode of the transfer (one of the MODE constants). - * @param input the input stream containing the data to be sent - * @param hostname The name of the remote host receiving the file. - * @throws IOException If an I/O error occurs. The nature of the - * error will be reported in the message. - * @throws UnknownHostException If the hostname cannot be resolved. - ***/ - public void sendFile(String filename, int mode, InputStream input, - String hostname) - throws UnknownHostException, IOException - { - sendFile(filename, mode, input, InetAddress.getByName(hostname), - DEFAULT_PORT); + * @param numTimeouts The maximum number of timeouts to allow. Values less than 1 should not be used, but if they are, they are treated as 1. + */ + public void setMaxTimeouts(final int numTimeouts) { + maxTimeouts = Math.max(numTimeouts, 1); } } diff --git a/src/main/java/org/apache/commons/net/tftp/TFTPDataPacket.java b/src/main/java/org/apache/commons/net/tftp/TFTPDataPacket.java index 53f24f1..63e7c45 100644 --- a/src/main/java/org/apache/commons/net/tftp/TFTPDataPacket.java +++ b/src/main/java/org/apache/commons/net/tftp/TFTPDataPacket.java @@ -20,250 +20,207 @@ package org.apache.commons.net.tftp; import java.net.DatagramPacket; import java.net.InetAddress; -/*** - * A final class derived from TFTPPacket definiing the TFTP Data - * packet type. +/** + * A final class derived from TFTPPacket definiing the TFTP Data packet type. * <p> - * Details regarding the TFTP protocol and the format of TFTP packets can - * be found in RFC 783. But the point of these classes is to keep you - * from having to worry about the internals. Additionally, only very - * few people should have to care about any of the TFTPPacket classes - * or derived classes. Almost all users should only be concerned with the - * {@link org.apache.commons.net.tftp.TFTPClient} class - * {@link org.apache.commons.net.tftp.TFTPClient#receiveFile receiveFile()} - * and - * {@link org.apache.commons.net.tftp.TFTPClient#sendFile sendFile()} - * methods. + * Details regarding the TFTP protocol and the format of TFTP packets can be found in RFC 783. But the point of these classes is to keep you from having to + * worry about the internals. Additionally, only very few people should have to care about any of the TFTPPacket classes or derived classes. Almost all users + * should only be concerned with the {@link org.apache.commons.net.tftp.TFTPClient} class {@link org.apache.commons.net.tftp.TFTPClient#receiveFile + * receiveFile()} and {@link org.apache.commons.net.tftp.TFTPClient#sendFile sendFile()} methods. * * * @see TFTPPacket * @see TFTPPacketException * @see TFTP - ***/ + */ -public final class TFTPDataPacket extends TFTPPacket -{ - /*** The maximum number of bytes in a TFTP data packet (512) ***/ +public final class TFTPDataPacket extends TFTPPacket { + /** The maximum number of bytes in a TFTP data packet (512) */ public static final int MAX_DATA_LENGTH = 512; - /*** The minimum number of bytes in a TFTP data packet (0) ***/ + /** The minimum number of bytes in a TFTP data packet (0) */ public static final int MIN_DATA_LENGTH = 0; - /*** The block number of the packet. ***/ - int _blockNumber; + /** The block number of the packet. */ + int blockNumber; - /*** The length of the data. ***/ - int _length; + /** The length of the data. */ + private int length; - /*** The offset into the _data array at which the data begins. ***/ - int _offset; + /** The offset into the _data array at which the data begins. */ + private int offset; - /*** The data stored in the packet. ***/ - byte[] _data; + /** The data stored in the packet. */ + private byte[] data; - /*** - * Creates a data packet to be sent to a host at a given port - * with a given block number. The actual data to be sent is passed as - * an array, an offset, and a length. The offset is the offset into - * the byte array where the data starts. The length is the length of - * the data. If the length is greater than MAX_DATA_LENGTH, it is - * truncated. + /** + * Creates a data packet based from a received datagram. Assumes the datagram is at least length 4, else an ArrayIndexOutOfBoundsException may be thrown. * - * @param destination The host to which the packet is going to be sent. - * @param port The port to which the packet is going to be sent. - * @param blockNumber The block number of the data. - * @param data The byte array containing the data. - * @param offset The offset into the array where the data starts. - * @param length The length of the data. - ***/ - public TFTPDataPacket(InetAddress destination, int port, int blockNumber, - byte[] data, int offset, int length) - { - super(TFTPPacket.DATA, destination, port); + * @param datagram The datagram containing the received data. + * @throws TFTPPacketException If the datagram isn't a valid TFTP data packet. + */ + TFTPDataPacket(final DatagramPacket datagram) throws TFTPPacketException { + super(TFTPPacket.DATA, datagram.getAddress(), datagram.getPort()); + + this.data = datagram.getData(); + this.offset = 4; + + if (getType() != this.data[1]) { + throw new TFTPPacketException("TFTP operator code does not match type."); + } + + this.blockNumber = (((this.data[2] & 0xff) << 8) | (this.data[3] & 0xff)); - _blockNumber = blockNumber; - _data = data; - _offset = offset; + this.length = datagram.getLength() - 4; - if (length > MAX_DATA_LENGTH) { - _length = MAX_DATA_LENGTH; - } else { - _length = length; + if (this.length > MAX_DATA_LENGTH) { + this.length = MAX_DATA_LENGTH; } } - public TFTPDataPacket(InetAddress destination, int port, int blockNumber, - byte[] data) - { + public TFTPDataPacket(final InetAddress destination, final int port, final int blockNumber, final byte[] data) { this(destination, port, blockNumber, data, 0, data.length); } - - /*** - * Creates a data packet based from a received - * datagram. Assumes the datagram is at least length 4, else an - * ArrayIndexOutOfBoundsException may be thrown. + /** + * Creates a data packet to be sent to a host at a given port with a given block number. The actual data to be sent is passed as an array, an offset, and a + * length. The offset is the offset into the byte array where the data starts. The length is the length of the data. If the length is greater than + * MAX_DATA_LENGTH, it is truncated. * - * @param datagram The datagram containing the received data. - * @throws TFTPPacketException If the datagram isn't a valid TFTP - * data packet. - ***/ - TFTPDataPacket(DatagramPacket datagram) throws TFTPPacketException - { - super(TFTPPacket.DATA, datagram.getAddress(), datagram.getPort()); - - _data = datagram.getData(); - _offset = 4; - - if (getType() != _data[1]) { - throw new TFTPPacketException("TFTP operator code does not match type."); - } - - _blockNumber = (((_data[2] & 0xff) << 8) | (_data[3] & 0xff)); + * @param destination The host to which the packet is going to be sent. + * @param port The port to which the packet is going to be sent. + * @param blockNumber The block number of the data. + * @param data The byte array containing the data. + * @param offset The offset into the array where the data starts. + * @param length The length of the data. + */ + public TFTPDataPacket(final InetAddress destination, final int port, final int blockNumber, final byte[] data, final int offset, final int length) { + super(TFTPPacket.DATA, destination, port); - _length = datagram.getLength() - 4; + this.blockNumber = blockNumber; + this.data = data; + this.offset = offset; - if (_length > MAX_DATA_LENGTH) { - _length = MAX_DATA_LENGTH; - } + this.length = Math.min(length, MAX_DATA_LENGTH); } - /*** - * This is a method only available within the package for - * implementing efficient datagram transport by elminating buffering. - * It takes a datagram as an argument, and a byte buffer in which - * to store the raw datagram data. Inside the method, the data - * is set as the datagram's data and the datagram returned. + /** + * Returns the block number of the data packet. * - * @param datagram The datagram to create. - * @param data The buffer to store the packet and to use in the datagram. - * @return The datagram argument. - ***/ - @Override - DatagramPacket _newDatagram(DatagramPacket datagram, byte[] data) - { - data[0] = 0; - data[1] = (byte)_type; - data[2] = (byte)((_blockNumber & 0xffff) >> 8); - data[3] = (byte)(_blockNumber & 0xff); + * @return The block number of the data packet. + */ + public int getBlockNumber() { + return blockNumber; + } - // Doublecheck we're not the same - if (data != _data) { - System.arraycopy(_data, _offset, data, 4, _length); - } + /** + * Returns the byte array containing the packet data. + * + * @return The byte array containing the packet data. + */ + public byte[] getData() { + return data; + } - datagram.setAddress(_address); - datagram.setPort(_port); - datagram.setData(data); - datagram.setLength(_length + 4); + /** + * Returns the length of the data part of the data packet. + * + * @return The length of the data part of the data packet. + */ + public int getDataLength() { + return length; + } - return datagram; + /** + * Returns the offset into the byte array where the packet data actually starts. + * + * @return The offset into the byte array where the packet data actually starts. + */ + public int getDataOffset() { + return offset; } - /*** - * Creates a UDP datagram containing all the TFTP - * data packet data in the proper format. - * This is a method exposed to the programmer in case he - * wants to implement his own TFTP client instead of using - * the {@link org.apache.commons.net.tftp.TFTPClient} - * class. - * Under normal circumstances, you should not have a need to call this - * method. + /** + * Creates a UDP datagram containing all the TFTP data packet data in the proper format. This is a method exposed to the programmer in case he wants to + * implement his own TFTP client instead of using the {@link org.apache.commons.net.tftp.TFTPClient} class. Under normal circumstances, you should not have + * a need to call this method. * * @return A UDP datagram containing the TFTP data packet. - ***/ + */ @Override - public DatagramPacket newDatagram() - { - byte[] data; + public DatagramPacket newDatagram() { + final byte[] data; - data = new byte[_length + 4]; + data = new byte[length + 4]; data[0] = 0; - data[1] = (byte)_type; - data[2] = (byte)((_blockNumber & 0xffff) >> 8); - data[3] = (byte)(_blockNumber & 0xff); + data[1] = (byte) type; + data[2] = (byte) ((blockNumber & 0xffff) >> 8); + data[3] = (byte) (blockNumber & 0xff); - System.arraycopy(_data, _offset, data, 4, _length); + System.arraycopy(this.data, offset, data, 4, length); - return new DatagramPacket(data, _length + 4, _address, _port); + return new DatagramPacket(data, length + 4, address, port); } - /*** - * Returns the block number of the data packet. + /** + * This is a method only available within the package for implementing efficient datagram transport by elminating buffering. It takes a datagram as an + * argument, and a byte buffer in which to store the raw datagram data. Inside the method, the data is set as the datagram's data and the datagram returned. * - * @return The block number of the data packet. - ***/ - public int getBlockNumber() - { - return _blockNumber; + * @param datagram The datagram to create. + * @param data The buffer to store the packet and to use in the datagram. + * @return The datagram argument. + */ + @Override + DatagramPacket newDatagram(final DatagramPacket datagram, final byte[] data) { + data[0] = 0; + data[1] = (byte) type; + data[2] = (byte) ((blockNumber & 0xffff) >> 8); + data[3] = (byte) (blockNumber & 0xff); + + // Doublecheck we're not the same + if (data != this.data) { + System.arraycopy(this.data, offset, data, 4, length); + } + + datagram.setAddress(address); + datagram.setPort(port); + datagram.setData(data); + datagram.setLength(length + 4); + + return datagram; } - /*** Sets the block number of the data packet. + /** + * Sets the block number of the data packet. + * * @param blockNumber the number to set - ***/ - public void setBlockNumber(int blockNumber) - { - _blockNumber = blockNumber; + */ + public void setBlockNumber(final int blockNumber) { + this.blockNumber = blockNumber; } - /*** + /** * Sets the data for the data packet. * - * @param data The byte array containing the data. + * @param data The byte array containing the data. * @param offset The offset into the array where the data starts. * @param length The length of the data. - ***/ - public void setData(byte[] data, int offset, int length) - { - _data = data; - _offset = offset; - _length = length; - - if (length > MAX_DATA_LENGTH) { - _length = MAX_DATA_LENGTH; - } else { - _length = length; - } - } - - /*** - * Returns the length of the data part of the data packet. - * - * @return The length of the data part of the data packet. - ***/ - public int getDataLength() - { - return _length; - } - - /*** - * Returns the offset into the byte array where the packet data actually - * starts. - * - * @return The offset into the byte array where the packet data actually - * starts. - ***/ - public int getDataOffset() - { - return _offset; - } + */ + public void setData(final byte[] data, final int offset, final int length) { + this.data = data; + this.offset = offset; + this.length = length; - /*** - * Returns the byte array containing the packet data. - * - * @return The byte array containing the packet data. - ***/ - public byte[] getData() - { - return _data; + this.length = Math.min(length, MAX_DATA_LENGTH); } /** * For debugging + * * @since 3.6 */ @Override public String toString() { - return super.toString() + " DATA " + _blockNumber + " " + _length; + return super.toString() + " DATA " + blockNumber + " " + length; } } diff --git a/src/main/java/org/apache/commons/net/tftp/TFTPErrorPacket.java b/src/main/java/org/apache/commons/net/tftp/TFTPErrorPacket.java index 2924bd0..e77bec9 100644 --- a/src/main/java/org/apache/commons/net/tftp/TFTPErrorPacket.java +++ b/src/main/java/org/apache/commons/net/tftp/TFTPErrorPacket.java @@ -20,94 +20,65 @@ package org.apache.commons.net.tftp; import java.net.DatagramPacket; import java.net.InetAddress; -/*** - * A final class derived from TFTPPacket definiing the TFTP Error - * packet type. +/** + * A final class derived from TFTPPacket definiing the TFTP Error packet type. * <p> - * Details regarding the TFTP protocol and the format of TFTP packets can - * be found in RFC 783. But the point of these classes is to keep you - * from having to worry about the internals. Additionally, only very - * few people should have to care about any of the TFTPPacket classes - * or derived classes. Almost all users should only be concerned with the - * {@link org.apache.commons.net.tftp.TFTPClient} class - * {@link org.apache.commons.net.tftp.TFTPClient#receiveFile receiveFile()} - * and - * {@link org.apache.commons.net.tftp.TFTPClient#sendFile sendFile()} - * methods. + * Details regarding the TFTP protocol and the format of TFTP packets can be found in RFC 783. But the point of these classes is to keep you from having to + * worry about the internals. Additionally, only very few people should have to care about any of the TFTPPacket classes or derived classes. Almost all users + * should only be concerned with the {@link org.apache.commons.net.tftp.TFTPClient} class {@link org.apache.commons.net.tftp.TFTPClient#receiveFile + * receiveFile()} and {@link org.apache.commons.net.tftp.TFTPClient#sendFile sendFile()} methods. * * * @see TFTPPacket * @see TFTPPacketException * @see TFTP - ***/ + */ -public final class TFTPErrorPacket extends TFTPPacket -{ - /*** The undefined error code according to RFC 783, value 0. ***/ +public final class TFTPErrorPacket extends TFTPPacket { + /** The undefined error code according to RFC 783, value 0. */ public static final int UNDEFINED = 0; - /*** The file not found error code according to RFC 783, value 1. ***/ + /** The file not found error code according to RFC 783, value 1. */ public static final int FILE_NOT_FOUND = 1; - /*** The access violation error code according to RFC 783, value 2. ***/ + /** The access violation error code according to RFC 783, value 2. */ public static final int ACCESS_VIOLATION = 2; - /*** The disk full error code according to RFC 783, value 3. ***/ + /** The disk full error code according to RFC 783, value 3. */ public static final int OUT_OF_SPACE = 3; - /*** + /** * The illegal TFTP operation error code according to RFC 783, value 4. - ***/ + */ public static final int ILLEGAL_OPERATION = 4; - /*** The unknown transfer id error code according to RFC 783, value 5. ***/ + /** The unknown transfer id error code according to RFC 783, value 5. */ public static final int UNKNOWN_TID = 5; - /*** The file already exists error code according to RFC 783, value 6. ***/ + /** The file already exists error code according to RFC 783, value 6. */ public static final int FILE_EXISTS = 6; - /*** The no such user error code according to RFC 783, value 7. ***/ + /** The no such user error code according to RFC 783, value 7. */ public static final int NO_SUCH_USER = 7; - /*** The error code of this packet. ***/ - int _error; - - /*** The error message of this packet. ***/ - String _message; + /** The error code of this packet. */ + private final int error; - /*** - * Creates an error packet to be sent to a host at a given port - * with an error code and error message. - * - * @param destination The host to which the packet is going to be sent. - * @param port The port to which the packet is going to be sent. - * @param error The error code of the packet. - * @param message The error message of the packet. - ***/ - public TFTPErrorPacket(InetAddress destination, int port, - int error, String message) - { - super(TFTPPacket.ERROR, destination, port); - - _error = error; - _message = message; - } + /** The error message of this packet. */ + private final String message; - /*** - * Creates an error packet based from a received - * datagram. Assumes the datagram is at least length 4, else an - * ArrayIndexOutOfBoundsException may be thrown. + /** + * Creates an error packet based from a received datagram. Assumes the datagram is at least length 4, else an ArrayIndexOutOfBoundsException may be thrown. * - * @param datagram The datagram containing the received error. - * @throws TFTPPacketException If the datagram isn't a valid TFTP - * error packet. - ***/ - TFTPErrorPacket(DatagramPacket datagram) throws TFTPPacketException - { + * @param datagram The datagram containing the received error. + * @throws TFTPPacketException If the datagram isn't a valid TFTP error packet. + */ + TFTPErrorPacket(final DatagramPacket datagram) throws TFTPPacketException { super(TFTPPacket.ERROR, datagram.getAddress(), datagram.getPort()); - int index, length; - byte[] data; - StringBuilder buffer; + int index; + final int length; + final byte[] data; + final StringBuilder buffer; data = datagram.getData(); length = datagram.getLength(); @@ -116,7 +87,7 @@ public final class TFTPErrorPacket extends TFTPPacket throw new TFTPPacketException("TFTP operator code does not match type."); } - _error = (((data[2] & 0xff) << 8) | (data[3] & 0xff)); + error = (data[2] & 0xff) << 8 | data[3] & 0xff; if (length < 5) { throw new TFTPPacketException("Bad error packet. No message."); @@ -125,112 +96,112 @@ public final class TFTPErrorPacket extends TFTPPacket index = 4; buffer = new StringBuilder(); - while (index < length && data[index] != 0) - { - buffer.append((char)data[index]); + while (index < length && data[index] != 0) { + buffer.append((char) data[index]); ++index; } - _message = buffer.toString(); + message = buffer.toString(); } - /*** - * This is a method only available within the package for - * implementing efficient datagram transport by elminating buffering. - * It takes a datagram as an argument, and a byte buffer in which - * to store the raw datagram data. Inside the method, the data - * is set as the datagram's data and the datagram returned. + /** + * Creates an error packet to be sent to a host at a given port with an error code and error message. * - * @param datagram The datagram to create. - * @param data The buffer to store the packet and to use in the datagram. - * @return The datagram argument. - ***/ - @Override - DatagramPacket _newDatagram(DatagramPacket datagram, byte[] data) - { - int length; - - length = _message.length(); - - data[0] = 0; - data[1] = (byte)_type; - data[2] = (byte)((_error & 0xffff) >> 8); - data[3] = (byte)(_error & 0xff); - - System.arraycopy(_message.getBytes(), 0, data, 4, length); - - data[length + 4] = 0; + * @param destination The host to which the packet is going to be sent. + * @param port The port to which the packet is going to be sent. + * @param error The error code of the packet. + * @param message The error message of the packet. + */ + public TFTPErrorPacket(final InetAddress destination, final int port, final int error, final String message) { + super(TFTPPacket.ERROR, destination, port); - datagram.setAddress(_address); - datagram.setPort(_port); - datagram.setData(data); - datagram.setLength(length + 4); + this.error = error; + this.message = message; + } - return datagram; + /** + * Returns the error code of the packet. + * + * @return The error code of the packet. + */ + public int getError() { + return error; } + /** + * Returns the error message of the packet. + * + * @return The error message of the packet. + */ + public String getMessage() { + return message; + } - /*** - * Creates a UDP datagram containing all the TFTP - * error packet data in the proper format. - * This is a method exposed to the programmer in case he - * wants to implement his own TFTP client instead of using - * the {@link org.apache.commons.net.tftp.TFTPClient} - * class. - * Under normal circumstances, you should not have a need to call this - * method. + /** + * Creates a UDP datagram containing all the TFTP error packet data in the proper format. This is a method exposed to the programmer in case he wants to + * implement his own TFTP client instead of using the {@link org.apache.commons.net.tftp.TFTPClient} class. Under normal circumstances, you should not have + * a need to call this method. * * @return A UDP datagram containing the TFTP error packet. - ***/ + */ @Override - public DatagramPacket newDatagram() - { - byte[] data; - int length; + public DatagramPacket newDatagram() { + final byte[] data; + final int length; - length = _message.length(); + length = message.length(); data = new byte[length + 5]; data[0] = 0; - data[1] = (byte)_type; - data[2] = (byte)((_error & 0xffff) >> 8); - data[3] = (byte)(_error & 0xff); + data[1] = (byte) type; + data[2] = (byte) ((error & 0xffff) >> 8); + data[3] = (byte) (error & 0xff); - System.arraycopy(_message.getBytes(), 0, data, 4, length); + System.arraycopy(message.getBytes(), 0, data, 4, length); data[length + 4] = 0; - return new DatagramPacket(data, data.length, _address, _port); + return new DatagramPacket(data, data.length, address, port); } - - /*** - * Returns the error code of the packet. + /** + * This is a method only available within the package for implementing efficient datagram transport by elminating buffering. It takes a datagram as an + * argument, and a byte buffer in which to store the raw datagram data. Inside the method, the data is set as the datagram's data and the datagram returned. * - * @return The error code of the packet. - ***/ - public int getError() - { - return _error; - } + * @param datagram The datagram to create. + * @param data The buffer to store the packet and to use in the datagram. + * @return The datagram argument. + */ + @Override + DatagramPacket newDatagram(final DatagramPacket datagram, final byte[] data) { + final int length; + length = message.length(); - /*** - * Returns the error message of the packet. - * - * @return The error message of the packet. - ***/ - public String getMessage() - { - return _message; + data[0] = 0; + data[1] = (byte) type; + data[2] = (byte) ((error & 0xffff) >> 8); + data[3] = (byte) (error & 0xff); + + System.arraycopy(message.getBytes(), 0, data, 4, length); + + data[length + 4] = 0; + + datagram.setAddress(address); + datagram.setPort(port); + datagram.setData(data); + datagram.setLength(length + 4); + + return datagram; } /** * For debugging + * * @since 3.6 */ @Override public String toString() { - return super.toString() + " ERR " + _error + " " + _message; + return super.toString() + " ERR " + error + " " + message; } } diff --git a/src/main/java/org/apache/commons/net/tftp/TFTPPacket.java b/src/main/java/org/apache/commons/net/tftp/TFTPPacket.java index 4019455..293284e 100644 --- a/src/main/java/org/apache/commons/net/tftp/TFTPPacket.java +++ b/src/main/java/org/apache/commons/net/tftp/TFTPPacket.java @@ -20,122 +20,77 @@ package org.apache.commons.net.tftp; import java.net.DatagramPacket; import java.net.InetAddress; -/*** - * TFTPPacket is an abstract class encapsulating the functionality common - * to the 5 types of TFTP packets. It also provides a static factory - * method that will create the correct TFTP packet instance from a - * datagram. This relieves the programmer from having to figure out what - * kind of TFTP packet is contained in a datagram and create it himself. +/** + * TFTPPacket is an abstract class encapsulating the functionality common to the 5 types of TFTP packets. It also provides a static factory method that will + * create the correct TFTP packet instance from a datagram. This relieves the programmer from having to figure out what kind of TFTP packet is contained in a + * datagram and create it himself. * <p> - * Details regarding the TFTP protocol and the format of TFTP packets can - * be found in RFC 783. But the point of these classes is to keep you - * from having to worry about the internals. Additionally, only very - * few people should have to care about any of the TFTPPacket classes - * or derived classes. Almost all users should only be concerned with the - * {@link org.apache.commons.net.tftp.TFTPClient} class - * {@link org.apache.commons.net.tftp.TFTPClient#receiveFile receiveFile()} - * and - * {@link org.apache.commons.net.tftp.TFTPClient#sendFile sendFile()} - * methods. + * Details regarding the TFTP protocol and the format of TFTP packets can be found in RFC 783. But the point of these classes is to keep you from having to + * worry about the internals. Additionally, only very few people should have to care about any of the TFTPPacket classes or derived classes. Almost all users + * should only be concerned with the {@link org.apache.commons.net.tftp.TFTPClient} class {@link org.apache.commons.net.tftp.TFTPClient#receiveFile + * receiveFile()} and {@link org.apache.commons.net.tftp.TFTPClient#sendFile sendFile()} methods. * * * @see TFTPPacketException * @see TFTP - ***/ + */ -public abstract class TFTPPacket -{ - /*** - * The minimum size of a packet. This is 4 bytes. It is enough - * to store the opcode and blocknumber or other required data - * depending on the packet type. - ***/ +public abstract class TFTPPacket { + /** + * The minimum size of a packet. This is 4 bytes. It is enough to store the opcode and blocknumber or other required data depending on the packet type. + */ static final int MIN_PACKET_SIZE = 4; - /*** - * This is the actual TFTP spec - * identifier and is equal to 1. - * Identifier returned by {@link #getType getType()} - * indicating a read request packet. - ***/ + /** + * This is the actual TFTP spec identifier and is equal to 1. Identifier returned by {@link #getType getType()} indicating a read request packet. + */ public static final int READ_REQUEST = 1; - /*** - * This is the actual TFTP spec - * identifier and is equal to 2. - * Identifier returned by {@link #getType getType()} - * indicating a write request packet. - ***/ + /** + * This is the actual TFTP spec identifier and is equal to 2. Identifier returned by {@link #getType getType()} indicating a write request packet. + */ public static final int WRITE_REQUEST = 2; - /*** - * This is the actual TFTP spec - * identifier and is equal to 3. - * Identifier returned by {@link #getType getType()} - * indicating a data packet. - ***/ + /** + * This is the actual TFTP spec identifier and is equal to 3. Identifier returned by {@link #getType getType()} indicating a data packet. + */ public static final int DATA = 3; - /*** - * This is the actual TFTP spec - * identifier and is equal to 4. - * Identifier returned by {@link #getType getType()} - * indicating an acknowledgement packet. - ***/ + /** + * This is the actual TFTP spec identifier and is equal to 4. Identifier returned by {@link #getType getType()} indicating an acknowledgement packet. + */ public static final int ACKNOWLEDGEMENT = 4; - /*** - * This is the actual TFTP spec - * identifier and is equal to 5. - * Identifier returned by {@link #getType getType()} - * indicating an error packet. - ***/ + /** + * This is the actual TFTP spec identifier and is equal to 5. Identifier returned by {@link #getType getType()} indicating an error packet. + */ public static final int ERROR = 5; - /*** - * The TFTP data packet maximum segment size in bytes. This is 512 - * and is useful for those familiar with the TFTP protocol who want - * to use the {@link org.apache.commons.net.tftp.TFTP} - * class methods to implement their own TFTP servers or clients. - ***/ + /** + * The TFTP data packet maximum segment size in bytes. This is 512 and is useful for those familiar with the TFTP protocol who want to use the + * {@link org.apache.commons.net.tftp.TFTP} class methods to implement their own TFTP servers or clients. + */ public static final int SEGMENT_SIZE = 512; - /*** The type of packet. ***/ - int _type; - - /*** The port the packet came from or is going to. ***/ - int _port; - - /*** The host the packet is going to be sent or where it came from. ***/ - InetAddress _address; - - /*** - * When you receive a datagram that you expect to be a TFTP packet, you use - * this factory method to create the proper TFTPPacket object - * encapsulating the data contained in that datagram. This method is the - * only way you can instantiate a TFTPPacket derived class from a - * datagram. + /** + * When you receive a datagram that you expect to be a TFTP packet, you use this factory method to create the proper TFTPPacket object encapsulating the + * data contained in that datagram. This method is the only way you can instantiate a TFTPPacket derived class from a datagram. * - * @param datagram The datagram containing a TFTP packet. + * @param datagram The datagram containing a TFTP packet. * @return The TFTPPacket object corresponding to the datagram. - * @throws TFTPPacketException If the datagram does not contain a valid - * TFTP packet. - ***/ - public static final TFTPPacket newTFTPPacket(DatagramPacket datagram) - throws TFTPPacketException - { - byte[] data; + * @throws TFTPPacketException If the datagram does not contain a valid TFTP packet. + */ + public static final TFTPPacket newTFTPPacket(final DatagramPacket datagram) throws TFTPPacketException { + final byte[] data; TFTPPacket packet = null; if (datagram.getLength() < MIN_PACKET_SIZE) { - throw new TFTPPacketException( - "Bad packet. Datagram data length is too short."); + throw new TFTPPacketException("Bad packet. Datagram data length is too short."); } data = datagram.getData(); - switch (data[1]) - { + switch (data[1]) { case READ_REQUEST: packet = new TFTPReadRequestPacket(datagram); break; @@ -152,110 +107,106 @@ public abstract class TFTPPacket packet = new TFTPErrorPacket(datagram); break; default: - throw new TFTPPacketException( - "Bad packet. Invalid TFTP operator code."); + throw new TFTPPacketException("Bad packet. Invalid TFTP operator code."); } return packet; } - /*** - * This constructor is not visible outside of the package. It is used - * by subclasses within the package to initialize base data. + /** The type of packet. */ + int type; + + /** The port the packet came from or is going to. */ + int port; + + /** The host the packet is going to be sent or where it came from. */ + InetAddress address; + + /** + * This constructor is not visible outside of the package. It is used by subclasses within the package to initialize base data. * - * @param type The type of the packet. + * @param type The type of the packet. * @param address The host the packet came from or is going to be sent. - * @param port The port the packet came from or is going to be sent. + * @param port The port the packet came from or is going to be sent. **/ - TFTPPacket(int type, InetAddress address, int port) - { - _type = type; - _address = address; - _port = port; + TFTPPacket(final int type, final InetAddress address, final int port) { + this.type = type; + this.address = address; + this.port = port; } - /*** - * This is an abstract method only available within the package for - * implementing efficient datagram transport by elminating buffering. - * It takes a datagram as an argument, and a byte buffer in which - * to store the raw datagram data. Inside the method, the data - * should be set as the datagram's data and the datagram returned. + /** + * Returns the address of the host where the packet is going to be sent or where it came from. * - * @param datagram The datagram to create. - * @param data The buffer to store the packet and to use in the datagram. - * @return The datagram argument. - ***/ - abstract DatagramPacket _newDatagram(DatagramPacket datagram, byte[] data); + * @return The type of the packet. + */ + public final InetAddress getAddress() { + return address; + } - /*** - * Creates a UDP datagram containing all the TFTP packet - * data in the proper format. - * This is an abstract method, exposed to the programmer in case he - * wants to implement his own TFTP client instead of using - * the {@link org.apache.commons.net.tftp.TFTPClient} - * class. - * Under normal circumstances, you should not have a need to call this - * method. + /** + * Returns the port where the packet is going to be sent or where it came from. * - * @return A UDP datagram containing the TFTP packet. - ***/ - public abstract DatagramPacket newDatagram(); + * @return The port where the packet came from or where it is going. + */ + public final int getPort() { + return port; + } - /*** + /** * Returns the type of the packet. * * @return The type of the packet. - ***/ - public final int getType() - { - return _type; + */ + public final int getType() { + return type; } - /*** - * Returns the address of the host where the packet is going to be sent - * or where it came from. + /** + * Creates a UDP datagram containing all the TFTP packet data in the proper format. This is an abstract method, exposed to the programmer in case he wants + * to implement his own TFTP client instead of using the {@link org.apache.commons.net.tftp.TFTPClient} class. Under normal circumstances, you should not + * have a need to call this method. * - * @return The type of the packet. - ***/ - public final InetAddress getAddress() - { - return _address; - } + * @return A UDP datagram containing the TFTP packet. + */ + public abstract DatagramPacket newDatagram(); - /*** - * Returns the port where the packet is going to be sent - * or where it came from. + /** + * This is an abstract method only available within the package for implementing efficient datagram transport by elminating buffering. It takes a datagram + * as an argument, and a byte buffer in which to store the raw datagram data. Inside the method, the data should be set as the datagram's data and the + * datagram returned. * - * @return The port where the packet came from or where it is going. - ***/ - public final int getPort() - { - return _port; + * @param datagram The datagram to create. + * @param data The buffer to store the packet and to use in the datagram. + * @return The datagram argument. + */ + abstract DatagramPacket newDatagram(DatagramPacket datagram, byte[] data); + + /** + * Sets the host address where the packet is going to be sent. + * + * @param address the address to set + */ + public final void setAddress(final InetAddress address) { + this.address = address; } - /*** + /** * Sets the port where the packet is going to be sent. + * * @param port the port to set - ***/ - public final void setPort(int port) - { - _port = port; - } - - /*** Sets the host address where the packet is going to be sent. - * @param address the address to set - ***/ - public final void setAddress(InetAddress address) - { - _address = address; + */ + public final void setPort(final int port) { + this.port = port; } /** * For debugging + * * @since 3.6 */ @Override public String toString() { - return _address + " " + _port + " " + _type; + return address + " " + port + " " + type; } } diff --git a/src/main/java/org/apache/commons/net/tftp/TFTPPacketException.java b/src/main/java/org/apache/commons/net/tftp/TFTPPacketException.java index b44500b..16f60c1 100644 --- a/src/main/java/org/apache/commons/net/tftp/TFTPPacketException.java +++ b/src/main/java/org/apache/commons/net/tftp/TFTPPacketException.java @@ -17,43 +17,33 @@ package org.apache.commons.net.tftp; -/*** - * A class used to signify the occurrence of an error in the creation of - * a TFTP packet. It is not declared final so that it may be subclassed - * to identify more specific errors. You would only want to do this if - * you were building your own TFTP client or server on top of the - * {@link org.apache.commons.net.tftp.TFTP} - * class if you - * wanted more functionality than the - * {@link org.apache.commons.net.tftp.TFTPClient#receiveFile receiveFile()} - * and - * {@link org.apache.commons.net.tftp.TFTPClient#sendFile sendFile()} - * methods provide. +/** + * A class used to signify the occurrence of an error in the creation of a TFTP packet. It is not declared final so that it may be subclassed to identify more + * specific errors. You would only want to do this if you were building your own TFTP client or server on top of the {@link org.apache.commons.net.tftp.TFTP} + * class if you wanted more functionality than the {@link org.apache.commons.net.tftp.TFTPClient#receiveFile receiveFile()} and + * {@link org.apache.commons.net.tftp.TFTPClient#sendFile sendFile()} methods provide. * * * @see TFTPPacket * @see TFTP - ***/ + */ -public class TFTPPacketException extends Exception -{ +public class TFTPPacketException extends Exception { private static final long serialVersionUID = -8114699256840851439L; - /*** + /** * Simply calls the corresponding constructor of its superclass. - ***/ - public TFTPPacketException() - { - super(); + */ + public TFTPPacketException() { } - /*** + /** * Simply calls the corresponding constructor of its superclass. + * * @param message the message - ***/ - public TFTPPacketException(String message) - { + */ + public TFTPPacketException(final String message) { super(message); } } diff --git a/src/main/java/org/apache/commons/net/tftp/TFTPReadRequestPacket.java b/src/main/java/org/apache/commons/net/tftp/TFTPReadRequestPacket.java index fbb84d3..c26ca10 100644 --- a/src/main/java/org/apache/commons/net/tftp/TFTPReadRequestPacket.java +++ b/src/main/java/org/apache/commons/net/tftp/TFTPReadRequestPacket.java @@ -20,64 +20,49 @@ package org.apache.commons.net.tftp; import java.net.DatagramPacket; import java.net.InetAddress; -/*** - * A class derived from TFTPRequestPacket definiing a TFTP read request - * packet type. +/** + * A class derived from TFTPRequestPacket definiing a TFTP read request packet type. * <p> - * Details regarding the TFTP protocol and the format of TFTP packets can - * be found in RFC 783. But the point of these classes is to keep you - * from having to worry about the internals. Additionally, only very - * few people should have to care about any of the TFTPPacket classes - * or derived classes. Almost all users should only be concerned with the - * {@link org.apache.commons.net.tftp.TFTPClient} class - * {@link org.apache.commons.net.tftp.TFTPClient#receiveFile receiveFile()} - * and - * {@link org.apache.commons.net.tftp.TFTPClient#sendFile sendFile()} - * methods. + * Details regarding the TFTP protocol and the format of TFTP packets can be found in RFC 783. But the point of these classes is to keep you from having to + * worry about the internals. Additionally, only very few people should have to care about any of the TFTPPacket classes or derived classes. Almost all users + * should only be concerned with the {@link org.apache.commons.net.tftp.TFTPClient} class {@link org.apache.commons.net.tftp.TFTPClient#receiveFile + * receiveFile()} and {@link org.apache.commons.net.tftp.TFTPClient#sendFile sendFile()} methods. * * * @see TFTPPacket * @see TFTPRequestPacket * @see TFTPPacketException * @see TFTP - ***/ + */ -public final class TFTPReadRequestPacket extends TFTPRequestPacket -{ +public final class TFTPReadRequestPacket extends TFTPRequestPacket { - /*** - * Creates a read request packet to be sent to a host at a - * given port with a filename and transfer mode request. + /** + * Creates a read request packet of based on a received datagram and assumes the datagram has already been identified as a read request. Assumes the + * datagram is at least length 4, else an ArrayIndexOutOfBoundsException may be thrown. * - * @param destination The host to which the packet is going to be sent. - * @param port The port to which the packet is going to be sent. - * @param filename The requested filename. - * @param mode The requested transfer mode. This should be on of the TFTP - * class MODE constants (e.g., TFTP.NETASCII_MODE). - ***/ - public TFTPReadRequestPacket(InetAddress destination, int port, - String filename, int mode) - { - super(destination, port, TFTPPacket.READ_REQUEST, filename, mode); + * @param datagram The datagram containing the received request. + * @throws TFTPPacketException If the datagram isn't a valid TFTP request packet. + */ + TFTPReadRequestPacket(final DatagramPacket datagram) throws TFTPPacketException { + super(TFTPPacket.READ_REQUEST, datagram); } - /*** - * Creates a read request packet of based on a received - * datagram and assumes the datagram has already been identified as a - * read request. Assumes the datagram is at least length 4, else an - * ArrayIndexOutOfBoundsException may be thrown. + /** + * Creates a read request packet to be sent to a host at a given port with a file name and transfer mode request. * - * @param datagram The datagram containing the received request. - * @throws TFTPPacketException If the datagram isn't a valid TFTP - * request packet. - ***/ - TFTPReadRequestPacket(DatagramPacket datagram) throws TFTPPacketException - { - super(TFTPPacket.READ_REQUEST, datagram); + * @param destination The host to which the packet is going to be sent. + * @param port The port to which the packet is going to be sent. + * @param fileName The requested file name. + * @param mode The requested transfer mode. This should be on of the TFTP class MODE constants (e.g., TFTP.NETASCII_MODE). + */ + public TFTPReadRequestPacket(final InetAddress destination, final int port, final String fileName, final int mode) { + super(destination, port, TFTPPacket.READ_REQUEST, fileName, mode); } /** * For debugging + * * @since 3.6 */ @Override diff --git a/src/main/java/org/apache/commons/net/tftp/TFTPRequestPacket.java b/src/main/java/org/apache/commons/net/tftp/TFTPRequestPacket.java index 126b5e0..fc97c31 100644 --- a/src/main/java/org/apache/commons/net/tftp/TFTPRequestPacket.java +++ b/src/main/java/org/apache/commons/net/tftp/TFTPRequestPacket.java @@ -20,24 +20,14 @@ package org.apache.commons.net.tftp; import java.net.DatagramPacket; import java.net.InetAddress; -/*** - * An abstract class derived from TFTPPacket definiing a TFTP Request - * packet type. It is subclassed by the - * {@link org.apache.commons.net.tftp.TFTPReadRequestPacket} - * and - * {@link org.apache.commons.net.tftp.TFTPWriteRequestPacket} - * classes. +/** + * An abstract class derived from TFTPPacket definiing a TFTP Request packet type. It is subclassed by the + * {@link org.apache.commons.net.tftp.TFTPReadRequestPacket} and {@link org.apache.commons.net.tftp.TFTPWriteRequestPacket} classes. * <p> - * Details regarding the TFTP protocol and the format of TFTP packets can - * be found in RFC 783. But the point of these classes is to keep you - * from having to worry about the internals. Additionally, only very - * few people should have to care about any of the TFTPPacket classes - * or derived classes. Almost all users should only be concerned with the - * {@link org.apache.commons.net.tftp.TFTPClient} class - * {@link org.apache.commons.net.tftp.TFTPClient#receiveFile receiveFile()} - * and - * {@link org.apache.commons.net.tftp.TFTPClient#sendFile sendFile()} - * methods. + * Details regarding the TFTP protocol and the format of TFTP packets can be found in RFC 783. But the point of these classes is to keep you from having to + * worry about the internals. Additionally, only very few people should have to care about any of the TFTPPacket classes or derived classes. Almost all users + * should only be concerned with the {@link org.apache.commons.net.tftp.TFTPClient} class {@link org.apache.commons.net.tftp.TFTPClient#receiveFile + * receiveFile()} and {@link org.apache.commons.net.tftp.TFTPClient#sendFile sendFile()} methods. * * * @see TFTPPacket @@ -45,208 +35,174 @@ import java.net.InetAddress; * @see TFTPWriteRequestPacket * @see TFTPPacketException * @see TFTP - ***/ - -public abstract class TFTPRequestPacket extends TFTPPacket -{ - /*** - * An array containing the string names of the transfer modes and indexed - * by the transfer mode constants. - ***/ - static final String[] _modeStrings = { "netascii", "octet" }; - - /*** - * A null terminated byte array representation of the ascii names of the - * transfer mode constants. This is convenient for creating the TFTP - * request packets. - ***/ - private static final byte[] _modeBytes[] = { - { (byte)'n', (byte)'e', (byte)'t', (byte)'a', (byte)'s', (byte)'c', - (byte)'i', (byte)'i', 0 }, - { (byte)'o', (byte)'c', (byte)'t', (byte)'e', (byte)'t', 0 } - }; - - /*** The transfer mode of the request. ***/ - private final int _mode; - - /*** The filename of the request. ***/ - private final String _filename; - - /*** - * Creates a request packet of a given type to be sent to a host at a - * given port with a filename and transfer mode request. + */ + +public abstract class TFTPRequestPacket extends TFTPPacket { + /** + * An array containing the string names of the transfer modes and indexed by the transfer mode constants. + */ + static final String[] modeStrings = { "netascii", "octet" }; + + /** + * A null terminated byte array representation of the ascii names of the transfer mode constants. This is convenient for creating the TFTP request packets. + */ + private static final byte[] modeBytes[] = { { (byte) 'n', (byte) 'e', (byte) 't', (byte) 'a', (byte) 's', (byte) 'c', (byte) 'i', (byte) 'i', 0 }, + { (byte) 'o', (byte) 'c', (byte) 't', (byte) 'e', (byte) 't', 0 } }; + + /** The transfer mode of the request. */ + private final int mode; + + /** The file name of the request. */ + private final String fileName; + + /** + * Creates a request packet of a given type to be sent to a host at a given port with a file name and transfer mode request. * - * @param destination The host to which the packet is going to be sent. - * @param port The port to which the packet is going to be sent. - * @param type The type of the request (either TFTPPacket.READ_REQUEST or - * TFTPPacket.WRITE_REQUEST). - * @param filename The requested filename. - * @param mode The requested transfer mode. This should be on of the TFTP - * class MODE constants (e.g., TFTP.NETASCII_MODE). - ***/ - TFTPRequestPacket(InetAddress destination, int port, - int type, String filename, int mode) - { + * @param destination The host to which the packet is going to be sent. + * @param port The port to which the packet is going to be sent. + * @param type The type of the request (either TFTPPacket.READ_REQUEST or TFTPPacket.WRITE_REQUEST). + * @param fileName The requested file name. + * @param mode The requested transfer mode. This should be on of the TFTP class MODE constants (e.g., TFTP.NETASCII_MODE). + */ + TFTPRequestPacket(final InetAddress destination, final int port, final int type, final String fileName, final int mode) { super(type, destination, port); - _filename = filename; - _mode = mode; + this.fileName = fileName; + this.mode = mode; } - /*** - * Creates a request packet of a given type based on a received - * datagram. Assumes the datagram is at least length 4, else an - * ArrayIndexOutOfBoundsException may be thrown. + /** + * Creates a request packet of a given type based on a received datagram. Assumes the datagram is at least length 4, else an ArrayIndexOutOfBoundsException + * may be thrown. * - * @param type The type of the request (either TFTPPacket.READ_REQUEST or - * TFTPPacket.WRITE_REQUEST). - * @param datagram The datagram containing the received request. - * @throws TFTPPacketException If the datagram isn't a valid TFTP - * request packet of the appropriate type. - ***/ - TFTPRequestPacket(int type, DatagramPacket datagram) - throws TFTPPacketException - { + * @param type The type of the request (either TFTPPacket.READ_REQUEST or TFTPPacket.WRITE_REQUEST). + * @param datagram The datagram containing the received request. + * @throws TFTPPacketException If the datagram isn't a valid TFTP request packet of the appropriate type. + */ + TFTPRequestPacket(final int type, final DatagramPacket datagram) throws TFTPPacketException { super(type, datagram.getAddress(), datagram.getPort()); - byte[] data = datagram.getData(); + final byte[] data = datagram.getData(); if (getType() != data[1]) { throw new TFTPPacketException("TFTP operator code does not match type."); } - StringBuilder buffer = new StringBuilder(); + final StringBuilder buffer = new StringBuilder(); int index = 2; int length = datagram.getLength(); - while (index < length && data[index] != 0) - { - buffer.append((char)data[index]); + while (index < length && data[index] != 0) { + buffer.append((char) data[index]); ++index; } - _filename = buffer.toString(); + this.fileName = buffer.toString(); if (index >= length) { - throw new TFTPPacketException("Bad filename and mode format."); + throw new TFTPPacketException("Bad file name and mode format."); } buffer.setLength(0); ++index; // need to advance beyond the end of string marker - while (index < length && data[index] != 0) - { - buffer.append((char)data[index]); + while (index < length && data[index] != 0) { + buffer.append((char) data[index]); ++index; } - String modeString = buffer.toString().toLowerCase(java.util.Locale.ENGLISH); - length = _modeStrings.length; + final String modeString = buffer.toString().toLowerCase(java.util.Locale.ENGLISH); + length = modeStrings.length; int mode = 0; - for (index = 0; index < length; index++) - { - if (modeString.equals(_modeStrings[index])) - { + for (index = 0; index < length; index++) { + if (modeString.equals(modeStrings[index])) { mode = index; break; } } - _mode = mode; + this.mode = mode; - if (index >= length) - { + if (index >= length) { throw new TFTPPacketException("Unrecognized TFTP transfer mode: " + modeString); // May just want to default to binary mode instead of throwing // exception. - //_mode = TFTP.OCTET_MODE; + // _mode = TFTP.OCTET_MODE; } } + /** + * Returns the requested file name. + * + * @return The requested file name. + */ + public final String getFilename() { + return fileName; + } - /*** - * This is a method only available within the package for - * implementing efficient datagram transport by elminating buffering. - * It takes a datagram as an argument, and a byte buffer in which - * to store the raw datagram data. Inside the method, the data - * is set as the datagram's data and the datagram returned. + /** + * Returns the transfer mode of the request. * - * @param datagram The datagram to create. - * @param data The buffer to store the packet and to use in the datagram. - * @return The datagram argument. - ***/ + * @return The transfer mode of the request. + */ + public final int getMode() { + return mode; + } + + /** + * Creates a UDP datagram containing all the TFTP request packet data in the proper format. This is a method exposed to the programmer in case he wants to + * implement his own TFTP client instead of using the {@link org.apache.commons.net.tftp.TFTPClient} class. Under normal circumstances, you should not have + * a need to call this method. + * + * @return A UDP datagram containing the TFTP request packet. + */ @Override - final DatagramPacket _newDatagram(DatagramPacket datagram, byte[] data) - { - int fileLength, modeLength; + public final DatagramPacket newDatagram() { + final int fileLength; + final int modeLength; + final byte[] data; - fileLength = _filename.length(); - modeLength = _modeBytes[_mode].length; + fileLength = fileName.length(); + modeLength = modeBytes[mode].length; + data = new byte[fileLength + modeLength + 4]; data[0] = 0; - data[1] = (byte)_type; - System.arraycopy(_filename.getBytes(), 0, data, 2, fileLength); + data[1] = (byte) type; + System.arraycopy(fileName.getBytes(), 0, data, 2, fileLength); data[fileLength + 2] = 0; - System.arraycopy(_modeBytes[_mode], 0, data, fileLength + 3, - modeLength); + System.arraycopy(modeBytes[mode], 0, data, fileLength + 3, modeLength); - datagram.setAddress(_address); - datagram.setPort(_port); - datagram.setData(data); - datagram.setLength(fileLength + modeLength + 3); - - return datagram; + return new DatagramPacket(data, data.length, address, port); } - /*** - * Creates a UDP datagram containing all the TFTP - * request packet data in the proper format. - * This is a method exposed to the programmer in case he - * wants to implement his own TFTP client instead of using - * the {@link org.apache.commons.net.tftp.TFTPClient} - * class. Under normal circumstances, you should not have a need to call - * this method. + /** + * This is a method only available within the package for implementing efficient datagram transport by elminating buffering. It takes a datagram as an + * argument, and a byte buffer in which to store the raw datagram data. Inside the method, the data is set as the datagram's data and the datagram returned. * - * @return A UDP datagram containing the TFTP request packet. - ***/ + * @param datagram The datagram to create. + * @param data The buffer to store the packet and to use in the datagram. + * @return The datagram argument. + */ @Override - public final DatagramPacket newDatagram() - { - int fileLength, modeLength; - byte[] data; + final DatagramPacket newDatagram(final DatagramPacket datagram, final byte[] data) { + final int fileLength; + final int modeLength; - fileLength = _filename.length(); - modeLength = _modeBytes[_mode].length; + fileLength = fileName.length(); + modeLength = modeBytes[mode].length; - data = new byte[fileLength + modeLength + 4]; data[0] = 0; - data[1] = (byte)_type; - System.arraycopy(_filename.getBytes(), 0, data, 2, fileLength); + data[1] = (byte) type; + System.arraycopy(fileName.getBytes(), 0, data, 2, fileLength); data[fileLength + 2] = 0; - System.arraycopy(_modeBytes[_mode], 0, data, fileLength + 3, - modeLength); - - return new DatagramPacket(data, data.length, _address, _port); - } + System.arraycopy(modeBytes[mode], 0, data, fileLength + 3, modeLength); - /*** - * Returns the transfer mode of the request. - * - * @return The transfer mode of the request. - ***/ - public final int getMode() - { - return _mode; - } + datagram.setAddress(address); + datagram.setPort(port); + datagram.setData(data); + datagram.setLength(fileLength + modeLength + 3); - /*** - * Returns the requested filename. - * - * @return The requested filename. - ***/ - public final String getFilename() - { - return _filename; + return datagram; } } diff --git a/src/main/java/org/apache/commons/net/tftp/TFTPWriteRequestPacket.java b/src/main/java/org/apache/commons/net/tftp/TFTPWriteRequestPacket.java index 7286454..a9e2fa1 100644 --- a/src/main/java/org/apache/commons/net/tftp/TFTPWriteRequestPacket.java +++ b/src/main/java/org/apache/commons/net/tftp/TFTPWriteRequestPacket.java @@ -20,64 +20,49 @@ package org.apache.commons.net.tftp; import java.net.DatagramPacket; import java.net.InetAddress; -/*** - * A class derived from TFTPRequestPacket definiing a TFTP write request - * packet type. +/** + * A class derived from TFTPRequestPacket definiing a TFTP write request packet type. * <p> - * Details regarding the TFTP protocol and the format of TFTP packets can - * be found in RFC 783. But the point of these classes is to keep you - * from having to worry about the internals. Additionally, only very - * few people should have to care about any of the TFTPPacket classes - * or derived classes. Almost all users should only be concerned with the - * {@link org.apache.commons.net.tftp.TFTPClient} class - * {@link org.apache.commons.net.tftp.TFTPClient#receiveFile receiveFile()} - * and - * {@link org.apache.commons.net.tftp.TFTPClient#sendFile sendFile()} - * methods. + * Details regarding the TFTP protocol and the format of TFTP packets can be found in RFC 783. But the point of these classes is to keep you from having to + * worry about the internals. Additionally, only very few people should have to care about any of the TFTPPacket classes or derived classes. Almost all users + * should only be concerned with the {@link org.apache.commons.net.tftp.TFTPClient} class {@link org.apache.commons.net.tftp.TFTPClient#receiveFile + * receiveFile()} and {@link org.apache.commons.net.tftp.TFTPClient#sendFile sendFile()} methods. * * * @see TFTPPacket * @see TFTPRequestPacket * @see TFTPPacketException * @see TFTP - ***/ + */ -public final class TFTPWriteRequestPacket extends TFTPRequestPacket -{ +public final class TFTPWriteRequestPacket extends TFTPRequestPacket { - /*** - * Creates a write request packet to be sent to a host at a - * given port with a filename and transfer mode request. + /** + * Creates a write request packet of based on a received datagram and assumes the datagram has already been identified as a write request. Assumes the + * datagram is at least length 4, else an ArrayIndexOutOfBoundsException may be thrown. * - * @param destination The host to which the packet is going to be sent. - * @param port The port to which the packet is going to be sent. - * @param filename The requested filename. - * @param mode The requested transfer mode. This should be on of the TFTP - * class MODE constants (e.g., TFTP.NETASCII_MODE). - ***/ - public TFTPWriteRequestPacket(InetAddress destination, int port, - String filename, int mode) - { - super(destination, port, TFTPPacket.WRITE_REQUEST, filename, mode); + * @param datagram The datagram containing the received request. + * @throws TFTPPacketException If the datagram isn't a valid TFTP request packet. + */ + TFTPWriteRequestPacket(final DatagramPacket datagram) throws TFTPPacketException { + super(TFTPPacket.WRITE_REQUEST, datagram); } - /*** - * Creates a write request packet of based on a received - * datagram and assumes the datagram has already been identified as a - * write request. Assumes the datagram is at least length 4, else an - * ArrayIndexOutOfBoundsException may be thrown. + /** + * Creates a write request packet to be sent to a host at a given port with a file name and transfer mode request. * - * @param datagram The datagram containing the received request. - * @throws TFTPPacketException If the datagram isn't a valid TFTP - * request packet. - ***/ - TFTPWriteRequestPacket(DatagramPacket datagram) throws TFTPPacketException - { - super(TFTPPacket.WRITE_REQUEST, datagram); + * @param destination The host to which the packet is going to be sent. + * @param port The port to which the packet is going to be sent. + * @param fileName The requested file name. + * @param mode The requested transfer mode. This should be on of the TFTP class MODE constants (e.g., TFTP.NETASCII_MODE). + */ + public TFTPWriteRequestPacket(final InetAddress destination, final int port, final String fileName, final int mode) { + super(destination, port, TFTPPacket.WRITE_REQUEST, fileName, mode); } /** * For debugging + * * @since 3.6 */ @Override diff --git a/src/main/java/org/apache/commons/net/time/TimeTCPClient.java b/src/main/java/org/apache/commons/net/time/TimeTCPClient.java index 5f82c2a..250e527 100644 --- a/src/main/java/org/apache/commons/net/time/TimeTCPClient.java +++ b/src/main/java/org/apache/commons/net/time/TimeTCPClient.java @@ -23,86 +23,60 @@ import java.util.Date; import org.apache.commons.net.SocketClient; -/*** - * The TimeTCPClient class is a TCP implementation of a client for the - * Time protocol described in RFC 868. To use the class, merely - * establish a connection with - * {@link org.apache.commons.net.SocketClient#connect connect } - * and call either {@link #getTime getTime() } or - * {@link #getDate getDate() } to retrieve the time, then - * call {@link org.apache.commons.net.SocketClient#disconnect disconnect } - * to close the connection properly. +/** + * The TimeTCPClient class is a TCP implementation of a client for the Time protocol described in RFC 868. To use the class, merely establish a connection with + * {@link org.apache.commons.net.SocketClient#connect connect } and call either {@link #getTime getTime() } or {@link #getDate getDate() } to retrieve the time, + * then call {@link org.apache.commons.net.SocketClient#disconnect disconnect } to close the connection properly. * * * @see TimeUDPClient - ***/ + */ -public final class TimeTCPClient extends SocketClient -{ - /*** The default time port. It is set to 37 according to RFC 868. ***/ +public final class TimeTCPClient extends SocketClient { + /** The default time port. It is set to 37 according to RFC 868. */ public static final int DEFAULT_PORT = 37; - /*** - * The number of seconds between 00:00 1 January 1900 and - * 00:00 1 January 1970. This value can be useful for converting - * time values to other formats. - ***/ + /** + * The number of seconds between 00:00 1 January 1900 and 00:00 1 January 1970. This value can be useful for converting time values to other formats. + */ public static final long SECONDS_1900_TO_1970 = 2208988800L; - /*** - * The default TimeTCPClient constructor. It merely sets the default - * port to <code> DEFAULT_PORT </code>. - ***/ - public TimeTCPClient () - { + /** + * The default TimeTCPClient constructor. It merely sets the default port to <code> DEFAULT_PORT </code>. + */ + public TimeTCPClient() { setDefaultPort(DEFAULT_PORT); } - /*** - * Retrieves the time from the server and returns it. The time - * is the number of seconds since 00:00 (midnight) 1 January 1900 GMT, - * as specified by RFC 868. This method reads the raw 32-bit big-endian - * unsigned integer from the server, converts it to a Java long, and - * returns the value. + /** + * Retrieves the time from the server and returns a Java Date containing the time converted to the local time zone. * <p> - * The server will have closed the connection at this point, so you should - * call - * {@link org.apache.commons.net.SocketClient#disconnect disconnect } - * after calling this method. To retrieve another time, you must - * initiate another connection with - * {@link org.apache.commons.net.SocketClient#connect connect } - * before calling <code> getTime() </code> again. + * The server will have closed the connection at this point, so you should call {@link org.apache.commons.net.SocketClient#disconnect disconnect } after + * calling this method. To retrieve another time, you must initiate another connection with {@link org.apache.commons.net.SocketClient#connect connect } + * before calling <code> getDate() </code> again. * - * @return The time value retrieved from the server. - * @throws IOException If an error occurs while fetching the time. - ***/ - public long getTime() throws IOException - { - DataInputStream input; - input = new DataInputStream(_input_); - return (input.readInt() & 0xffffffffL); + * @return A Date value containing the time retrieved from the server converted to the local time zone. + * @throws IOException If an error occurs while fetching the time. + */ + public Date getDate() throws IOException { + return new Date((getTime() - SECONDS_1900_TO_1970) * 1000L); } - /*** - * Retrieves the time from the server and returns a Java Date - * containing the time converted to the local timezone. + /** + * Retrieves the time from the server and returns it. The time is the number of seconds since 00:00 (midnight) 1 January 1900 GMT, as specified by RFC 868. + * This method reads the raw 32-bit big-endian unsigned integer from the server, converts it to a Java long, and returns the value. * <p> - * The server will have closed the connection at this point, so you should - * call - * {@link org.apache.commons.net.SocketClient#disconnect disconnect } - * after calling this method. To retrieve another time, you must - * initiate another connection with - * {@link org.apache.commons.net.SocketClient#connect connect } - * before calling <code> getDate() </code> again. + * The server will have closed the connection at this point, so you should call {@link org.apache.commons.net.SocketClient#disconnect disconnect } after + * calling this method. To retrieve another time, you must initiate another connection with {@link org.apache.commons.net.SocketClient#connect connect } + * before calling <code> getTime() </code> again. * - * @return A Date value containing the time retrieved from the server - * converted to the local timezone. - * @throws IOException If an error occurs while fetching the time. - ***/ - public Date getDate() throws IOException - { - return new Date((getTime() - SECONDS_1900_TO_1970)*1000L); + * @return The time value retrieved from the server. + * @throws IOException If an error occurs while fetching the time. + */ + public long getTime() throws IOException { + final DataInputStream input; + input = new DataInputStream(_input_); + return input.readInt() & 0xffffffffL; } } - diff --git a/src/main/java/org/apache/commons/net/time/TimeUDPClient.java b/src/main/java/org/apache/commons/net/time/TimeUDPClient.java index 57e4fa1..fd1b416 100644 --- a/src/main/java/org/apache/commons/net/time/TimeUDPClient.java +++ b/src/main/java/org/apache/commons/net/time/TimeUDPClient.java @@ -24,111 +24,90 @@ import java.util.Date; import org.apache.commons.net.DatagramSocketClient; -/*** - * The TimeUDPClient class is a UDP implementation of a client for the - * Time protocol described in RFC 868. To use the class, merely - * open a local datagram socket with - * {@link org.apache.commons.net.DatagramSocketClient#open open } - * and call {@link #getTime getTime } or - * {@link #getTime getDate } to retrieve the time. Then call - * {@link org.apache.commons.net.DatagramSocketClient#close close } - * to close the connection properly. Unlike - * {@link org.apache.commons.net.time.TimeTCPClient}, - * successive calls to {@link #getTime getTime } or - * {@link #getDate getDate } are permitted - * without re-establishing a connection. That is because UDP is a - * connectionless protocol and the Time protocol is stateless. +/** + * The TimeUDPClient class is a UDP implementation of a client for the Time protocol described in RFC 868. To use the class, merely open a local datagram socket + * with {@link org.apache.commons.net.DatagramSocketClient#open open } and call {@link #getTime getTime } or {@link #getTime getDate } to retrieve the time. + * Then call {@link org.apache.commons.net.DatagramSocketClient#close close } to close the connection properly. Unlike + * {@link org.apache.commons.net.time.TimeTCPClient}, successive calls to {@link #getTime getTime } or {@link #getDate getDate } are permitted without + * re-establishing a connection. That is because UDP is a connectionless protocol and the Time protocol is stateless. * * * @see TimeTCPClient - ***/ + */ -public final class TimeUDPClient extends DatagramSocketClient -{ - /*** The default time port. It is set to 37 according to RFC 868. ***/ +public final class TimeUDPClient extends DatagramSocketClient { + /** The default time port. It is set to 37 according to RFC 868. */ public static final int DEFAULT_PORT = 37; - /*** - * The number of seconds between 00:00 1 January 1900 and - * 00:00 1 January 1970. This value can be useful for converting - * time values to other formats. - ***/ + /** + * The number of seconds between 00:00 1 January 1900 and 00:00 1 January 1970. This value can be useful for converting time values to other formats. + */ public static final long SECONDS_1900_TO_1970 = 2208988800L; - private final byte[] __dummyData = new byte[1]; - private final byte[] __timeData = new byte[4]; + private final byte[] dummyData = new byte[1]; + private final byte[] timeData = new byte[4]; - /*** - * Retrieves the time from the specified server and port and - * returns it. The time is the number of seconds since - * 00:00 (midnight) 1 January 1900 GMT, as specified by RFC 868. - * This method reads the raw 32-bit big-endian - * unsigned integer from the server, converts it to a Java long, and - * returns the value. + /** + * Same as <code> getTime(host, DEFAULT_PORT); </code> + * + * @param host the time server + * @return the date + * @throws IOException on error + */ + public Date getDate(final InetAddress host) throws IOException { + return new Date((getTime(host, DEFAULT_PORT) - SECONDS_1900_TO_1970) * 1000L); + } + + /** + * Retrieves the time from the server and returns a Java Date containing the time converted to the local time zone. * * @param host The address of the server. * @param port The port of the service. - * @return The time value retrieved from the server. - * @throws IOException If an error occurs while retrieving the time. - ***/ - public long getTime(InetAddress host, int port) throws IOException - { - long time; - DatagramPacket sendPacket, receivePacket; - - sendPacket = - new DatagramPacket(__dummyData, __dummyData.length, host, port); - receivePacket = new DatagramPacket(__timeData, __timeData.length); - - _socket_.send(sendPacket); - _socket_.receive(receivePacket); - - time = 0L; - time |= (((__timeData[0] & 0xff) << 24) & 0xffffffffL); - time |= (((__timeData[1] & 0xff) << 16) & 0xffffffffL); - time |= (((__timeData[2] & 0xff) << 8) & 0xffffffffL); - time |= ((__timeData[3] & 0xff) & 0xffffffffL); - - return time; + * @return A Date value containing the time retrieved from the server converted to the local time zone. + * @throws IOException If an error occurs while fetching the time. + */ + public Date getDate(final InetAddress host, final int port) throws IOException { + return new Date((getTime(host, port) - SECONDS_1900_TO_1970) * 1000L); } - /*** Same as <code> getTime(host, DEFAULT_PORT); </code> + /** + * Same as <code> getTime(host, DEFAULT_PORT); </code> + * * @param host the time server * @return the time returned from the server * @throws IOException on error - ***/ - public long getTime(InetAddress host) throws IOException - { + */ + public long getTime(final InetAddress host) throws IOException { return getTime(host, DEFAULT_PORT); } - - /*** - * Retrieves the time from the server and returns a Java Date - * containing the time converted to the local timezone. + /** + * Retrieves the time from the specified server and port and returns it. The time is the number of seconds since 00:00 (midnight) 1 January 1900 GMT, as + * specified by RFC 868. This method reads the raw 32-bit big-endian unsigned integer from the server, converts it to a Java long, and returns the value. * * @param host The address of the server. * @param port The port of the service. - * @return A Date value containing the time retrieved from the server - * converted to the local timezone. - * @throws IOException If an error occurs while fetching the time. - ***/ - public Date getDate(InetAddress host, int port) throws IOException - { - return new Date((getTime(host, port) - SECONDS_1900_TO_1970)*1000L); - } + * @return The time value retrieved from the server. + * @throws IOException If an error occurs while retrieving the time. + */ + public long getTime(final InetAddress host, final int port) throws IOException { + long time; + final DatagramPacket sendPacket; + final DatagramPacket receivePacket; + sendPacket = new DatagramPacket(dummyData, dummyData.length, host, port); + receivePacket = new DatagramPacket(timeData, timeData.length); - /*** Same as <code> getTime(host, DEFAULT_PORT); </code> - * @param host the time server - * @return the date - * @throws IOException on error - ***/ - public Date getDate(InetAddress host) throws IOException - { - return new Date((getTime(host, DEFAULT_PORT) - - SECONDS_1900_TO_1970)*1000L); + _socket_.send(sendPacket); + _socket_.receive(receivePacket); + + time = 0L; + time |= (((timeData[0] & 0xff) << 24) & 0xffffffffL); + time |= (((timeData[1] & 0xff) << 16) & 0xffffffffL); + time |= (((timeData[2] & 0xff) << 8) & 0xffffffffL); + time |= ((timeData[3] & 0xff) & 0xffffffffL); + + return time; } } - diff --git a/src/main/java/org/apache/commons/net/util/Base64.java b/src/main/java/org/apache/commons/net/util/Base64.java index 4e85988..b866720 100644 --- a/src/main/java/org/apache/commons/net/util/Base64.java +++ b/src/main/java/org/apache/commons/net/util/Base64.java @@ -17,34 +17,31 @@ package org.apache.commons.net.util; -import java.io.UnsupportedEncodingException; import java.math.BigInteger; - - +import java.nio.charset.StandardCharsets; +import java.util.Objects; /** * Provides Base64 encoding and decoding as defined by RFC 2045. * * <p> - * This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose - * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by Freed and Borenstein. + * This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose Internet Mail Extensions (MIME) Part One: + * Format of Internet Message Bodies</cite> by Freed and Borenstein. * </p> * <p> * The class can be parameterized in the following manner with various constructors: * <ul> * <li>URL-safe mode: Default off.</li> - * <li>Line length: Default 76. Line length that aren't multiples of 4 will still essentially end up being multiples of - * 4 in the encoded data. + * <li>Line length: Default 76. Line length that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. * <li>Line separator: Default is CRLF ("\r\n")</li> * </ul> * <p> - * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode - * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). + * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode character encodings which are + * compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). * </p> * * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a> * @since 2.2 - * @version $Id: Base64.java 1697293 2015-08-24 01:01:00Z sebb $ */ public class Base64 { private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2; @@ -55,8 +52,7 @@ public class Base64 { * Chunk size per RFC 2045 section 6.8. * * <p> - * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any - * equal signs. + * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any equal signs. * </p> * * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a> @@ -68,37 +64,25 @@ public class Base64 { * * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a> */ - private static final byte[] CHUNK_SEPARATOR = {'\r', '\n'}; - - private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + private static final byte[] CHUNK_SEPARATOR = { '\r', '\n' }; /** - * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet" - * equivalents as specified in Table 1 of RFC 2045. + * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet" equivalents as specified in Table 1 of RFC + * 2045. * - * Thanks to "commons" project in ws.apache.org for this code. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + * Thanks to "commons" project in ws.apache.org for this code. http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ */ - private static final byte[] STANDARD_ENCODE_TABLE = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' - }; + private static final byte[] STANDARD_ENCODE_TABLE = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', + 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; /** - * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and / - * changed to - and _ to make the encoded Base64 results more URL-SAFE. - * This table is only used when the Base64's mode is set to URL-SAFE. + * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and / changed to - and _ to make the encoded Base64 results more URL-SAFE. This table is + * only used when the Base64's mode is set to URL-SAFE. */ - private static final byte[] URL_SAFE_ENCODE_TABLE = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' - }; + private static final byte[] URL_SAFE_ENCODE_TABLE = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', + 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' }; /** * Byte used to pad output. @@ -106,25 +90,18 @@ public class Base64 { private static final byte PAD = '='; /** - * This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified in - * Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64 - * alphabet but fall within the bounds of the array are translated to -1. + * This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into their 6-bit + * positive integer equivalents. Characters that are not in the Base64 alphabet but fall within the bounds of the array are translated to -1. * - * Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both - * URL_SAFE and STANDARD base64. (The encoder, on the other hand, needs to know ahead of time what to emit). + * Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both URL_SAFE and STANDARD base64. (The + * encoder, on the other hand, needs to know ahead of time what to emit). * - * Thanks to "commons" project in ws.apache.org for this code. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + * Thanks to "commons" project in ws.apache.org for this code. http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ */ - private static final byte[] DECODE_TABLE = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54, - 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, - 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, - 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 - }; + private static final byte[] DECODE_TABLE = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 }; /** Mask used to extract 6 bits, used when encoding */ private static final int MASK_6BITS = 0x3f; @@ -137,15 +114,314 @@ public class Base64 { // some state be preserved between calls of encode() and decode(). /** - * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static because it is able - * to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a member variable so we can switch - * between the two modes. + * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. + * + * @param arrayOctet byte array to test + * @return <code>true</code> if any byte is a valid character in the Base64 alphabet; false herwise + */ + private static boolean containsBase64Byte(final byte[] arrayOctet) { + for (final byte element : arrayOctet) { + if (isBase64(element)) { + return true; + } + } + return false; + } + + /** + * Decodes Base64 data into octets. + * + * @param base64Data Byte array containing Base64 data + * @return Array containing decoded data. + */ + public static byte[] decodeBase64(final byte[] base64Data) { + return new Base64().decode(base64Data); + } + + /** + * Decodes a Base64 String into octets. + * + * @param base64String String containing Base64 data + * @return Array containing decoded data. + * @since 1.4 + */ + public static byte[] decodeBase64(final String base64String) { + return new Base64().decode(base64String); + } + + // Implementation of integer encoding used for crypto + /** + * Decodes a byte64-encoded integer according to crypto standards such as W3C's XML-Signature + * + * @param pArray a byte array containing base64 character data + * @return A BigInteger + * @since 1.4 + */ + public static BigInteger decodeInteger(final byte[] pArray) { + return new BigInteger(1, decodeBase64(pArray)); + } + + /** + * Encodes binary data using the base64 algorithm but does not chunk the output. + * + * @param binaryData binary data to encode + * @return byte[] containing Base64 characters in their UTF-8 representation. + */ + public static byte[] encodeBase64(final byte[] binaryData) { + return encodeBase64(binaryData, false); + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. + * + * @param binaryData Array containing binary data to encode. + * @param isChunked if <code>true</code> this encoder will chunk the base64 output into 76 character blocks + * @return Base64-encoded data. + * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} + */ + public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked) { + return encodeBase64(binaryData, isChunked, false); + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. + * + * @param binaryData Array containing binary data to encode. + * @param isChunked if <code>true</code> this encoder will chunk the base64 output into 76 character blocks + * @param urlSafe if <code>true</code> this encoder will emit - and _ instead of the usual + and / characters. + * @return Base64-encoded data. + * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} + * @since 1.4 + */ + public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, final boolean urlSafe) { + return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE); + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. + * + * @param binaryData Array containing binary data to encode. + * @param isChunked if <code>true</code> this encoder will chunk the base64 output into 76 character blocks + * @param urlSafe if <code>true</code> this encoder will emit - and _ instead of the usual + and / characters. + * @param maxResultSize The maximum result size to accept. + * @return Base64-encoded data. + * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than maxResultSize + * @since 1.4 + */ + public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, final boolean urlSafe, final int maxResultSize) { + if (binaryData == null || binaryData.length == 0) { + return binaryData; + } + + final long len = getEncodeLength(binaryData, isChunked ? CHUNK_SIZE : 0, isChunked ? CHUNK_SEPARATOR : NetConstants.EMPTY_BTYE_ARRAY); + if (len > maxResultSize) { + throw new IllegalArgumentException( + "Input array too big, the output array would be bigger (" + len + ") than the specified maxium size of " + maxResultSize); + } + + final Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe); + return b64.encode(binaryData); + } + + /** + * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks + * + * @param binaryData binary data to encode + * @return Base64 characters chunked in 76 character blocks + */ + public static byte[] encodeBase64Chunked(final byte[] binaryData) { + return encodeBase64(binaryData, true); + } + + /** + * Encodes binary data using the base64 algorithm into 76 character blocks separated by CRLF. + * <p> + * For a non-chunking version, see {@link #encodeBase64StringUnChunked(byte[])}. + * + * @param binaryData binary data to encode + * @return String containing Base64 characters. + * @since 1.4 + */ + public static String encodeBase64String(final byte[] binaryData) { + return newStringUtf8(encodeBase64(binaryData, true)); + } + + /** + * Encodes binary data using the base64 algorithm. + * + * @param binaryData binary data to encode + * @param useChunking whether to split the output into chunks + * @return String containing Base64 characters. + * @since 3.2 + */ + public static String encodeBase64String(final byte[] binaryData, final boolean useChunking) { + return newStringUtf8(encodeBase64(binaryData, useChunking)); + } + + /** + * Encodes binary data using the base64 algorithm, without using chunking. + * <p> + * For a chunking version, see {@link #encodeBase64String(byte[])}. + * + * @param binaryData binary data to encode + * @return String containing Base64 characters. + * @since 3.2 + */ + public static String encodeBase64StringUnChunked(final byte[] binaryData) { + return newStringUtf8(encodeBase64(binaryData, false)); + } + + /** + * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The url-safe variation emits - and _ instead of + + * and / characters. + * + * @param binaryData binary data to encode + * @return byte[] containing Base64 characters in their UTF-8 representation. + * @since 1.4 + */ + public static byte[] encodeBase64URLSafe(final byte[] binaryData) { + return encodeBase64(binaryData, false, true); + } + + /** + * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The url-safe variation emits - and _ instead of + + * and / characters. + * + * @param binaryData binary data to encode + * @return String containing Base64 characters + * @since 1.4 + */ + public static String encodeBase64URLSafeString(final byte[] binaryData) { + return newStringUtf8(encodeBase64(binaryData, false, true)); + } + + /** + * Encodes to a byte64-encoded integer according to crypto standards such as W3C's XML-Signature + * + * @param bigInt a BigInteger + * @return A byte array containing base64 character data + * @throws NullPointerException if null is passed in + * @since 1.4 + */ + public static byte[] encodeInteger(final BigInteger bigInt) { + return encodeBase64(toIntegerBytes(bigInt), false); + } + + /** + * Pre-calculates the amount of space needed to base64-encode the supplied array. + * + * @param pArray byte[] array which will later be encoded + * @param chunkSize line-length of the output (<= 0 means no chunking) between each chunkSeparator (e.g. CRLF). + * @param chunkSeparator the sequence of bytes used to separate chunks of output (e.g. CRLF). + * + * @return amount of space needed to encoded the supplied array. Returns a long since a max-len array will require Integer.MAX_VALUE + 33%. + */ + private static long getEncodeLength(final byte[] pArray, int chunkSize, final byte[] chunkSeparator) { + // base64 always encodes to multiples of 4. + chunkSize = (chunkSize / 4) * 4; + + long len = (pArray.length * 4) / 3; + final long mod = len % 4; + if (mod != 0) { + len += 4 - mod; + } + if (chunkSize > 0) { + final boolean lenChunksPerfectly = len % chunkSize == 0; + len += (len / chunkSize) * chunkSeparator.length; + if (!lenChunksPerfectly) { + len += chunkSeparator.length; + } + } + return len; + } + + /** + * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the method treats whitespace as valid. + * + * @param arrayOctet byte array to test + * @return <code>true</code> if all bytes are valid characters in the Base64 alphabet or if the byte array is empty; false, otherwise + */ + public static boolean isArrayByteBase64(final byte[] arrayOctet) { + for (final byte element : arrayOctet) { + if (!isBase64(element) && !isWhiteSpace(element)) { + return false; + } + } + return true; + } + + /** + * Returns whether or not the <code>octet</code> is in the base 64 alphabet. + * + * @param octet The value to test + * @return <code>true</code> if the value is defined in the the base 64 alphabet, <code>false</code> otherwise. + * @since 1.4 + */ + public static boolean isBase64(final byte octet) { + return octet == PAD || (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1); + } + + /** + * Checks if a byte value is whitespace or not. + * + * @param byteToCheck the byte to check + * @return true if byte is whitespace, false otherwise + */ + private static boolean isWhiteSpace(final byte byteToCheck) { + switch (byteToCheck) { + case ' ': + case '\n': + case '\r': + case '\t': + return true; + default: + return false; + } + } + + private static String newStringUtf8(final byte[] encode) { + return new String(encode, StandardCharsets.UTF_8); + } + + /** + * Returns a byte-array representation of a <code>BigInteger</code> without sign bit. + * + * @param bigInt <code>BigInteger</code> to be converted + * @return a byte array representation of the BigInteger parameter + */ + static byte[] toIntegerBytes(final BigInteger bigInt) { + Objects.requireNonNull(bigInt, "bigInt"); + int bitlen = bigInt.bitLength(); + // round bitlen + bitlen = ((bitlen + 7) >> 3) << 3; + final byte[] bigBytes = bigInt.toByteArray(); + + if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) { + return bigBytes; + } + // set up params for copying everything but sign bit + int startSrc = 0; + int len = bigBytes.length; + + // if bigInt is exactly byte-aligned, just skip signbit in copy + if ((bigInt.bitLength() % 8) == 0) { + startSrc = 1; + len--; + } + final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec + final byte[] resizedBytes = new byte[bitlen / 8]; + System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len); + return resizedBytes; + } + + /** + * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static because it is able to decode both STANDARD and URL_SAFE + * streams, but the encodeTable must be a member variable so we can switch between the two modes. */ private final byte[] encodeTable; /** - * Line length for encoding. Not used when decoding. A value of zero or less implies no chunking of the base64 - * encoded data. + * Line length for encoding. Not used when decoding. A value of zero or less implies no chunking of the base64 encoded data. */ private final int lineLength; @@ -182,26 +458,24 @@ public class Base64 { private int readPos; /** - * Variable tracks how many characters have been written to the current line. Only used when encoding. We use it to - * make sure each encoded line never goes beyond lineLength (if lineLength > 0). + * Variable tracks how many characters have been written to the current line. Only used when encoding. We use it to make sure each encoded line never goes + * beyond lineLength (if lineLength > 0). */ private int currentLinePos; /** - * Writes to the buffer only occur after every 3 reads when encoding, an every 4 reads when decoding. This variable - * helps track that. + * Writes to the buffer only occur after every 3 reads when encoding, an every 4 reads when decoding. This variable helps track that. */ private int modulus; /** - * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this Base64 object becomes useless, - * and must be thrown away. + * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this Base64 object becomes useless, and must be thrown away. */ private boolean eof; /** - * Place holder for the 3 bytes we're dealing with for our base64 logic. Bitwise operations store and extract the - * base64 encoding or decoding from this variable. + * Place holder for the 3 bytes we're dealing with for our base64 logic. Bitwise operations store and extract the base64 encoding or decoding from this + * variable. */ private int x; @@ -229,20 +503,17 @@ public class Base64 { * When decoding all variants are supported. * </p> * - * @param urlSafe - * if <code>true</code>, URL-safe encoding is used. In most cases this should be set to - * <code>false</code>. + * @param urlSafe if <code>true</code>, URL-safe encoding is used. In most cases this should be set to <code>false</code>. * @since 1.4 */ - public Base64(boolean urlSafe) { + public Base64(final boolean urlSafe) { this(CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe); } /** * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. * <p> - * When encoding the line length is given in the constructor, the line separator is CRLF, and the encoding table is - * STANDARD_ENCODE_TABLE. + * When encoding the line length is given in the constructor, the line separator is CRLF, and the encoding table is STANDARD_ENCODE_TABLE. * </p> * <p> * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. @@ -251,20 +522,18 @@ public class Base64 { * When decoding all variants are supported. * </p> * - * @param lineLength - * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of 4). - * If {@code lineLength <= 0}, then the output will not be divided into lines (chunks). Ignored when decoding. + * @param lineLength Each line of encoded data will be at most of the given length (rounded down to nearest multiple of 4). If {@code lineLength <= 0}, then + * the output will not be divided into lines (chunks). Ignored when decoding. * @since 1.4 */ - public Base64(int lineLength) { + public Base64(final int lineLength) { this(lineLength, CHUNK_SEPARATOR); } /** * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. * <p> - * When encoding the line length and line separator are given in the constructor, and the encoding table is - * STANDARD_ENCODE_TABLE. + * When encoding the line length and line separator are given in the constructor, and the encoding table is STANDARD_ENCODE_TABLE. * </p> * <p> * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. @@ -273,24 +542,20 @@ public class Base64 { * When decoding all variants are supported. * </p> * - * @param lineLength - * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of 4). - * If {@code lineLength <= 0}, then the output will not be divided into lines (chunks). Ignored when decoding. - * @param lineSeparator - * Each line of encoded data will end with this sequence of bytes. - * @throws IllegalArgumentException - * Thrown when the provided lineSeparator included some base64 characters. + * @param lineLength Each line of encoded data will be at most of the given length (rounded down to nearest multiple of 4). If {@code lineLength <= 0}, + * then the output will not be divided into lines (chunks). Ignored when decoding. + * @param lineSeparator Each line of encoded data will end with this sequence of bytes. + * @throws IllegalArgumentException Thrown when the provided lineSeparator included some base64 characters. * @since 1.4 */ - public Base64(int lineLength, byte[] lineSeparator) { + public Base64(final int lineLength, final byte[] lineSeparator) { this(lineLength, lineSeparator, false); } /** * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. * <p> - * When encoding the line length and line separator are given in the constructor, and the encoding table is - * STANDARD_ENCODE_TABLE. + * When encoding the line length and line separator are given in the constructor, and the encoding table is STANDARD_ENCODE_TABLE. * </p> * <p> * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. @@ -299,22 +564,18 @@ public class Base64 { * When decoding all variants are supported. * </p> * - * @param lineLength - * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of 4). - * If {@code lineLength <= 0}, then the output will not be divided into lines (chunks). Ignored when decoding. - * @param lineSeparator - * Each line of encoded data will end with this sequence of bytes. - * @param urlSafe - * Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode - * operations. Decoding seamlessly handles both modes. - * @throws IllegalArgumentException - * The provided lineSeparator included some base64 characters. That's not going to work! + * @param lineLength Each line of encoded data will be at most of the given length (rounded down to nearest multiple of 4). If {@code lineLength <= 0}, + * then the output will not be divided into lines (chunks). Ignored when decoding. + * @param lineSeparator Each line of encoded data will end with this sequence of bytes. + * @param urlSafe Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode operations. Decoding seamlessly + * handles both modes. + * @throws IllegalArgumentException The provided lineSeparator included some base64 characters. That's not going to work! * @since 1.4 */ - public Base64(int lineLength, byte[] lineSeparator, boolean urlSafe) { + public Base64(int lineLength, byte[] lineSeparator, final boolean urlSafe) { if (lineSeparator == null) { - lineLength = 0; // disable chunk-separating - lineSeparator = EMPTY_BYTE_ARRAY; // this just gets ignored + lineLength = 0; // disable chunk-separating + lineSeparator = NetConstants.EMPTY_BTYE_ARRAY; // this just gets ignored } this.lineLength = lineLength > 0 ? (lineLength / 4) * 4 : 0; this.lineSeparator = new byte[lineSeparator.length]; @@ -326,31 +587,12 @@ public class Base64 { } this.decodeSize = this.encodeSize - 1; if (containsBase64Byte(lineSeparator)) { - String sep = newStringUtf8(lineSeparator); + final String sep = newStringUtf8(lineSeparator); throw new IllegalArgumentException("lineSeperator must not contain base64 characters: [" + sep + "]"); } this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE; } - /** - * Returns our current encode mode. True if we're URL-SAFE, false otherwise. - * - * @return true if we're in URL-SAFE mode, false otherwise. - * @since 1.4 - */ - public boolean isUrlSafe() { - return this.encodeTable == URL_SAFE_ENCODE_TABLE; - } - - /** - * Returns true if this Base64 object has buffered data for reading. - * - * @return true if there is Base64 object still available for reading. - */ - boolean hasData() { - return this.buffer != null; - } - /** * Returns the amount of buffered data available for reading. * @@ -360,178 +602,53 @@ public class Base64 { return buffer != null ? pos - readPos : 0; } - /** Doubles our buffer. */ - private void resizeBuffer() { - if (buffer == null) { - buffer = new byte[DEFAULT_BUFFER_SIZE]; - pos = 0; - readPos = 0; - } else { - byte[] b = new byte[buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR]; - System.arraycopy(buffer, 0, b, 0, buffer.length); - buffer = b; - } - } - /** - * Extracts buffered data into the provided byte[] array, starting at position bPos, up to a maximum of bAvail - * bytes. Returns how many bytes were actually extracted. + * Decodes a byte[] containing containing characters in the Base64 alphabet. * - * @param b - * byte[] array to extract the buffered data into. - * @param bPos - * position in byte[] array to start extraction at. - * @param bAvail - * amount of bytes we're allowed to extract. We may extract fewer (if fewer are available). - * @return The number of bytes successfully extracted into the provided byte[] array. + * @param pArray A byte array containing Base64 character data + * @return a byte array containing binary data */ - int readResults(byte[] b, int bPos, int bAvail) { - if (buffer != null) { - int len = Math.min(avail(), bAvail); - if (buffer != b) { - System.arraycopy(buffer, readPos, b, bPos, len); - readPos += len; - if (readPos >= pos) { - buffer = null; - } - } else { - // Re-using the original consumer's output array is only - // allowed for one round. - buffer = null; - } - return len; + public byte[] decode(final byte[] pArray) { + reset(); + if (pArray == null || pArray.length == 0) { + return pArray; } - return eof ? -1 : 0; - } + final long len = (pArray.length * 3) / 4; + final byte[] buf = new byte[(int) len]; + setInitialBuffer(buf, 0, buf.length); + decode(pArray, 0, pArray.length); + decode(pArray, 0, -1); // Notify decoder of EOF. - /** - * Sets the streaming buffer. This is a small optimization where we try to buffer directly to the consumer's output - * array for one round (if the consumer calls this method first) instead of starting our own buffer. - * - * @param out - * byte[] array to buffer directly to. - * @param outPos - * Position to start buffering into. - * @param outAvail - * Amount of bytes available for direct buffering. - */ - void setInitialBuffer(byte[] out, int outPos, int outAvail) { - // We can re-use consumer's original output array under - // special circumstances, saving on some System.arraycopy(). - if (out != null && out.length == outAvail) { - buffer = out; - pos = outPos; - readPos = outPos; - } - } + // Would be nice to just return buf (like we sometimes do in the encode + // logic), but we have no idea what the line-length was (could even be + // variable). So we cannot determine ahead of time exactly how big an + // array is necessary. Hence the need to construct a 2nd byte array to + // hold the final result: - /** - * <p> - * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with - * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, so flush last - * remaining bytes (if not multiple of 3). - * </p> - * <p> - * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. - * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ - * </p> - * - * @param in - * byte[] array of binary data to base64 encode. - * @param inPos - * Position to start reading data from. - * @param inAvail - * Amount of bytes available from input for encoding. - */ - void encode(byte[] in, int inPos, int inAvail) { - if (eof) { - return; - } - // inAvail < 0 is how we're informed of EOF in the underlying data we're - // encoding. - if (inAvail < 0) { - eof = true; - if (buffer == null || buffer.length - pos < encodeSize) { - resizeBuffer(); - } - switch (modulus) { - case 1 : - buffer[pos++] = encodeTable[(x >> 2) & MASK_6BITS]; - buffer[pos++] = encodeTable[(x << 4) & MASK_6BITS]; - // URL-SAFE skips the padding to further reduce size. - if (encodeTable == STANDARD_ENCODE_TABLE) { - buffer[pos++] = PAD; - buffer[pos++] = PAD; - } - break; - - case 2 : - buffer[pos++] = encodeTable[(x >> 10) & MASK_6BITS]; - buffer[pos++] = encodeTable[(x >> 4) & MASK_6BITS]; - buffer[pos++] = encodeTable[(x << 2) & MASK_6BITS]; - // URL-SAFE skips the padding to further reduce size. - if (encodeTable == STANDARD_ENCODE_TABLE) { - buffer[pos++] = PAD; - } - break; - default: - break; // other values ignored - } - if (lineLength > 0 && pos > 0) { - System.arraycopy(lineSeparator, 0, buffer, pos, lineSeparator.length); - pos += lineSeparator.length; - } - } else { - for (int i = 0; i < inAvail; i++) { - if (buffer == null || buffer.length - pos < encodeSize) { - resizeBuffer(); - } - modulus = (++modulus) % 3; - int b = in[inPos++]; - if (b < 0) { - b += 256; - } - x = (x << 8) + b; - if (0 == modulus) { - buffer[pos++] = encodeTable[(x >> 18) & MASK_6BITS]; - buffer[pos++] = encodeTable[(x >> 12) & MASK_6BITS]; - buffer[pos++] = encodeTable[(x >> 6) & MASK_6BITS]; - buffer[pos++] = encodeTable[x & MASK_6BITS]; - currentLinePos += 4; - if (lineLength > 0 && lineLength <= currentLinePos) { - System.arraycopy(lineSeparator, 0, buffer, pos, lineSeparator.length); - pos += lineSeparator.length; - currentLinePos = 0; - } - } - } - } + final byte[] result = new byte[pos]; + readResults(result, 0, result.length); + return result; } /** * <p> - * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once - * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1" - * call is not necessary when decoding, but it doesn't hurt, either. + * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once with the data to decode, and once with + * inAvail set to "-1" to alert decoder that EOF has been reached. The "-1" call is not necessary when decoding, but it doesn't hurt, either. * </p> * <p> - * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are - * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in, - * garbage-out philosophy: it will not check the provided data for validity. + * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are silently ignored, but has implications + * for other bytes, too. This method subscribes to the garbage-in, garbage-out philosophy: it will not check the provided data for validity. * </p> * <p> * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ * </p> * - * @param in - * byte[] array of ascii data to base64 decode. - * @param inPos - * Position to start reading data from. - * @param inAvail - * Amount of bytes available from input for encoding. + * @param in byte[] array of ascii data to base64 decode. + * @param inPos Position to start reading data from. + * @param inAvail Amount of bytes available from input for encoding. */ - void decode(byte[] in, int inPos, int inAvail) { + void decode(final byte[] in, int inPos, final int inAvail) { if (eof) { return; } @@ -542,22 +659,21 @@ public class Base64 { if (buffer == null || buffer.length - pos < decodeSize) { resizeBuffer(); } - byte b = in[inPos++]; + final byte b = in[inPos++]; if (b == PAD) { // We're done. eof = true; break; - } else { - if (b >= 0 && b < DECODE_TABLE.length) { - int result = DECODE_TABLE[b]; - if (result >= 0) { - modulus = (++modulus) % 4; - x = (x << 6) + result; - if (modulus == 0) { - buffer[pos++] = (byte) ((x >> 16) & MASK_8BITS); - buffer[pos++] = (byte) ((x >> 8) & MASK_8BITS); - buffer[pos++] = (byte) (x & MASK_8BITS); - } + } + if (b >= 0 && b < DECODE_TABLE.length) { + final int result = DECODE_TABLE[b]; + if (result >= 0) { + modulus = (++modulus) % 4; + x = (x << 6) + result; + if (modulus == 0) { + buffer[pos++] = (byte) ((x >> 16) & MASK_8BITS); + buffer[pos++] = (byte) ((x >> 8) & MASK_8BITS); + buffer[pos++] = (byte) (x & MASK_8BITS); } } } @@ -569,350 +685,43 @@ public class Base64 { if (eof && modulus != 0) { x = x << 6; switch (modulus) { - case 2 : - x = x << 6; - buffer[pos++] = (byte) ((x >> 16) & MASK_8BITS); - break; - case 3 : - buffer[pos++] = (byte) ((x >> 16) & MASK_8BITS); - buffer[pos++] = (byte) ((x >> 8) & MASK_8BITS); - break; - default: - break; // other values ignored - } - } - } - - /** - * Returns whether or not the <code>octet</code> is in the base 64 alphabet. - * - * @param octet - * The value to test - * @return <code>true</code> if the value is defined in the the base 64 alphabet, <code>false</code> otherwise. - * @since 1.4 - */ - public static boolean isBase64(byte octet) { - return octet == PAD || (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1); - } - - /** - * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the - * method treats whitespace as valid. - * - * @param arrayOctet - * byte array to test - * @return <code>true</code> if all bytes are valid characters in the Base64 alphabet or if the byte array is empty; - * false, otherwise - */ - public static boolean isArrayByteBase64(byte[] arrayOctet) { - for (int i = 0; i < arrayOctet.length; i++) { - if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) { - return false; - } - } - return true; - } - - /** - * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. - * - * @param arrayOctet - * byte array to test - * @return <code>true</code> if any byte is a valid character in the Base64 alphabet; false herwise - */ - private static boolean containsBase64Byte(byte[] arrayOctet) { - for (byte element : arrayOctet) - { - if (isBase64(element)) { - return true; + case 2: + x = x << 6; + buffer[pos++] = (byte) ((x >> 16) & MASK_8BITS); + break; + case 3: + buffer[pos++] = (byte) ((x >> 16) & MASK_8BITS); + buffer[pos++] = (byte) ((x >> 8) & MASK_8BITS); + break; + default: + break; // other values ignored } } - return false; - } - - /** - * Encodes binary data using the base64 algorithm but does not chunk the output. - * - * @param binaryData - * binary data to encode - * @return byte[] containing Base64 characters in their UTF-8 representation. - */ - public static byte[] encodeBase64(byte[] binaryData) { - return encodeBase64(binaryData, false); - } - - /** - * Encodes binary data using the base64 algorithm into 76 character blocks separated by CRLF. - * <p> - * For a non-chunking version, see {@link #encodeBase64StringUnChunked(byte[])}. - * - * @param binaryData - * binary data to encode - * @return String containing Base64 characters. - * @since 1.4 - */ - public static String encodeBase64String(byte[] binaryData) { - return newStringUtf8(encodeBase64(binaryData, true)); - } - - /** - * Encodes binary data using the base64 algorithm, without using chunking. - * <p> - * For a chunking version, see {@link #encodeBase64String(byte[])}. - * - * @param binaryData - * binary data to encode - * @return String containing Base64 characters. - * @since 3.2 - */ - public static String encodeBase64StringUnChunked(byte[] binaryData) { - return newStringUtf8(encodeBase64(binaryData, false)); - } - - /** - * Encodes binary data using the base64 algorithm. - * - * @param binaryData - * binary data to encode - * @param useChunking whether to split the output into chunks - * @return String containing Base64 characters. - * @since 3.2 - */ - public static String encodeBase64String(byte[] binaryData, boolean useChunking) { - return newStringUtf8(encodeBase64(binaryData, useChunking)); - } - - /** - * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The - * url-safe variation emits - and _ instead of + and / characters. - * - * @param binaryData - * binary data to encode - * @return byte[] containing Base64 characters in their UTF-8 representation. - * @since 1.4 - */ - public static byte[] encodeBase64URLSafe(byte[] binaryData) { - return encodeBase64(binaryData, false, true); - } - - /** - * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The - * url-safe variation emits - and _ instead of + and / characters. - * - * @param binaryData - * binary data to encode - * @return String containing Base64 characters - * @since 1.4 - */ - public static String encodeBase64URLSafeString(byte[] binaryData) { - return newStringUtf8(encodeBase64(binaryData, false, true)); - } - - /** - * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks - * - * @param binaryData - * binary data to encode - * @return Base64 characters chunked in 76 character blocks - */ - public static byte[] encodeBase64Chunked(byte[] binaryData) { - return encodeBase64(binaryData, true); } /** * Decodes a String containing containing characters in the Base64 alphabet. * - * @param pArray - * A String containing Base64 character data + * @param pArray A String containing Base64 character data * @return a byte array containing binary data * @since 1.4 */ - public byte[] decode(String pArray) { + public byte[] decode(final String pArray) { return decode(getBytesUtf8(pArray)); } - private byte[] getBytesUtf8(String pArray) { - try { - return pArray.getBytes("UTF8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - /** - * Decodes a byte[] containing containing characters in the Base64 alphabet. - * - * @param pArray - * A byte array containing Base64 character data - * @return a byte array containing binary data - */ - public byte[] decode(byte[] pArray) { - reset(); - if (pArray == null || pArray.length == 0) { - return pArray; - } - long len = (pArray.length * 3) / 4; - byte[] buf = new byte[(int) len]; - setInitialBuffer(buf, 0, buf.length); - decode(pArray, 0, pArray.length); - decode(pArray, 0, -1); // Notify decoder of EOF. - - // Would be nice to just return buf (like we sometimes do in the encode - // logic), but we have no idea what the line-length was (could even be - // variable). So we cannot determine ahead of time exactly how big an - // array is necessary. Hence the need to construct a 2nd byte array to - // hold the final result: - - byte[] result = new byte[pos]; - readResults(result, 0, result.length); - return result; - } - - /** - * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. - * - * @param binaryData - * Array containing binary data to encode. - * @param isChunked - * if <code>true</code> this encoder will chunk the base64 output into 76 character blocks - * @return Base64-encoded data. - * @throws IllegalArgumentException - * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} - */ - public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) { - return encodeBase64(binaryData, isChunked, false); - } - - /** - * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. - * - * @param binaryData - * Array containing binary data to encode. - * @param isChunked - * if <code>true</code> this encoder will chunk the base64 output into 76 character blocks - * @param urlSafe - * if <code>true</code> this encoder will emit - and _ instead of the usual + and / characters. - * @return Base64-encoded data. - * @throws IllegalArgumentException - * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} - * @since 1.4 - */ - public static byte[] encodeBase64(byte[] binaryData, boolean isChunked, boolean urlSafe) { - return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE); - } - - /** - * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. - * - * @param binaryData - * Array containing binary data to encode. - * @param isChunked - * if <code>true</code> this encoder will chunk the base64 output into 76 character blocks - * @param urlSafe - * if <code>true</code> this encoder will emit - and _ instead of the usual + and / characters. - * @param maxResultSize - * The maximum result size to accept. - * @return Base64-encoded data. - * @throws IllegalArgumentException - * Thrown when the input array needs an output array bigger than maxResultSize - * @since 1.4 - */ - public static byte[] encodeBase64(byte[] binaryData, boolean isChunked, boolean urlSafe, int maxResultSize) { - if (binaryData == null || binaryData.length == 0) { - return binaryData; - } - - long len = getEncodeLength(binaryData, isChunked ? CHUNK_SIZE : 0, isChunked ? CHUNK_SEPARATOR : EMPTY_BYTE_ARRAY); - if (len > maxResultSize) { - throw new IllegalArgumentException("Input array too big, the output array would be bigger (" + - len + - ") than the specified maxium size of " + - maxResultSize); - } - - Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe); - return b64.encode(binaryData); - } - - /** - * Decodes a Base64 String into octets - * - * @param base64String - * String containing Base64 data - * @return Array containing decoded data. - * @since 1.4 - */ - public static byte[] decodeBase64(String base64String) { - return new Base64().decode(base64String); - } - - /** - * Decodes Base64 data into octets - * - * @param base64Data - * Byte array containing Base64 data - * @return Array containing decoded data. - */ - public static byte[] decodeBase64(byte[] base64Data) { - return new Base64().decode(base64Data); - } - - - - /** - * Checks if a byte value is whitespace or not. - * - * @param byteToCheck - * the byte to check - * @return true if byte is whitespace, false otherwise - */ - private static boolean isWhiteSpace(byte byteToCheck) { - switch (byteToCheck) { - case ' ' : - case '\n' : - case '\r' : - case '\t' : - return true; - default : - return false; - } - } - - /** - * Encodes a byte[] containing binary data, into a String containing characters in the Base64 alphabet. - * - * @param pArray - * a byte array containing binary data - * @return A String containing only Base64 character data - * @since 1.4 - */ - public String encodeToString(byte[] pArray) { - return newStringUtf8(encode(pArray)); - } - - private static String newStringUtf8(byte[] encode) { - String str = null; - try { - str = new String(encode, "UTF8"); - } catch (UnsupportedEncodingException ue) { - throw new RuntimeException(ue); - } - return str; - } - /** * Encodes a byte[] containing binary data, into a byte[] containing characters in the Base64 alphabet. * - * @param pArray - * a byte array containing binary data + * @param pArray a byte array containing binary data * @return A byte array containing only Base64 character data */ - public byte[] encode(byte[] pArray) { + public byte[] encode(final byte[] pArray) { reset(); if (pArray == null || pArray.length == 0) { return pArray; } - long len = getEncodeLength(pArray, lineLength, lineSeparator); + final long len = getEncodeLength(pArray, lineLength, lineSeparator); byte[] buf = new byte[(int) len]; setInitialBuffer(buf, 0, buf.length); encode(pArray, 0, pArray.length); @@ -924,7 +733,7 @@ public class Base64 { // In URL-SAFE mode we skip the padding characters, so sometimes our // final length is a bit smaller. if (isUrlSafe() && pos < buf.length) { - byte[] smallerBuf = new byte[pos]; + final byte[] smallerBuf = new byte[pos]; System.arraycopy(buf, 0, smallerBuf, 0, pos); buf = smallerBuf; } @@ -932,94 +741,152 @@ public class Base64 { } /** - * Pre-calculates the amount of space needed to base64-encode the supplied array. - * - * @param pArray byte[] array which will later be encoded - * @param chunkSize line-length of the output (<= 0 means no chunking) between each - * chunkSeparator (e.g. CRLF). - * @param chunkSeparator the sequence of bytes used to separate chunks of output (e.g. CRLF). + * <p> + * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with the data to encode, and once with + * inAvail set to "-1" to alert encoder that EOF has been reached, so flush last remaining bytes (if not multiple of 3). + * </p> + * <p> + * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + * </p> * - * @return amount of space needed to encoded the supplied array. Returns - * a long since a max-len array will require Integer.MAX_VALUE + 33%. + * @param in byte[] array of binary data to base64 encode. + * @param inPos Position to start reading data from. + * @param inAvail Amount of bytes available from input for encoding. */ - private static long getEncodeLength(byte[] pArray, int chunkSize, byte[] chunkSeparator) { - // base64 always encodes to multiples of 4. - chunkSize = (chunkSize / 4) * 4; - - long len = (pArray.length * 4) / 3; - long mod = len % 4; - if (mod != 0) { - len += 4 - mod; + void encode(final byte[] in, int inPos, final int inAvail) { + if (eof) { + return; } - if (chunkSize > 0) { - boolean lenChunksPerfectly = len % chunkSize == 0; - len += (len / chunkSize) * chunkSeparator.length; - if (!lenChunksPerfectly) { - len += chunkSeparator.length; + // inAvail < 0 is how we're informed of EOF in the underlying data we're + // encoding. + if (inAvail < 0) { + eof = true; + if (buffer == null || buffer.length - pos < encodeSize) { + resizeBuffer(); + } + switch (modulus) { + case 1: + buffer[pos++] = encodeTable[(x >> 2) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x << 4) & MASK_6BITS]; + // URL-SAFE skips the padding to further reduce size. + if (encodeTable == STANDARD_ENCODE_TABLE) { + buffer[pos++] = PAD; + buffer[pos++] = PAD; + } + break; + + case 2: + buffer[pos++] = encodeTable[(x >> 10) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x >> 4) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x << 2) & MASK_6BITS]; + // URL-SAFE skips the padding to further reduce size. + if (encodeTable == STANDARD_ENCODE_TABLE) { + buffer[pos++] = PAD; + } + break; + default: + break; // other values ignored + } + if (lineLength > 0 && pos > 0) { + System.arraycopy(lineSeparator, 0, buffer, pos, lineSeparator.length); + pos += lineSeparator.length; + } + } else { + for (int i = 0; i < inAvail; i++) { + if (buffer == null || buffer.length - pos < encodeSize) { + resizeBuffer(); + } + modulus = (++modulus) % 3; + int b = in[inPos++]; + if (b < 0) { + b += 256; + } + x = (x << 8) + b; + if (0 == modulus) { + buffer[pos++] = encodeTable[(x >> 18) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x >> 12) & MASK_6BITS]; + buffer[pos++] = encodeTable[(x >> 6) & MASK_6BITS]; + buffer[pos++] = encodeTable[x & MASK_6BITS]; + currentLinePos += 4; + if (lineLength > 0 && lineLength <= currentLinePos) { + System.arraycopy(lineSeparator, 0, buffer, pos, lineSeparator.length); + pos += lineSeparator.length; + currentLinePos = 0; + } + } } } - return len; } - // Implementation of integer encoding used for crypto /** - * Decodes a byte64-encoded integer according to crypto standards such as W3C's XML-Signature + * Encodes a byte[] containing binary data, into a String containing characters in the Base64 alphabet. * - * @param pArray - * a byte array containing base64 character data - * @return A BigInteger + * @param pArray a byte array containing binary data + * @return A String containing only Base64 character data * @since 1.4 */ - public static BigInteger decodeInteger(byte[] pArray) { - return new BigInteger(1, decodeBase64(pArray)); + public String encodeToString(final byte[] pArray) { + return newStringUtf8(encode(pArray)); + } + + private byte[] getBytesUtf8(final String pArray) { + return pArray.getBytes(StandardCharsets.UTF_8); + } + + int getLineLength() { + return lineLength; + } + + byte[] getLineSeparator() { + return lineSeparator.clone(); } /** - * Encodes to a byte64-encoded integer according to crypto standards such as W3C's XML-Signature + * Returns true if this Base64 object has buffered data for reading. * - * @param bigInt - * a BigInteger - * @return A byte array containing base64 character data - * @throws NullPointerException - * if null is passed in - * @since 1.4 + * @return true if there is Base64 object still available for reading. */ - public static byte[] encodeInteger(BigInteger bigInt) { - if (bigInt == null) { - throw new NullPointerException("encodeInteger called with null parameter"); - } - return encodeBase64(toIntegerBytes(bigInt), false); + boolean hasData() { + return this.buffer != null; } /** - * Returns a byte-array representation of a <code>BigInteger</code> without sign bit. + * Returns our current encode mode. True if we're URL-SAFE, false otherwise. * - * @param bigInt - * <code>BigInteger</code> to be converted - * @return a byte array representation of the BigInteger parameter + * @return true if we're in URL-SAFE mode, false otherwise. + * @since 1.4 */ - static byte[] toIntegerBytes(BigInteger bigInt) { - int bitlen = bigInt.bitLength(); - // round bitlen - bitlen = ((bitlen + 7) >> 3) << 3; - byte[] bigBytes = bigInt.toByteArray(); - - if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) { - return bigBytes; - } - // set up params for copying everything but sign bit - int startSrc = 0; - int len = bigBytes.length; + public boolean isUrlSafe() { + return this.encodeTable == URL_SAFE_ENCODE_TABLE; + } - // if bigInt is exactly byte-aligned, just skip signbit in copy - if ((bigInt.bitLength() % 8) == 0) { - startSrc = 1; - len--; + /** + * Extracts buffered data into the provided byte[] array, starting at position bPos, up to a maximum of bAvail bytes. Returns how many bytes were actually + * extracted. + * + * @param b byte[] array to extract the buffered data into. + * @param bPos position in byte[] array to start extraction at. + * @param bAvail amount of bytes we're allowed to extract. We may extract fewer (if fewer are available). + * @return The number of bytes successfully extracted into the provided byte[] array. + */ + int readResults(final byte[] b, final int bPos, final int bAvail) { + if (buffer != null) { + final int len = Math.min(avail(), bAvail); + if (buffer != b) { + System.arraycopy(buffer, readPos, b, bPos, len); + readPos += len; + if (readPos >= pos) { + buffer = null; + } + } else { + // Re-using the original consumer's output array is only + // allowed for one round. + buffer = null; + } + return len; } - int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec - byte[] resizedBytes = new byte[bitlen / 8]; - System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len); - return resizedBytes; + return eof ? -1 : 0; } /** @@ -1036,11 +903,34 @@ public class Base64 { // Getters for use in testing - int getLineLength() { - return lineLength; + /** Doubles our buffer. */ + private void resizeBuffer() { + if (buffer == null) { + buffer = new byte[DEFAULT_BUFFER_SIZE]; + pos = 0; + readPos = 0; + } else { + final byte[] b = new byte[buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR]; + System.arraycopy(buffer, 0, b, 0, buffer.length); + buffer = b; + } } - byte[] getLineSeparator() { - return lineSeparator.clone(); + /** + * Sets the streaming buffer. This is a small optimization where we try to buffer directly to the consumer's output array for one round (if the consumer + * calls this method first) instead of starting our own buffer. + * + * @param out byte[] array to buffer directly to. + * @param outPos Position to start buffering into. + * @param outAvail Amount of bytes available for direct buffering. + */ + void setInitialBuffer(final byte[] out, final int outPos, final int outAvail) { + // We can re-use consumer's original output array under + // special circumstances, saving on some System.arraycopy(). + if (out != null && out.length == outAvail) { + buffer = out; + pos = outPos; + readPos = outPos; + } } } diff --git a/src/main/java/org/apache/commons/net/util/Charsets.java b/src/main/java/org/apache/commons/net/util/Charsets.java index bf18c6c..40a9c8a 100644 --- a/src/main/java/org/apache/commons/net/util/Charsets.java +++ b/src/main/java/org/apache/commons/net/util/Charsets.java @@ -29,26 +29,22 @@ public class Charsets { /** * Returns a charset object for the given charset name. * - * @param charsetName - * The name of the requested charset; may be a canonical name, an alias, or null. If null, return the - * default charset. + * @param charsetName The name of the requested charset; may be a canonical name, an alias, or null. If null, return the default charset. * @return A charset object for the named charset */ - public static Charset toCharset(String charsetName) { + public static Charset toCharset(final String charsetName) { return charsetName == null ? Charset.defaultCharset() : Charset.forName(charsetName); } /** * Returns a charset object for the given charset name. * - * @param charsetName - * The name of the requested charset; may be a canonical name, an alias, or null. - * If null, return the default charset. + * @param charsetName The name of the requested charset; may be a canonical name, an alias, or null. If null, return the default charset. * @param defaultCharsetName the charset name to use if the requested charset is null * * @return A charset object for the named charset */ - public static Charset toCharset(String charsetName, String defaultCharsetName) { + public static Charset toCharset(final String charsetName, final String defaultCharsetName) { return charsetName == null ? Charset.forName(defaultCharsetName) : Charset.forName(charsetName); } } diff --git a/src/main/java/org/apache/commons/net/util/KeyManagerUtils.java b/src/main/java/org/apache/commons/net/util/KeyManagerUtils.java index 01a0b8a..8135f3c 100644 --- a/src/main/java/org/apache/commons/net/util/KeyManagerUtils.java +++ b/src/main/java/org/apache/commons/net/util/KeyManagerUtils.java @@ -29,6 +29,7 @@ import java.security.Principal; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.Enumeration; import javax.net.ssl.KeyManager; @@ -40,6 +41,7 @@ import org.apache.commons.net.io.Util; * General KeyManager utilities * <p> * How to use with a client certificate: + * * <pre> * KeyManager km = KeyManagerUtils.createClientKeyManager("JKS", * "/path/to/privatekeystore.jks","storepassword", @@ -48,10 +50,10 @@ import org.apache.commons.net.io.Util; * cl.setKeyManager(km); * cl.connect(...); * </pre> - * If using the default store type and the key password is the same as the - * store password, these parameters can be omitted. <br> - * If the desired key is the first or only key in the keystore, the keyAlias parameter - * can be omitted, in which case the code becomes: + * + * If using the default store type and the key password is the same as the store password, these parameters can be omitted. <br> + * If the desired key is the first or only key in the keystore, the keyAlias parameter can be omitted, in which case the code becomes: + * * <pre> * KeyManager km = KeyManagerUtils.createClientKeyManager( * "/path/to/privatekeystore.jks","storepassword"); @@ -64,126 +66,23 @@ import org.apache.commons.net.io.Util; */ public final class KeyManagerUtils { - private static final String DEFAULT_STORE_TYPE = KeyStore.getDefaultType(); - - private KeyManagerUtils(){ - // Not instantiable - } - - /** - * Create a client key manager which returns a particular key. - * Does not handle server keys. - * - * @param ks the keystore to use - * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used - * @param keyPass the password of the key to use - * @return the customised KeyManager - * @throws GeneralSecurityException if there is a problem creating the keystore - */ - public static KeyManager createClientKeyManager(KeyStore ks, String keyAlias, String keyPass) - throws GeneralSecurityException - { - ClientKeyStore cks = new ClientKeyStore(ks, keyAlias != null ? keyAlias : findAlias(ks), keyPass); - return new X509KeyManager(cks); - } - - /** - * Create a client key manager which returns a particular key. - * Does not handle server keys. - * - * @param storeType the type of the keyStore, e.g. "JKS" - * @param storePath the path to the keyStore - * @param storePass the keyStore password - * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used - * @param keyPass the password of the key to use - * @return the customised KeyManager - * @throws GeneralSecurityException if there is a problem creating the keystore - * @throws IOException if there is a problem creating the keystore - */ - public static KeyManager createClientKeyManager( - String storeType, File storePath, String storePass, String keyAlias, String keyPass) - throws IOException, GeneralSecurityException - { - KeyStore ks = loadStore(storeType, storePath, storePass); - return createClientKeyManager(ks, keyAlias, keyPass); - } - - /** - * Create a client key manager which returns a particular key. - * Does not handle server keys. - * Uses the default store type and assumes the key password is the same as the store password - * - * @param storePath the path to the keyStore - * @param storePass the keyStore password - * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used - * @return the customised KeyManager - * @throws IOException if there is a problem creating the keystore - * @throws GeneralSecurityException if there is a problem creating the keystore - */ - public static KeyManager createClientKeyManager(File storePath, String storePass, String keyAlias) - throws IOException, GeneralSecurityException - { - return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, keyAlias, storePass); - } - - /** - * Create a client key manager which returns a particular key. - * Does not handle server keys. - * Uses the default store type and assumes the key password is the same as the store password. - * The key alias is found by searching the keystore for the first private key entry - * - * @param storePath the path to the keyStore - * @param storePass the keyStore password - * @return the customised KeyManager - * @throws IOException if there is a problem creating the keystore - * @throws GeneralSecurityException if there is a problem creating the keystore - */ - public static KeyManager createClientKeyManager(File storePath, String storePass) - throws IOException, GeneralSecurityException - { - return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, null, storePass); - } - - private static KeyStore loadStore(String storeType, File storePath, String storePass) - throws KeyStoreException, IOException, GeneralSecurityException { - KeyStore ks = KeyStore.getInstance(storeType); - FileInputStream stream = null; - try { - stream = new FileInputStream(storePath); - ks.load(stream, storePass.toCharArray()); - } finally { - Util.closeQuietly(stream); - } - return ks; - } - - private static String findAlias(KeyStore ks) throws KeyStoreException { - Enumeration<String> e = ks.aliases(); - while(e.hasMoreElements()) { - String entry = e.nextElement(); - if (ks.isKeyEntry(entry)) { - return entry; - } - } - throw new KeyStoreException("Cannot find a private key entry"); - } - private static class ClientKeyStore { private final X509Certificate[] certChain; private final PrivateKey key; private final String keyAlias; - ClientKeyStore(KeyStore ks, String keyAlias, String keyPass) throws GeneralSecurityException - { + ClientKeyStore(final KeyStore ks, final String keyAlias, final String keyPass) throws GeneralSecurityException { this.keyAlias = keyAlias; this.key = (PrivateKey) ks.getKey(this.keyAlias, keyPass.toCharArray()); - Certificate[] certs = ks.getCertificateChain(this.keyAlias); - X509Certificate[] X509certs = new X509Certificate[certs.length]; - for (int i=0; i < certs.length; i++) { - X509certs[i] = (X509Certificate) certs[i]; - } - this.certChain = X509certs; + final Certificate[] certs = ks.getCertificateChain(this.keyAlias); + final X509Certificate[] x509certs = new X509Certificate[certs.length]; + Arrays.setAll(x509certs, i -> (X509Certificate) certs[i]); + this.certChain = x509certs; + } + + final String getAlias() { + return this.keyAlias; } final X509Certificate[] getCertificateChain() { @@ -193,13 +92,9 @@ public final class KeyManagerUtils { final PrivateKey getPrivateKey() { return this.key; } - - final String getAlias() { - return this.keyAlias; - } } - private static class X509KeyManager extends X509ExtendedKeyManager { + private static class X509KeyManager extends X509ExtendedKeyManager { private final ClientKeyStore keyStore; @@ -209,38 +104,129 @@ public final class KeyManagerUtils { // Call sequence: 1 @Override - public String chooseClientAlias(String[] keyType, Principal[] issuers, - Socket socket) { + public String chooseClientAlias(final String[] keyType, final Principal[] issuers, final Socket socket) { return keyStore.getAlias(); } + @Override + public String chooseServerAlias(final String keyType, final Principal[] issuers, final Socket socket) { + return null; + } + // Call sequence: 2 @Override - public X509Certificate[] getCertificateChain(String alias) { + public X509Certificate[] getCertificateChain(final String alias) { return keyStore.getCertificateChain(); } @Override - public String[] getClientAliases(String keyType, Principal[] issuers) { - return new String[]{ keyStore.getAlias()}; + public String[] getClientAliases(final String keyType, final Principal[] issuers) { + return new String[] { keyStore.getAlias() }; } // Call sequence: 3 @Override - public PrivateKey getPrivateKey(String alias) { + public PrivateKey getPrivateKey(final String alias) { return keyStore.getPrivateKey(); } @Override - public String[] getServerAliases(String keyType, Principal[] issuers) { + public String[] getServerAliases(final String keyType, final Principal[] issuers) { return null; } - @Override - public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { - return null; + } + + private static final String DEFAULT_STORE_TYPE = KeyStore.getDefaultType(); + + /** + * Create a client key manager which returns a particular key. Does not handle server keys. Uses the default store type and assumes the key password is the + * same as the store password. The key alias is found by searching the keystore for the first private key entry + * + * @param storePath the path to the keyStore + * @param storePass the keyStore password + * @return the customised KeyManager + * @throws IOException if there is a problem creating the keystore + * @throws GeneralSecurityException if there is a problem creating the keystore + */ + public static KeyManager createClientKeyManager(final File storePath, final String storePass) throws IOException, GeneralSecurityException { + return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, null, storePass); + } + + /** + * Create a client key manager which returns a particular key. Does not handle server keys. Uses the default store type and assumes the key password is the + * same as the store password + * + * @param storePath the path to the keyStore + * @param storePass the keyStore password + * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used + * @return the customised KeyManager + * @throws IOException if there is a problem creating the keystore + * @throws GeneralSecurityException if there is a problem creating the keystore + */ + public static KeyManager createClientKeyManager(final File storePath, final String storePass, final String keyAlias) + throws IOException, GeneralSecurityException { + return createClientKeyManager(DEFAULT_STORE_TYPE, storePath, storePass, keyAlias, storePass); + } + + /** + * Create a client key manager which returns a particular key. Does not handle server keys. + * + * @param ks the keystore to use + * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used + * @param keyPass the password of the key to use + * @return the customised KeyManager + * @throws GeneralSecurityException if there is a problem creating the keystore + */ + public static KeyManager createClientKeyManager(final KeyStore ks, final String keyAlias, final String keyPass) throws GeneralSecurityException { + final ClientKeyStore cks = new ClientKeyStore(ks, keyAlias != null ? keyAlias : findAlias(ks), keyPass); + return new X509KeyManager(cks); + } + + /** + * Create a client key manager which returns a particular key. Does not handle server keys. + * + * @param storeType the type of the keyStore, e.g. "JKS" + * @param storePath the path to the keyStore + * @param storePass the keyStore password + * @param keyAlias the alias of the key to use, may be {@code null} in which case the first key entry alias is used + * @param keyPass the password of the key to use + * @return the customised KeyManager + * @throws GeneralSecurityException if there is a problem creating the keystore + * @throws IOException if there is a problem creating the keystore + */ + public static KeyManager createClientKeyManager(final String storeType, final File storePath, final String storePass, final String keyAlias, + final String keyPass) throws IOException, GeneralSecurityException { + final KeyStore ks = loadStore(storeType, storePath, storePass); + return createClientKeyManager(ks, keyAlias, keyPass); + } + + private static String findAlias(final KeyStore ks) throws KeyStoreException { + final Enumeration<String> e = ks.aliases(); + while (e.hasMoreElements()) { + final String entry = e.nextElement(); + if (ks.isKeyEntry(entry)) { + return entry; + } } + throw new KeyStoreException("Cannot find a private key entry"); + } + private static KeyStore loadStore(final String storeType, final File storePath, final String storePass) + throws KeyStoreException, IOException, GeneralSecurityException { + final KeyStore ks = KeyStore.getInstance(storeType); + FileInputStream stream = null; + try { + stream = new FileInputStream(storePath); + ks.load(stream, storePass.toCharArray()); + } finally { + Util.closeQuietly(stream); + } + return ks; + } + + private KeyManagerUtils() { + // Not instantiable } } diff --git a/src/main/java/org/apache/commons/net/util/ListenerList.java b/src/main/java/org/apache/commons/net/util/ListenerList.java index 10a3ffc..fe8d51d 100644 --- a/src/main/java/org/apache/commons/net/util/ListenerList.java +++ b/src/main/java/org/apache/commons/net/util/ListenerList.java @@ -17,6 +17,8 @@ package org.apache.commons.net.util; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.EventListener; import java.util.Iterator; @@ -25,42 +27,48 @@ import java.util.concurrent.CopyOnWriteArrayList; /** */ -public class ListenerList implements Serializable, Iterable<EventListener> -{ +public class ListenerList implements Serializable, Iterable<EventListener> { private static final long serialVersionUID = -1934227607974228213L; - private final CopyOnWriteArrayList<EventListener> __listeners; + private final CopyOnWriteArrayList<EventListener> listeners; - public ListenerList() - { - __listeners = new CopyOnWriteArrayList<EventListener>(); + public ListenerList() { + listeners = new CopyOnWriteArrayList<>(); } - public void addListener(EventListener listener) - { - __listeners.add(listener); + public void addListener(final EventListener listener) { + listeners.add(listener); } - public void removeListener(EventListener listener) - { - __listeners.remove(listener); - } - - public int getListenerCount() - { - return __listeners.size(); + public int getListenerCount() { + return listeners.size(); } /** * Return an {@link Iterator} for the {@link EventListener} instances. * * @return an {@link Iterator} for the {@link EventListener} instances - * @since 2.0 - * TODO Check that this is a good defensive strategy + * @since 2.0 TODO Check that this is a good defensive strategy */ @Override public Iterator<EventListener> iterator() { - return __listeners.iterator(); + return listeners.iterator(); + } + + private void readObject(final ObjectInputStream in) { + throw new UnsupportedOperationException("Serialization is not supported"); + } + + /* + * Serialization is unnecessary for this class. Reject attempts to do so until such time as the Serializable attribute can be dropped. + */ + + public void removeListener(final EventListener listener) { + listeners.remove(listener); + } + + private void writeObject(final ObjectOutputStream out) { + throw new UnsupportedOperationException("Serialization is not supported"); } } diff --git a/src/main/java/org/apache/commons/net/util/NetConstants.java b/src/main/java/org/apache/commons/net/util/NetConstants.java new file mode 100644 index 0000000..666f12e --- /dev/null +++ b/src/main/java/org/apache/commons/net/util/NetConstants.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.net.util; + +import java.security.cert.X509Certificate; + +/** + * Constants provided as public only for our own implementation, you can consider this private for now. + * + * @since 3.8.0 + */ +public class NetConstants { + + /** + * An empty immutable {@code String} array. + */ + public static final String[] EMPTY_STRING_ARRAY = {}; + + /** + * An empty immutable {@code byte} array. + */ + public static final byte[] EMPTY_BTYE_ARRAY = {}; + + /** + * An empty immutable {link X509Certificate} array. + */ + public static final X509Certificate[] EMPTY_X509_CERTIFICATE_ARRAY = {}; + + /** + * The index value when the end of the stream has been reached {@code -1}. + * + * @since 3.9.0 + */ + public static final int EOS = -1; + + /** + * Prevents instantiation. + */ + private NetConstants() { + } +} diff --git a/src/main/java/org/apache/commons/net/util/SSLContextUtils.java b/src/main/java/org/apache/commons/net/util/SSLContextUtils.java index 61c8dda..7b44c99 100644 --- a/src/main/java/org/apache/commons/net/util/SSLContextUtils.java +++ b/src/main/java/org/apache/commons/net/util/SSLContextUtils.java @@ -20,54 +20,55 @@ package org.apache.commons.net.util; import java.io.IOException; import java.security.GeneralSecurityException; + import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; /** * General utilities for SSLContext. + * * @since 3.0 */ public class SSLContextUtils { - private SSLContextUtils() { - // Not instantiable - } - /** - * Create and initialise an SSLContext. - * @param protocol the protocol used to instatiate the context - * @param keyManager the key manager, may be {@code null} + * Create and initialize an SSLContext. + * + * @param protocol the protocol used to instatiate the context + * @param keyManager the key manager, may be {@code null} * @param trustManager the trust manager, may be {@code null} - * @return the initialised context. + * @return the initialized context. * @throws IOException this is used to wrap any {@link GeneralSecurityException} that occurs */ - public static SSLContext createSSLContext(String protocol, KeyManager keyManager, TrustManager trustManager) - throws IOException { - return createSSLContext(protocol, - keyManager == null ? null : new KeyManager[] { keyManager }, + public static SSLContext createSSLContext(final String protocol, final KeyManager keyManager, final TrustManager trustManager) throws IOException { + return createSSLContext(protocol, keyManager == null ? null : new KeyManager[] { keyManager }, trustManager == null ? null : new TrustManager[] { trustManager }); } /** - * Create and initialise an SSLContext. - * @param protocol the protocol used to instatiate the context - * @param keyManagers the array of key managers, may be {@code null} but array entries must not be {@code null} + * Create and initialize an SSLContext. + * + * @param protocol the protocol used to instatiate the context + * @param keyManagers the array of key managers, may be {@code null} but array entries must not be {@code null} * @param trustManagers the array of trust managers, may be {@code null} but array entries must not be {@code null} - * @return the initialised context. + * @return the initialized context. * @throws IOException this is used to wrap any {@link GeneralSecurityException} that occurs */ - public static SSLContext createSSLContext(String protocol, KeyManager[] keyManagers, TrustManager[] trustManagers) - throws IOException { - SSLContext ctx; + public static SSLContext createSSLContext(final String protocol, final KeyManager[] keyManagers, final TrustManager[] trustManagers) throws IOException { + final SSLContext ctx; try { ctx = SSLContext.getInstance(protocol); - ctx.init(keyManagers, trustManagers, /*SecureRandom*/ null); - } catch (GeneralSecurityException e) { - IOException ioe = new IOException("Could not initialize SSL context"); + ctx.init(keyManagers, trustManagers, /* SecureRandom */ null); + } catch (final GeneralSecurityException e) { + final IOException ioe = new IOException("Could not initialize SSL context"); ioe.initCause(e); throw ioe; } return ctx; } + + private SSLContextUtils() { + // Not instantiable + } } diff --git a/src/main/java/org/apache/commons/net/util/SSLSocketUtils.java b/src/main/java/org/apache/commons/net/util/SSLSocketUtils.java index bd8ce84..5485a8b 100644 --- a/src/main/java/org/apache/commons/net/util/SSLSocketUtils.java +++ b/src/main/java/org/apache/commons/net/util/SSLSocketUtils.java @@ -18,46 +18,33 @@ package org.apache.commons.net.util; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - +import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSocket; /** * General utilities for SSLSocket. + * * @since 3.4 */ public class SSLSocketUtils { - private SSLSocketUtils() { - // Not instantiable - } /** * Enable the HTTPS endpoint identification algorithm on an SSLSocket. + * * @param socket the SSL socket - * @return {@code true} on success (this is only supported on Java 1.7+) + * @return {@code true} on success */ - public static boolean enableEndpointNameVerification(SSLSocket socket) { - try { - Class<?> cls = Class.forName("javax.net.ssl.SSLParameters"); - Method setEndpointIdentificationAlgorithm = cls.getDeclaredMethod("setEndpointIdentificationAlgorithm", String.class); - Method getSSLParameters = SSLSocket.class.getDeclaredMethod("getSSLParameters"); - Method setSSLParameters = SSLSocket.class.getDeclaredMethod("setSSLParameters", cls); - if (setEndpointIdentificationAlgorithm != null && getSSLParameters != null && setSSLParameters != null) { - Object sslParams = getSSLParameters.invoke(socket); - if (sslParams != null) { - setEndpointIdentificationAlgorithm.invoke(sslParams, "HTTPS"); - setSSLParameters.invoke(socket, sslParams); - return true; - } - } - } catch (SecurityException e) { // Ignored - } catch (ClassNotFoundException e) { // Ignored - } catch (NoSuchMethodException e) { // Ignored - } catch (IllegalArgumentException e) { // Ignored - } catch (IllegalAccessException e) { // Ignored - } catch (InvocationTargetException e) { // Ignored + public static boolean enableEndpointNameVerification(final SSLSocket socket) { + final SSLParameters sslParameters = socket.getSSLParameters(); + if (sslParameters != null) { + sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); + socket.setSSLParameters(sslParameters); + return true; } return false; } + + private SSLSocketUtils() { + // Not instantiable + } } diff --git a/src/main/java/org/apache/commons/net/util/SubnetUtils.java b/src/main/java/org/apache/commons/net/util/SubnetUtils.java index 4c7ba5c..e93b75e 100644 --- a/src/main/java/org/apache/commons/net/util/SubnetUtils.java +++ b/src/main/java/org/apache/commons/net/util/SubnetUtils.java @@ -20,343 +20,356 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * A class that performs some subnet calculations given a network address and a subnet mask. + * Performs some subnet calculations given a network address and a subnet mask. + * * @see "http://www.faqs.org/rfcs/rfc1519.html" * @since 2.0 */ public class SubnetUtils { - private static final String IP_ADDRESS = "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})"; - private static final String SLASH_FORMAT = IP_ADDRESS + "/(\\d{1,3})"; - private static final Pattern addressPattern = Pattern.compile(IP_ADDRESS); - private static final Pattern cidrPattern = Pattern.compile(SLASH_FORMAT); - private static final int NBITS = 32; - - private int netmask = 0; - private int address = 0; - private int network = 0; - private int broadcast = 0; - - /** Whether the broadcast/network address are included in host count */ - private boolean inclusiveHostCount = false; - - - /** - * Constructor that takes a CIDR-notation string, e.g. "192.168.0.1/16" - * @param cidrNotation A CIDR-notation string, e.g. "192.168.0.1/16" - * @throws IllegalArgumentException if the parameter is invalid, - * i.e. does not match n.n.n.n/m where n=1-3 decimal digits, m = 1-3 decimal digits in range 1-32 - */ - public SubnetUtils(String cidrNotation) { - calculate(cidrNotation); - } - - /** - * Constructor that takes a dotted decimal address and a dotted decimal mask. - * @param address An IP address, e.g. "192.168.0.1" - * @param mask A dotted decimal netmask e.g. "255.255.0.0" - * @throws IllegalArgumentException if the address or mask is invalid, - * i.e. does not match n.n.n.n where n=1-3 decimal digits and the mask is not all zeros - */ - public SubnetUtils(String address, String mask) { - calculate(toCidrNotation(address, mask)); - } - - - /** - * Returns <code>true</code> if the return value of {@link SubnetInfo#getAddressCount()} - * includes the network and broadcast addresses. - * @since 2.2 - * @return true if the hostcount includes the network and broadcast addresses - */ - public boolean isInclusiveHostCount() { - return inclusiveHostCount; - } - - /** - * Set to <code>true</code> if you want the return value of {@link SubnetInfo#getAddressCount()} - * to include the network and broadcast addresses. - * @param inclusiveHostCount true if network and broadcast addresses are to be included - * @since 2.2 - */ - public void setInclusiveHostCount(boolean inclusiveHostCount) { - this.inclusiveHostCount = inclusiveHostCount; - } - - - /** * Convenience container for subnet summary information. - * */ public final class SubnetInfo { - /* Mask to convert unsigned int to a long (i.e. keep 32 bits) */ - private static final long UNSIGNED_INT_MASK = 0x0FFFFFFFFL; - - private SubnetInfo() {} - private int netmask() { return netmask; } - private int network() { return network; } - private int address() { return address; } - private int broadcast() { return broadcast; } + /** Mask to convert unsigned int to a long (i.e. keep 32 bits). */ + private static final long UNSIGNED_INT_MASK = 0x0FFFFFFFFL; - // long versions of the values (as unsigned int) which are more suitable for range checking - private long networkLong() { return network & UNSIGNED_INT_MASK; } - private long broadcastLong(){ return broadcast & UNSIGNED_INT_MASK; } + private SubnetInfo() { + } - private int low() { - return (isInclusiveHostCount() ? network() : - broadcastLong() - networkLong() > 1 ? network() + 1 : 0); + public int asInteger(final String address) { + return toInteger(address); } - private int high() { - return (isInclusiveHostCount() ? broadcast() : - broadcastLong() - networkLong() > 1 ? broadcast() -1 : 0); + private long broadcastLong() { + return broadcast & UNSIGNED_INT_MASK; } /** - * Returns true if the parameter <code>address</code> is in the - * range of usable endpoint addresses for this subnet. This excludes the - * network and broadcast adresses. - * @param address A dot-delimited IPv4 address, e.g. "192.168.0.1" - * @return True if in range, false otherwise + * Converts a 4-element array into dotted decimal format. */ - public boolean isInRange(String address) { - return isInRange(toInteger(address)); + private String format(final int[] octets) { + final int last = octets.length - 1; + final StringBuilder builder = new StringBuilder(); + for (int i = 0;; i++) { + builder.append(octets[i]); + if (i == last) { + return builder.toString(); + } + builder.append('.'); + } + } + + public String getAddress() { + return format(toArray(address)); } /** + * Gets the count of available addresses. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. * - * @param address the address to check - * @return true if it is in range - * @since 3.4 (made public) + * @return the count of addresses, may be zero. + * @throws RuntimeException if the correct count is greater than {@code Integer.MAX_VALUE} + * @deprecated (3.4) use {@link #getAddressCountLong()} instead */ - public boolean isInRange(int address) { - long addLong = address & UNSIGNED_INT_MASK; - long lowLong = low() & UNSIGNED_INT_MASK; - long highLong = high() & UNSIGNED_INT_MASK; - return addLong >= lowLong && addLong <= highLong; + @Deprecated + public int getAddressCount() { + final long countLong = getAddressCountLong(); + if (countLong > Integer.MAX_VALUE) { + throw new RuntimeException("Count is larger than an integer: " + countLong); + } + // Cannot be negative here + return (int) countLong; } - public String getBroadcastAddress() { - return format(toArray(broadcast())); + /** + * Gets the count of available addresses. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * + * @return the count of addresses, may be zero. + * @since 3.4 + */ + public long getAddressCountLong() { + final long b = broadcastLong(); + final long n = networkLong(); + final long count = b - n + (isInclusiveHostCount() ? 1 : -1); + return count < 0 ? 0 : count; } - public String getNetworkAddress() { - return format(toArray(network())); + public String[] getAllAddresses() { + final int ct = getAddressCount(); + final String[] addresses = new String[ct]; + if (ct == 0) { + return addresses; + } + for (int add = low(), j = 0; add <= high(); ++add, ++j) { + addresses[j] = format(toArray(add)); + } + return addresses; } - public String getNetmask() { - return format(toArray(netmask())); + public String getBroadcastAddress() { + return format(toArray(broadcast)); } - public String getAddress() { - return format(toArray(address())); + public String getCidrSignature() { + return format(toArray(address)) + "/" + Integer.bitCount(netmask); } /** - * Return the low address as a dotted IP address. - * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * Gets the high address as a dotted IP address. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. * * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address */ - public String getLowAddress() { - return format(toArray(low())); + public String getHighAddress() { + return format(toArray(high())); } /** - * Return the high address as a dotted IP address. - * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * Gets the low address as a dotted IP address. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. * * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address */ - public String getHighAddress() { - return format(toArray(high())); + public String getLowAddress() { + return format(toArray(low())); + } + + public String getNetmask() { + return format(toArray(netmask)); + } + + public String getNetworkAddress() { + return format(toArray(network)); + } + + public String getNextAddress() { + return format(toArray(address + 1)); + } + + public String getPreviousAddress() { + return format(toArray(address - 1)); + } + + private int high() { + return isInclusiveHostCount() ? broadcast : broadcastLong() - networkLong() > 1 ? broadcast - 1 : 0; } /** - * Get the count of available addresses. - * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. - * @return the count of addresses, may be zero. - * @throws RuntimeException if the correct count is greater than {@code Integer.MAX_VALUE} - * @deprecated (3.4) use {@link #getAddressCountLong()} instead + * Tests if the parameter <code>address</code> is in the range of usable endpoint addresses for this subnet. This excludes the network and broadcast + * addresses by default. Use {@link SubnetUtils#setInclusiveHostCount(boolean)} to change this. + * + * @param address the address to check + * @return true if it is in range + * @since 3.4 (made public) */ - @Deprecated - public int getAddressCount() { - long countLong = getAddressCountLong(); - if (countLong > Integer.MAX_VALUE) { - throw new RuntimeException("Count is larger than an integer: " + countLong); + public boolean isInRange(final int address) { + if (address == 0) { // cannot ever be in range; rejecting now avoids problems with CIDR/31,32 + return false; } - // N.B. cannot be negative - return (int)countLong; + final long addLong = address & UNSIGNED_INT_MASK; + final long lowLong = low() & UNSIGNED_INT_MASK; + final long highLong = high() & UNSIGNED_INT_MASK; + return addLong >= lowLong && addLong <= highLong; } /** - * Get the count of available addresses. - * Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. - * @return the count of addresses, may be zero. - * @since 3.4 + * Tests if the parameter <code>address</code> is in the range of usable endpoint addresses for this subnet. This excludes the network and broadcast + * addresses. Use {@link SubnetUtils#setInclusiveHostCount(boolean)} to change this. + * + * @param address A dot-delimited IPv4 address, e.g. "192.168.0.1" + * @return True if in range, false otherwise */ - public long getAddressCountLong() { - long b = broadcastLong(); - long n = networkLong(); - long count = b - n + (isInclusiveHostCount() ? 1 : -1); - return count < 0 ? 0 : count; + public boolean isInRange(final String address) { + return isInRange(toInteger(address)); } - public int asInteger(String address) { - return toInteger(address); + private int low() { + return isInclusiveHostCount() ? network : broadcastLong() - networkLong() > 1 ? network + 1 : 0; } - public String getCidrSignature() { - return toCidrNotation( - format(toArray(address())), - format(toArray(netmask())) - ); + /** long versions of the values (as unsigned int) which are more suitable for range checking. */ + private long networkLong() { + return network & UNSIGNED_INT_MASK; } - public String[] getAllAddresses() { - int ct = getAddressCount(); - String[] addresses = new String[ct]; - if (ct == 0) { - return addresses; - } - for (int add = low(), j=0; add <= high(); ++add, ++j) { - addresses[j] = format(toArray(add)); + /** + * Converts a packed integer address into a 4-element array + */ + private int[] toArray(final int val) { + final int ret[] = new int[4]; + for (int j = 3; j >= 0; --j) { + ret[j] |= val >>> 8 * (3 - j) & 0xff; } - return addresses; + return ret; } /** * {@inheritDoc} + * * @since 2.2 */ @Override public String toString() { final StringBuilder buf = new StringBuilder(); - buf.append("CIDR Signature:\t[").append(getCidrSignature()).append("]") - .append(" Netmask: [").append(getNetmask()).append("]\n") - .append("Network:\t[").append(getNetworkAddress()).append("]\n") - .append("Broadcast:\t[").append(getBroadcastAddress()).append("]\n") - .append("First Address:\t[").append(getLowAddress()).append("]\n") - .append("Last Address:\t[").append(getHighAddress()).append("]\n") - .append("# Addresses:\t[").append(getAddressCount()).append("]\n"); + // @formatter:off + buf.append("CIDR Signature:\t[").append(getCidrSignature()).append("]\n") + .append(" Netmask: [").append(getNetmask()).append("]\n") + .append(" Network: [").append(getNetworkAddress()).append("]\n") + .append(" Broadcast: [").append(getBroadcastAddress()).append("]\n") + .append(" First address: [").append(getLowAddress()).append("]\n") + .append(" Last address: [").append(getHighAddress()).append("]\n") + .append(" Address Count: [").append(getAddressCountLong()).append("]\n"); + // @formatter:on return buf.toString(); } } - /** - * Return a {@link SubnetInfo} instance that contains subnet-specific statistics - * @return new instance - */ - public final SubnetInfo getInfo() { return new SubnetInfo(); } - - /* - * Initialize the internal fields from the supplied CIDR mask - */ - private void calculate(String mask) { - Matcher matcher = cidrPattern.matcher(mask); - - if (matcher.matches()) { - address = matchAddress(matcher); + private static final String IP_ADDRESS = "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})"; + private static final String SLASH_FORMAT = IP_ADDRESS + "/(\\d{1,2})"; // 0 -> 32 + private static final Pattern ADDRESS_PATTERN = Pattern.compile(IP_ADDRESS); + private static final Pattern CIDR_PATTERN = Pattern.compile(SLASH_FORMAT); - /* Create a binary netmask from the number of bits specification /x */ - int cidrPart = rangeCheck(Integer.parseInt(matcher.group(5)), 0, NBITS); - for (int j = 0; j < cidrPart; ++j) { - netmask |= (1 << 31 - j); - } + private static final int NBITS = 32; - /* Calculate base network address */ - network = (address & netmask); + private static final String PARSE_FAIL = "Could not parse [%s]"; - /* Calculate broadcast address */ - broadcast = network | ~(netmask); - } else { - throw new IllegalArgumentException("Could not parse [" + mask + "]"); + /* + * Extracts the components of a dotted decimal address and pack into an integer using a regex match + */ + private static int matchAddress(final Matcher matcher) { + int addr = 0; + for (int i = 1; i <= 4; ++i) { + final int n = rangeCheck(Integer.parseInt(matcher.group(i)), 0, 255); + addr |= (n & 0xff) << 8 * (4 - i); } + return addr; } /* - * Convert a dotted decimal format address to a packed integer format + * Checks integer boundaries. Checks if a value x is in the range [begin,end]. Returns x if it is in range, throws an exception otherwise. */ - private int toInteger(String address) { - Matcher matcher = addressPattern.matcher(address); - if (matcher.matches()) { - return matchAddress(matcher); - } else { - throw new IllegalArgumentException("Could not parse [" + address + "]"); + private static int rangeCheck(final int value, final int begin, final int end) { + if (value >= begin && value <= end) { // (begin,end] + return value; } + throw new IllegalArgumentException("Value [" + value + "] not in range [" + begin + "," + end + "]"); } /* - * Convenience method to extract the components of a dotted decimal address and - * pack into an integer using a regex match + * Converts a dotted decimal format address to a packed integer format */ - private int matchAddress(Matcher matcher) { - int addr = 0; - for (int i = 1; i <= 4; ++i) { - int n = (rangeCheck(Integer.parseInt(matcher.group(i)), 0, 255)); - addr |= ((n & 0xff) << 8*(4-i)); + private static int toInteger(final String address) { + final Matcher matcher = ADDRESS_PATTERN.matcher(address); + if (matcher.matches()) { + return matchAddress(matcher); } - return addr; + throw new IllegalArgumentException(String.format(PARSE_FAIL, address)); } - /* - * Convert a packed integer address into a 4-element array + private final int netmask; + + private final int address; + + private final int network; + + private final int broadcast; + + /** Whether the broadcast/network address are included in host count */ + private boolean inclusiveHostCount; + + /** + * Constructs an instance from a CIDR-notation string, e.g. "192.168.0.1/16" + * + * @param cidrNotation A CIDR-notation string, e.g. "192.168.0.1/16" + * @throws IllegalArgumentException if the parameter is invalid, i.e. does not match n.n.n.n/m where n=1-3 decimal digits, m = 1-2 decimal digits in range + * 0-32 */ - private int[] toArray(int val) { - int ret[] = new int[4]; - for (int j = 3; j >= 0; --j) { - ret[j] |= ((val >>> 8*(3-j)) & (0xff)); + public SubnetUtils(final String cidrNotation) { + final Matcher matcher = CIDR_PATTERN.matcher(cidrNotation); + + if (!matcher.matches()) { + throw new IllegalArgumentException(String.format(PARSE_FAIL, cidrNotation)); } - return ret; + this.address = matchAddress(matcher); + + // Create a binary netmask from the number of bits specification /x + + final int trailingZeroes = NBITS - rangeCheck(Integer.parseInt(matcher.group(5)), 0, NBITS); + + // + // An IPv4 netmask consists of 32 bits, a contiguous sequence + // of the specified number of ones followed by all zeros. + // So, it can be obtained by shifting an unsigned integer (32 bits) to the left by + // the number of trailing zeros which is (32 - the # bits specification). + // Note that there is no unsigned left shift operator, so we have to use + // a long to ensure that the left-most bit is shifted out correctly. + // + this.netmask = (int) (0x0FFFFFFFFL << trailingZeroes); + + // Calculate base network address + this.network = address & netmask; + + // Calculate broadcast address + this.broadcast = network | ~netmask; } - /* - * Convert a 4-element array into dotted decimal format + /** + * Constructs an instance from a dotted decimal address and a dotted decimal mask. + * + * @param address An IP address, e.g. "192.168.0.1" + * @param mask A dotted decimal netmask e.g. "255.255.0.0" + * @throws IllegalArgumentException if the address or mask is invalid, i.e. does not match n.n.n.n where n=1-3 decimal digits and the mask is not all zeros */ - private String format(int[] octets) { - StringBuilder str = new StringBuilder(); - for (int i =0; i < octets.length; ++i){ - str.append(octets[i]); - if (i != octets.length - 1) { - str.append("."); - } + public SubnetUtils(final String address, final String mask) { + this.address = toInteger(address); + this.netmask = toInteger(mask); + + if ((this.netmask & -this.netmask) - 1 != ~this.netmask) { + throw new IllegalArgumentException(String.format(PARSE_FAIL, mask)); } - return str.toString(); + + // Calculate base network address + this.network = this.address & this.netmask; + + // Calculate broadcast address + this.broadcast = this.network | ~this.netmask; } - /* - * Convenience function to check integer boundaries. - * Checks if a value x is in the range [begin,end]. - * Returns x if it is in range, throws an exception otherwise. + /** + * Gets a {@link SubnetInfo} instance that contains subnet-specific statistics + * + * @return new instance */ - private int rangeCheck(int value, int begin, int end) { - if (value >= begin && value <= end) { // (begin,end] - return value; - } + public final SubnetInfo getInfo() { + return new SubnetInfo(); + } - throw new IllegalArgumentException("Value [" + value + "] not in range ["+begin+","+end+"]"); + public SubnetUtils getNext() { + return new SubnetUtils(getInfo().getNextAddress(), getInfo().getNetmask()); } - /* - * Count the number of 1-bits in a 32-bit integer using a divide-and-conquer strategy - * see Hacker's Delight section 5.1 + public SubnetUtils getPrevious() { + return new SubnetUtils(getInfo().getPreviousAddress(), getInfo().getNetmask()); + } + + /** + * Tests if the return value of {@link SubnetInfo#getAddressCount()} includes the network and broadcast addresses. + * + * @since 2.2 + * @return true if the host count includes the network and broadcast addresses */ - int pop(int x) { - x = x - ((x >>> 1) & 0x55555555); - x = (x & 0x33333333) + ((x >>> 2) & 0x33333333); - x = (x + (x >>> 4)) & 0x0F0F0F0F; - x = x + (x >>> 8); - x = x + (x >>> 16); - return x & 0x0000003F; + public boolean isInclusiveHostCount() { + return inclusiveHostCount; } - /* Convert two dotted decimal addresses to a single xxx.xxx.xxx.xxx/yy format - * by counting the 1-bit population in the mask address. (It may be better to count - * NBITS-#trailing zeroes for this case) + /** + * Sets to <code>true</code> if you want the return value of {@link SubnetInfo#getAddressCount()} to include the network and broadcast addresses. This also + * applies to {@link SubnetInfo#isInRange(int)} + * + * @param inclusiveHostCount true if network and broadcast addresses are to be included + * @since 2.2 */ - private String toCidrNotation(String addr, String mask) { - return addr + "/" + pop(toInteger(mask)); + public void setInclusiveHostCount(final boolean inclusiveHostCount) { + this.inclusiveHostCount = inclusiveHostCount; } + } diff --git a/src/main/java/org/apache/commons/net/util/TrustManagerUtils.java b/src/main/java/org/apache/commons/net/util/TrustManagerUtils.java index c2649f7..9efa550 100644 --- a/src/main/java/org/apache/commons/net/util/TrustManagerUtils.java +++ b/src/main/java/org/apache/commons/net/util/TrustManagerUtils.java @@ -30,15 +30,13 @@ import javax.net.ssl.X509TrustManager; * * @since 3.0 */ -public final class TrustManagerUtils -{ - private static final X509Certificate[] EMPTY_X509CERTIFICATE_ARRAY = new X509Certificate[]{}; +public final class TrustManagerUtils { private static class TrustManager implements X509TrustManager { private final boolean checkServerValidity; - TrustManager(boolean checkServerValidity) { + TrustManager(final boolean checkServerValidity) { this.checkServerValidity = checkServerValidity; } @@ -46,18 +44,13 @@ public final class TrustManagerUtils * Never generates a CertificateException. */ @Override - public void checkClientTrusted(X509Certificate[] certificates, String authType) - { - return; + public void checkClientTrusted(final X509Certificate[] certificates, final String authType) { } @Override - public void checkServerTrusted(X509Certificate[] certificates, String authType) - throws CertificateException - { + public void checkServerTrusted(final X509Certificate[] certificates, final String authType) throws CertificateException { if (checkServerValidity) { - for (X509Certificate certificate : certificates) - { + for (final X509Certificate certificate : certificates) { certificate.checkValidity(); } } @@ -67,51 +60,49 @@ public final class TrustManagerUtils * @return an empty array of certificates */ @Override - public X509Certificate[] getAcceptedIssuers() - { - return EMPTY_X509CERTIFICATE_ARRAY; + public X509Certificate[] getAcceptedIssuers() { + return NetConstants.EMPTY_X509_CERTIFICATE_ARRAY; } } - private static final X509TrustManager ACCEPT_ALL=new TrustManager(false); + private static final X509TrustManager ACCEPT_ALL = new TrustManager(false); - private static final X509TrustManager CHECK_SERVER_VALIDITY=new TrustManager(true); + private static final X509TrustManager CHECK_SERVER_VALIDITY = new TrustManager(true); /** * Generate a TrustManager that performs no checks. * * @return the TrustManager */ - public static X509TrustManager getAcceptAllTrustManager(){ + public static X509TrustManager getAcceptAllTrustManager() { return ACCEPT_ALL; } - /** - * Generate a TrustManager that checks server certificates for validity, - * but otherwise performs no checks. - * - * @return the validating TrustManager - */ - public static X509TrustManager getValidateServerCertificateTrustManager(){ - return CHECK_SERVER_VALIDITY; - } - /** * Return the default TrustManager provided by the JVM. * <p> * This should be the same as the default used by - * {@link javax.net.ssl.SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], java.security.SecureRandom) - * SSLContext#init(KeyManager[], TrustManager[], SecureRandom)} - * when the TrustManager parameter is set to {@code null} + * {@link javax.net.ssl.SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], java.security.SecureRandom) SSLContext#init(KeyManager[], + * TrustManager[], SecureRandom)} when the TrustManager parameter is set to {@code null} + * * @param keyStore the KeyStore to use, may be {@code null} * @return the default TrustManager * @throws GeneralSecurityException if an error occurs */ - public static X509TrustManager getDefaultTrustManager(KeyStore keyStore) throws GeneralSecurityException { - String defaultAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); - TrustManagerFactory instance = TrustManagerFactory.getInstance(defaultAlgorithm); + public static X509TrustManager getDefaultTrustManager(final KeyStore keyStore) throws GeneralSecurityException { + final String defaultAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + final TrustManagerFactory instance = TrustManagerFactory.getInstance(defaultAlgorithm); instance.init(keyStore); return (X509TrustManager) instance.getTrustManagers()[0]; } + /** + * Generate a TrustManager that checks server certificates for validity, but otherwise performs no checks. + * + * @return the validating TrustManager + */ + public static X509TrustManager getValidateServerCertificateTrustManager() { + return CHECK_SERVER_VALIDITY; + } + } diff --git a/src/main/java/org/apache/commons/net/whois/WhoisClient.java b/src/main/java/org/apache/commons/net/whois/WhoisClient.java index 816f58b..e2218ec 100644 --- a/src/main/java/org/apache/commons/net/whois/WhoisClient.java +++ b/src/main/java/org/apache/commons/net/whois/WhoisClient.java @@ -22,87 +22,71 @@ import java.io.InputStream; import org.apache.commons.net.finger.FingerClient; -/*** - * The WhoisClient class implements the client side of the Internet Whois - * Protocol defined in RFC 954. To query a host you create a - * WhoisClient instance, connect to the host, query the host, and finally - * disconnect from the host. If the whois service you want to query is on - * a non-standard port, connect to the host at that port. - * Here's a sample use: +/** + * The WhoisClient class implements the client side of the Internet Whois Protocol defined in RFC 954. To query a host you create a WhoisClient instance, + * connect to the host, query the host, and finally disconnect from the host. If the whois service you want to query is on a non-standard port, connect to the + * host at that port. Here's a sample use: + * * <pre> - * WhoisClient whois; + * WhoisClient whois; * - * whois = new WhoisClient(); + * whois = new WhoisClient(); * - * try { - * whois.connect(WhoisClient.DEFAULT_HOST); - * System.out.println(whois.query("foobar")); - * whois.disconnect(); - * } catch(IOException e) { - * System.err.println("Error I/O exception: " + e.getMessage()); - * return; - * } + * try { + * whois.connect(WhoisClient.DEFAULT_HOST); + * System.out.println(whois.query("foobar")); + * whois.disconnect(); + * } catch (IOException e) { + * System.err.println("Error I/O exception: " + e.getMessage()); + * return; + * } * </pre> * * * - ***/ + */ -public final class WhoisClient extends FingerClient -{ - /*** - * The default whois host to query. It is set to whois.internic.net. - ***/ +public final class WhoisClient extends FingerClient { + /** + * The default whois host to query. It is set to whois.internic.net. + */ public static final String DEFAULT_HOST = "whois.internic.net"; - /*** - * The default whois port. It is set to 43 according to RFC 954. - ***/ + /** + * The default whois port. It is set to 43 according to RFC 954. + */ public static final int DEFAULT_PORT = 43; - - /*** - * The default whois constructor. Initializes the - * default port to <code> DEFAULT_PORT </code>. - ***/ - public WhoisClient() - { + /** + * The default whois constructor. Initializes the default port to <code> DEFAULT_PORT </code>. + */ + public WhoisClient() { setDefaultPort(DEFAULT_PORT); } - /*** - * Queries the connected whois server for information regarding - * the given handle. It is up to the programmer to be familiar with the - * handle syntax of the whois server. You must first connect to a whois - * server before calling this method, and you should disconnect afterward. + /** + * Queries the connected whois server for information regarding the given handle and returns the InputStream of the network connection. It is up to the + * programmer to be familiar with the handle syntax of the whois server. You must first connect to a finger server before calling this method, and you + * should disconnect after finishing reading the stream. * - * @param handle The handle to lookup. - * @return The result of the whois query. - * @throws IOException If an I/O error occurs during the operation. - ***/ - public String query(String handle) throws IOException - { - return query(false, handle); + * @param handle The handle to lookup. + * @return The InputStream of the network connection of the whois query. Can be read to obtain whois results. + * @throws IOException If an I/O error occurs during the operation. + */ + public InputStream getInputStream(final String handle) throws IOException { + return getInputStream(false, handle); } - - /*** - * Queries the connected whois server for information regarding - * the given handle and returns the InputStream of the network connection. - * It is up to the programmer to be familiar with the handle syntax - * of the whois server. You must first connect to a finger server before - * calling this method, and you should disconnect after finishing reading - * the stream. + /** + * Queries the connected whois server for information regarding the given handle. It is up to the programmer to be familiar with the handle syntax of the + * whois server. You must first connect to a whois server before calling this method, and you should disconnect afterward. * - * @param handle The handle to lookup. - * @return The InputStream of the network connection of the whois query. - * Can be read to obtain whois results. - * @throws IOException If an I/O error occurs during the operation. - ***/ - public InputStream getInputStream(String handle) throws IOException - { - return getInputStream(false, handle); + * @param handle The handle to lookup. + * @return The result of the whois query. + * @throws IOException If an I/O error occurs during the operation. + */ + public String query(final String handle) throws IOException { + return query(false, handle); } } - diff --git a/src/main/resources/examples/examples.properties b/src/main/resources/examples/examples.properties deleted file mode 100644 index 5108eb7..0000000 --- a/src/main/resources/examples/examples.properties +++ /dev/null @@ -1,51 +0,0 @@ -################################################################################ -# Apache Commons Net Examples Property file -################################################################################ - -## Licensed to the Apache Software Foundation (ASF) under one or more -## contributor license agreements. See the NOTICE file distributed with -## this work for additional information regarding copyright ownership. -## The ASF licenses this file to You under the Apache License, Version 2.0 -## (the "License"); you may not use this file except in compliance with -## the License. You may obtain a copy of the License at -## -## http://www.apache.org/licenses/LICENSE-2.0 -## -## Unless required by applicable law or agreed to in writing, software -## distributed under the License is distributed on an "AS IS" BASIS, -## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -## See the License for the specific language governing permissions and -## limitations under the License. - -# List of aliases for example class names. -# Note that the "/" separators are converted to "." - -# alias full class name -SubnetUtilsExample examples/cidr/SubnetUtilsExample -FTPClientExample examples/ftp/FTPClientExample -ServerToServerFTP examples/ftp/ServerToServerFTP -TFTPExample examples/ftp/TFTPExample -IMAPExportMbox examples/mail/IMAPExportMbox -IMAPImportMbox examples/mail/IMAPImportMbox -IMAPMail examples/mail/IMAPMail -POP3Mail examples/mail/POP3Mail -SMTPMail examples/mail/SMTPMail -ArticleReader examples/nntp/ArticleReader -ExtendedNNTPOps examples/nntp/ExtendedNNTPOps -ListNewsgroups examples/nntp/ListNewsgroups -MessageThreading examples/nntp/MessageThreading -PostMessage examples/nntp/PostMessage -NTPClient examples/ntp/NTPClient -SimpleNTPServer examples/ntp/SimpleNTPServer -TimeClient examples/ntp/TimeClient -TelnetClientExample examples/telnet/TelnetClientExample -WeatherTelnet examples/telnet/WeatherTelnet -chargen examples/unix/chargen -daytime examples/unix/daytime -echo examples/unix/echo -finger examples/unix/finger -fwhois examples/unix/fwhois -rdate examples/unix/rdate -rexec examples/unix/rexec -rlogin examples/unix/rlogin -rshell examples/unix/rshell diff --git a/src/main/resources/org/apache/commons/net/examples/examples.properties b/src/main/resources/org/apache/commons/net/examples/examples.properties new file mode 100644 index 0000000..f295937 --- /dev/null +++ b/src/main/resources/org/apache/commons/net/examples/examples.properties @@ -0,0 +1,51 @@ +################################################################################ +# Apache Commons Net Examples Property file +################################################################################ + +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. + +# List of aliases for example class names. + +# alias full class name +SubnetUtilsExample org.apache.commons.net.examples.cidr.SubnetUtilsExample +FTPClientExample org.apache.commons.net.examples.ftp.FTPClientExample +ServerToServerFTP org.apache.commons.net.examples.ftp.ServerToServerFTP +TFTPExample org.apache.commons.net.examples.ftp.TFTPExample +IMAPExportMbox org.apache.commons.net.examples.mail.IMAPExportMbox +IMAPImportMbox org.apache.commons.net.examples.mail.IMAPImportMbox +IMAPMail org.apache.commons.net.examples.mail.IMAPMail +POP3ExportMbox org.apache.commons.net.examples.mail.POP3ExportMbox +POP3Mail org.apache.commons.net.examples.mail.POP3Mail +SMTPMail org.apache.commons.net.examples.mail.SMTPMail +ArticleReader org.apache.commons.net.examples.nntp.ArticleReader +ExtendedNNTPOps org.apache.commons.net.examples.nntp.ExtendedNNTPOps +ListNewsgroups org.apache.commons.net.examples.nntp.ListNewsgroups +MessageThreading org.apache.commons.net.examples.nntp.MessageThreading +PostMessage org.apache.commons.net.examples.nntp.PostMessage +NTPClient org.apache.commons.net.examples.ntp.NTPClient +SimpleNTPServer org.apache.commons.net.examples.ntp.SimpleNTPServer +TimeClient org.apache.commons.net.examples.ntp.TimeClient +TelnetClientExample org.apache.commons.net.examples.telnet.TelnetClientExample +WeatherTelnet org.apache.commons.net.examples.telnet.WeatherTelnet +chargen org.apache.commons.net.examples.unix.chargen +daytime org.apache.commons.net.examples.unix.daytime +echo org.apache.commons.net.examples.unix.echo +finger org.apache.commons.net.examples.unix.finger +fwhois org.apache.commons.net.examples.unix.fwhois +rdate org.apache.commons.net.examples.unix.rdate +rexec org.apache.commons.net.examples.unix.rexec +rlogin org.apache.commons.net.examples.unix.rlogin +rshell org.apache.commons.net.examples.unix.rshell diff --git a/src/site/site.xml b/src/site/site.xml index 9157dc0..5affdcd 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -26,18 +26,18 @@ <body> <menu name="Documentation"> - <item name="Overview" href="index.html"/> - <item name="Migration How-to" href="migration.html"/> - <item name="FAQ" href="faq.html"/> - <item name="Download" href="/download_net.cgi"/> - <item name="Javadoc 3.5 (Java 1.6)" href="/javadocs/api-3.5/index.html"/> - <item name="Javadoc 1.4.1 (Java 1.3)" href="/javadocs/api-1.4.1/index.html"/> + <item name="Overview" href="index.html"/> + <item name="Migration How-to" href="migration.html"/> + <item name="FAQ" href="faq.html"/> + <item name="Download" href="/download_net.cgi"/> + <item name="Javadoc" href="/apidocs/index.html"/> + <item name="Javadoc Archive" href="https://javadoc.io/doc/commons-net/commons-net/latest/index.html"/> </menu> <menu name="Development"> - <item name="Coding Specifications" href="code-standards.html"/> - <item name="Mailing lists" href="mail-lists.html"/> - <item name="Issue Tracking" href="issue-tracking.html"/> - <item name="SVN repository" href="source-repository.html"/> + <item name="Coding Specifications" href="code-standards.html"/> + <item name="Mailing lists" href="mail-lists.html"/> + <item name="Issue Tracking" href="issue-tracking.html"/> + <item name="Repository" href="scm.html"/> </menu> </body> diff --git a/src/site/xdoc/code-standards.xml b/src/site/xdoc/code-standards.xml index 6cc29a3..24b782e 100644 --- a/src/site/xdoc/code-standards.xml +++ b/src/site/xdoc/code-standards.xml @@ -115,10 +115,10 @@ Platform specific files should have the platform specific linefeeds. </p> <p> -5. JavaDoc <strong>MUST</strong> exist on all public and protected methods. -JavaDoc on private and default access methods and members is preferred and +5. Javadoc <strong>MUST</strong> exist on all public and protected methods. +Javadoc on private and default access methods and members is preferred and encouraged. If your code modifications use an existing class/method/variable -which lacks JavaDoc, it is required that you add it. This will improve the +which lacks Javadoc, it is required that you add it. This will improve the project as a whole. </p> diff --git a/src/site/xdoc/download_net.xml b/src/site/xdoc/download_net.xml index 1766846..d3ac9c7 100644 --- a/src/site/xdoc/download_net.xml +++ b/src/site/xdoc/download_net.xml @@ -26,22 +26,24 @@ limitations under the License. | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates | +======================================================================+ | | - | 1) Re-generate using: mvn commons:download-page | + | 1) Re-generate using: mvn commons-build:download-page | | | | 2) Set the following properties in the component's pom: | - | - commons.componentid (required, alphabetic, lower case) | + | - commons.componentid (required, alphabetic, lower case) | | - commons.release.version (required) | | - commons.release.name (required) | | - commons.binary.suffix (optional) | | (defaults to "-bin", set to "" for pre-maven2 releases) | | - commons.release.desc (optional) | | - commons.release.subdir (optional) | + | - commons.release.hash (optional, lowercase, default sha512) | | | - | - commons.release.2/3.version (conditional) | - | - commons.release.2/3.name (conditional) | - | - commons.release.2/3.binary.suffix (optional) | - | - commons.release.2/3.desc (optional) | - | - commons.release.2/3.subdir (optional) | + | - commons.release.[234].version (conditional) | + | - commons.release.[234].name (conditional) | + | - commons.release.[234].binary.suffix (optional) | + | - commons.release.[234].desc (optional) | + | - commons.release.[234].subdir (optional) | + | - commons.release.[234].hash (optional, lowercase, [sha512])| | | | 3) Example Properties | | (commons.release.name inherited by parent: | @@ -64,7 +66,7 @@ limitations under the License. <subsection name="Using a Mirror"> <p> We recommend you use a mirror to download our release - builds, but you <strong>must</strong> <a href="http://www.apache.org/info/verification.html">verify the integrity</a> of + builds, but you <strong>must</strong> <a href="https://www.apache.org/info/verification.html">verify the integrity</a> of the downloaded files using signatures downloaded from our main distribution directories. Recent releases (48 hours) may not yet be available from all the mirrors. @@ -102,71 +104,41 @@ limitations under the License. It is essential that you <a href="https://www.apache.org/info/verification.html">verify the integrity</a> of downloaded files, preferably using the <code>PGP</code> signature (<code>*.asc</code> files); - failing that using the <code>MD5</code> hash (<code>*.md5</code> checksum files). + failing that using the <code>SHA512</code> hash (<code>*.sha512</code> checksum files). </p> <p> - The <a href="https://www.apache.org/dist/commons/KEYS">KEYS</a> + The <a href="https://downloads.apache.org/commons/KEYS">KEYS</a> file contains the public PGP keys used by Apache Commons developers to sign releases. </p> </subsection> </section> - <section name="Apache Commons Net 3.6 (Requires Java 1.6 or later)"> + <section name="Apache Commons Net 3.9.0 (Requires Java 1.8 or later)"> <subsection name="Binaries"> <table> <tr> - <td><a href="[preferred]/commons/net/binaries/commons-net-3.6-bin.tar.gz">commons-net-3.6-bin.tar.gz</a></td> - <td><a href="https://www.apache.org/dist/commons/net/binaries/commons-net-3.6-bin.tar.gz.md5">md5</a></td> - <td><a href="https://www.apache.org/dist/commons/net/binaries/commons-net-3.6-bin.tar.gz.asc">pgp</a></td> + <td><a href="[preferred]/commons/net/binaries/commons-net-3.9.0-bin.tar.gz">commons-net-3.9.0-bin.tar.gz</a></td> + <td><a href="https://downloads.apache.org/commons/net/binaries/commons-net-3.9.0-bin.tar.gz.sha512">sha512</a></td> + <td><a href="https://downloads.apache.org/commons/net/binaries/commons-net-3.9.0-bin.tar.gz.asc">pgp</a></td> </tr> <tr> - <td><a href="[preferred]/commons/net/binaries/commons-net-3.6-bin.zip">commons-net-3.6-bin.zip</a></td> - <td><a href="https://www.apache.org/dist/commons/net/binaries/commons-net-3.6-bin.zip.md5">md5</a></td> - <td><a href="https://www.apache.org/dist/commons/net/binaries/commons-net-3.6-bin.zip.asc">pgp</a></td> + <td><a href="[preferred]/commons/net/binaries/commons-net-3.9.0-bin.zip">commons-net-3.9.0-bin.zip</a></td> + <td><a href="https://downloads.apache.org/commons/net/binaries/commons-net-3.9.0-bin.zip.sha512">sha512</a></td> + <td><a href="https://downloads.apache.org/commons/net/binaries/commons-net-3.9.0-bin.zip.asc">pgp</a></td> </tr> </table> </subsection> <subsection name="Source"> <table> <tr> - <td><a href="[preferred]/commons/net/source/commons-net-3.6-src.tar.gz">commons-net-3.6-src.tar.gz</a></td> - <td><a href="https://www.apache.org/dist/commons/net/source/commons-net-3.6-src.tar.gz.md5">md5</a></td> - <td><a href="https://www.apache.org/dist/commons/net/source/commons-net-3.6-src.tar.gz.asc">pgp</a></td> + <td><a href="[preferred]/commons/net/source/commons-net-3.9.0-src.tar.gz">commons-net-3.9.0-src.tar.gz</a></td> + <td><a href="https://downloads.apache.org/commons/net/source/commons-net-3.9.0-src.tar.gz.sha512">sha512</a></td> + <td><a href="https://downloads.apache.org/commons/net/source/commons-net-3.9.0-src.tar.gz.asc">pgp</a></td> </tr> <tr> - <td><a href="[preferred]/commons/net/source/commons-net-3.6-src.zip">commons-net-3.6-src.zip</a></td> - <td><a href="https://www.apache.org/dist/commons/net/source/commons-net-3.6-src.zip.md5">md5</a></td> - <td><a href="https://www.apache.org/dist/commons/net/source/commons-net-3.6-src.zip.asc">pgp</a></td> - </tr> - </table> - </subsection> - </section> - <section name="Apache Commons Net 1.4.1 (Requires Java 1.3 or later)"> - <subsection name="Binaries"> - <table> - <tr> - <td><a href="[preferred]/commons/net/binaries/commons-net-1.4.1.tar.gz">commons-net-1.4.1.tar.gz</a></td> - <td><a href="https://www.apache.org/dist/commons/net/binaries/commons-net-1.4.1.tar.gz.md5">md5</a></td> - <td><a href="https://www.apache.org/dist/commons/net/binaries/commons-net-1.4.1.tar.gz.asc">pgp</a></td> - </tr> - <tr> - <td><a href="[preferred]/commons/net/binaries/commons-net-1.4.1.zip">commons-net-1.4.1.zip</a></td> - <td><a href="https://www.apache.org/dist/commons/net/binaries/commons-net-1.4.1.zip.md5">md5</a></td> - <td><a href="https://www.apache.org/dist/commons/net/binaries/commons-net-1.4.1.zip.asc">pgp</a></td> - </tr> - </table> - </subsection> - <subsection name="Source"> - <table> - <tr> - <td><a href="[preferred]/commons/net/source/commons-net-1.4.1-src.tar.gz">commons-net-1.4.1-src.tar.gz</a></td> - <td><a href="https://www.apache.org/dist/commons/net/source/commons-net-1.4.1-src.tar.gz.md5">md5</a></td> - <td><a href="https://www.apache.org/dist/commons/net/source/commons-net-1.4.1-src.tar.gz.asc">pgp</a></td> - </tr> - <tr> - <td><a href="[preferred]/commons/net/source/commons-net-1.4.1-src.zip">commons-net-1.4.1-src.zip</a></td> - <td><a href="https://www.apache.org/dist/commons/net/source/commons-net-1.4.1-src.zip.md5">md5</a></td> - <td><a href="https://www.apache.org/dist/commons/net/source/commons-net-1.4.1-src.zip.asc">pgp</a></td> + <td><a href="[preferred]/commons/net/source/commons-net-3.9.0-src.zip">commons-net-3.9.0-src.zip</a></td> + <td><a href="https://downloads.apache.org/commons/net/source/commons-net-3.9.0-src.zip.sha512">sha512</a></td> + <td><a href="https://downloads.apache.org/commons/net/source/commons-net-3.9.0-src.zip.asc">pgp</a></td> </tr> </table> </subsection> diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml index 9c739df..be82bcd 100644 --- a/src/site/xdoc/index.xml +++ b/src/site/xdoc/index.xml @@ -82,12 +82,12 @@ </p> To use one of the sample applications, ensure that the example and main jars are both in the same directory. Then run the class as per the following example: - <pre>java -jar [path/]commons-net-examples-3.5.jar FTPClientExample [parameters]</pre> + <pre>java -jar [path/]commons-net-examples-3.8.0.jar FTPClientExample [parameters]</pre> This uses the helper application which supports shorthand class names. <br/> Alternatively, ensure that the example and main jars are on the classpath. Then invoke the class directly, for example: - <pre>java -cp commons-net-examples-3.5.jar;commons-net-3.5.jar examples/ftp/FTPClientExample [parameters]</pre> + <pre>java -cp commons-net-examples-3.8.0.jar;commons-net-3.8.0.jar examples/ftp/FTPClientExample [parameters]</pre> <subsection name="FTP (package: examples/ftp)"> <ul> @@ -210,7 +210,7 @@ </section> <section name="Further Information"> <p> - For more info, see the JavaDoc, or look at some of the following articles: + For more info, see the Javadoc, or look at some of the following articles: <ul> <li><a href="http://www.informit.com/guides/content.asp?g=java&seqNum=40">http://www.informit.com/guides/content.asp?g=java&seqNum=40</a>Jakarta Commons - Net Class Library</li> <li><a href="http://www.onjava.com/pub/a/onjava/2003/06/25/commons.html?page=3">http://www.onjava.com/pub/a/onjava/2003/06/25/commons.html?page=3</a>Using the Jakarta Commons, Part 1</li> diff --git a/src/site/xdoc/issue-tracking.xml b/src/site/xdoc/issue-tracking.xml index a599b85..113aa6c 100644 --- a/src/site/xdoc/issue-tracking.xml +++ b/src/site/xdoc/issue-tracking.xml @@ -26,7 +26,7 @@ limitations under the License. | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates | +======================================================================+ | | - | 1) Re-generate using: mvn commons:jira-page | + | 1) Re-generate using: mvn commons-build:jira-page | | | | 2) Set the following properties in the component's pom: | | - commons.jira.id (required, alphabetic, upper case) | @@ -85,8 +85,8 @@ limitations under the License. </p> <p> - For more information on subversion and creating patches see the - <a href="http://www.apache.org/dev/contributors.html">Apache Contributors Guide</a>. + For more information on creating patches see the + <a href="https://www.apache.org/dev/contributors.html">Apache Contributors Guide</a>. </p> <p> diff --git a/src/site/xdoc/mail-lists.xml b/src/site/xdoc/mail-lists.xml index 682623a..a569486 100644 --- a/src/site/xdoc/mail-lists.xml +++ b/src/site/xdoc/mail-lists.xml @@ -26,7 +26,7 @@ limitations under the License. | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates | +======================================================================+ | | - | 1) Re-generate using: mvn commons:mail-page | + | 1) Re-generate using: mvn commons-build:mail-page | | | | 2) Set the following properties in the component's pom: | | - commons.componentid (required, alphabetic, lower case) | @@ -49,7 +49,7 @@ limitations under the License. <section name="Overview"> <p> <a href="index.html">Apache Commons Net</a> shares mailing lists with all the other - <a href="http://commons.apache.org/components.html">Commons Components</a>. + <a href="https://commons.apache.org/components.html">Commons Components</a>. To make it easier for people to only read messages related to components they are interested in, the convention in Commons is to prefix the subject line of messages with the component's name, for example: @@ -59,9 +59,9 @@ limitations under the License. </p> <p> Questions related to the usage of Apache Commons Net should be posted to the - <a href="http://mail-archives.apache.org/mod_mbox/commons-user/">User List</a>. + <a href="https://mail-archives.apache.org/mod_mbox/commons-user/">User List</a>. <br /> - The <a href="http://mail-archives.apache.org/mod_mbox/commons-dev/">Developer List</a> + The <a href="https://mail-archives.apache.org/mod_mbox/commons-dev/">Developer List</a> is for questions and discussion related to the development of Apache Commons Net. <br /> Please do not cross-post; developers are also subscribed to the user list. @@ -105,10 +105,12 @@ limitations under the License. <td><a href="mailto:user-subscribe@commons.apache.org">Subscribe</a></td> <td><a href="mailto:user-unsubscribe@commons.apache.org">Unsubscribe</a></td> <td><a href="mailto:user@commons.apache.org?subject=[net]">Post</a></td> - <td><a href="http://mail-archives.apache.org/mod_mbox/commons-user/">mail-archives.apache.org</a></td> - <td><a href="http://markmail.org/list/org.apache.commons.users/">markmail.org</a><br /> - <a href="http://www.mail-archive.com/user@commons.apache.org/">www.mail-archive.com</a><br /> - <a href="http://news.gmane.org/gmane.comp.jakarta.commons.devel">news.gmane.org</a> + <td><a href="https://mail-archives.apache.org/mod_mbox/commons-user/">mail-archives.apache.org</a><br /> + <a href="https://lists.apache.org/list.html?user@commons.apache.org">lists.apache.org</a> + </td> + <td><a href="https://markmail.org/list/org.apache.commons.users/">markmail.org</a><br /> + <a href="https://www.mail-archive.com/user@commons.apache.org/">www.mail-archive.com</a><br /> + <a href="https://news.gmane.org/gmane.comp.jakarta.commons.devel">news.gmane.org</a> </td> </tr> @@ -123,10 +125,12 @@ limitations under the License. <td><a href="mailto:dev-subscribe@commons.apache.org">Subscribe</a></td> <td><a href="mailto:dev-unsubscribe@commons.apache.org">Unsubscribe</a></td> <td><a href="mailto:dev@commons.apache.org?subject=[net]">Post</a></td> - <td><a href="http://mail-archives.apache.org/mod_mbox/commons-dev/">mail-archives.apache.org</a></td> - <td><a href="http://markmail.org/list/org.apache.commons.dev/">markmail.org</a><br /> - <a href="http://www.mail-archive.com/dev@commons.apache.org/">www.mail-archive.com</a><br /> - <a href="http://news.gmane.org/gmane.comp.jakarta.commons.devel">news.gmane.org</a> + <td><a href="https://mail-archives.apache.org/mod_mbox/commons-dev/">mail-archives.apache.org</a><br /> + <a href="https://lists.apache.org/list.html?dev@commons.apache.org">lists.apache.org</a> + </td> + <td><a href="https://markmail.org/list/org.apache.commons.dev/">markmail.org</a><br /> + <a href="https://www.mail-archive.com/dev@commons.apache.org/">www.mail-archive.com</a><br /> + <a href="https://news.gmane.org/gmane.comp.jakarta.commons.devel">news.gmane.org</a> </td> </tr> @@ -141,9 +145,11 @@ limitations under the License. <td><a href="mailto:issues-subscribe@commons.apache.org">Subscribe</a></td> <td><a href="mailto:issues-unsubscribe@commons.apache.org">Unsubscribe</a></td> <td><i>read only</i></td> - <td><a href="http://mail-archives.apache.org/mod_mbox/commons-issues/">mail-archives.apache.org</a></td> - <td><a href="http://markmail.org/list/org.apache.commons.issues/">markmail.org</a><br /> - <a href="http://www.mail-archive.com/issues@commons.apache.org/">www.mail-archive.com</a> + <td><a href="https://mail-archives.apache.org/mod_mbox/commons-issues/">mail-archives.apache.org</a><br /> + <a href="https://lists.apache.org/list.html?issues@commons.apache.org">lists.apache.org</a> + </td> + <td><a href="https://markmail.org/list/org.apache.commons.issues/">markmail.org</a><br /> + <a href="https://www.mail-archive.com/issues@commons.apache.org/">www.mail-archive.com</a> </td> </tr> @@ -152,15 +158,17 @@ limitations under the License. <td> <strong>Commons Commits List</strong> <br /><br /> - Only for e-mails automatically generated by the <a href="source-repository.html">source control</a> sytem. + Only for e-mails automatically generated by the <a href="scm.html">source control</a> system. <br /><br /> </td> <td><a href="mailto:commits-subscribe@commons.apache.org">Subscribe</a></td> <td><a href="mailto:commits-unsubscribe@commons.apache.org">Unsubscribe</a></td> <td><i>read only</i></td> - <td><a href="http://mail-archives.apache.org/mod_mbox/commons-commits/">mail-archives.apache.org</a></td> - <td><a href="http://markmail.org/list/org.apache.commons.commits/">markmail.org</a><br /> - <a href="http://www.mail-archive.com/commits@commons.apache.org/">www.mail-archive.com</a> + <td><a href="https://mail-archives.apache.org/mod_mbox/commons-commits/">mail-archives.apache.org</a><br /> + <a href="https://lists.apache.org/list.html?commits@commons.apache.org">lists.apache.org</a> + </td> + <td><a href="https://markmail.org/list/org.apache.commons.commits/">markmail.org</a><br /> + <a href="https://www.mail-archive.com/commits@commons.apache.org/">www.mail-archive.com</a> </td> </tr> @@ -191,11 +199,13 @@ limitations under the License. <td><a class="externalLink" href="mailto:announce-subscribe@apache.org">Subscribe</a></td> <td><a class="externalLink" href="mailto:announce-unsubscribe@apache.org">Unsubscribe</a></td> <td><i>read only</i></td> - <td><a class="externalLink" href="http://mail-archives.apache.org/mod_mbox/www-announce/">mail-archives.apache.org</a></td> - <td><a class="externalLink" href="http://markmail.org/list/org.apache.announce/">markmail.org</a><br /> - <a class="externalLink" href="http://old.nabble.com/Apache-News-and-Announce-f109.html">old.nabble.com</a><br /> - <a class="externalLink" href="http://www.mail-archive.com/announce@apache.org/">www.mail-archive.com</a><br /> - <a class="externalLink" href="http://news.gmane.org/gmane.comp.apache.announce">news.gmane.org</a> + <td><a class="externalLink" href="https://mail-archives.apache.org/mod_mbox/www-announce/">mail-archives.apache.org</a><br /> + <a class="externalLink" href="https://lists.apache.org/list.html?announce@apache.org">lists.apache.org</a> + </td> + <td><a class="externalLink" href="https://markmail.org/list/org.apache.announce/">markmail.org</a><br /> + <a class="externalLink" href="https://old.nabble.com/Apache-News-and-Announce-f109.html">old.nabble.com</a><br /> + <a class="externalLink" href="https://www.mail-archive.com/announce@apache.org/">www.mail-archive.com</a><br /> + <a class="externalLink" href="https://news.gmane.org/gmane.comp.apache.announce">news.gmane.org</a> </td> </tr> </table> diff --git a/src/test/java/examples/MainTest.java b/src/test/java/examples/MainTest.java deleted file mode 100644 index 8e86eae..0000000 --- a/src/test/java/examples/MainTest.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package examples; - -import static org.junit.Assert.fail; - -import java.io.File; -import java.io.IOException; -import java.net.URLDecoder; -import java.security.CodeSource; -import java.util.Enumeration; -import java.util.Properties; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -import org.junit.Test; - -public class MainTest { - - @Test - public void checkExamplesPropertiesIsComplete() throws Exception { - Properties cp = scanClasses(); - Properties fp = new Properties(); - fp.load(this.getClass().getResourceAsStream("examples.properties")); - @SuppressWarnings("unchecked") // OK - final Enumeration<String> propertyNames = (Enumeration<String>) cp.propertyNames(); - while(propertyNames.hasMoreElements()){ - String c = propertyNames.nextElement(); - String fv = fp.getProperty(c); - final String cv = cp.getProperty(c); - if (fv == null) { - System.out.printf("%-25s %s - missing from examples.properties%n",c,cv); - } else if (!fv.equals(cv)) { - System.out.printf("%-25s %s - expected value %s %n",c,fv,cv); - } - } - } - - private Properties scanClasses() throws IOException { - CodeSource codeSource = Main.class.getProtectionDomain().getCodeSource(); - // ensure special characters are decoded OK by uing the charset - final String sourceFile = URLDecoder.decode(codeSource.getLocation().getFile(),"UTF-8"); - Properties p = new Properties(); - if (sourceFile.endsWith(".jar")) { - JarFile jf = new JarFile(sourceFile); - Enumeration<JarEntry> e = jf.entries(); - while (e.hasMoreElements()) { - JarEntry je = e.nextElement(); - String name = je.getName(); - processFileName(name, p); - } - jf.close(); - } else { - File examples = new File(sourceFile, "examples"); // must match top level examples package name - if (examples.exists()) { - scanForClasses(sourceFile.length(), examples, p); - } else { - fail("Could not find examples classes: " + examples.getCanonicalPath()); - } - } - return p; - } - - private static void scanForClasses(int rootLength, File current, Properties p) { - for(File file : current.listFiles()) { - if (file.isDirectory()) { - scanForClasses(rootLength, file, p); - } else { - processFileName(file.getPath().substring(rootLength), p); - } - } - } - - private static void processFileName(String name, Properties p) { - if (!name.endsWith(".class") - || name.contains("$") // subclasses - || name.equals("examples/Main.class") // the initial class, don't want to add that - || !hasMainMethod(name) - ) { - return; - } - name = name.replace(".class", ""); - final int lastSep = name.lastIndexOf('/'); - final String alias = name.substring(lastSep+1); - if (p.containsKey(alias)) { - System.out.printf("Duplicate alias: %-25s %s %s %n",alias,name,p.getProperty(alias)); - } else { - p.setProperty(alias, name); - } - } - - private static boolean hasMainMethod(String name) { - name = name.replace(".class", ""); - name = name.replace("/", "."); - try { - Class<?> clazz = Class.forName(name, false, MainTest.class.getClassLoader()); - clazz.getMethod("main", new Class[]{String[].class}); - return true; - } catch (ClassNotFoundException e) { - System.out.println("Cannot find " + name); - return false; - } catch (NoSuchMethodException e) { - return false; - } catch (SecurityException e) { - e.printStackTrace(); - } - return true; - } -} diff --git a/src/test/java/org/apache/commons/net/SocketClientFunctionalTest.java b/src/test/java/org/apache/commons/net/SocketClientFunctionalTest.java index fb73b45..1030eaa 100644 --- a/src/test/java/org/apache/commons/net/SocketClientFunctionalTest.java +++ b/src/test/java/org/apache/commons/net/SocketClientFunctionalTest.java @@ -16,48 +16,46 @@ */ package org.apache.commons.net; -import junit.framework.TestCase; - import java.net.InetSocketAddress; import java.net.Proxy; import org.apache.commons.net.ftp.FTPClient; +import junit.framework.TestCase; + /** * A simple functional test class for SocketClients. * * Requires a Java-compatible SOCK proxy server on 127.0.0.1:9050 and access to ftp.gnu.org. */ -public class SocketClientFunctionalTest extends TestCase -{ - // any subclass will do, but it should be able to connect to the destination host - SocketClient sc = new FTPClient(); +public class SocketClientFunctionalTest extends TestCase { private static final String PROXY_HOST = "127.0.0.1"; private static final int PROXY_PORT = 9050; private static final String DEST_HOST = "ftp.gnu.org"; private static final int DEST_PORT = 21; + // any subclass will do, but it should be able to connect to the destination host + SocketClient sc = new FTPClient(); /** * The constructor for this test case. + * * @param name passed to TestCase */ - public SocketClientFunctionalTest(String name) - { + public SocketClientFunctionalTest(final String name) { super(name); } /** * A simple test to verify that the Proxy settings work. + * * @throws Exception in case of connection errors */ - public void testProxySettings() throws Exception - { + public void testProxySettings() throws Exception { // NOTE: HTTP Proxies seem to be invalid for raw sockets - Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(PROXY_HOST, PROXY_PORT)); + final Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(PROXY_HOST, PROXY_PORT)); sc.setProxy(proxy); sc.connect(DEST_HOST, DEST_PORT); assertTrue(sc.isConnected()); sc.disconnect(); } } - diff --git a/src/test/java/org/apache/commons/net/SocketClientTest.java b/src/test/java/org/apache/commons/net/SocketClientTest.java index fab194f..0f3f925 100644 --- a/src/test/java/org/apache/commons/net/SocketClientTest.java +++ b/src/test/java/org/apache/commons/net/SocketClientTest.java @@ -16,31 +16,29 @@ */ package org.apache.commons.net; -import junit.framework.TestCase; - import java.net.InetSocketAddress; import java.net.Proxy; import org.apache.commons.net.ftp.FTPClient; +import junit.framework.TestCase; + /** * A simple test class for SocketClient settings. * * @since 3.2 */ -public class SocketClientTest extends TestCase -{ +public class SocketClientTest extends TestCase { private static final String PROXY_HOST = "127.0.0.1"; private static final int PROXY_PORT = 1080; /** * A simple test to verify that the Proxy is being set. */ - public void testProxySettings() - { - SocketClient socketClient = new FTPClient(); + public void testProxySettings() { + final SocketClient socketClient = new FTPClient(); assertNull(socketClient.getProxy()); - Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(PROXY_HOST, PROXY_PORT)); + final Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(PROXY_HOST, PROXY_PORT)); socketClient.setProxy(proxy); assertEquals(proxy, socketClient.getProxy()); assertFalse(socketClient.isConnected()); diff --git a/src/test/java/org/apache/commons/net/SubnetUtilsTest.java b/src/test/java/org/apache/commons/net/SubnetUtilsTest.java index b62cbef..e60b264 100644 --- a/src/test/java/org/apache/commons/net/SubnetUtilsTest.java +++ b/src/test/java/org/apache/commons/net/SubnetUtilsTest.java @@ -17,6 +17,10 @@ package org.apache.commons.net; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + import org.apache.commons.net.util.SubnetUtils; import org.apache.commons.net.util.SubnetUtils.SubnetInfo; @@ -25,17 +29,30 @@ import junit.framework.TestCase; @SuppressWarnings("deprecation") // deliberate use of deprecated methods public class SubnetUtilsTest extends TestCase { - // TODO Lower address test public void testAddresses() { - SubnetUtils utils = new SubnetUtils("192.168.0.1/29"); - SubnetInfo info = utils.getInfo(); + final SubnetUtils utils = new SubnetUtils("192.168.0.1/29"); + final SubnetInfo info = utils.getInfo(); assertTrue(info.isInRange("192.168.0.1")); + assertTrue(info.isInRange("192.168.0.2")); + assertTrue(info.isInRange("192.168.0.3")); + assertTrue(info.isInRange("192.168.0.4")); + assertTrue(info.isInRange("192.168.0.5")); + assertTrue(info.isInRange("192.168.0.6")); // We don't count the broadcast address as usable assertFalse(info.isInRange("192.168.0.7")); assertFalse(info.isInRange("192.168.0.8")); assertFalse(info.isInRange("10.10.2.1")); assertFalse(info.isInRange("192.168.1.1")); assertFalse(info.isInRange("192.168.0.255")); + // + assertEquals(-1062731775, info.asInteger("192.168.0.1")); + assertThrows(IllegalArgumentException.class, () -> info.asInteger("bad")); + // + assertArrayEquals(new String[] { "192.168.0.1", "192.168.0.2", "192.168.0.3", "192.168.0.4", "192.168.0.5", "192.168.0.6" }, info.getAllAddresses()); + } + + public void testAddressIllegalArgument() { + assertThrows(IllegalArgumentException.class, () -> new SubnetUtils("bad")); } /** @@ -48,6 +65,18 @@ public class SubnetUtilsTest extends TestCase { assertEquals("255.0.0.0", info.getNetmask()); assertEquals(16777216, info.getAddressCount()); + utils = new SubnetUtils("192.168.0.1/0"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("0.0.0.0", info.getNetmask()); + assertEquals(4294967296L, info.getAddressCountLong()); + + utils = new SubnetUtils("192.168.0.1/1"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("128.0.0.0", info.getNetmask()); + assertEquals(2147483648L, info.getAddressCountLong()); + utils = new SubnetUtils("192.168.0.1/9"); utils.setInclusiveHostCount(true); info = utils.getInfo(); @@ -192,74 +221,163 @@ public class SubnetUtilsTest extends TestCase { assertEquals("255.255.255.255", info.getNetmask()); assertEquals(1, info.getAddressCount()); - new SubnetUtils("192.168.0.1/1"); } public void testInvalidMasks() { try { new SubnetUtils("192.168.0.1/33"); fail("Should have thrown IllegalArgumentException"); - } catch (IllegalArgumentException expected) { + } catch (final IllegalArgumentException expected) { // Ignored } } - public void testNET428_31() throws Exception { + public void testNET428_31() { final SubnetUtils subnetUtils = new SubnetUtils("1.2.3.4/31"); assertEquals(0, subnetUtils.getInfo().getAddressCount()); - String[] address = subnetUtils.getInfo().getAllAddresses(); + final String[] address = subnetUtils.getInfo().getAllAddresses(); assertNotNull(address); assertEquals(0, address.length); } - public void testNET428_32() throws Exception { + public void testNET428_32() { final SubnetUtils subnetUtils = new SubnetUtils("1.2.3.4/32"); assertEquals(0, subnetUtils.getInfo().getAddressCount()); - String[] address = subnetUtils.getInfo().getAllAddresses(); + final String[] address = subnetUtils.getInfo().getAllAddresses(); assertNotNull(address); assertEquals(0, address.length); } + public void testNET520() { + final SubnetUtils utils = new SubnetUtils("0.0.0.0/0"); + utils.setInclusiveHostCount(true); + final SubnetInfo info = utils.getInfo(); + assertEquals("0.0.0.0", info.getNetworkAddress()); + assertEquals("255.255.255.255", info.getBroadcastAddress()); + assertTrue(info.isInRange("127.0.0.0")); + utils.setInclusiveHostCount(false); + assertTrue(info.isInRange("127.0.0.0")); + } + + public void testNET521() { + SubnetUtils utils; + SubnetInfo info; + + utils = new SubnetUtils("0.0.0.0/0"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("0.0.0.0", info.getNetmask()); + assertEquals(4294967296L, info.getAddressCountLong()); + try { + info.getAddressCount(); + fail("Expected RuntimeException"); + } catch (final RuntimeException expected) { + // ignored + } + utils = new SubnetUtils("128.0.0.0/1"); + utils.setInclusiveHostCount(true); + info = utils.getInfo(); + assertEquals("128.0.0.0", info.getNetmask()); + assertEquals(2147483648L, info.getAddressCountLong()); + try { + info.getAddressCount(); + fail("Expected RuntimeException"); + } catch (final RuntimeException expected) { + // ignored + } + // if we exclude the broadcast and network addresses, the count is less than Integer.MAX_VALUE + utils.setInclusiveHostCount(false); + info = utils.getInfo(); + assertEquals(2147483646, info.getAddressCount()); + } + + public void testNET624() { + new SubnetUtils("0.0.0.0/0"); + new SubnetUtils("0.0.0.0", "0.0.0.0"); + new SubnetUtils("0.0.0.0", "128.0.0.0"); + try { + new SubnetUtils("0.0.0.0", "64.0.0.0"); + fail("Should have thrown IllegalArgumentException"); + } catch (final IllegalArgumentException expected) { + // Ignored + } + try { + new SubnetUtils("0.0.0.0", "0.0.0.1"); + fail("Should have thrown IllegalArgumentException"); + } catch (final IllegalArgumentException expected) { + // Ignored + } + } + + public void testNET641() { + assertFalse(new SubnetUtils("192.168.1.0/00").getInfo().isInRange("0.0.0.0")); + assertFalse(new SubnetUtils("192.168.1.0/30").getInfo().isInRange("0.0.0.0")); + assertFalse(new SubnetUtils("192.168.1.0/31").getInfo().isInRange("0.0.0.0")); + assertFalse(new SubnetUtils("192.168.1.0/32").getInfo().isInRange("0.0.0.0")); + } + + public void testNET675() { + final SubnetUtils utils = new SubnetUtils("192.168.0.15/32"); + utils.setInclusiveHostCount(true); + final SubnetInfo info = utils.getInfo(); + assertTrue(info.isInRange("192.168.0.15")); + } + + public void testNET679() { + final SubnetUtils utils = new SubnetUtils("10.213.160.0/16"); + utils.setInclusiveHostCount(true); + final SubnetInfo info = utils.getInfo(); + assertTrue(info.isInRange("10.213.0.0")); + assertTrue(info.isInRange("10.213.255.255")); + } + + public void testNext() { + final SubnetUtils utils = new SubnetUtils("192.168.0.1/29"); + assertEquals("192.168.0.2", utils.getNext().getInfo().getAddress()); + } + public void testParseSimpleNetmask() { final String address = "192.168.0.1"; - final String masks[] = new String[] { "255.0.0.0", "255.255.0.0", "255.255.255.0", "255.255.255.248" }; - final String bcastAddresses[] = new String[] { "192.255.255.255", "192.168.255.255", "192.168.0.255", - "192.168.0.7" }; - final String lowAddresses[] = new String[] { "192.0.0.1", "192.168.0.1", "192.168.0.1", "192.168.0.1" }; - final String highAddresses[] = new String[] { "192.255.255.254", "192.168.255.254", "192.168.0.254", - "192.168.0.6" }; - final String networkAddresses[] = new String[] { "192.0.0.0", "192.168.0.0", "192.168.0.0", "192.168.0.0" }; - final String cidrSignatures[] = new String[] { "192.168.0.1/8", "192.168.0.1/16", "192.168.0.1/24", - "192.168.0.1/29" }; - final int usableAddresses[] = new int[] { 16777214, 65534, 254, 6 }; + final String masks[] = { "255.0.0.0", "255.255.0.0", "255.255.255.0", "255.255.255.248" }; + final String bcastAddresses[] = { "192.255.255.255", "192.168.255.255", "192.168.0.255", "192.168.0.7" }; + final String lowAddresses[] = { "192.0.0.1", "192.168.0.1", "192.168.0.1", "192.168.0.1" }; + final String highAddresses[] = { "192.255.255.254", "192.168.255.254", "192.168.0.254", "192.168.0.6" }; + final String nextAddresses[] = { "192.168.0.2", "192.168.0.2", "192.168.0.2", "192.168.0.2" }; + final String previousAddresses[] = { "192.168.0.0", "192.168.0.0", "192.168.0.0", "192.168.0.0" }; + final String networkAddresses[] = { "192.0.0.0", "192.168.0.0", "192.168.0.0", "192.168.0.0" }; + final String cidrSignatures[] = { "192.168.0.1/8", "192.168.0.1/16", "192.168.0.1/24", "192.168.0.1/29" }; + final int usableAddresses[] = { 16777214, 65534, 254, 6 }; for (int i = 0; i < masks.length; ++i) { - SubnetUtils utils = new SubnetUtils(address, masks[i]); - SubnetInfo info = utils.getInfo(); + final SubnetUtils utils = new SubnetUtils(address, masks[i]); + final SubnetInfo info = utils.getInfo(); + assertEquals(address, info.getAddress()); assertEquals(bcastAddresses[i], info.getBroadcastAddress()); assertEquals(cidrSignatures[i], info.getCidrSignature()); assertEquals(lowAddresses[i], info.getLowAddress()); assertEquals(highAddresses[i], info.getHighAddress()); + assertEquals(nextAddresses[i], info.getNextAddress()); + assertEquals(previousAddresses[i], info.getPreviousAddress()); assertEquals(networkAddresses[i], info.getNetworkAddress()); assertEquals(usableAddresses[i], info.getAddressCount()); } } public void testParseSimpleNetmaskExclusive() { - String address = "192.168.15.7"; - String masks[] = new String[] { "255.255.255.252", "255.255.255.254", "255.255.255.255" }; - String bcast[] = new String[] { "192.168.15.7", "192.168.15.7", "192.168.15.7" }; - String netwk[] = new String[] { "192.168.15.4", "192.168.15.6", "192.168.15.7" }; - String lowAd[] = new String[] { "192.168.15.5", "0.0.0.0", "0.0.0.0" }; - String highA[] = new String[] { "192.168.15.6", "0.0.0.0", "0.0.0.0" }; - String cidrS[] = new String[] { "192.168.15.7/30", "192.168.15.7/31", "192.168.15.7/32" }; - int usableAd[] = new int[] { 2, 0, 0 }; + final String address = "192.168.15.7"; + final String masks[] = { "255.255.255.252", "255.255.255.254", "255.255.255.255" }; + final String bcast[] = { "192.168.15.7", "192.168.15.7", "192.168.15.7" }; + final String netwk[] = { "192.168.15.4", "192.168.15.6", "192.168.15.7" }; + final String lowAd[] = { "192.168.15.5", "0.0.0.0", "0.0.0.0" }; + final String highA[] = { "192.168.15.6", "0.0.0.0", "0.0.0.0" }; + final String cidrS[] = { "192.168.15.7/30", "192.168.15.7/31", "192.168.15.7/32" }; + final int usableAd[] = { 2, 0, 0 }; // low and high addresses don't exist for (int i = 0; i < masks.length; ++i) { - SubnetUtils utils = new SubnetUtils(address, masks[i]); + final SubnetUtils utils = new SubnetUtils(address, masks[i]); utils.setInclusiveHostCount(false); - SubnetInfo info = utils.getInfo(); + final SubnetInfo info = utils.getInfo(); assertEquals("ci " + masks[i], cidrS[i], info.getCidrSignature()); assertEquals("bc " + masks[i], bcast[i], info.getBroadcastAddress()); assertEquals("nw " + masks[i], netwk[i], info.getNetworkAddress()); @@ -270,19 +388,19 @@ public class SubnetUtilsTest extends TestCase { } public void testParseSimpleNetmaskInclusive() { - String address = "192.168.15.7"; - String masks[] = new String[] { "255.255.255.252", "255.255.255.254", "255.255.255.255" }; - String bcast[] = new String[] { "192.168.15.7", "192.168.15.7", "192.168.15.7" }; - String netwk[] = new String[] { "192.168.15.4", "192.168.15.6", "192.168.15.7" }; - String lowAd[] = new String[] { "192.168.15.4", "192.168.15.6", "192.168.15.7" }; - String highA[] = new String[] { "192.168.15.7", "192.168.15.7", "192.168.15.7" }; - String cidrS[] = new String[] { "192.168.15.7/30", "192.168.15.7/31", "192.168.15.7/32" }; - int usableAd[] = new int[] { 4, 2, 1 }; + final String address = "192.168.15.7"; + final String masks[] = { "255.255.255.252", "255.255.255.254", "255.255.255.255" }; + final String bcast[] = { "192.168.15.7", "192.168.15.7", "192.168.15.7" }; + final String netwk[] = { "192.168.15.4", "192.168.15.6", "192.168.15.7" }; + final String lowAd[] = { "192.168.15.4", "192.168.15.6", "192.168.15.7" }; + final String highA[] = { "192.168.15.7", "192.168.15.7", "192.168.15.7" }; + final String cidrS[] = { "192.168.15.7/30", "192.168.15.7/31", "192.168.15.7/32" }; + final int usableAd[] = { 4, 2, 1 }; for (int i = 0; i < masks.length; ++i) { - SubnetUtils utils = new SubnetUtils(address, masks[i]); + final SubnetUtils utils = new SubnetUtils(address, masks[i]); utils.setInclusiveHostCount(true); - SubnetInfo info = utils.getInfo(); + final SubnetInfo info = utils.getInfo(); assertEquals("ci " + masks[i], cidrS[i], info.getCidrSignature()); assertEquals("bc " + masks[i], bcast[i], info.getBroadcastAddress()); assertEquals("ac " + masks[i], usableAd[i], info.getAddressCount()); @@ -292,50 +410,20 @@ public class SubnetUtilsTest extends TestCase { } } - public void testZeroAddressAndCidr() { - new SubnetUtils("0.0.0.0/0"); + public void testPrevious() { + final SubnetUtils utils = new SubnetUtils("192.168.0.1/29"); + assertEquals("192.168.0.0", utils.getPrevious().getInfo().getAddress()); } - public void testNET521() { - SubnetUtils utils; - SubnetInfo info; - - utils = new SubnetUtils("0.0.0.0/0"); - utils.setInclusiveHostCount(true); - info = utils.getInfo(); - assertEquals("0.0.0.0", info.getNetmask()); - assertEquals(4294967296L, info.getAddressCountLong()); - try { - info.getAddressCount(); - fail("Expected RuntimeException"); - } catch (RuntimeException expected) { - // ignored - } - utils = new SubnetUtils("128.0.0.0/1"); - utils.setInclusiveHostCount(true); - info = utils.getInfo(); - assertEquals("128.0.0.0", info.getNetmask()); - assertEquals(2147483648L, info.getAddressCountLong()); - try { - info.getAddressCount(); - fail("Expected RuntimeException"); - } catch (RuntimeException expected) { - // ignored - } - // if we exclude the broadcast and network addresses, the count is less than Integer.MAX_VALUE - utils.setInclusiveHostCount(false); - info = utils.getInfo(); - assertEquals(2147483646, info.getAddressCount()); + public void testToString() { + final SubnetUtils utils = new SubnetUtils("192.168.0.1/29"); + assertDoesNotThrow(() -> utils.toString()); + final SubnetInfo info = utils.getInfo(); + assertDoesNotThrow(() -> info.toString()); } - public void testNET520() { - SubnetUtils utils = new SubnetUtils("0.0.0.0/0"); - utils.setInclusiveHostCount(true); - SubnetInfo info = utils.getInfo(); - assertEquals("0.0.0.0",info.getNetworkAddress()); - assertEquals("255.255.255.255",info.getBroadcastAddress()); - assertTrue(info.isInRange("127.0.0.0")); - utils.setInclusiveHostCount(false); - assertTrue(info.isInRange("127.0.0.0")); + public void testZeroAddressAndCidr() { + final SubnetUtils snu = new SubnetUtils("0.0.0.0/0"); + assertNotNull(snu); } } diff --git a/src/test/java/org/apache/commons/net/examples/MainTest.java b/src/test/java/org/apache/commons/net/examples/MainTest.java new file mode 100644 index 0000000..e8701b1 --- /dev/null +++ b/src/test/java/org/apache/commons/net/examples/MainTest.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.net.examples; + +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URLDecoder; +import java.security.CodeSource; +import java.util.Enumeration; +import java.util.Properties; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.junit.Test; + +public class MainTest { + + private static boolean hasMainMethod(String name) { + name = name.replace(".class", ""); + try { + final Class<?> clazz = Class.forName(name, false, MainTest.class.getClassLoader()); + clazz.getMethod("main", String[].class); + return true; + } catch (final ClassNotFoundException e) { + System.out.println("Cannot find " + name); + return false; + } catch (final NoSuchMethodException e) { + return false; + } catch (final SecurityException e) { + e.printStackTrace(); + } + return true; + } + + private static void processFileName(String name, final Properties p) { + name = name.replace(File.separatorChar, '.'); + if (!name.endsWith(".class") || name.contains("$") // subclasses + || name.endsWith("examples.Main.class") // the initial class, don't want to add that + || !hasMainMethod(name)) { + return; + } + name = name.replace(".class", ""); + final int lastSep = name.lastIndexOf('.'); + final String alias = name.substring(lastSep + 1); + if (p.containsKey(alias)) { + System.out.printf("Duplicate alias: %-25s %s %s %n", alias, name, p.getProperty(alias)); + } else { + p.setProperty(alias, name); + } + } + + private static void scanForClasses(final int rootLength, final File current, final Properties p) { + final File[] files = current.listFiles(); + if (files != null) { + for (final File file : files) { + if (file.isDirectory()) { + scanForClasses(rootLength, file, p); + } else { + processFileName(file.getPath().substring(rootLength), p); + } + } + } + } + + @Test + public void checkExamplesPropertiesIsComplete() throws Exception { + final Properties cp = scanClasses(); + final Properties fp = new Properties(); + try (final InputStream inputStream = this.getClass().getResourceAsStream("examples.properties")) { + fp.load(inputStream); + } + @SuppressWarnings("unchecked") // OK + final Enumeration<String> propertyNames = (Enumeration<String>) cp.propertyNames(); + while (propertyNames.hasMoreElements()) { + final String c = propertyNames.nextElement(); + final String fv = fp.getProperty(c); + final String cv = cp.getProperty(c); + if (fv == null) { + System.out.printf("%-25s %s - missing from examples.properties%n", c, cv); + } else if (!fv.equals(cv)) { + System.out.printf("%-25s %s - expected value %s %n", c, fv, cv); + } + } + } + + private Properties scanClasses() throws IOException { + final CodeSource codeSource = Main.class.getProtectionDomain().getCodeSource(); + // ensure special characters are decoded OK by uing the charset + // Use canonical path to ensure consistency with Windows + final String sourceFile = new File(URLDecoder.decode(codeSource.getLocation().getFile(), "UTF-8")).getCanonicalPath(); + final Properties p = new Properties(); + if (sourceFile.endsWith(".jar")) { + try (final JarFile jf = new JarFile(sourceFile)) { + final Enumeration<JarEntry> e = jf.entries(); + while (e.hasMoreElements()) { + final JarEntry je = e.nextElement(); + final String name = je.getName(); + processFileName(name, p); + } + } + } else { + final File examples = new File(sourceFile, "org/apache/commons/net/examples"); // must match top level examples package name + if (examples.exists()) { + // need to add 1 to allow for path separator between root and file + scanForClasses(sourceFile.length() + 1, examples, p); + } else { + fail("Could not find examples classes: " + examples.getCanonicalPath()); + } + } + return p; + } +} diff --git a/src/test/java/org/apache/commons/net/ftp/AbstractFtpsTest.java b/src/test/java/org/apache/commons/net/ftp/AbstractFtpsTest.java new file mode 100644 index 0000000..4344307 --- /dev/null +++ b/src/test/java/org/apache/commons/net/ftp/AbstractFtpsTest.java @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.net.ftp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.net.SocketException; +import java.net.URL; +import java.time.Duration; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.output.NullOutputStream; +import org.apache.commons.net.PrintCommandListener; +import org.apache.ftpserver.FtpServer; +import org.apache.ftpserver.FtpServerFactory; +import org.apache.ftpserver.ftplet.FtpException; +import org.apache.ftpserver.ftplet.UserManager; +import org.apache.ftpserver.listener.ListenerFactory; +import org.apache.ftpserver.ssl.SslConfiguration; +import org.apache.ftpserver.ssl.SslConfigurationFactory; +import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory; +import org.apache.ftpserver.usermanager.impl.BaseUser; +import org.junit.Assert; + +/** + * Tests {@link FTPSClient}. + * <p> + * To get our test certificate to work on Java 11, this test must be run with: + * </p> + * + * <pre> + * -Djdk.tls.client.protocols="TLSv1.1" + * </pre> + * <p> + * This test does the above programmatically. + * </p> + */ +public abstract class AbstractFtpsTest { + + private static int SocketPort; + private static FtpServer EmbeddedFtpServer; + protected static final boolean IMPLICIT = false; + protected static final long TEST_TIMEOUT = 10000; // individual test timeout + private static final boolean TRACE_CALLS = Boolean.parseBoolean(System.getenv("TRACE_CALLS")); + private static final boolean ADD_LISTENER = Boolean.parseBoolean(System.getenv("ADD_LISTENER")); + private static final long startTime = System.nanoTime(); + + /** + * Returns the test directory as a String. + * @param defaultHome A default value. + * + * @return the test directory as a String + */ + protected static String getTestHomeDirectory(final String defaultHome) { + return System.getProperty("test.basedir", defaultHome); + } + + /** + * Creates and starts an embedded Apache MINA FTP Server. + * + * @param implicit FTPS connection mode. + * @param userPropertiesResource resource path to user properties file. + * @param serverJksResourceResource resource path to server JKS file. + * @param defaultHome default home folder + * @throws FtpException Thrown when a the FTP classes cannot fulfill a request. + */ + protected synchronized static void setupServer(final boolean implicit, final String userPropertiesResource, final String serverJksResourceResource, final String defaultHome) + throws FtpException { + if (EmbeddedFtpServer != null) { + return; + } + // Let the OS find use an ephemeral port by using 0. + SocketPort = 0; + final FtpServerFactory serverFactory = new FtpServerFactory(); + final PropertiesUserManagerFactory propertiesUserManagerFactory = new PropertiesUserManagerFactory(); + final URL userPropsResource = ClassLoader.getSystemClassLoader().getResource(userPropertiesResource); + Assert.assertNotNull(userPropertiesResource, userPropsResource); + propertiesUserManagerFactory.setUrl(userPropsResource); + final UserManager userManager = propertiesUserManagerFactory.createUserManager(); + final BaseUser user = (BaseUser) userManager.getUserByName("test"); + // Pickup the home dir value at runtime even though we have it set in the userprop file + // The user prop file requires the "homedirectory" to be set + user.setHomeDirectory(getTestHomeDirectory(defaultHome)); + serverFactory.setUserManager(userManager); + final ListenerFactory factory = new ListenerFactory(); + factory.setPort(SocketPort); + + // define SSL configuration + final URL serverJksResource = ClassLoader.getSystemClassLoader().getResource(serverJksResourceResource); + Assert.assertNotNull(serverJksResourceResource, serverJksResource); + System.out.println("Loading " + serverJksResource); + final SslConfigurationFactory sllConfigFactory = new SslConfigurationFactory(); + final File keyStoreFile = FileUtils.toFile(serverJksResource); + Assert.assertTrue(keyStoreFile.toString(), keyStoreFile.exists()); + sllConfigFactory.setKeystoreFile(keyStoreFile); + sllConfigFactory.setKeystorePassword("password"); + + // set the SSL configuration for the listener + final SslConfiguration sslConfiguration = sllConfigFactory.createSslConfiguration(); + final NoProtocolSslConfigurationProxy noProtocolSslConfigurationProxy = new NoProtocolSslConfigurationProxy(sslConfiguration); + factory.setSslConfiguration(noProtocolSslConfigurationProxy); + factory.setImplicitSsl(implicit); + + // replace the default listener + serverFactory.addListener("default", factory.createListener()); + + // start the server + EmbeddedFtpServer = serverFactory.createServer(); + EmbeddedFtpServer.start(); + SocketPort = ((org.apache.ftpserver.impl.DefaultFtpServer) EmbeddedFtpServer).getListener("default").getPort(); + // System.out.printf("jdk.tls.disabledAlgorithms = %s%n", System.getProperty("jdk.tls.disabledAlgorithms")); + trace("Server started"); + } + + protected static void trace(final String msg) { + if (TRACE_CALLS) { + System.err.println(msg + " " + (System.nanoTime() - startTime)); + } + } + + private final boolean endpointCheckingEnabled; + + public AbstractFtpsTest(final boolean endpointCheckingEnabled, final String userPropertiesResource, final String serverJksResource) { + this.endpointCheckingEnabled = endpointCheckingEnabled; + } + + protected void assertClientCode(final FTPSClient client) { + final int replyCode = client.getReplyCode(); + assertTrue(FTPReply.isPositiveCompletion(replyCode)); + } + + protected FTPSClient loginClient() throws SocketException, IOException { + trace(">>loginClient"); + final FTPSClient client = new FTPSClient(IMPLICIT); + if (ADD_LISTENER) { + client.addProtocolCommandListener(new PrintCommandListener(System.err)); + } + // + client.setControlKeepAliveReplyTimeout(null); + assertEquals(0, client.getControlKeepAliveReplyTimeoutDuration().getSeconds()); + client.setControlKeepAliveReplyTimeout(Duration.ofSeconds(60)); + assertEquals(60, client.getControlKeepAliveReplyTimeoutDuration().getSeconds()); + // + client.setControlKeepAliveTimeout(null); + assertEquals(0, client.getControlKeepAliveTimeoutDuration().getSeconds()); + client.setControlKeepAliveTimeout(Duration.ofSeconds(61)); + assertEquals(61, client.getControlKeepAliveTimeoutDuration().getSeconds()); + // + client.setDataTimeout(null); + assertEquals(0, client.getDataTimeout().getSeconds()); + client.setDataTimeout(Duration.ofSeconds(62)); + assertEquals(62, client.getDataTimeout().getSeconds()); + // + client.setEndpointCheckingEnabled(endpointCheckingEnabled); + client.connect("localhost", SocketPort); + // + assertClientCode(client); + assertEquals(SocketPort, client.getRemotePort()); + // + try { + // HACK: Without this sleep, the user command sometimes does not reach the ftpserver + // This only seems to affect GitHub builds, and only Java 11+ + Thread.sleep(200); // 100 seems to be not always enough + } catch (final InterruptedException ignore) { + // ignore + } + assertTrue(client.login("test", "test")); + assertClientCode(client); + // + client.setFileType(FTP.BINARY_FILE_TYPE); + assertClientCode(client); + // + client.execPBSZ(0); + assertClientCode(client); + // + client.execPROT("P"); + assertClientCode(client); + trace("<<loginClient"); + return client; + } + + protected void retrieveFile(final String pathname) throws SocketException, IOException { + final FTPSClient client = loginClient(); + try { + // Do it twice. + // Just testing that we are not getting an SSL error (the file MUST be present). + assertTrue(pathname, client.retrieveFile(pathname, NullOutputStream.NULL_OUTPUT_STREAM)); + assertTrue(pathname, client.retrieveFile(pathname, NullOutputStream.NULL_OUTPUT_STREAM)); + } finally { + client.disconnect(); + } + } +} diff --git a/src/test/java/org/apache/commons/net/ftp/FTPClientConfigFunctionalTest.java b/src/test/java/org/apache/commons/net/ftp/FTPClientConfigFunctionalTest.java index 26db09f..8306c83 100644 --- a/src/test/java/org/apache/commons/net/ftp/FTPClientConfigFunctionalTest.java +++ b/src/test/java/org/apache/commons/net/ftp/FTPClientConfigFunctionalTest.java @@ -17,100 +17,47 @@ package org.apache.commons.net.ftp; import java.io.IOException; -import java.net.SocketException; +import java.time.Duration; +import java.time.Instant; import java.util.Calendar; -import java.util.Comparator; import java.util.TreeSet; import junit.framework.TestCase; -/* - * This test was contributed in a different form by W. McDonald Buck - * of Boulder, Colorado, to help fix some bugs with the FTPClientConfig - * in a real world setting. It is a perfect functional test for the - * Time Zone functionality of FTPClientConfig. +/** + * This test was contributed in a different form by W. McDonald Buck of Boulder, Colorado, to help fix some bugs with the FTPClientConfig in a real world + * setting. It is a perfect functional test for the Time Zone functionality of FTPClientConfig. * - * A publicly accessible FTP server at the US National Oceanographic and - * Atmospheric Adminstration houses a directory which contains - * 300 files, named sn.0000 to sn.0300. Every ten minutes or so - * the next file in sequence is rewritten with new data. Thus the directory - * contains observations for more than 24 hours of data. Since the server - * has its clock set to GMT this is an excellent functional test for any - * machine in a different time zone. + * A publicly accessible FTP server at the US National Oceanographic and Atmospheric Adminstration houses a directory which contains 300 files, named sn.0000 to + * sn.0300. Every ten minutes or so the next file in sequence is rewritten with new data. Thus the directory contains observations for more than 24 hours of + * data. Since the server has its clock set to GMT this is an excellent functional test for any machine in a different time zone. * - * Noteworthy is the fact that the ftp routines in some web browsers don't - * work as well as this. They can't, since they have no way of knowing the - * server's time zone. Depending on the local machine's position relative - * to GMT and the time of day, the browsers may decide that a timestamp - * would be in the future if given the current year, so they assume the - * year to be last year. This illustrates the value of FTPClientConfig's - * time zone functionality. + * Noteworthy is the fact that the FTP routines in some web browsers don't work as well as this. They can't, since they have no way of knowing the server's time + * zone. Depending on the local machine's position relative to GMT and the time of day, the browsers may decide that a timestamp would be in the future if given + * the current year, so they assume the year to be last year. This illustrates the value of FTPClientConfig's time zone functionality. */ - public class FTPClientConfigFunctionalTest extends TestCase { - private final FTPClient FTP = new FTPClient(); - private FTPClientConfig FTPConf; - + private final FTPClient ftpClient = new FTPClient(); + private FTPClientConfig ftpClientConfig; /** * */ public FTPClientConfigFunctionalTest() { - super(); } - /* - * @throws java.lang.Exception - */ - @Override - protected void setUp() throws Exception { - super.setUp(); - FTPConf = new FTPClientConfig(FTPClientConfig.SYST_UNIX); - FTPConf.setServerTimeZoneId("GMT"); - FTP.configure(FTPConf); - try { - FTP.connect("tgftp.nws.noaa.gov"); - FTP.login("anonymous","testing@apache.org"); - FTP.changeWorkingDirectory("SL.us008001/DF.an/DC.sflnd/DS.metar"); - FTP.enterLocalPassiveMode(); - } catch (SocketException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - /* - * @throws java.lang.Exception - */ - @Override - protected void tearDown() throws Exception { - FTP.disconnect(); - super.tearDown(); - } - - public FTPClientConfigFunctionalTest(String arg0) { + public FTPClientConfigFunctionalTest(final String arg0) { super(arg0); } - private TreeSet<FTPFile> getSortedList(FTPFile[] files) { + private TreeSet<FTPFile> getSortedSet(final FTPFile[] files) { // create a TreeSet which will sort each element // as it is added. - TreeSet<FTPFile> sorted = new TreeSet<FTPFile>(new Comparator<Object>() { - - @Override - public int compare(Object o1, Object o2) { - FTPFile f1 = (FTPFile) o1; - FTPFile f2 = (FTPFile) o2; - return f1.getTimestamp().getTime().compareTo(f2.getTimestamp().getTime()); - } - - }); - + final TreeSet<FTPFile> sorted = new TreeSet<>((o1, o2) -> o1.getTimestamp().getTime().compareTo(o2.getTimestamp().getTime())); - for (FTPFile file : files) - { + for (final FTPFile file : files) { // The directory contains a few additional files at the beginning // which aren't in the series we want. The series we want consists // of files named sn.dddd. This adjusts the file list to get rid @@ -122,46 +69,73 @@ public class FTPClientConfigFunctionalTest extends TestCase { return sorted; } + /** + * @throws Exception + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + ftpClientConfig = new FTPClientConfig(FTPClientConfig.SYST_UNIX); + ftpClientConfig.setServerTimeZoneId("GMT"); + ftpClient.configure(ftpClientConfig); + try { + ftpClient.connect("tgftp.nws.noaa.gov"); + ftpClient.login("anonymous", "testing@apache.org"); + ftpClient.changeWorkingDirectory("SL.us008001/DF.an/DC.sflnd/DS.metar"); + ftpClient.enterLocalPassiveMode(); + } catch (final IOException e) { + e.printStackTrace(); + } + } + + /** + * @throws Exception + */ + @Override + protected void tearDown() throws Exception { + ftpClient.disconnect(); + super.tearDown(); + } + public void testTimeZoneFunctionality() throws Exception { - java.util.Date now = new java.util.Date(); - FTPFile[] files = FTP.listFiles(); - TreeSet<FTPFile> sorted = getSortedList(files); - //SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm z" ); - FTPFile lastfile = null; - FTPFile firstfile = null; - for (FTPFile thisfile : sorted) { - if (firstfile == null) { - firstfile = thisfile; + final java.util.Date nowDate = new java.util.Date(); + final Instant nowInstant = nowDate.toInstant(); + final FTPFile[] files = ftpClient.listFiles(); + final TreeSet<FTPFile> sortedSet = getSortedSet(files); + // SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm z" ); + FTPFile lastFile = null; + FTPFile firstFile = null; + for (final FTPFile thisFile : sortedSet) { + if (firstFile == null) { + firstFile = thisFile; } - //System.out.println(sdf.format(thisfile.getTimestamp().getTime()) - // + " " +thisfile.getName()); - if (lastfile != null) { + // System.out.println(sdf.format(thisFile.getTimestamp().getTime()) + // + " " +thisFile.getName()); + if (lastFile != null) { // verify that the list is sorted earliest to latest. - assertTrue(lastfile.getTimestamp() - .before(thisfile.getTimestamp())); + assertTrue(lastFile.getTimestamp().before(thisFile.getTimestamp())); + assertTrue(lastFile.getTimestampInstant().isBefore(thisFile.getTimestampInstant())); } - lastfile = thisfile; + lastFile = thisFile; } - if (firstfile == null || lastfile == null) { + if (firstFile == null || lastFile == null) { fail("No files found"); } else { // test that notwithstanding any time zone differences, the newest file // is older than now. - assertTrue(lastfile.getTimestamp().getTime().before(now)); - Calendar first = firstfile.getTimestamp(); + assertTrue(lastFile.getTimestamp().getTime().before(nowDate)); + assertTrue(lastFile.getTimestampInstant().isBefore(nowInstant)); + final Calendar firstCal = firstFile.getTimestamp(); + final Instant firstInstant = firstFile.getTimestampInstant().plus(Duration.ofDays(2)); // test that the oldest is less than two days older than the newest // and, in particular, that no files have been considered "future" // by the parser and therefore been relegated to the same date a // year ago. - first.add(Calendar.DAY_OF_MONTH, 2); - assertTrue(lastfile.getTimestamp().getTime().toString() + - " before "+ first.getTime().toString(),lastfile.getTimestamp().before(first)); + firstCal.add(Calendar.DAY_OF_MONTH, 2); + assertTrue(lastFile.getTimestamp().getTime() + " before " + firstCal.getTime(), lastFile.getTimestamp().before(firstCal)); + assertTrue(lastFile.getTimestampInstant() + " before " + firstInstant, lastFile.getTimestampInstant().isBefore(firstInstant)); } } } - - - - diff --git a/src/test/java/org/apache/commons/net/ftp/FTPClientConfigTest.java b/src/test/java/org/apache/commons/net/ftp/FTPClientConfigTest.java index a201fa2..0ba8810 100644 --- a/src/test/java/org/apache/commons/net/ftp/FTPClientConfigTest.java +++ b/src/test/java/org/apache/commons/net/ftp/FTPClientConfigTest.java @@ -26,11 +26,25 @@ import junit.framework.TestCase; public class FTPClientConfigTest extends TestCase { + private static final String A = "A"; + + private static final String B = "B"; + private static final String C = "C"; + private static final String D = "D"; + private static final String E = "E"; + private static final String F = "F"; + private static final String badDelim = "jan,feb,mar,apr,may,jun,jul,aug.sep,oct,nov,dec"; + + private static final String tooLong = "jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|jan"; + + private static final String tooShort = "jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov"; + private static final String fakeLang = "abc|def|ghi|jkl|mno|pqr|stu|vwx|yza|bcd|efg|hij"; + /* * Class under test for void FTPClientConfig(String) */ public void testFTPClientConfigString() { - FTPClientConfig config = new FTPClientConfig(FTPClientConfig.SYST_VMS); + final FTPClientConfig config = new FTPClientConfig(FTPClientConfig.SYST_VMS); assertEquals(FTPClientConfig.SYST_VMS, config.getServerSystemKey()); assertNull(config.getDefaultDateFormatStr()); assertNull(config.getRecentDateFormatStr()); @@ -39,18 +53,11 @@ public class FTPClientConfigTest extends TestCase { assertNull(config.getServerLanguageCode()); } - private static final String A = "A"; - private static final String B = "B"; - private static final String C = "C"; - private static final String D = "D"; - private static final String E = "E"; - private static final String F = "F"; - /* * Class under test for void FTPClientConfig(String, String, String, String, String, String) */ public void testFTPClientConfigStringStringStringStringStringString() { - FTPClientConfig conf = new FTPClientConfig(A,B,C,D,E,F); + final FTPClientConfig conf = new FTPClientConfig(A, B, C, D, E, F); assertEquals("A", conf.getServerSystemKey()); assertEquals("B", conf.getDefaultDateFormatStr()); @@ -60,13 +67,63 @@ public class FTPClientConfigTest extends TestCase { assertEquals("D", conf.getServerLanguageCode()); } + public void testGetDateFormatSymbols() { - private static final String badDelim = "jan,feb,mar,apr,may,jun,jul,aug.sep,oct,nov,dec"; - private static final String tooLong = "jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|jan"; - private static final String tooShort = "jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov"; - private static final String fakeLang = "abc|def|ghi|jkl|mno|pqr|stu|vwx|yza|bcd|efg|hij"; + try { + FTPClientConfig.getDateFormatSymbols(badDelim); + fail("bad delimiter"); + } catch (final IllegalArgumentException e) { + // should have failed + } + try { + FTPClientConfig.getDateFormatSymbols(tooLong); + fail("more than 12 months"); + } catch (final IllegalArgumentException e) { + // should have failed + } + try { + FTPClientConfig.getDateFormatSymbols(tooShort); + fail("fewer than 12 months"); + } catch (final IllegalArgumentException e) { + // should have failed + } + DateFormatSymbols dfs2 = null; + try { + dfs2 = FTPClientConfig.getDateFormatSymbols(fakeLang); + } catch (final Exception e) { + fail("rejected valid short month string"); + } + final SimpleDateFormat sdf1 = new SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH); + final SimpleDateFormat sdf2 = new SimpleDateFormat("MMM dd, yyyy", dfs2); + + Date d1 = null; + Date d2 = null; + try { + d1 = sdf1.parse("dec 31, 2004"); + } catch (final ParseException px) { + fail("failed.to.parse.std"); + } + try { + d2 = sdf2.parse("hij 31, 2004"); + } catch (final ParseException px) { + fail("failed.to.parse.weird"); + } + + assertEquals("different.parser.same.date", d1, d2); + + try { + sdf1.parse("hij 31, 2004"); + fail("should.have.failed.to.parse.weird"); + } catch (final ParseException px) { + // expected + } + try { + sdf2.parse("dec 31, 2004"); + fail("should.have.failed.to.parse.standard"); + } catch (final ParseException px) { + // expected + } - public void testSetShortMonthNames() { } public void testGetServerLanguageCode() { @@ -78,51 +135,50 @@ public class FTPClientConfigTest extends TestCase { DateFormatSymbols dfs3 = null; DateFormatSymbols dfs4 = null; - try { dfs1 = FTPClientConfig.lookupDateFormatSymbols("fr"); - } catch (IllegalArgumentException e){ + } catch (final IllegalArgumentException e) { fail("french"); } try { dfs2 = FTPClientConfig.lookupDateFormatSymbols("sq"); - } catch (IllegalArgumentException e){ + } catch (final IllegalArgumentException e) { fail("albanian"); } try { dfs3 = FTPClientConfig.lookupDateFormatSymbols("ru"); - } catch (IllegalArgumentException e){ + } catch (final IllegalArgumentException e) { fail("unusupported.default.to.en"); } try { dfs4 = FTPClientConfig.lookupDateFormatSymbols(fakeLang); - } catch (IllegalArgumentException e){ + } catch (final IllegalArgumentException e) { fail("not.language.code.but.defaults"); } - assertEquals(dfs3,dfs4); + assertEquals(dfs3, dfs4); - SimpleDateFormat sdf1 = new SimpleDateFormat("d MMM yyyy", dfs1); - SimpleDateFormat sdf2 = new SimpleDateFormat("MMM dd, yyyy", dfs2); - SimpleDateFormat sdf3 = new SimpleDateFormat("MMM dd, yyyy", dfs3); + final SimpleDateFormat sdf1 = new SimpleDateFormat("d MMM yyyy", dfs1); + final SimpleDateFormat sdf2 = new SimpleDateFormat("MMM dd, yyyy", dfs2); + final SimpleDateFormat sdf3 = new SimpleDateFormat("MMM dd, yyyy", dfs3); Date d1 = null; Date d2 = null; Date d3 = null; try { d1 = sdf1.parse("31 d\u00e9c 2004"); - } catch (ParseException px) { + } catch (final ParseException px) { fail("failed.to.parse.french"); } try { d2 = sdf2.parse("dhj 31, 2004"); - } catch (ParseException px) { + } catch (final ParseException px) { fail("failed.to.parse.albanian"); } try { d3 = sdf3.parse("DEC 31, 2004"); - } catch (ParseException px) { + } catch (final ParseException px) { fail("failed.to.parse.'russian'"); } assertEquals("different.parser.same.date", d1, d2); @@ -130,65 +186,7 @@ public class FTPClientConfigTest extends TestCase { } - public void testGetDateFormatSymbols() { - - try { - FTPClientConfig.getDateFormatSymbols(badDelim); - fail("bad delimiter"); - } catch (IllegalArgumentException e){ - // should have failed - } - try { - FTPClientConfig.getDateFormatSymbols(tooLong); - fail("more than 12 months"); - } catch (IllegalArgumentException e){ - // should have failed - } - try { - FTPClientConfig.getDateFormatSymbols(tooShort); - fail("fewer than 12 months"); - } catch (IllegalArgumentException e){ - // should have failed - } - DateFormatSymbols dfs2 = null; - try { - dfs2 = FTPClientConfig.getDateFormatSymbols(fakeLang); - } catch (Exception e){ - fail("rejected valid short month string"); - } - SimpleDateFormat sdf1 = - new SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH); - SimpleDateFormat sdf2 = new SimpleDateFormat("MMM dd, yyyy", dfs2); - - Date d1 = null; - Date d2 = null; - try { - d1 = sdf1.parse("dec 31, 2004"); - } catch (ParseException px) { - fail("failed.to.parse.std"); - } - try { - d2 = sdf2.parse("hij 31, 2004"); - } catch (ParseException px) { - fail("failed.to.parse.weird"); - } - - assertEquals("different.parser.same.date",d1, d2); - - try { - d2 = sdf1.parse("hij 31, 2004"); - fail("should.have.failed.to.parse.weird"); - } catch (ParseException px) { - // expected - } - try { - d2 = sdf2.parse("dec 31, 2004"); - fail("should.have.failed.to.parse.standard"); - } catch (ParseException px) { - // expected - } - - + public void testSetShortMonthNames() { } } diff --git a/src/test/java/org/apache/commons/net/ftp/FTPClientTest.java b/src/test/java/org/apache/commons/net/ftp/FTPClientTest.java index e43f075..fe8bfe0 100644 --- a/src/test/java/org/apache/commons/net/ftp/FTPClientTest.java +++ b/src/test/java/org/apache/commons/net/ftp/FTPClientTest.java @@ -22,7 +22,6 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.InetAddress; -import java.net.UnknownHostException; import org.apache.commons.net.ftp.parser.UnixFTPEntryParser; @@ -30,107 +29,24 @@ import junit.framework.TestCase; public class FTPClientTest extends TestCase { - private static final String[] TESTS = { - "257 /path/without/quotes", - "/path/without/quotes", - - "257 \"/path/with/delimiting/quotes/without/commentary\"", - "/path/with/delimiting/quotes/without/commentary", - - "257 \"/path/with/quotes\"\" /inside/but/without/commentary\"", - "/path/with/quotes\" /inside/but/without/commentary", - - "257 \"/path/with/quotes\"\" /inside/string\" and with commentary", - "/path/with/quotes\" /inside/string", - - "257 \"/path/with/quotes\"\" /inside/string\" and with commentary that also \"contains quotes\"", - "/path/with/quotes\" /inside/string", - - "257 \"/path/without/trailing/quote", // invalid syntax, return all after reply code prefix - "\"/path/without/trailing/quote", - - "257 root is current directory.", // NET-442 - "root is current directory.", - - "257 \"/\"", // NET-502 - "/", - }; - - public FTPClientTest(String name) { - super(name); - } - - public void testParseClient() { - for(int i=0; i<TESTS.length; i+=2) { - assertEquals("Failed to parse",TESTS[i+1], FTPClient.__parsePathname(TESTS[i])); - } - } - - public void testParserCachingWithKey() throws Exception { - FTPClient client = new FTPClient(); - assertNull(client.getEntryParser()); - client.__createParser(FTPClientConfig.SYST_UNIX); - final FTPFileEntryParser entryParserSYST = client.getEntryParser(); - assertNotNull(entryParserSYST); - client.__createParser(FTPClientConfig.SYST_UNIX); - assertSame(entryParserSYST, client.getEntryParser()); // the previous entry was cached - client.__createParser(FTPClientConfig.SYST_VMS); - final FTPFileEntryParser entryParserVMS = client.getEntryParser(); - assertNotSame(entryParserSYST, entryParserVMS); // the previous entry was replaced - client.__createParser(FTPClientConfig.SYST_VMS); - assertSame(entryParserVMS, client.getEntryParser()); // the previous entry was cached - client.__createParser(FTPClientConfig.SYST_UNIX); // revert - assertNotSame(entryParserVMS, client.getEntryParser()); // the previous entry was replaced - } - private static class LocalClient extends FTPClient { + private String systemType; + @Override public String getSystemType() throws IOException { return systemType; } - public void setSystemType(String type) { + + public void setSystemType(final String type) { systemType = type; } } - public void testParserCachingNullKey() throws Exception { - LocalClient client = new LocalClient(); - client.setSystemType(FTPClientConfig.SYST_UNIX); - assertNull(client.getEntryParser()); - client.__createParser(null); - final FTPFileEntryParser entryParser = client.getEntryParser(); - assertNotNull(entryParser); - client.__createParser(null); - assertSame(entryParser, client.getEntryParser()); // parser was cached - client.setSystemType(FTPClientConfig.SYST_NT); - client.__createParser(null); - assertSame(entryParser, client.getEntryParser()); // parser was cached - } - public void testUnparseableFiles() throws Exception { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - baos.write("-rwxr-xr-x 2 root root 4096 Mar 2 15:13 zxbox".getBytes()); - baos.write(new byte[]{'\r','\n'}); - baos.write("zrwxr-xr-x 2 root root 4096 Mar 2 15:13 zxbox".getBytes()); - baos.write(new byte[]{'\r','\n'}); - FTPFileEntryParser parser = new UnixFTPEntryParser(); - FTPClientConfig config = new FTPClientConfig(); - FTPListParseEngine engine = new FTPListParseEngine(parser, config); - config.setUnparseableEntries(false); - engine.readServerList(new ByteArrayInputStream(baos.toByteArray()), null); // use default encoding - FTPFile[] files = engine.getFiles(); - assertEquals(1, files.length); - config.setUnparseableEntries(true); - engine = new FTPListParseEngine(parser, config ); - engine.readServerList(new ByteArrayInputStream(baos.toByteArray()), null); // use default encoding - files = engine.getFiles(); - assertEquals(2, files.length); - } - private static class PassiveNatWorkAroundLocalClient extends FTPClient { - private String passiveModeServerIP; + private final String passiveModeServerIP; - public PassiveNatWorkAroundLocalClient(String passiveModeServerIP) { + public PassiveNatWorkAroundLocalClient(final String passiveModeServerIP) { this.passiveModeServerIP = passiveModeServerIP; } @@ -138,65 +54,167 @@ public class FTPClientTest extends TestCase { public InetAddress getRemoteAddress() { try { return InetAddress.getByName(passiveModeServerIP); - } catch (Exception e) { + } catch (final Exception e) { throw new RuntimeException(e); } } } + private static final String[] TESTS = { "257 /path/without/quotes", "/path/without/quotes", + + "257 \"/path/with/delimiting/quotes/without/commentary\"", "/path/with/delimiting/quotes/without/commentary", + + "257 \"/path/with/quotes\"\" /inside/but/without/commentary\"", "/path/with/quotes\" /inside/but/without/commentary", + + "257 \"/path/with/quotes\"\" /inside/string\" and with commentary", "/path/with/quotes\" /inside/string", + + "257 \"/path/with/quotes\"\" /inside/string\" and with commentary that also \"contains quotes\"", "/path/with/quotes\" /inside/string", + + "257 \"/path/without/trailing/quote", // invalid syntax, return all after reply code prefix + "\"/path/without/trailing/quote", + + "257 root is current directory.", // NET-442 + "root is current directory.", + + "257 \"/\"", // NET-502 + "/", }; + + public FTPClientTest(final String name) { + super(name); + } + + public void testParseClient() { + for (int i = 0; i < TESTS.length; i += 2) { + assertEquals("Failed to parse", TESTS[i + 1], FTPClient.parsePathname(TESTS[i])); + } + } + public void testParsePassiveModeReplyForLocalAddressWithNatWorkaround() throws Exception { - FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8"); + final FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8"); + client.setIpAddressFromPasvResponse(true); client._parsePassiveModeReply("227 Entering Passive Mode (172,16,204,138,192,22)."); assertEquals("8.8.8.8", client.getPassiveHost()); - } - - public void testParsePassiveModeReplyForNonLocalAddressWithNatWorkaround() throws Exception { - FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8"); - client._parsePassiveModeReply("227 Entering Passive Mode (8,8,4,4,192,22)."); - assertEquals("8.8.4.4", client.getPassiveHost()); + client.setIpAddressFromPasvResponse(false); + client._parsePassiveModeReply("227 Entering Passive Mode (172,16,204,138,192,22)."); + assertNull(client.getPassiveHost()); } @SuppressWarnings("deprecation") // testing deprecated code public void testParsePassiveModeReplyForLocalAddressWithNatWorkaroundDisabled() throws Exception { - FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8"); + final FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8"); client.setPassiveNatWorkaround(false); + client.setIpAddressFromPasvResponse(true); + client._parsePassiveModeReply("227 Entering Passive Mode (172,16,204,138,192,22)."); + assertEquals("172.16.204.138", client.getPassiveHost()); + client.setIpAddressFromPasvResponse(false); + client._parsePassiveModeReply("227 Entering Passive Mode (172,16,204,138,192,22)."); + assertNull(client.getPassiveHost()); + } + + public void testParsePassiveModeReplyForLocalAddressWithoutNatWorkaroundStrategy() throws Exception { + final FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8"); + client.setPassiveNatWorkaroundStrategy(null); + client.setIpAddressFromPasvResponse(true); client._parsePassiveModeReply("227 Entering Passive Mode (172,16,204,138,192,22)."); assertEquals("172.16.204.138", client.getPassiveHost()); + client.setIpAddressFromPasvResponse(false); + client._parsePassiveModeReply("227 Entering Passive Mode (172,16,204,138,192,22)."); + assertNull(client.getPassiveHost()); + } + + public void testParsePassiveModeReplyForLocalAddressWithSimpleNatWorkaroundStrategy() throws Exception { + final FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8"); + client.setPassiveNatWorkaroundStrategy(hostname -> "4.4.4.4"); + client.setIpAddressFromPasvResponse(true); + client._parsePassiveModeReply("227 Entering Passive Mode (172,16,204,138,192,22)."); + assertEquals("4.4.4.4", client.getPassiveHost()); + client.setIpAddressFromPasvResponse(false); + client._parsePassiveModeReply("227 Entering Passive Mode (172,16,204,138,192,22)."); + assertNull(client.getPassiveHost()); + } + + public void testParsePassiveModeReplyForNonLocalAddressWithNatWorkaround() throws Exception { + final FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8"); + client.setIpAddressFromPasvResponse(true); + client._parsePassiveModeReply("227 Entering Passive Mode (8,8,4,4,192,22)."); + assertEquals("8.8.4.4", client.getPassiveHost()); + client.setIpAddressFromPasvResponse(false); + client._parsePassiveModeReply("227 Entering Passive Mode (8,8,4,4,192,22)."); + assertNull(client.getPassiveHost()); } @SuppressWarnings("deprecation") // testing deprecated code public void testParsePassiveModeReplyForNonLocalAddressWithNatWorkaroundDisabled() throws Exception { - FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8"); + final FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8"); client.setPassiveNatWorkaround(false); + client.setIpAddressFromPasvResponse(true); client._parsePassiveModeReply("227 Entering Passive Mode (8,8,4,4,192,22)."); assertEquals("8.8.4.4", client.getPassiveHost()); - } - - public void testParsePassiveModeReplyForLocalAddressWithoutNatWorkaroundStrategy() throws Exception { - FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8"); - client.setPassiveNatWorkaroundStrategy(null); - client._parsePassiveModeReply("227 Entering Passive Mode (172,16,204,138,192,22)."); - assertEquals("172.16.204.138", client.getPassiveHost()); + client.setIpAddressFromPasvResponse(false); + client._parsePassiveModeReply("227 Entering Passive Mode (8,8,4,4,192,22)."); + assertNull(client.getPassiveHost()); } public void testParsePassiveModeReplyForNonLocalAddressWithoutNatWorkaroundStrategy() throws Exception { - FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8"); + final FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8"); client.setPassiveNatWorkaroundStrategy(null); + client.setIpAddressFromPasvResponse(true); client._parsePassiveModeReply("227 Entering Passive Mode (8,8,4,4,192,22)."); assertEquals("8.8.4.4", client.getPassiveHost()); + client.setIpAddressFromPasvResponse(false); + client._parsePassiveModeReply("227 Entering Passive Mode (8,8,4,4,192,22)."); + assertNull(client.getPassiveHost()); } - public void testParsePassiveModeReplyForLocalAddressWithSimpleNatWorkaroundStrategy() throws Exception { - FTPClient client = new PassiveNatWorkAroundLocalClient("8.8.8.8"); - client.setPassiveNatWorkaroundStrategy(new FTPClient.HostnameResolver() { - @Override - public String resolve(String hostname) throws UnknownHostException { - return "4.4.4.4"; - } + public void testParserCachingNullKey() throws Exception { + final LocalClient client = new LocalClient(); + client.setSystemType(FTPClientConfig.SYST_UNIX); + assertNull(client.getEntryParser()); + client.createParser(null); + final FTPFileEntryParser entryParser = client.getEntryParser(); + assertNotNull(entryParser); + client.createParser(null); + assertSame(entryParser, client.getEntryParser()); // parser was cached + client.setSystemType(FTPClientConfig.SYST_NT); + client.createParser(null); + assertSame(entryParser, client.getEntryParser()); // parser was cached + } - }); - client._parsePassiveModeReply("227 Entering Passive Mode (172,16,204,138,192,22)."); - assertEquals("4.4.4.4", client.getPassiveHost()); + public void testParserCachingWithKey() throws Exception { + final FTPClient client = new FTPClient(); + assertNull(client.getEntryParser()); + client.createParser(FTPClientConfig.SYST_UNIX); + final FTPFileEntryParser entryParserSYST = client.getEntryParser(); + assertNotNull(entryParserSYST); + client.createParser(FTPClientConfig.SYST_UNIX); + assertSame(entryParserSYST, client.getEntryParser()); // the previous entry was cached + client.createParser(FTPClientConfig.SYST_VMS); + final FTPFileEntryParser entryParserVMS = client.getEntryParser(); + assertNotSame(entryParserSYST, entryParserVMS); // the previous entry was replaced + client.createParser(FTPClientConfig.SYST_VMS); + assertSame(entryParserVMS, client.getEntryParser()); // the previous entry was cached + client.createParser(FTPClientConfig.SYST_UNIX); // revert + assertNotSame(entryParserVMS, client.getEntryParser()); // the previous entry was replaced + } + + public void testUnparseableFiles() throws Exception { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write("-rwxr-xr-x 2 root root 4096 Mar 2 15:13 zxbox".getBytes()); + baos.write(new byte[] { '\r', '\n' }); + baos.write("zrwxr-xr-x 2 root root 4096 Mar 2 15:13 zxbox".getBytes()); + baos.write(new byte[] { '\r', '\n' }); + final FTPFileEntryParser parser = new UnixFTPEntryParser(); + final FTPClientConfig config = new FTPClientConfig(); + FTPListParseEngine engine = new FTPListParseEngine(parser, config); + config.setUnparseableEntries(false); + engine.readServerList(new ByteArrayInputStream(baos.toByteArray()), null); // use default encoding + FTPFile[] files = engine.getFiles(); + assertEquals(1, files.length); + config.setUnparseableEntries(true); + engine = new FTPListParseEngine(parser, config); + engine.readServerList(new ByteArrayInputStream(baos.toByteArray()), null); // use default encoding + files = engine.getFiles(); + assertEquals(2, files.length); } - } +} diff --git a/src/test/java/org/apache/commons/net/ftp/FTPCommandTest.java b/src/test/java/org/apache/commons/net/ftp/FTPCommandTest.java index b4edbe0..137d3ac 100644 --- a/src/test/java/org/apache/commons/net/ftp/FTPCommandTest.java +++ b/src/test/java/org/apache/commons/net/ftp/FTPCommandTest.java @@ -22,7 +22,7 @@ import junit.framework.TestCase; public class FTPCommandTest extends TestCase { - public FTPCommandTest(String name) { + public FTPCommandTest(final String name) { super(name); } diff --git a/src/test/java/org/apache/commons/net/ftp/FTPSClientTest.java b/src/test/java/org/apache/commons/net/ftp/FTPSClientTest.java new file mode 100644 index 0000000..f1d7d3b --- /dev/null +++ b/src/test/java/org/apache/commons/net/ftp/FTPSClientTest.java @@ -0,0 +1,198 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.net.ftp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.net.SocketException; +import java.time.Instant; +import java.util.Calendar; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** + * Tests {@link FTPSClient}. + * <p> + * To get our test cert to work on Java 11, this test must be run with: + * </p> + * + * <pre> + * -Djdk.tls.client.protocols="TLSv1.1" + * </pre> + * <p> + * This test does the above programmatically. + * </p> + */ +@RunWith(Parameterized.class) +public class FTPSClientTest extends AbstractFtpsTest { + + private static final String USER_PROPS_RES = "org/apache/commons/net/ftpsserver/users.properties"; + + private static final String SERVER_JKS_RES = "org/apache/commons/net/ftpsserver/ftpserver.jks"; + + @BeforeClass + public static void setupServer() throws Exception { + setupServer(IMPLICIT, USER_PROPS_RES, SERVER_JKS_RES, "target/test-classes/org/apache/commons/net/test-data"); + } + + @Parameters(name = "endpointCheckingEnabled={0}") + public static Boolean[] testConstructurData() { + return new Boolean[] { Boolean.FALSE, Boolean.TRUE }; + } + + public FTPSClientTest(final boolean endpointCheckingEnabled) { + super(endpointCheckingEnabled, null, null); + } + + @Test(timeout = TEST_TIMEOUT) + public void testHasFeature() throws SocketException, IOException { + trace(">>testHasFeature"); + loginClient().disconnect(); + trace("<<testHasFeature"); + } + + private void testListFiles(final String pathname) throws SocketException, IOException { + final FTPSClient client = loginClient(); + try { + // do it twice + assertNotNull(client.listFiles(pathname)); + assertNotNull(client.listFiles(pathname)); + } finally { + client.disconnect(); + } + } + + @Test(timeout = TEST_TIMEOUT) + public void testListFilesPathNameEmpty() throws SocketException, IOException { + trace(">>testListFilesPathNameEmpty"); + testListFiles(""); + trace("<<testListFilesPathNameEmpty"); + } + + @Test(timeout = TEST_TIMEOUT) + public void testListFilesPathNameJunk() throws SocketException, IOException { + trace(">>testListFilesPathNameJunk"); + testListFiles(" Junk "); + trace("<<testListFilesPathNameJunk"); + } + + @Test(timeout = TEST_TIMEOUT) + public void testListFilesPathNameNull() throws SocketException, IOException { + trace(">>testListFilesPathNameNull"); + testListFiles(null); + trace("<<testListFilesPathNameNull"); + } + + @Test(timeout = TEST_TIMEOUT) + public void testListFilesPathNameRoot() throws SocketException, IOException { + trace(">>testListFilesPathNameRoot"); + testListFiles("/"); + trace("<<testListFilesPathNameRoot"); + } + + @Test(timeout = TEST_TIMEOUT) + public void testMdtmCalendar() throws SocketException, IOException { + trace(">>testMdtmCalendar"); + testMdtmCalendar("/file.txt"); + trace("<<testMdtmCalendar"); + } + + private void testMdtmCalendar(final String pathname) throws SocketException, IOException { + final FTPSClient client = loginClient(); + try { + // do it twice + final Calendar mdtmCalendar1 = client.mdtmCalendar(pathname); + final Calendar mdtmCalendar2 = client.mdtmCalendar(pathname); + assertNotNull(mdtmCalendar1); + assertNotNull(mdtmCalendar2); + assertEquals(mdtmCalendar1, mdtmCalendar2); + } finally { + client.disconnect(); + } + } + + @Test(timeout = TEST_TIMEOUT) + public void testMdtmFile() throws SocketException, IOException { + trace(">>testMdtmFile"); + testMdtmFile("/file.txt"); + trace("<<testMdtmFile"); + } + + private void testMdtmFile(final String pathname) throws SocketException, IOException { + final FTPSClient client = loginClient(); + try { + // do it twice + final FTPFile mdtmFile1 = client.mdtmFile(pathname); + final FTPFile mdtmFile2 = client.mdtmFile(pathname); + assertNotNull(mdtmFile1); + assertNotNull(mdtmFile2); + assertEquals(mdtmFile1.toString(), mdtmFile2.toString()); + } finally { + client.disconnect(); + } + } + + @Test(timeout = TEST_TIMEOUT) + public void testMdtmInstant() throws SocketException, IOException { + trace(">>testMdtmInstant"); + testMdtmInstant("/file.txt"); + trace("<<testMdtmInstant"); + } + + private void testMdtmInstant(final String pathname) throws SocketException, IOException { + final FTPSClient client = loginClient(); + try { + // do it twice + final Instant mdtmInstant1 = client.mdtmInstant(pathname); + final Instant mdtmInstant2 = client.mdtmInstant(pathname); + assertNotNull(mdtmInstant1); + assertNotNull(mdtmInstant2); + assertEquals(mdtmInstant1, mdtmInstant2); + } finally { + client.disconnect(); + } + } + + @Test(timeout = TEST_TIMEOUT) + public void testOpenClose() throws SocketException, IOException { + trace(">>testOpenClose"); + final FTPSClient ftpsClient = loginClient(); + try { + assertTrue(ftpsClient.hasFeature("MODE")); + assertTrue(ftpsClient.hasFeature(FTPCmd.MODE)); + } finally { + ftpsClient.disconnect(); + } + trace("<<testOpenClose"); + } + + @Test(timeout = TEST_TIMEOUT) + public void testRetrieveFilePathNameRoot() throws SocketException, IOException { + trace(">>testRetrieveFilePathNameRoot"); + retrieveFile("/file.txt"); + trace("<<testRetrieveFilePathNameRoot"); + } + +} diff --git a/src/test/java/org/apache/commons/net/ftp/ListingFunctionalTest.java b/src/test/java/org/apache/commons/net/ftp/ListingFunctionalTest.java index ccedb97..28b3b29 100644 --- a/src/test/java/org/apache/commons/net/ftp/ListingFunctionalTest.java +++ b/src/test/java/org/apache/commons/net/ftp/ListingFunctionalTest.java @@ -15,6 +15,9 @@ * limitations under the License. */ package org.apache.commons.net.ftp; + +import static org.junit.Assert.assertArrayEquals; + import java.io.IOException; import java.lang.reflect.Method; import java.util.Arrays; @@ -27,10 +30,8 @@ import junit.framework.TestSuite; /** * A functional test suite for checking that site listings work. - * @version $Id: ListingFunctionalTest.java 1697293 2015-08-24 01:01:00Z sebb $ */ -public class ListingFunctionalTest extends TestCase -{ +public class ListingFunctionalTest extends TestCase { // Offsets within testData below static final int HOSTNAME = 0; static final int VALID_PARSERKEY = 1; @@ -40,50 +41,30 @@ public class ListingFunctionalTest extends TestCase static final int VALID_PATH = 5; static final int PATH_PWD = 6; // response to PWD - public static final Test suite() - { - String[][] testData = - { - { - "ftp.ibiblio.org", "unix", "vms", - "HA!", "javaio.jar", - "pub/languages/java/javafaq", - "/pub/languages/java/javafaq", - }, - { - "apache.cs.utah.edu", "unix", "vms", - "HA!", "HEADER.html", - "apache.org", - "/apache.org", - }, + public static final Test suite() { + final String[][] testData = { { "ftp.ibiblio.org", "unix", "vms", "HA!", "javaio.jar", "pub/languages/java/javafaq", "/pub/languages/java/javafaq", }, + { "apache.cs.utah.edu", "unix", "vms", "HA!", "HEADER.html", "apache.org", "/apache.org", }, // { // not available // "ftp.wacom.com", "windows", "VMS", "HA!", // "wacom97.zip", "pub\\drivers" // }, - { - "ftp.decuslib.com", "vms", "windows", // VMS OpenVMS V8.3 - "[.HA!]", "FREEWARE_SUBMISSION_INSTRUCTIONS.TXT;1", - "[.FREEWAREV80.FREEWARE]", - "DECUSLIB:[DECUS.FREEWAREV80.FREEWARE]" - }, + { "ftp.decuslib.com", "vms", "windows", // VMS OpenVMS V8.3 + "[.HA!]", "FREEWARE_SUBMISSION_INSTRUCTIONS.TXT;1", "[.FREEWAREV80.FREEWARE]", "DECUSLIB:[DECUS.FREEWAREV80.FREEWARE]" }, // { // VMS TCPware V5.7-2 does not return (RWED) permissions // "ftp.process.com", "vms", "windows", // "[.HA!]", "MESSAGE.;1", // "[.VMS-FREEWARE.FREE-VMS]" // // }, - }; - Class<?> clasz = ListingFunctionalTest.class; - Method[] methods = clasz.getDeclaredMethods(); - TestSuite allSuites = new TestSuite("FTP Listing Functional Test Suite"); - - for (String[] element : testData) - { - TestSuite suite = new TestSuite(element[VALID_PARSERKEY]+ " @ " +element[HOSTNAME]); - - for (Method method : methods) - { - if (method.getName().startsWith("test")) - { + }; + final Class<?> clasz = ListingFunctionalTest.class; + final Method[] methods = clasz.getDeclaredMethods(); + final TestSuite allSuites = new TestSuite("FTP Listing Functional Test Suite"); + + for (final String[] element : testData) { + final TestSuite suite = new TestSuite(element[VALID_PARSERKEY] + " @ " + element[HOSTNAME]); + + for (final Method method : methods) { + if (method.getName().startsWith("test")) { suite.addTest(new ListingFunctionalTest(method.getName(), element)); } } @@ -103,8 +84,7 @@ public class ListingFunctionalTest extends TestCase private final String validPath; private final String pwdPath; - public ListingFunctionalTest(String arg0, String[] settings) - { + public ListingFunctionalTest(final String arg0, final String[] settings) { super(arg0); invalidParserKey = settings[INVALID_PARSERKEY]; validParserKey = settings[VALID_PARSERKEY]; @@ -115,26 +95,21 @@ public class ListingFunctionalTest extends TestCase hostName = settings[HOSTNAME]; } - private boolean findByName(List<?> fileList, String string) - { + private boolean findByName(final List<?> fileList, final String string) { boolean found = false; - Iterator<?> iter = fileList.iterator(); + final Iterator<?> iter = fileList.iterator(); - while (iter.hasNext() && !found) - { - Object element = iter.next(); + while (iter.hasNext() && !found) { + final Object element = iter.next(); - if (element instanceof FTPFile) - { - FTPFile file = (FTPFile) element; + if (element instanceof FTPFile) { + final FTPFile file = (FTPFile) element; found = file.getName().equals(string); - } - else - { - String filename = (String) element; + } else { + final String fileName = (String) element; - found = filename.endsWith(string); + found = fileName.endsWith(string); } } @@ -145,8 +120,7 @@ public class ListingFunctionalTest extends TestCase * @see TestCase#setUp() */ @Override - protected void setUp() throws Exception - { + protected void setUp() throws Exception { super.setUp(); client = new FTPClient(); client.connect(hostName); @@ -159,20 +133,14 @@ public class ListingFunctionalTest extends TestCase * @see TestCase#tearDown() */ @Override - protected void tearDown() - throws Exception - { - try - { + protected void tearDown() throws Exception { + try { client.logout(); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); } - if (client.isConnected()) - { + if (client.isConnected()) { client.disconnect(); } @@ -183,13 +151,11 @@ public class ListingFunctionalTest extends TestCase /* * Test for FTPListParseEngine initiateListParsing() */ - public void testInitiateListParsing() - throws IOException - { + public void testInitiateListParsing() throws IOException { client.changeWorkingDirectory(validPath); - FTPListParseEngine engine = client.initiateListParsing(); - List<FTPFile> files = Arrays.asList(engine.getNext(25)); + final FTPListParseEngine engine = client.initiateListParsing(); + final List<FTPFile> files = Arrays.asList(engine.getNext(25)); assertTrue(files.toString(), findByName(files, validFilename)); } @@ -197,12 +163,9 @@ public class ListingFunctionalTest extends TestCase /* * Test for FTPListParseEngine initiateListParsing(String, String) */ - public void testInitiateListParsingWithPath() - throws IOException - { - FTPListParseEngine engine = client.initiateListParsing(validParserKey, - validPath); - List<FTPFile> files = Arrays.asList(engine.getNext(25)); + public void testInitiateListParsingWithPath() throws IOException { + final FTPListParseEngine engine = client.initiateListParsing(validParserKey, validPath); + final List<FTPFile> files = Arrays.asList(engine.getNext(25)); assertTrue(files.toString(), findByName(files, validFilename)); } @@ -210,11 +173,9 @@ public class ListingFunctionalTest extends TestCase /* * Test for FTPListParseEngine initiateListParsing(String) */ - public void testInitiateListParsingWithPathAndAutodetection() - throws IOException - { - FTPListParseEngine engine = client.initiateListParsing(validPath); - List<FTPFile> files = Arrays.asList(engine.getNext(25)); + public void testInitiateListParsingWithPathAndAutodetection() throws IOException { + final FTPListParseEngine engine = client.initiateListParsing(validPath); + final List<FTPFile> files = Arrays.asList(engine.getNext(25)); assertTrue(files.toString(), findByName(files, validFilename)); } @@ -222,10 +183,8 @@ public class ListingFunctionalTest extends TestCase /* * Test for FTPListParseEngine initiateListParsing(String) */ - public void testInitiateListParsingWithPathAndAutodetectionButEmpty() - throws IOException - { - FTPListParseEngine engine = client.initiateListParsing(invalidPath); + public void testInitiateListParsingWithPathAndAutodetectionButEmpty() throws IOException { + final FTPListParseEngine engine = client.initiateListParsing(invalidPath); assertFalse(engine.hasNext()); } @@ -233,11 +192,8 @@ public class ListingFunctionalTest extends TestCase /* * Test for FTPListParseEngine initiateListParsing(String, String) */ - public void testInitiateListParsingWithPathAndIncorrectParser() - throws IOException - { - FTPListParseEngine engine = client.initiateListParsing(invalidParserKey, - invalidPath); + public void testInitiateListParsingWithPathAndIncorrectParser() throws IOException { + final FTPListParseEngine engine = client.initiateListParsing(invalidParserKey, invalidPath); assertFalse(engine.hasNext()); } @@ -245,52 +201,42 @@ public class ListingFunctionalTest extends TestCase /* * Test for FTPFile[] listFiles(String, String) */ - public void testListFiles() - throws IOException - { - FTPClientConfig config = new FTPClientConfig(validParserKey); + public void testListFiles() throws IOException { + final FTPClientConfig config = new FTPClientConfig(validParserKey); client.configure(config); - List<FTPFile> files = Arrays.asList(client.listFiles(validPath)); + final List<FTPFile> files = Arrays.asList(client.listFiles(validPath)); - assertTrue(files.toString(), - findByName(files, validFilename)); + assertTrue(files.toString(), findByName(files, validFilename)); } - public void testListFilesWithAutodection() - throws IOException - { + public void testListFilesWithAutodection() throws IOException { client.changeWorkingDirectory(validPath); - List<FTPFile> files = Arrays.asList(client.listFiles()); + final List<FTPFile> files = Arrays.asList(client.listFiles()); - assertTrue(files.toString(), - findByName(files, validFilename)); + assertTrue(files.toString(), findByName(files, validFilename)); } /* * Test for FTPFile[] listFiles(String, String) */ - public void testListFilesWithIncorrectParser() - throws IOException - { - FTPClientConfig config = new FTPClientConfig(invalidParserKey); + public void testListFilesWithIncorrectParser() throws IOException { + final FTPClientConfig config = new FTPClientConfig(invalidParserKey); client.configure(config); - FTPFile[] files = client.listFiles(validPath); + final FTPFile[] files = client.listFiles(validPath); assertNotNull(files); // This may well fail, e.g. window parser for VMS listing - assertTrue("Expected empty array: "+Arrays.toString(files), Arrays.equals(new FTPFile[]{}, files)); + assertArrayEquals("Expected empty array: " + Arrays.toString(files), new FTPFile[] {}, files); } /* * Test for FTPFile[] listFiles(String) */ - public void testListFilesWithPathAndAutodectionButEmpty() - throws IOException - { - FTPFile[] files = client.listFiles(invalidPath); + public void testListFilesWithPathAndAutodectionButEmpty() throws IOException { + final FTPFile[] files = client.listFiles(invalidPath); assertEquals(0, files.length); } @@ -298,28 +244,23 @@ public class ListingFunctionalTest extends TestCase /* * Test for FTPFile[] listFiles(String) */ - public void testListFilesWithPathAndAutodetection() - throws IOException - { - List<FTPFile> files = Arrays.asList(client.listFiles(validPath)); + public void testListFilesWithPathAndAutodetection() throws IOException { + final List<FTPFile> files = Arrays.asList(client.listFiles(validPath)); - assertTrue(files.toString(), - findByName(files, validFilename)); + assertTrue(files.toString(), findByName(files, validFilename)); } /* * Test for String[] listNames() */ - public void testListNames() - throws IOException - { + public void testListNames() throws IOException { client.changeWorkingDirectory(validPath); - String[] names = client.listNames(); + final String[] names = client.listNames(); assertNotNull(names); - List<String> lnames = Arrays.asList(names); + final List<String> lnames = Arrays.asList(names); assertTrue(lnames.toString(), lnames.contains(validFilename)); } @@ -327,29 +268,23 @@ public class ListingFunctionalTest extends TestCase /* * Test for String[] listNames(String) */ - public void testListNamesWithPath() - throws IOException - { - String[] listNames = client.listNames(validPath); + public void testListNamesWithPath() throws IOException { + final String[] listNames = client.listNames(validPath); assertNotNull("listNames not null", listNames); - List<String> names = Arrays.asList(listNames); + final List<String> names = Arrays.asList(listNames); assertTrue(names.toString(), findByName(names, validFilename)); } - public void testListNamesWithPathButEmpty() - throws IOException - { - String[] names = client.listNames(invalidPath); + public void testListNamesWithPathButEmpty() throws IOException { + final String[] names = client.listNames(invalidPath); assertNull(names); } - public void testPrintWorkingDirectory() - throws IOException - { + public void testPrintWorkingDirectory() throws IOException { client.changeWorkingDirectory(validPath); - String pwd = client.printWorkingDirectory(); + final String pwd = client.printWorkingDirectory(); assertEquals(pwdPath, pwd); } } diff --git a/src/test/java/org/apache/commons/net/ftp/NoProtocolSslConfigurationProxy.java b/src/test/java/org/apache/commons/net/ftp/NoProtocolSslConfigurationProxy.java new file mode 100644 index 0000000..3338154 --- /dev/null +++ b/src/test/java/org/apache/commons/net/ftp/NoProtocolSslConfigurationProxy.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.net.ftp; + +import java.security.GeneralSecurityException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; + +import org.apache.ftpserver.ssl.ClientAuth; +import org.apache.ftpserver.ssl.SslConfiguration; + +/** + * see https://issues.apache.org/jira/browse/FTPSERVER-491 + */ +public class NoProtocolSslConfigurationProxy implements SslConfiguration { + + private final SslConfiguration sslConfiguration; + + public NoProtocolSslConfigurationProxy(final SslConfiguration sslConfiguration) { + this.sslConfiguration = sslConfiguration; + } + + @Override + public ClientAuth getClientAuth() { + return this.sslConfiguration.getClientAuth(); + } + + @Override + public String[] getEnabledCipherSuites() { + return this.sslConfiguration.getEnabledCipherSuites(); + } + + @Override + public String[] getEnabledProtocols() { + return null; + } + + @Override + public SSLSocketFactory getSocketFactory() throws GeneralSecurityException { + return this.sslConfiguration.getSocketFactory(); + } + + @Override + public SSLContext getSSLContext() throws GeneralSecurityException { + return this.sslConfiguration.getSSLContext(); + } + + @Override + public SSLContext getSSLContext(final String protocol) throws GeneralSecurityException { + return this.sslConfiguration.getSSLContext(protocol); + } + +} diff --git a/src/test/java/org/apache/commons/net/ftp/TestConnectTimeout.java b/src/test/java/org/apache/commons/net/ftp/TestConnectTimeout.java index a3bbaf8..92158ac 100644 --- a/src/test/java/org/apache/commons/net/ftp/TestConnectTimeout.java +++ b/src/test/java/org/apache/commons/net/ftp/TestConnectTimeout.java @@ -30,7 +30,7 @@ import junit.framework.TestCase; public class TestConnectTimeout extends TestCase { public void testConnectTimeout() throws SocketException, IOException { - FTPClient client = new FTPClient(); + final FTPClient client = new FTPClient(); client.setConnectTimeout(1000); try { @@ -38,14 +38,7 @@ public class TestConnectTimeout extends TestCase { // TODO use a local server if possible client.connect("www.apache.org", 1234); fail("Expecting an Exception"); - } - catch (ConnectException se) { - assertTrue(true); - } - catch (SocketTimeoutException se) { - assertTrue(true); - } - catch (UnknownHostException ue) { + } catch (final ConnectException | SocketTimeoutException | UnknownHostException ue) { // Not much we can do about this, we may be firewalled assertTrue(true); } diff --git a/src/test/java/org/apache/commons/net/ftp/parser/CompositeFTPParseTestFramework.java b/src/test/java/org/apache/commons/net/ftp/parser/CompositeFTPParseTestFramework.java index 371df13..59e9723 100644 --- a/src/test/java/org/apache/commons/net/ftp/parser/CompositeFTPParseTestFramework.java +++ b/src/test/java/org/apache/commons/net/ftp/parser/CompositeFTPParseTestFramework.java @@ -20,82 +20,71 @@ import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPFileEntryParser; /** - * @version $Id: CompositeFTPParseTestFramework.java 1697293 2015-08-24 01:01:00Z sebb $ */ -public abstract class CompositeFTPParseTestFramework extends FTPParseTestFramework -{ - public CompositeFTPParseTestFramework(String name) - { +public abstract class CompositeFTPParseTestFramework extends FTPParseTestFramework { + public CompositeFTPParseTestFramework(final String name) { super(name); } @Override - protected String[] getGoodListing() - { - return (getGoodListings()[0]); + protected String[] getBadListing() { + return getBadListings()[0]; } /** - * Method getBadListing. - * Implementors must provide multiple listing that contains failures and - * must force the composite parser to switch the FtpEntryParser + * Method getBadListing. Implementors must provide multiple listing that contains failures and must force the composite parser to switch the FtpEntryParser * * @return String[] */ protected abstract String[][] getBadListings(); + @Override + protected String[] getGoodListing() { + return getGoodListings()[0]; + } + /** - * Method getGoodListing. - * Implementors must provide multiple listing that passes and - * must force the composite parser to switch the FtpEntryParser + * Method getGoodListing. Implementors must provide multiple listing that passes and must force the composite parser to switch the FtpEntryParser * * @return String[] */ protected abstract String[][] getGoodListings(); - @Override - protected String[] getBadListing() - { - return (getBadListings()[0]); - } - - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#testGoodListing() */ - public void testConsistentListing() throws Exception - { - String goodsamples[][] = getGoodListings(); - - for (String[] goodsample : goodsamples) - { - FTPFileEntryParser parser = getParser(); - for (String test : goodsample) { - FTPFile f = parser.parseFTPEntry(test); - assertNotNull("Failed to parse " + test, - f); + @Override + public void testBadListing() { + final String badsamples[][] = getBadListings(); - doAdditionalGoodTests(test, f); + for (final String[] badsample : badsamples) { + final FTPFileEntryParser parser = getParser(); + for (final String test : badsample) { + final FTPFile f = parser.parseFTPEntry(test); + assertNull("Should have Failed to parse " + test, nullFileOrNullDate(f)); + + doAdditionalBadTests(test, f); } } } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#testGoodListing() */ - @Override - public void testBadListing() throws Exception - { - String badsamples[][] = getBadListings(); - - for (String[] badsample : badsamples) - { - FTPFileEntryParser parser = getParser(); - for (String test : badsample) { - FTPFile f = parser.parseFTPEntry(test); - assertNull("Should have Failed to parse " + test, - nullFileOrNullDate(f)); + public void testConsistentListing() { + final String goodsamples[][] = getGoodListings(); - doAdditionalBadTests(test, f); + for (final String[] goodsample : goodsamples) { + final FTPFileEntryParser parser = getParser(); + for (final String test : goodsample) { + final FTPFile f = parser.parseFTPEntry(test); + assertNotNull("Failed to parse " + test, f); + + doAdditionalGoodTests(test, f); } } } @@ -103,19 +92,16 @@ public abstract class CompositeFTPParseTestFramework extends FTPParseTestFramewo // even though all these listings are good using one parser // or the other, this tests that a parser that has succeeded // on one format will fail if another format is substituted. - public void testInconsistentListing() throws Exception - { - String goodsamples[][] = getGoodListings(); + public void testInconsistentListing() { + final String goodsamples[][] = getGoodListings(); - FTPFileEntryParser parser = getParser(); + final FTPFileEntryParser parser = getParser(); - for (int i = 0; i < goodsamples.length; i++) - { - String test = goodsamples[i][0]; - FTPFile f = parser.parseFTPEntry(test); + for (int i = 0; i < goodsamples.length; i++) { + final String test = goodsamples[i][0]; + final FTPFile f = parser.parseFTPEntry(test); - switch (i) - { + switch (i) { case 0: assertNotNull("Failed to parse " + test, f); break; diff --git a/src/test/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactoryTest.java b/src/test/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactoryTest.java index 0bcb6e6..6002314 100644 --- a/src/test/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactoryTest.java +++ b/src/test/java/org/apache/commons/net/ftp/parser/DefaultFTPFileEntryParserFactoryTest.java @@ -15,28 +15,33 @@ * limitations under the License. */ package org.apache.commons.net.ftp.parser; -import junit.framework.TestCase; import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPFileEntryParser; +import junit.framework.TestCase; -public class DefaultFTPFileEntryParserFactoryTest extends TestCase -{ - public void testDefaultParserFactory() throws Exception { - DefaultFTPFileEntryParserFactory factory = - new DefaultFTPFileEntryParserFactory(); +public class DefaultFTPFileEntryParserFactoryTest extends TestCase { + private void checkParserClass(final FTPFileEntryParserFactory fact, final String key, final Class<?> expected) { + final FTPClientConfig config = key == null ? new FTPClientConfig() : new FTPClientConfig(key); + final FTPFileEntryParser parser = fact.createFileEntryParser(config); + assertNotNull(parser); + assertTrue("Expected " + expected.getCanonicalName() + " got " + parser.getClass().getCanonicalName(), expected.isInstance(parser)); + } + + public void testDefaultParserFactory() { + final DefaultFTPFileEntryParserFactory factory = new DefaultFTPFileEntryParserFactory(); FTPFileEntryParser parser = factory.createFileEntryParser("unix"); assertTrue(parser instanceof UnixFTPEntryParser); parser = factory.createFileEntryParser("UNIX"); assertTrue(parser instanceof UnixFTPEntryParser); - assertFalse(((UnixFTPEntryParser)parser).trimLeadingSpaces); + assertFalse(((UnixFTPEntryParser) parser).trimLeadingSpaces); parser = factory.createFileEntryParser("UNIX_LTRIM"); assertTrue(parser instanceof UnixFTPEntryParser); - assertTrue(((UnixFTPEntryParser)parser).trimLeadingSpaces); + assertTrue(((UnixFTPEntryParser) parser).trimLeadingSpaces); parser = factory.createFileEntryParser("Unix"); assertTrue(parser instanceof UnixFTPEntryParser); @@ -52,10 +57,9 @@ public class DefaultFTPFileEntryParserFactoryTest extends TestCase try { parser = factory.createFileEntryParser("NT"); fail("Exception should have been thrown. \"NT\" is not a recognized key"); - } catch (ParserInitializationException pie) { + } catch (final ParserInitializationException pie) { assertNull(pie.getCause()); - assertTrue(pie.getMessage()+ "should contain 'Unknown parser type:'", - pie.getMessage().contains("Unknown parser type:")); + assertTrue(pie.getMessage() + "should contain 'Unknown parser type:'", pie.getMessage().contains("Unknown parser type:")); } parser = factory.createFileEntryParser("WindowsNT"); @@ -81,20 +85,18 @@ public class DefaultFTPFileEntryParserFactoryTest extends TestCase try { parser = factory.createFileEntryParser("OS2FTPFileEntryParser"); fail("Exception should have been thrown. \"OS2FTPFileEntryParser\" is not a recognized key"); - } catch (ParserInitializationException pie) { + } catch (final ParserInitializationException pie) { assertNull(pie.getCause()); } - parser = factory.createFileEntryParser( - "org.apache.commons.net.ftp.parser.OS2FTPEntryParser"); + parser = factory.createFileEntryParser("org.apache.commons.net.ftp.parser.OS2FTPEntryParser"); assertTrue(parser instanceof OS2FTPEntryParser); try { - parser = factory.createFileEntryParser( - "org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory"); + factory.createFileEntryParser("org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory"); fail("Exception should have been thrown. \"DefaultFTPFileEntryParserFactory\" does not implement FTPFileEntryParser"); - } catch (ParserInitializationException pie) { - Throwable root = pie.getCause(); + } catch (final ParserInitializationException pie) { + final Throwable root = pie.getCause(); assertTrue(root instanceof ClassCastException); } @@ -102,35 +104,27 @@ public class DefaultFTPFileEntryParserFactoryTest extends TestCase // Class exists, but is an interface factory.createFileEntryParser("org.apache.commons.net.ftp.parser.FTPFileEntryParserFactory"); fail("ParserInitializationException should have been thrown."); - } catch (ParserInitializationException pie){ - Throwable root = pie.getCause(); + } catch (final ParserInitializationException pie) { + final Throwable root = pie.getCause(); assertTrue(root instanceof InstantiationException); } try { // Class exists, but is abstract factory.createFileEntryParser("org.apache.commons.net.ftp.FTPFileEntryParserImpl"); fail("ParserInitializationException should have been thrown."); - } catch (ParserInitializationException pie){ - Throwable root = pie.getCause(); + } catch (final ParserInitializationException pie) { + final Throwable root = pie.getCause(); assertTrue(root instanceof InstantiationException); } } - private void checkParserClass(FTPFileEntryParserFactory fact, String key, Class<?> expected){ - FTPClientConfig config = key == null ? new FTPClientConfig() : new FTPClientConfig(key); - FTPFileEntryParser parser = fact.createFileEntryParser(config); - assertNotNull(parser); - assertTrue("Expected "+expected.getCanonicalName()+" got "+parser.getClass().getCanonicalName(), - expected.isInstance(parser)); - } public void testDefaultParserFactoryConfig() throws Exception { - DefaultFTPFileEntryParserFactory factory = - new DefaultFTPFileEntryParserFactory(); + final DefaultFTPFileEntryParserFactory factory = new DefaultFTPFileEntryParserFactory(); try { - factory.createFileEntryParser((FTPClientConfig)null); + factory.createFileEntryParser((FTPClientConfig) null); fail("Expected NullPointerException"); - } catch (NullPointerException npe) { + } catch (final NullPointerException npe) { // expected } checkParserClass(factory, null, UnixFTPEntryParser.class); diff --git a/src/test/java/org/apache/commons/net/ftp/parser/DownloadListings.java b/src/test/java/org/apache/commons/net/ftp/parser/DownloadListings.java index e69b575..1d354c7 100644 --- a/src/test/java/org/apache/commons/net/ftp/parser/DownloadListings.java +++ b/src/test/java/org/apache/commons/net/ftp/parser/DownloadListings.java @@ -43,80 +43,34 @@ public class DownloadListings extends FTPClient { // Also used by MLDSComparison static final String DOWNLOAD_DIR = "target/ftptest"; - private PrintCommandListener listener; - private PrintWriter out; - - private boolean open(String host, int port) throws Exception{ - System.out.println("Connecting to "+host); - out = new PrintWriter(new FileWriter(new File(DOWNLOAD_DIR, host+"_info.txt"))); - listener = new PrintCommandListener(out); - addProtocolCommandListener(listener); - setConnectTimeout(30000); - try { - connect(host, port); - } catch (Exception e) { - System.out.println(e); - return false; - } - enterLocalPassiveMode(); // this is reset by connect - System.out.println("Logging in to "+host); - return login("anonymous", "user@localhost"); - } - - private void info() throws IOException { - syst(); - help(); - feat(); - removeProtocolCommandListener(listener); - } - - private void download(String path, FTPCmd command, File filename) throws Exception { - Socket socket; - if ((socket = _openDataConnection_(command, getListArguments(path))) == null) { - System.out.println(getReplyString()); - return; - } - InputStream inputStream = socket.getInputStream(); - OutputStream outputStream = new FileOutputStream(filename); - Util.copyStream(inputStream, outputStream ); - inputStream.close(); - socket.close(); - outputStream.close(); - - if (!completePendingCommand()) - { - System.out.println(getReplyString()); - } - } - - public static void main(String[] args) throws Exception { + public static void main(final String[] args) throws Exception { String host;// = "ftp.funet.fi"; - int port = 21; + final int port = 21; String path;// = "/"; new File(DOWNLOAD_DIR).mkdirs(); - DownloadListings self = new DownloadListings(); - OutputStream os = new FileOutputStream(new File(DOWNLOAD_DIR, "session.log")); + final DownloadListings self = new DownloadListings(); + final OutputStream os = new FileOutputStream(new File(DOWNLOAD_DIR, "session.log")); self.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(os), true)); - Reader is = new FileReader("mirrors.list"); - BufferedReader rdr = new BufferedReader(is); + final Reader is = new FileReader("mirrors.list"); + final BufferedReader rdr = new BufferedReader(is); String line; - while((line=rdr.readLine()) != null){ - if (line.startsWith("ftp")){ - String []parts = line.split("\\s+"); - String target = parts[2]; + while ((line = rdr.readLine()) != null) { + if (line.startsWith("ftp")) { + final String[] parts = line.split("\\s+"); + final String target = parts[2]; host = target.substring("ftp://".length()); - int slash = host.indexOf('/'); + final int slash = host.indexOf('/'); path = host.substring(slash); - host = host.substring(0,slash); - System.out.println(host+ " "+path); + host = host.substring(0, slash); + System.out.println(host + " " + path); if (self.open(host, port)) { try { self.info(); - self.download(path, FTPCmd.LIST, new File(DOWNLOAD_DIR, host+"_list.txt")); - self.download(path, FTPCmd.MLSD, new File(DOWNLOAD_DIR, host+"_mlsd.txt")); - } catch (Exception e) { + self.download(path, FTPCmd.LIST, new File(DOWNLOAD_DIR, host + "_list.txt")); + self.download(path, FTPCmd.MLSD, new File(DOWNLOAD_DIR, host + "_mlsd.txt")); + } catch (final Exception e) { e.printStackTrace(); } finally { self.disconnect(); @@ -129,4 +83,50 @@ public class DownloadListings extends FTPClient { os.close(); rdr.close(); } + + private PrintCommandListener listener; + + private PrintWriter out; + + private void download(final String path, final FTPCmd command, final File fileName) throws Exception { + final Socket socket; + if ((socket = _openDataConnection_(command, getListArguments(path))) == null) { + System.out.println(getReplyString()); + return; + } + final InputStream inputStream = socket.getInputStream(); + final OutputStream outputStream = new FileOutputStream(fileName); + Util.copyStream(inputStream, outputStream); + inputStream.close(); + socket.close(); + outputStream.close(); + + if (!completePendingCommand()) { + System.out.println(getReplyString()); + } + } + + private void info() throws IOException { + syst(); + help(); + feat(); + removeProtocolCommandListener(listener); + } + + private boolean open(final String host, final int port) throws Exception { + System.out.println("Connecting to " + host); + out = new PrintWriter(new FileWriter(new File(DOWNLOAD_DIR, host + "_info.txt"))); + listener = new PrintCommandListener(out); + addProtocolCommandListener(listener); + setConnectTimeout(30000); + try { + connect(host, port); + } catch (final Exception e) { + System.out.println(e); + return false; + } + enterLocalPassiveMode(); // this is reset by connect + System.out.println("Logging in to " + host); + return login("anonymous", "user@localhost"); + } } diff --git a/src/test/java/org/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParserTest.java b/src/test/java/org/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParserTest.java index 99d0b71..2d3b454 100644 --- a/src/test/java/org/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParserTest.java +++ b/src/test/java/org/apache/commons/net/ftp/parser/EnterpriseUnixFTPEntryParserTest.java @@ -16,7 +16,12 @@ */ package org.apache.commons.net.ftp.parser; +import java.time.Instant; +import java.time.Month; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Calendar; +import java.util.TimeZone; import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPFileEntryParser; @@ -24,99 +29,77 @@ import org.apache.commons.net.ftp.FTPFileEntryParser; /** * Tests the EnterpriseUnixFTPEntryParser * - * @version $Id: EnterpriseUnixFTPEntryParserTest.java 1738405 2016-04-10 02:44:21Z ggregory $ */ -public class EnterpriseUnixFTPEntryParserTest extends FTPParseTestFramework -{ - - private static final String[] BADSAMPLES = - { - "zrwxr-xr-x 2 root root 4096 Mar 2 15:13 zxbox", - "dxrwr-xr-x 2 root root 4096 Aug 24 2001 zxjdbc", - "drwxr-xr-x 2 root root 4096 Jam 4 00:03 zziplib", - "drwxr-xr-x 2 root 99 4096 Feb 23 30:01 zzplayer", - "drwxr-xr-x 2 root root 4096 Aug 36 2001 zztpp", - "-rw-r--r-- 1 14 staff 80284 Aug 22 zxJDBC-1.2.3.tar.gz", - "-rw-r--r-- 1 14 staff 119:26 Aug 22 2000 zxJDBC-1.2.3.zip", - "-rw-r--r-- 1 ftp no group 83853 Jan 22 2001 zxJDBC-1.2.4.tar.gz", - "-rw-r--r-- 1ftp nogroup 126552 Jan 22 2001 zxJDBC-1.2.4.zip", - "-rw-r--r-- 1 root root 111325 Apr -7 18:79 zxJDBC-2.0.1b1.tar.gz", - "drwxr-xr-x 2 root root 4096 Mar 2 15:13 zxbox", - "drwxr-xr-x 1 usernameftp 512 Jan 29 23:32 prog", - "drwxr-xr-x 2 root root 4096 Aug 24 2001 zxjdbc", - "drwxr-xr-x 2 root root 4096 Jan 4 00:03 zziplib", - "drwxr-xr-x 2 root 99 4096 Feb 23 2001 zzplayer", - "drwxr-xr-x 2 root root 4096 Aug 6 2001 zztpp", - "-rw-r--r-- 1 14 staff 80284 Aug 22 2000 zxJDBC-1.2.3.tar.gz", - "-rw-r--r-- 1 14 staff 119926 Aug 22 2000 zxJDBC-1.2.3.zip", - "-rw-r--r-- 1 ftp nogroup 83853 Jan 22 2001 zxJDBC-1.2.4.tar.gz", - "-rw-r--r-- 1 ftp nogroup 126552 Jan 22 2001 zxJDBC-1.2.4.zip", - "-rw-r--r-- 1 root root 111325 Apr 27 2001 zxJDBC-2.0.1b1.tar.gz", - "-rw-r--r-- 1 root root 190144 Apr 27 2001 zxJDBC-2.0.1b1.zip", - "drwxr-xr-x 2 root root 4096 Aug 26 20 zztpp", - "drwxr-xr-x 2 root root 4096 Aug 26 201 zztpp", - "drwxr-xr-x 2 root root 4096 Aug 26 201O zztpp", // OH not zero - }; - private static final String[] GOODSAMPLES = - { - "-C--E-----FTP B QUA1I1 18128 41 Aug 12 13:56 QUADTEST", - "-C--E-----FTP A QUA1I1 18128 41 Aug 12 13:56 QUADTEST2", - "-C--E-----FTP A QUA1I1 18128 41 Apr 1 2014 QUADTEST3" +public class EnterpriseUnixFTPEntryParserTest extends FTPParseTestFramework { + + private static final String[] BADSAMPLES = { "zrwxr-xr-x 2 root root 4096 Mar 2 15:13 zxbox", + "dxrwr-xr-x 2 root root 4096 Aug 24 2001 zxjdbc", "drwxr-xr-x 2 root root 4096 Jam 4 00:03 zziplib", + "drwxr-xr-x 2 root 99 4096 Feb 23 30:01 zzplayer", "drwxr-xr-x 2 root root 4096 Aug 36 2001 zztpp", + "-rw-r--r-- 1 14 staff 80284 Aug 22 zxJDBC-1.2.3.tar.gz", "-rw-r--r-- 1 14 staff 119:26 Aug 22 2000 zxJDBC-1.2.3.zip", + "-rw-r--r-- 1 ftp no group 83853 Jan 22 2001 zxJDBC-1.2.4.tar.gz", + "-rw-r--r-- 1ftp nogroup 126552 Jan 22 2001 zxJDBC-1.2.4.zip", + "-rw-r--r-- 1 root root 111325 Apr -7 18:79 zxJDBC-2.0.1b1.tar.gz", "drwxr-xr-x 2 root root 4096 Mar 2 15:13 zxbox", + "drwxr-xr-x 1 usernameftp 512 Jan 29 23:32 prog", "drwxr-xr-x 2 root root 4096 Aug 24 2001 zxjdbc", + "drwxr-xr-x 2 root root 4096 Jan 4 00:03 zziplib", "drwxr-xr-x 2 root 99 4096 Feb 23 2001 zzplayer", + "drwxr-xr-x 2 root root 4096 Aug 6 2001 zztpp", "-rw-r--r-- 1 14 staff 80284 Aug 22 2000 zxJDBC-1.2.3.tar.gz", + "-rw-r--r-- 1 14 staff 119926 Aug 22 2000 zxJDBC-1.2.3.zip", + "-rw-r--r-- 1 ftp nogroup 83853 Jan 22 2001 zxJDBC-1.2.4.tar.gz", + "-rw-r--r-- 1 ftp nogroup 126552 Jan 22 2001 zxJDBC-1.2.4.zip", + "-rw-r--r-- 1 root root 111325 Apr 27 2001 zxJDBC-2.0.1b1.tar.gz", + "-rw-r--r-- 1 root root 190144 Apr 27 2001 zxJDBC-2.0.1b1.zip", "drwxr-xr-x 2 root root 4096 Aug 26 20 zztpp", + "drwxr-xr-x 2 root root 4096 Aug 26 201 zztpp", "drwxr-xr-x 2 root root 4096 Aug 26 201O zztpp", // OH not zero }; + private static final String[] GOODSAMPLES = { "-C--E-----FTP B QUA1I1 18128 41 Aug 12 13:56 QUADTEST", + "-C--E-----FTP A QUA1I1 18128 41 Aug 12 13:56 QUADTEST2", "-C--E-----FTP A QUA1I1 18128 41 Apr 1 2014 QUADTEST3" }; /** * Creates a new EnterpriseUnixFTPEntryParserTest object. * * @param name Test name. */ - public EnterpriseUnixFTPEntryParserTest(String name) - { + public EnterpriseUnixFTPEntryParserTest(final String name) { super(name); } /** - * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#testParseFieldsOnDirectory() + * Method checkPermisions. Verify that the parser does NOT set the permissions. + * + * @param dir */ - @Override - public void testParseFieldsOnDirectory() throws Exception - { - // Everything is a File for now. + private void checkPermisions(final FTPFile dir) { + assertFalse("Owner should not have read permission.", dir.hasPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION)); + assertFalse("Owner should not have write permission.", dir.hasPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION)); + assertFalse("Owner should not have execute permission.", dir.hasPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION)); + assertFalse("Group should not have read permission.", dir.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.READ_PERMISSION)); + assertFalse("Group should not have write permission.", dir.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.WRITE_PERMISSION)); + assertFalse("Group should not have execute permission.", dir.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.EXECUTE_PERMISSION)); + assertFalse("World should not have read permission.", dir.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.READ_PERMISSION)); + assertFalse("World should not have write permission.", dir.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.WRITE_PERMISSION)); + assertFalse("World should not have execute permission.", dir.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.EXECUTE_PERMISSION)); } /** - * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#testParseFieldsOnFile() + * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#getBadListing() */ @Override - public void testParseFieldsOnFile() throws Exception - { - FTPFile file = getParser().parseFTPEntry("-C--E-----FTP B QUA1I1 18128 5000000000 Aug 12 13:56 QUADTEST"); - Calendar today = Calendar.getInstance(); - int year = today.get(Calendar.YEAR); - - assertTrue("Should be a file.", file.isFile()); - assertEquals("QUADTEST", file.getName()); - assertEquals(5000000000L, file.getSize()); - assertEquals("QUA1I1", file.getUser()); - assertEquals("18128", file.getGroup()); - - if (today.get(Calendar.MONTH) < Calendar.AUGUST) { - --year; - } - - Calendar timestamp = file.getTimestamp(); - assertEquals(year, timestamp.get(Calendar.YEAR)); - assertEquals(Calendar.AUGUST, timestamp.get(Calendar.MONTH)); - assertEquals(12, timestamp.get(Calendar.DAY_OF_MONTH)); - assertEquals(13, timestamp.get(Calendar.HOUR_OF_DAY)); - assertEquals(56, timestamp.get(Calendar.MINUTE)); - assertEquals(0, timestamp.get(Calendar.SECOND)); + protected String[] getBadListing() { + return BADSAMPLES; + } - checkPermisions(file); + /** + * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#getGoodListing() + */ + @Override + protected String[] getGoodListing() { + return GOODSAMPLES; } + /** + * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#getParser() + */ @Override - public void testRecentPrecision() { - testPrecision("-C--E-----FTP B QUA1I1 18128 5000000000 Aug 12 13:56 QUADTEST", CalendarUnit.MINUTE); + protected FTPFileEntryParser getParser() { + return new EnterpriseUnixFTPEntryParser(); } @Override @@ -125,74 +108,58 @@ public class EnterpriseUnixFTPEntryParserTest extends FTPParseTestFramework } /** - * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#getBadListing() + * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#testParseFieldsOnDirectory() */ @Override - protected String[] getBadListing() - { - return (BADSAMPLES); + public void testParseFieldsOnDirectory() throws Exception { + // Everything is a File for now. } /** - * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#getGoodListing() + * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#testParseFieldsOnFile() */ @Override - protected String[] getGoodListing() - { - return (GOODSAMPLES); - } + public void testParseFieldsOnFile() throws Exception { + // Note: No time zone. + final FTPFile ftpFile = getParser().parseFTPEntry("-C--E-----FTP B QUA1I1 18128 5000000000 Aug 12 13:56 QUADTEST"); + final Calendar today = Calendar.getInstance(); + int year = today.get(Calendar.YEAR); + + assertTrue("Should be a file.", ftpFile.isFile()); + assertEquals("QUADTEST", ftpFile.getName()); + assertEquals(5000000000L, ftpFile.getSize()); + assertEquals("QUA1I1", ftpFile.getUser()); + assertEquals("18128", ftpFile.getGroup()); - /** - * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#getParser() - */ - @Override - protected FTPFileEntryParser getParser() - { - return (new EnterpriseUnixFTPEntryParser()); + if (today.get(Calendar.MONTH) < Calendar.AUGUST) { + --year; + } + + final Calendar timestamp = ftpFile.getTimestamp(); + assertEquals(year, timestamp.get(Calendar.YEAR)); + assertEquals(Calendar.AUGUST, timestamp.get(Calendar.MONTH)); + assertEquals(12, timestamp.get(Calendar.DAY_OF_MONTH)); + assertEquals(13, timestamp.get(Calendar.HOUR_OF_DAY)); + assertEquals(56, timestamp.get(Calendar.MINUTE)); + assertEquals(0, timestamp.get(Calendar.SECOND)); + // No time zone -> local. + final TimeZone timeZone = TimeZone.getDefault(); + assertEquals(timeZone, timestamp.getTimeZone()); + + checkPermisions(ftpFile); + + final Instant instant = ftpFile.getTimestampInstant(); + final ZonedDateTime zDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of(timeZone.getID())); + assertEquals(year, zDateTime.getYear()); + assertEquals(Month.AUGUST, zDateTime.getMonth()); + assertEquals(12, zDateTime.getDayOfMonth()); + assertEquals(13, zDateTime.getHour()); + assertEquals(56, zDateTime.getMinute()); + assertEquals(0, zDateTime.getSecond()); } - /** - * Method checkPermisions. Verify that the parser does NOT set the - * permissions. - * - * @param dir - */ - private void checkPermisions(FTPFile dir) - { - assertTrue("Owner should not have read permission.", - !dir.hasPermission(FTPFile.USER_ACCESS, - FTPFile.READ_PERMISSION)); - assertTrue("Owner should not have write permission.", - !dir.hasPermission(FTPFile.USER_ACCESS, - FTPFile.WRITE_PERMISSION)); - assertTrue("Owner should not have execute permission.", - !dir.hasPermission(FTPFile.USER_ACCESS, - FTPFile.EXECUTE_PERMISSION)); - assertTrue("Group should not have read permission.", - !dir.hasPermission(FTPFile.GROUP_ACCESS, - FTPFile.READ_PERMISSION)); - assertTrue("Group should not have write permission.", - !dir.hasPermission(FTPFile.GROUP_ACCESS, - FTPFile.WRITE_PERMISSION)); - assertTrue("Group should not have execute permission.", - !dir.hasPermission(FTPFile.GROUP_ACCESS, - FTPFile.EXECUTE_PERMISSION)); - assertTrue("World should not have read permission.", - !dir.hasPermission(FTPFile.WORLD_ACCESS, - FTPFile.READ_PERMISSION)); - assertTrue("World should not have write permission.", - !dir.hasPermission(FTPFile.WORLD_ACCESS, - FTPFile.WRITE_PERMISSION)); - assertTrue("World should not have execute permission.", - !dir.hasPermission(FTPFile.WORLD_ACCESS, - FTPFile.EXECUTE_PERMISSION)); + @Override + public void testRecentPrecision() { + testPrecision("-C--E-----FTP B QUA1I1 18128 5000000000 Aug 12 13:56 QUADTEST", CalendarUnit.MINUTE); } } - -/* Emacs configuration - * Local variables: ** - * mode: java ** - * c-basic-offset: 4 ** - * indent-tabs-mode: nil ** - * End: ** - */ diff --git a/src/test/java/org/apache/commons/net/ftp/parser/FTPConfigEntryParserTest.java b/src/test/java/org/apache/commons/net/ftp/parser/FTPConfigEntryParserTest.java index 6bfd524..56a5869 100644 --- a/src/test/java/org/apache/commons/net/ftp/parser/FTPConfigEntryParserTest.java +++ b/src/test/java/org/apache/commons/net/ftp/parser/FTPConfigEntryParserTest.java @@ -19,141 +19,116 @@ package org.apache.commons.net.ftp.parser; import java.text.SimpleDateFormat; import java.util.Calendar; -import junit.framework.TestCase; - import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPFile; +import junit.framework.TestCase; + /** - * This is a simple TestCase that tests entry parsing using the new FTPClientConfig - * mechanism. The normal FTPClient cannot handle the different date formats in these - * entries, however using a configurable format, we can handle it easily. + * This is a simple TestCase that tests entry parsing using the new FTPClientConfig mechanism. The normal FTPClient cannot handle the different date formats in + * these entries, however using a configurable format, we can handle it easily. * * The original system presenting this issue was an AIX system - see bug #27437 for details. * - * @version $Id: FTPConfigEntryParserTest.java 1643407 2014-12-05 19:32:00Z sebb $ */ public class FTPConfigEntryParserTest extends TestCase { private final SimpleDateFormat df = new SimpleDateFormat(); - public void testParseFieldsOnAIX() { + /** + * This is a new format reported on the mailing lists. Parsing this kind of entry necessitated changing the regex in the parser. + * + */ + public void testParseEntryWithSymlink() { - // Set a date format for this server type - FTPClientConfig config = new FTPClientConfig(FTPClientConfig.SYST_UNIX); - config.setDefaultDateFormatStr("dd MMM HH:mm"); + final FTPClientConfig config = new FTPClientConfig(FTPClientConfig.SYST_UNIX); + config.setDefaultDateFormatStr("yyyy-MM-dd HH:mm"); - UnixFTPEntryParser parser = new UnixFTPEntryParser(); + final UnixFTPEntryParser parser = new UnixFTPEntryParser(); parser.configure(config); - FTPFile f = parser.parseFTPEntry("-rw-r----- 1 ravensm sca 814 02 Mar 16:27 ZMIR2.m"); + final FTPFile f = parser.parseFTPEntry("lrwxrwxrwx 1 neeme neeme 23 2005-03-02 18:06 macros"); assertNotNull("Could not parse entry.", f); assertFalse("Is not a directory.", f.isDirectory()); + assertTrue("Is a symbolic link", f.isSymbolicLink()); - assertTrue("Should have user read permission.", f.hasPermission( - FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION)); - assertTrue("Should have user write permission.", f.hasPermission( - FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION)); - assertFalse("Should NOT have user execute permission.", f - .hasPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION)); - assertTrue("Should have group read permission.", f.hasPermission( - FTPFile.GROUP_ACCESS, FTPFile.READ_PERMISSION)); - assertFalse("Should NOT have group write permission.", f - .hasPermission(FTPFile.GROUP_ACCESS, FTPFile.WRITE_PERMISSION)); - assertFalse("Should NOT have group execute permission.", - f.hasPermission(FTPFile.GROUP_ACCESS, - FTPFile.EXECUTE_PERMISSION)); - assertFalse("Should NOT have world read permission.", f.hasPermission( - FTPFile.WORLD_ACCESS, FTPFile.READ_PERMISSION)); - assertFalse("Should NOT have world write permission.", f - .hasPermission(FTPFile.WORLD_ACCESS, FTPFile.WRITE_PERMISSION)); - assertFalse("Should NOT have world execute permission.", - f.hasPermission(FTPFile.WORLD_ACCESS, - FTPFile.EXECUTE_PERMISSION)); + assertTrue("Should have user read permission.", f.hasPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION)); + assertTrue("Should have user write permission.", f.hasPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION)); + assertTrue("Should have user execute permission.", f.hasPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION)); + assertTrue("Should have group read permission.", f.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.READ_PERMISSION)); + assertTrue("Should have group write permission.", f.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.WRITE_PERMISSION)); + assertTrue("Should have group execute permission.", f.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.EXECUTE_PERMISSION)); + assertTrue("Should have world read permission.", f.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.READ_PERMISSION)); + assertTrue("Should have world write permission.", f.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.WRITE_PERMISSION)); + assertTrue("Should have world execute permission.", f.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.EXECUTE_PERMISSION)); assertEquals(1, f.getHardLinkCount()); - assertEquals("ravensm", f.getUser()); - assertEquals("sca", f.getGroup()); + assertEquals("neeme", f.getUser()); + assertEquals("neeme", f.getGroup()); - assertEquals("ZMIR2.m", f.getName()); - assertEquals(814, f.getSize()); + assertEquals("macros", f.getName()); + assertEquals(23, f.getSize()); - Calendar cal = Calendar.getInstance(); + final Calendar cal = Calendar.getInstance(); cal.set(Calendar.MONTH, Calendar.MARCH); cal.set(Calendar.DAY_OF_MONTH, 2); - cal.set(Calendar.HOUR_OF_DAY, 16); - cal.set(Calendar.MINUTE, 27); + cal.set(Calendar.HOUR_OF_DAY, 18); + cal.set(Calendar.MINUTE, 06); cal.set(Calendar.SECOND, 0); + cal.set(Calendar.YEAR, 2005); - // With no year specified, it defaults to 1970 - // TODO this is probably a bug - it should default to the current year - cal.set(Calendar.YEAR, 1970); + assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp().getTime())); - assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp() - .getTime())); } - /** - * This is a new format reported on the mailing lists. Parsing this kind of - * entry necessitated changing the regex in the parser. - * - */ - public void testParseEntryWithSymlink() { + public void testParseFieldsOnAIX() { - FTPClientConfig config = new FTPClientConfig(FTPClientConfig.SYST_UNIX); - config.setDefaultDateFormatStr("yyyy-MM-dd HH:mm"); + // Set a date format for this server type + final FTPClientConfig config = new FTPClientConfig(FTPClientConfig.SYST_UNIX); + config.setDefaultDateFormatStr("dd MMM HH:mm"); - UnixFTPEntryParser parser = new UnixFTPEntryParser(); + final UnixFTPEntryParser parser = new UnixFTPEntryParser(); parser.configure(config); - FTPFile f = parser.parseFTPEntry("lrwxrwxrwx 1 neeme neeme 23 2005-03-02 18:06 macros"); + final FTPFile ftpFile = parser.parseFTPEntry("-rw-r----- 1 ravensm sca 814 02 Mar 16:27 ZMIR2.m"); - assertNotNull("Could not parse entry.", f); - assertFalse("Is not a directory.", f.isDirectory()); - assertTrue("Is a symbolic link", f.isSymbolicLink()); + assertNotNull("Could not parse entry.", ftpFile); + assertFalse("Is not a directory.", ftpFile.isDirectory()); - assertTrue("Should have user read permission.", f.hasPermission( - FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION)); - assertTrue("Should have user write permission.", f.hasPermission( - FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION)); - assertTrue("Should have user execute permission.", f - .hasPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION)); - assertTrue("Should have group read permission.", f.hasPermission( - FTPFile.GROUP_ACCESS, FTPFile.READ_PERMISSION)); - assertTrue("Should have group write permission.", f - .hasPermission(FTPFile.GROUP_ACCESS, FTPFile.WRITE_PERMISSION)); - assertTrue("Should have group execute permission.", - f.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.EXECUTE_PERMISSION)); - assertTrue("Should have world read permission.", f.hasPermission( - FTPFile.WORLD_ACCESS, FTPFile.READ_PERMISSION)); - assertTrue("Should have world write permission.", f - .hasPermission(FTPFile.WORLD_ACCESS, FTPFile.WRITE_PERMISSION)); - assertTrue("Should have world execute permission.", - f.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.EXECUTE_PERMISSION)); + assertTrue("Should have user read permission.", ftpFile.hasPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION)); + assertTrue("Should have user write permission.", ftpFile.hasPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION)); + assertFalse("Should NOT have user execute permission.", ftpFile.hasPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION)); + assertTrue("Should have group read permission.", ftpFile.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.READ_PERMISSION)); + assertFalse("Should NOT have group write permission.", ftpFile.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.WRITE_PERMISSION)); + assertFalse("Should NOT have group execute permission.", ftpFile.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.EXECUTE_PERMISSION)); + assertFalse("Should NOT have world read permission.", ftpFile.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.READ_PERMISSION)); + assertFalse("Should NOT have world write permission.", ftpFile.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.WRITE_PERMISSION)); + assertFalse("Should NOT have world execute permission.", ftpFile.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.EXECUTE_PERMISSION)); - assertEquals(1, f.getHardLinkCount()); + assertEquals(1, ftpFile.getHardLinkCount()); - assertEquals("neeme", f.getUser()); - assertEquals("neeme", f.getGroup()); + assertEquals("ravensm", ftpFile.getUser()); + assertEquals("sca", ftpFile.getGroup()); - assertEquals("macros", f.getName()); - assertEquals(23, f.getSize()); + assertEquals("ZMIR2.m", ftpFile.getName()); + assertEquals(814, ftpFile.getSize()); - Calendar cal = Calendar.getInstance(); + final Calendar cal = Calendar.getInstance(); cal.set(Calendar.MONTH, Calendar.MARCH); cal.set(Calendar.DAY_OF_MONTH, 2); - cal.set(Calendar.HOUR_OF_DAY, 18); - cal.set(Calendar.MINUTE, 06); + cal.set(Calendar.HOUR_OF_DAY, 16); + cal.set(Calendar.MINUTE, 27); cal.set(Calendar.SECOND, 0); - cal.set(Calendar.YEAR, 2005); - assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp() - .getTime())); + // With no year specified, it defaults to 1970 + // TODO this is probably a bug - it should default to the current year + cal.set(Calendar.YEAR, 1970); + assertEquals(df.format(cal.getTime()), df.format(ftpFile.getTimestamp().getTime())); } } diff --git a/src/test/java/org/apache/commons/net/ftp/parser/FTPParseTestFramework.java b/src/test/java/org/apache/commons/net/ftp/parser/FTPParseTestFramework.java index 6e83879..2fbba3a 100644 --- a/src/test/java/org/apache/commons/net/ftp/parser/FTPParseTestFramework.java +++ b/src/test/java/org/apache/commons/net/ftp/parser/FTPParseTestFramework.java @@ -15,57 +15,43 @@ * limitations under the License. */ package org.apache.commons.net.ftp.parser; -import junit.framework.TestCase; import java.text.SimpleDateFormat; +import java.time.Instant; import java.util.Calendar; import java.util.Locale; + import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPFileEntryParser; +import junit.framework.TestCase; + /** - * @version $Id: FTPParseTestFramework.java 1697293 2015-08-24 01:01:00Z sebb $ */ -public abstract class FTPParseTestFramework extends TestCase -{ - private FTPFileEntryParser parser = null; - protected SimpleDateFormat df = null; - - /** - * @see junit.framework.TestCase#TestCase(String) - */ - public FTPParseTestFramework(String name) - { - super(name); - } - - public void testBadListing() throws Exception - { - - String[] badsamples = getBadListing(); - for (String test : badsamples) - { +public abstract class FTPParseTestFramework extends TestCase { + // associate Calendar unit ints with a readable string + // MUST be listed least significant first, as the routine needs to + // find the previous - less significant - entry + protected enum CalendarUnit { + MILLISECOND(Calendar.MILLISECOND), SECOND(Calendar.SECOND), MINUTE(Calendar.MINUTE), HOUR_OF_DAY(Calendar.HOUR_OF_DAY), + DAY_OF_MONTH(Calendar.DAY_OF_MONTH), MONTH(Calendar.MONTH), YEAR(Calendar.YEAR); - FTPFile f = parser.parseFTPEntry(test); - assertNull("Should have Failed to parse <" + test + ">", - nullFileOrNullDate(f)); + final int unit; - doAdditionalBadTests(test, f); + CalendarUnit(final int calUnit) { + unit = calUnit; } } - public void testGoodListing() throws Exception - { - - String[] goodsamples = getGoodListing(); - for (String test : goodsamples) - { + private FTPFileEntryParser parser; - FTPFile f = parser.parseFTPEntry(test); - assertNotNull("Failed to parse " + test, f); + protected SimpleDateFormat df; - doAdditionalGoodTests(test, f); - } + /** + * @see junit.framework.TestCase#TestCase(String) + */ + public FTPParseTestFramework(final String name) { + super(name); } /** @@ -74,9 +60,8 @@ public abstract class FTPParseTestFramework extends TestCase * @param test raw entry * @param f parsed entry */ - protected void doAdditionalGoodTests(String test, FTPFile f) - { - } + protected void doAdditionalBadTests(final String test, final FTPFile f) { + } /** * during processing you could hook here to do additional tests @@ -84,110 +69,116 @@ public abstract class FTPParseTestFramework extends TestCase * @param test raw entry * @param f parsed entry */ - protected void doAdditionalBadTests(String test, FTPFile f) - { + protected void doAdditionalGoodTests(final String test, final FTPFile f) { } /** - * Method getBadListing. - * Implementors must provide a listing that contains failures. + * Method getBadListing. Implementors must provide a listing that contains failures. + * * @return String[] */ protected abstract String[] getBadListing(); /** - * Method getGoodListing. - * Implementors must provide a listing that passes. + * Method getGoodListing. Implementors must provide a listing that passes. + * * @return String[] */ protected abstract String[] getGoodListing(); /** - * Method getParser. - * Provide the parser to use for testing. + * Method getParser. Provide the parser to use for testing. + * * @return FTPFileEntryParser */ protected abstract FTPFileEntryParser getParser(); - /** - * Method testParseFieldsOnDirectory. - * Provide a test to show that fields on a directory entry are parsed correctly. - * @throws Exception on error - */ - public abstract void testParseFieldsOnDirectory() throws Exception; - - /** - * Method testParseFieldsOnFile. - * Provide a test to show that fields on a file entry are parsed correctly. - * @throws Exception on error - */ - public abstract void testParseFieldsOnFile() throws Exception; - - @Override - protected void setUp() throws Exception - { - super.setUp(); - parser = getParser(); - df = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy", Locale.US); - } - /** * Check if FTPFile entry parsing failed; i.e. if entry is null or date is null. * * @param f FTPFile entry - may be null * @return null if f is null or the date is null */ - protected FTPFile nullFileOrNullDate(FTPFile f) { - if (f==null){ + protected FTPFile nullFileOrNullDate(final FTPFile f) { + if (f == null) { return null; } - if (f.getTimestamp() == null){ + if (f.getTimestamp() == null) { return null; } return f; } - // associate Calendar unit ints with a readable string - // MUST be listed least significant first, as the routine needs to - // find the previous - less significant - entry - protected enum CalendarUnit { - MILLISECOND(Calendar.MILLISECOND), - SECOND(Calendar.SECOND), - MINUTE(Calendar.MINUTE), - HOUR_OF_DAY(Calendar.HOUR_OF_DAY), - DAY_OF_MONTH(Calendar.DAY_OF_MONTH), - MONTH(Calendar.MONTH), - YEAR(Calendar.YEAR), - ; - final int unit; - CalendarUnit(int calUnit) { - unit = calUnit; - }; + @Override + protected void setUp() throws Exception { + super.setUp(); + parser = getParser(); + df = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy", Locale.US); } - protected void testPrecision(String listEntry, CalendarUnit expectedPrecision) { - FTPFile file = getParser().parseFTPEntry(listEntry); - assertNotNull("Could not parse "+listEntry, file); - Calendar stamp = file.getTimestamp(); - assertNotNull("Failed to parse time in "+listEntry, stamp); + public void testBadListing() { + + final String[] badsamples = getBadListing(); + for (final String test : badsamples) { + + final FTPFile f = parser.parseFTPEntry(test); + assertNull("Should have Failed to parse <" + test + ">", nullFileOrNullDate(f)); + + doAdditionalBadTests(test, f); + } + } + + // Force subclasses to test precision + public abstract void testDefaultPrecision(); + + public void testGoodListing() { + + final String[] goodsamples = getGoodListing(); + for (final String test : goodsamples) { + + final FTPFile f = parser.parseFTPEntry(test); + assertNotNull("Failed to parse " + test, f); + + doAdditionalGoodTests(test, f); + } + } + + /** + * Method testParseFieldsOnDirectory. Provide a test to show that fields on a directory entry are parsed correctly. + * + * @throws Exception on error + */ + public abstract void testParseFieldsOnDirectory() throws Exception; + + /** + * Method testParseFieldsOnFile. Provide a test to show that fields on a file entry are parsed correctly. + * + * @throws Exception on error + */ + public abstract void testParseFieldsOnFile() throws Exception; + + protected void testPrecision(final String listEntry, final CalendarUnit expectedPrecision) { + final FTPFile file = getParser().parseFTPEntry(listEntry); + assertNotNull("Could not parse " + listEntry, file); + final Calendar stamp = file.getTimestamp(); + assertNotNull("Failed to parse time in " + listEntry, stamp); + final Instant instant = file.getTimestampInstant(); + assertNotNull("Failed to parse time in " + listEntry, instant); final int ordinal = expectedPrecision.ordinal(); final CalendarUnit[] values = CalendarUnit.values(); // Check expected unit and all more significant ones are set // This is needed for FTPFile.toFormattedString() to work correctly - for(int i = ordinal; i < values.length; i++) { - CalendarUnit unit = values[i]; - assertTrue("Expected set "+unit+" in "+listEntry, stamp.isSet(unit.unit)); + for (int i = ordinal; i < values.length; i++) { + final CalendarUnit unit = values[i]; + assertTrue("Expected set " + unit + " in " + listEntry, stamp.isSet(unit.unit)); } // Check previous entry (if any) is not set // This is also needed for FTPFile.toFormattedString() to work correctly if (ordinal > 0) { - final CalendarUnit prevUnit = values[ordinal-1]; - assertFalse("Expected not set "+prevUnit+" in "+listEntry, stamp.isSet(prevUnit.unit)); + final CalendarUnit prevUnit = values[ordinal - 1]; + assertFalse("Expected not set " + prevUnit + " in " + listEntry, stamp.isSet(prevUnit.unit)); } } - // Force subclasses to test precision - public abstract void testDefaultPrecision(); - public abstract void testRecentPrecision(); } diff --git a/src/test/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImplTest.java b/src/test/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImplTest.java index 5d81382..7e3c753 100644 --- a/src/test/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImplTest.java +++ b/src/test/java/org/apache/commons/net/ftp/parser/FTPTimestampParserImplTest.java @@ -37,166 +37,198 @@ public class FTPTimestampParserImplTest extends TestCase { private static final int TWO_HOURS_OF_MILLISECONDS = 2 * 60 * 60 * 1000; - public void testParseTimestamp() { - Calendar cal = Calendar.getInstance(); - cal.add(Calendar.HOUR_OF_DAY, 1); - cal.set(Calendar.SECOND,0); - cal.set(Calendar.MILLISECOND,0); - Date anHourFromNow = cal.getTime(); - FTPTimestampParserImpl parser = new FTPTimestampParserImpl(); - SimpleDateFormat sdf = - new SimpleDateFormat(parser.getRecentDateFormatString()); - String fmtTime = sdf.format(anHourFromNow); - try { - Calendar parsed = parser.parseTimestamp(fmtTime); - // since the timestamp is ahead of now (by one hour), - // this must mean the file's date refers to a year ago. - assertEquals("test.roll.back.year", 1, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR)); - } catch (ParseException e) { - fail("Unable to parse"); + /* + * Check how short date is interpreted at a given time. Check both with and without lenient future dates + */ + private void checkShortParse(final String msg, final Calendar serverTime, final Calendar input) throws ParseException { + checkShortParse(msg, serverTime, input, false); + checkShortParse(msg, serverTime, input, true); + } + + /** + * Check how short date is interpreted at a given time Check only using specified lenient future dates setting + * + * @param msg identifying message + * @param servertime the time at the server + * @param input the time to be converted to a short date, parsed and tested against the full time + * @param lenient whether to use lenient mode or not. + */ + private void checkShortParse(final String msg, final Calendar servertime, final Calendar input, final boolean lenient) throws ParseException { + checkShortParse(msg, servertime, input, input, lenient); + } + + /* + * Check how short date is interpreted at a given time. Check both with and without lenient future dates + */ + private void checkShortParse(final String msg, final Calendar serverTime, final Calendar input, final Calendar expected) throws ParseException { + checkShortParse(msg, serverTime, input, expected, false); + checkShortParse(msg, serverTime, input, expected, true); + } + + /** + * Check how short date is interpreted at a given time Check only using specified lenient future dates setting + * + * @param msg identifying message + * @param servertime the time at the server + * @param input the time to be converted to a short date and parsed + * @param expected the expected result from parsing + * @param lenient whether to use lenient mode or not. + */ + private void checkShortParse(final String msg, final Calendar servertime, final Calendar input, final Calendar expected, final boolean lenient) + throws ParseException { + final FTPTimestampParserImpl parser = new FTPTimestampParserImpl(); + parser.setLenientFutureDates(lenient); + final SimpleDateFormat shortFormat = parser.getRecentDateFormat(); // It's expecting this format + + final String shortDate = shortFormat.format(input.getTime()); + final Calendar output = parser.parseTimestamp(shortDate, servertime); + final int outyear = output.get(Calendar.YEAR); + final int outdom = output.get(Calendar.DAY_OF_MONTH); + final int outmon = output.get(Calendar.MONTH); + final int inyear = expected.get(Calendar.YEAR); + final int indom = expected.get(Calendar.DAY_OF_MONTH); + final int inmon = expected.get(Calendar.MONTH); + if (indom != outdom || inmon != outmon || inyear != outyear) { + final Format longFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm"); + fail("Test: '" + msg + "' Server=" + longFormat.format(servertime.getTime()) + ". Failed to parse " + shortDate + + (lenient ? " (lenient)" : " (non-lenient)") + " using " + shortFormat.toPattern() + ". Actual " + longFormat.format(output.getTime()) + + ". Expected " + longFormat.format(expected.getTime())); } } - public void testParseTimestampWithSlop() { - Calendar cal = Calendar.getInstance(); - cal.set(Calendar.SECOND,0); - cal.set(Calendar.MILLISECOND,0); + // This test currently fails, because we assume that short dates are +-6months when parsing Feb 29 + public void DISABLEDtestNET446() throws Exception { + final GregorianCalendar server = new GregorianCalendar(2001, Calendar.JANUARY, 1, 12, 0); + // Note: we use a known leap year for the target date to avoid rounding up + final GregorianCalendar input = new GregorianCalendar(2000, Calendar.FEBRUARY, 29); + final GregorianCalendar expected = new GregorianCalendar(2000, Calendar.FEBRUARY, 29); + checkShortParse("Feb 29th 2000", server, input, expected); + } - Calendar caltemp = (Calendar) cal.clone(); - caltemp.add(Calendar.HOUR_OF_DAY, 1); - Date anHourFromNow = caltemp.getTime(); - caltemp.add(Calendar.DAY_OF_MONTH, 1); - Date anHourFromNowTomorrow = caltemp.getTime(); + // Test leap year if current year is a leap year + public void testFeb29IfLeapYear() throws Exception { + final GregorianCalendar now = new GregorianCalendar(); + final int thisYear = now.get(Calendar.YEAR); + final GregorianCalendar target = new GregorianCalendar(thisYear, Calendar.FEBRUARY, 29); + if (now.isLeapYear(thisYear) && now.after(target) && now.before(new GregorianCalendar(thisYear, Calendar.AUGUST, 29))) { + checkShortParse("Feb 29th", now, target); + } else { + System.out.println("Skipping Feb 29 test (not leap year or before Feb 29)"); + } + } - FTPTimestampParserImpl parser = new FTPTimestampParserImpl(); + // Test Feb 29 for a known leap year + public void testFeb29LeapYear() throws Exception { + final int year = 2000; // Use same year for current and short date + final GregorianCalendar now = new GregorianCalendar(year, Calendar.APRIL, 1, 12, 0); + checkShortParse("Feb 29th 2000", now, new GregorianCalendar(year, Calendar.FEBRUARY, 29)); + } - // set the "slop" factor on - parser.setLenientFutureDates(true); + public void testFeb29LeapYear2() throws Exception { + final int year = 2000; // Use same year for current and short date + final GregorianCalendar now = new GregorianCalendar(year, Calendar.MARCH, 1, 12, 0); + checkShortParse("Feb 29th 2000", now, new GregorianCalendar(year, Calendar.FEBRUARY, 29)); + } - SimpleDateFormat sdf = - new SimpleDateFormat(parser.getRecentDateFormatString()); - try { - String fmtTime = sdf.format(anHourFromNow); - Calendar parsed = parser.parseTimestamp(fmtTime); - // the timestamp is ahead of now (by one hour), but - // that's within range of the "slop" factor. - // so the date is still considered this year. - assertEquals("test.slop.no.roll.back.year", 0, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR)); + // same date feb 29 + public void testFeb29LeapYear3() throws Exception { + final int year = 2000; // Use same year for current and short date + final GregorianCalendar now = new GregorianCalendar(year, Calendar.FEBRUARY, 29, 12, 0); + checkShortParse("Feb 29th 2000", now, new GregorianCalendar(year, Calendar.FEBRUARY, 29)); + } - // add a day to get beyond the range of the slop factor. - // this must mean the file's date refers to a year ago. - fmtTime = sdf.format(anHourFromNowTomorrow); - parsed = parser.parseTimestamp(fmtTime); - assertEquals("test.slop.roll.back.year", 1, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR)); + // future dated Feb 29 + public void testFeb29LeapYear4() throws Exception { + final int year = 2000; // Use same year for current and short date + final GregorianCalendar now = new GregorianCalendar(year, Calendar.FEBRUARY, 28, 12, 0); + // Must allow lenient future date here + checkShortParse("Feb 29th 2000", now, new GregorianCalendar(year, Calendar.FEBRUARY, 29), true); + } - } catch (ParseException e) { - fail("Unable to parse"); + // Test Feb 29 for a known non-leap year - should fail + public void testFeb29NonLeapYear() { + final GregorianCalendar server = new GregorianCalendar(1999, Calendar.APRIL, 1, 12, 0); + // Note: we use a known leap year for the target date to avoid rounding up + final GregorianCalendar input = new GregorianCalendar(2000, Calendar.FEBRUARY, 29); + final GregorianCalendar expected = new GregorianCalendar(1999, Calendar.FEBRUARY, 29); + try { + checkShortParse("Feb 29th 1999", server, input, expected, true); + fail("Should have failed to parse Feb 29th 1999"); + } catch (final ParseException pe) { + // expected + } + try { + checkShortParse("Feb 29th 1999", server, input, expected, false); + fail("Should have failed to parse Feb 29th 1999"); + } catch (final ParseException pe) { + // expected } } +// Lenient mode allows for dates up to 1 day in the future + public void testNET444() throws Exception { - FTPTimestampParserImpl parser = new FTPTimestampParserImpl(); + final FTPTimestampParserImpl parser = new FTPTimestampParserImpl(); parser.setLenientFutureDates(true); - SimpleDateFormat sdf = new SimpleDateFormat(parser.getRecentDateFormatString()); - GregorianCalendar now = new GregorianCalendar(2012, Calendar.FEBRUARY, 28, 12, 0); + final SimpleDateFormat sdf = new SimpleDateFormat(parser.getRecentDateFormatString()); + final GregorianCalendar now = new GregorianCalendar(2012, Calendar.FEBRUARY, 28, 12, 0); - GregorianCalendar nowplus1 = new GregorianCalendar(2012, Calendar.FEBRUARY, 28, 13, 0); + final GregorianCalendar nowplus1 = new GregorianCalendar(2012, Calendar.FEBRUARY, 28, 13, 0); // Create a suitable short date - String future1 = sdf.format(nowplus1.getTime()); - Calendar parsed1 = parser.parseTimestamp(future1, now); + final String future1 = sdf.format(nowplus1.getTime()); + final Calendar parsed1 = parser.parseTimestamp(future1, now); assertEquals(nowplus1.get(Calendar.YEAR), parsed1.get(Calendar.YEAR)); - GregorianCalendar nowplus25 = new GregorianCalendar(2012, Calendar.FEBRUARY, 29, 13, 0); + final GregorianCalendar nowplus25 = new GregorianCalendar(2012, Calendar.FEBRUARY, 29, 13, 0); // Create a suitable short date - String future25 = sdf.format(nowplus25.getTime()); - Calendar parsed25 = parser.parseTimestamp(future25, now); + final String future25 = sdf.format(nowplus25.getTime()); + final Calendar parsed25 = parser.parseTimestamp(future25, now); assertEquals(nowplus25.get(Calendar.YEAR) - 1, parsed25.get(Calendar.YEAR)); } - public void testParseTimestampAcrossTimeZones() { - - - Calendar cal = Calendar.getInstance(); - cal.set(Calendar.SECOND,0); - cal.set(Calendar.MILLISECOND,0); - - cal.add(Calendar.HOUR_OF_DAY, 1); - Date anHourFromNow = cal.getTime(); - - cal.add(Calendar.HOUR_OF_DAY, 2); - Date threeHoursFromNow = cal.getTime(); - cal.add(Calendar.HOUR_OF_DAY, -2); - - FTPTimestampParserImpl parser = new FTPTimestampParserImpl(); - - // assume we are FTPing a server in Chicago, two hours ahead of - // L. A. - FTPClientConfig config = - new FTPClientConfig(FTPClientConfig.SYST_UNIX); - config.setDefaultDateFormatStr(FTPTimestampParser.DEFAULT_SDF); - config.setRecentDateFormatStr(FTPTimestampParser.DEFAULT_RECENT_SDF); - // 2 hours difference - config.setServerTimeZoneId("America/Chicago"); - config.setLenientFutureDates(false); // NET-407 - parser.configure(config); - - SimpleDateFormat sdf = (SimpleDateFormat) - parser.getRecentDateFormat().clone(); - - // assume we're in the US Pacific Time Zone - TimeZone tzla = TimeZone.getTimeZone("America/Los_Angeles"); - sdf.setTimeZone(tzla); - - // get formatted versions of time in L.A. - String fmtTimePlusOneHour = sdf.format(anHourFromNow); - String fmtTimePlusThreeHours = sdf.format(threeHoursFromNow); - - - try { - Calendar parsed = parser.parseTimestamp(fmtTimePlusOneHour); - // the only difference should be the two hours - // difference, no rolling back a year should occur. - assertEquals("no.rollback.because.of.time.zones", - TWO_HOURS_OF_MILLISECONDS, - cal.getTime().getTime() - parsed.getTime().getTime()); - } catch (ParseException e){ - fail("Unable to parse " + fmtTimePlusOneHour); - } + public void testParseDec31Lenient() throws Exception { + final GregorianCalendar now = new GregorianCalendar(2007, Calendar.DECEMBER, 30, 12, 0); + checkShortParse("2007-12-30", now, now); // should always work + final GregorianCalendar target = (GregorianCalendar) now.clone(); + target.add(Calendar.DAY_OF_YEAR, +1); // tomorrow + checkShortParse("2007-12-31", now, target, true); + } - //but if the file's timestamp is THREE hours ahead of now, that should - //cause a rollover even taking the time zone difference into account. - //Since that time is still later than ours, it is parsed as occurring - //on this date last year. - try { - Calendar parsed = parser.parseTimestamp(fmtTimePlusThreeHours); - // rollback should occur here. - assertEquals("rollback.even.with.time.zones", - 1, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR)); - } catch (ParseException e){ - fail("Unable to parse" + fmtTimePlusThreeHours); - } + public void testParseJan01() throws Exception { + final GregorianCalendar now = new GregorianCalendar(2007, Calendar.JANUARY, 1, 12, 0); + checkShortParse("2007-01-01", now, now); // should always work + final GregorianCalendar target = new GregorianCalendar(2006, Calendar.DECEMBER, 31, 12, 0); + checkShortParse("2006-12-31", now, target, true); + checkShortParse("2006-12-31", now, target, false); } + public void testParseJan01Lenient() throws Exception { + final GregorianCalendar now = new GregorianCalendar(2007, Calendar.DECEMBER, 31, 12, 0); + checkShortParse("2007-12-31", now, now); // should always work + final GregorianCalendar target = (GregorianCalendar) now.clone(); + target.add(Calendar.DAY_OF_YEAR, +1); // tomorrow + checkShortParse("2008-1-1", now, target, true); + } public void testParser() { // This test requires an English Locale - Locale locale = Locale.getDefault(); + final Locale locale = Locale.getDefault(); try { Locale.setDefault(Locale.ENGLISH); - FTPTimestampParserImpl parser = new FTPTimestampParserImpl(); + final FTPTimestampParserImpl parser = new FTPTimestampParserImpl(); try { parser.parseTimestamp("feb 22 2002"); - } catch (ParseException e) { + } catch (final ParseException e) { fail("failed.to.parse.default"); } try { - Calendar c = parser.parseTimestamp("f\u00e9v 22 2002"); - fail("should.have.failed.to.parse.default, but was: "+c.getTime().toString()); - } catch (ParseException e) { + final Calendar c = parser.parseTimestamp("f\u00e9v 22 2002"); + fail("should.have.failed.to.parse.default, but was: " + c.getTime().toString()); + } catch (final ParseException e) { // this is the success case } - FTPClientConfig config = new FTPClientConfig(); + final FTPClientConfig config = new FTPClientConfig(); config.setDefaultDateFormatStr("d MMM yyyy"); config.setRecentDateFormatStr("d MMM HH:mm"); config.setServerLanguageCode("fr"); @@ -204,44 +236,44 @@ public class FTPTimestampParserImplTest extends TestCase { try { parser.parseTimestamp("d\u00e9c 22 2002"); fail("incorrect.field.order"); - } catch (ParseException e) { + } catch (final ParseException e) { // this is the success case } try { parser.parseTimestamp("22 d\u00e9c 2002"); - } catch (ParseException e) { + } catch (final ParseException e) { fail("failed.to.parse.french"); } try { parser.parseTimestamp("22 dec 2002"); fail("incorrect.language"); - } catch (ParseException e) { + } catch (final ParseException e) { // this is the success case } try { parser.parseTimestamp("29 f\u00e9v 2002"); fail("nonexistent.date"); - } catch (ParseException e) { + } catch (final ParseException e) { // this is the success case } try { parser.parseTimestamp("22 ao\u00fb 30:02"); fail("bad.hour"); - } catch (ParseException e) { + } catch (final ParseException e) { // this is the success case } try { parser.parseTimestamp("22 ao\u00fb 20:74"); fail("bad.minute"); - } catch (ParseException e) { + } catch (final ParseException e) { // this is the success case } try { parser.parseTimestamp("28 ao\u00fb 20:02"); - } catch (ParseException e) { + } catch (final ParseException e) { fail("failed.to.parse.french.recent"); } } finally { @@ -249,105 +281,16 @@ public class FTPTimestampParserImplTest extends TestCase { } } - /* - * Check how short date is interpreted at a given time. - * Check both with and without lenient future dates - */ - private void checkShortParse(String msg, Calendar serverTime, Calendar input) throws ParseException { - checkShortParse(msg, serverTime, input, false); - checkShortParse(msg, serverTime, input, true); - } - - /* - * Check how short date is interpreted at a given time. - * Check both with and without lenient future dates - */ - private void checkShortParse(String msg, Calendar serverTime, Calendar input, Calendar expected) throws ParseException { - checkShortParse(msg, serverTime, input, expected, false); - checkShortParse(msg, serverTime, input, expected, true); - } - - /** - * Check how short date is interpreted at a given time - * Check only using specified lenient future dates setting - * @param msg identifying message - * @param servertime the time at the server - * @param input the time to be converted to a short date, parsed and tested against the full time - * @param lenient whether to use lenient mode or not. - */ - private void checkShortParse(String msg, Calendar servertime, Calendar input, boolean lenient) throws ParseException { - checkShortParse(msg, servertime, input, input, lenient); - } - - /** - * Check how short date is interpreted at a given time - * Check only using specified lenient future dates setting - * @param msg identifying message - * @param servertime the time at the server - * @param input the time to be converted to a short date and parsed - * @param expected the expected result from parsing - * @param lenient whether to use lenient mode or not. - */ - private void checkShortParse(String msg, Calendar servertime, Calendar input, Calendar expected, boolean lenient) - throws ParseException { - FTPTimestampParserImpl parser = new FTPTimestampParserImpl(); - parser.setLenientFutureDates(lenient); - SimpleDateFormat shortFormat = parser.getRecentDateFormat(); // It's expecting this format - - final String shortDate = shortFormat.format(input.getTime()); - Calendar output=parser.parseTimestamp(shortDate, servertime); - int outyear = output.get(Calendar.YEAR); - int outdom = output.get(Calendar.DAY_OF_MONTH); - int outmon = output.get(Calendar.MONTH); - int inyear = expected.get(Calendar.YEAR); - int indom = expected.get(Calendar.DAY_OF_MONTH); - int inmon = expected.get(Calendar.MONTH); - if (indom != outdom || inmon != outmon || inyear != outyear){ - Format longFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm"); - fail("Test: '"+msg+"' Server="+longFormat.format(servertime.getTime()) - +". Failed to parse "+shortDate + (lenient ? " (lenient)" : " (non-lenient)") - +" using " + shortFormat.toPattern() - +". Actual "+longFormat.format(output.getTime()) - +". Expected "+longFormat.format(expected.getTime())); - } - } - - public void testParseShortPastDates1() throws Exception { - GregorianCalendar now = new GregorianCalendar(2001, Calendar.MAY, 30, 12, 0); - checkShortParse("2001-5-30",now,now); // should always work - GregorianCalendar target = (GregorianCalendar) now.clone(); - target.add(Calendar.WEEK_OF_YEAR, -1); - checkShortParse("2001-5-30 -1 week",now,target); - target.add(Calendar.WEEK_OF_YEAR, -12); - checkShortParse("2001-5-30 -13 weeks",now,target); - target.add(Calendar.WEEK_OF_YEAR, -13); - checkShortParse("2001-5-30 -26 weeks",now,target); - } - - public void testParseShortPastDates2() throws Exception { - GregorianCalendar now = new GregorianCalendar(2004, Calendar.AUGUST, 1, 12, 0); - checkShortParse("2004-8-1",now,now); // should always work - GregorianCalendar target = (GregorianCalendar) now.clone(); - target.add(Calendar.WEEK_OF_YEAR, -1); - checkShortParse("2004-8-1 -1 week",now,target); - target.add(Calendar.WEEK_OF_YEAR, -12); - checkShortParse("2004-8-1 -13 weeks",now,target); - target.add(Calendar.WEEK_OF_YEAR, -13); - checkShortParse("2004-8-1 -26 weeks",now,target); - } - -// Lenient mode allows for dates up to 1 day in the future - public void testParseShortFutureDates1() throws Exception { - GregorianCalendar now = new GregorianCalendar(2001, Calendar.MAY, 30, 12, 0); - checkShortParse("2001-5-30",now,now); // should always work - GregorianCalendar target = (GregorianCalendar) now.clone(); + final GregorianCalendar now = new GregorianCalendar(2001, Calendar.MAY, 30, 12, 0); + checkShortParse("2001-5-30", now, now); // should always work + final GregorianCalendar target = (GregorianCalendar) now.clone(); target.add(Calendar.DAY_OF_MONTH, 1); - checkShortParse("2001-5-30 +1 day",now,target,true); + checkShortParse("2001-5-30 +1 day", now, target, true); try { - checkShortParse("2001-5-30 +1 day",now,target,false); + checkShortParse("2001-5-30 +1 day", now, target, false); fail("Expected AssertionFailedError"); - } catch (AssertionFailedError pe) { + } catch (final AssertionFailedError pe) { if (pe.getMessage().startsWith("Expected AssertionFailedError")) { // don't swallow our failure throw pe; } @@ -361,15 +304,15 @@ public class FTPTimestampParserImplTest extends TestCase { } public void testParseShortFutureDates2() throws Exception { - GregorianCalendar now = new GregorianCalendar(2004, Calendar.AUGUST, 1, 12, 0); - checkShortParse("2004-8-1",now,now); // should always work - GregorianCalendar target = (GregorianCalendar) now.clone(); + final GregorianCalendar now = new GregorianCalendar(2004, Calendar.AUGUST, 1, 12, 0); + checkShortParse("2004-8-1", now, now); // should always work + final GregorianCalendar target = (GregorianCalendar) now.clone(); target.add(Calendar.DAY_OF_MONTH, 1); - checkShortParse("2004-8-1 +1 day",now,target,true); + checkShortParse("2004-8-1 +1 day", now, target, true); try { - checkShortParse("2004-8-1 +1 day",now,target,false); + checkShortParse("2004-8-1 +1 day", now, target, false); fail("Expected AssertionFailedError"); - } catch (AssertionFailedError pe) { + } catch (final AssertionFailedError pe) { if (pe.getMessage().startsWith("Expected AssertionFailedError")) { // don't swallow our failure throw pe; } @@ -382,100 +325,140 @@ public class FTPTimestampParserImplTest extends TestCase { // checkShortParse("2004-8-1 +26 weeks",now,target); } - // Test leap year if current year is a leap year - public void testFeb29IfLeapYear() throws Exception{ - GregorianCalendar now = new GregorianCalendar(); - final int thisYear = now.get(Calendar.YEAR); - final GregorianCalendar target = new GregorianCalendar(thisYear,Calendar.FEBRUARY,29); - if (now.isLeapYear(thisYear) - && now.after(target) - && now.before(new GregorianCalendar(thisYear,Calendar.AUGUST,29)) - ){ - checkShortParse("Feb 29th",now,target); - } else { - System.out.println("Skipping Feb 29 test (not leap year or before Feb 29)"); - } + public void testParseShortPastDates1() throws Exception { + final GregorianCalendar now = new GregorianCalendar(2001, Calendar.MAY, 30, 12, 0); + checkShortParse("2001-5-30", now, now); // should always work + final GregorianCalendar target = (GregorianCalendar) now.clone(); + target.add(Calendar.WEEK_OF_YEAR, -1); + checkShortParse("2001-5-30 -1 week", now, target); + target.add(Calendar.WEEK_OF_YEAR, -12); + checkShortParse("2001-5-30 -13 weeks", now, target); + target.add(Calendar.WEEK_OF_YEAR, -13); + checkShortParse("2001-5-30 -26 weeks", now, target); } - // Test Feb 29 for a known leap year - public void testFeb29LeapYear() throws Exception{ - int year = 2000; // Use same year for current and short date - GregorianCalendar now = new GregorianCalendar(year, Calendar.APRIL, 1, 12, 0); - checkShortParse("Feb 29th 2000",now,new GregorianCalendar(year, Calendar.FEBRUARY,29)); + public void testParseShortPastDates2() throws Exception { + final GregorianCalendar now = new GregorianCalendar(2004, Calendar.AUGUST, 1, 12, 0); + checkShortParse("2004-8-1", now, now); // should always work + final GregorianCalendar target = (GregorianCalendar) now.clone(); + target.add(Calendar.WEEK_OF_YEAR, -1); + checkShortParse("2004-8-1 -1 week", now, target); + target.add(Calendar.WEEK_OF_YEAR, -12); + checkShortParse("2004-8-1 -13 weeks", now, target); + target.add(Calendar.WEEK_OF_YEAR, -13); + checkShortParse("2004-8-1 -26 weeks", now, target); } - public void testFeb29LeapYear2() throws Exception{ - int year = 2000; // Use same year for current and short date - GregorianCalendar now = new GregorianCalendar(year, Calendar.MARCH, 1, 12, 0); - checkShortParse("Feb 29th 2000", now, new GregorianCalendar(year, Calendar.FEBRUARY, 29)); + public void testParseTimestamp() { + final Calendar cal = Calendar.getInstance(); + cal.add(Calendar.HOUR_OF_DAY, 1); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + final Date anHourFromNow = cal.getTime(); + final FTPTimestampParserImpl parser = new FTPTimestampParserImpl(); + final SimpleDateFormat sdf = new SimpleDateFormat(parser.getRecentDateFormatString()); + final String fmtTime = sdf.format(anHourFromNow); + try { + final Calendar parsed = parser.parseTimestamp(fmtTime); + // since the timestamp is ahead of now (by one hour), + // this must mean the file's date refers to a year ago. + assertEquals("test.roll.back.year", 1, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR)); + } catch (final ParseException e) { + fail("Unable to parse"); + } } - // same date feb 29 - public void testFeb29LeapYear3() throws Exception{ - int year = 2000; // Use same year for current and short date - GregorianCalendar now = new GregorianCalendar(year, Calendar.FEBRUARY, 29, 12, 0); - checkShortParse("Feb 29th 2000", now, new GregorianCalendar(year, Calendar.FEBRUARY, 29)); - } + public void testParseTimestampAcrossTimeZones() { - // future dated Feb 29 - public void testFeb29LeapYear4() throws Exception{ - int year = 2000; // Use same year for current and short date - GregorianCalendar now = new GregorianCalendar(year, Calendar.FEBRUARY, 28, 12, 0); - // Must allow lenient future date here - checkShortParse("Feb 29th 2000", now, new GregorianCalendar(year, Calendar.FEBRUARY, 29), true); - } + final Calendar cal = Calendar.getInstance(); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + + cal.add(Calendar.HOUR_OF_DAY, 1); + final Date anHourFromNow = cal.getTime(); + + cal.add(Calendar.HOUR_OF_DAY, 2); + final Date threeHoursFromNow = cal.getTime(); + cal.add(Calendar.HOUR_OF_DAY, -2); + + final FTPTimestampParserImpl parser = new FTPTimestampParserImpl(); + + // assume we are FTPing a server in Chicago, two hours ahead of + // L. A. + final FTPClientConfig config = new FTPClientConfig(FTPClientConfig.SYST_UNIX); + config.setDefaultDateFormatStr(FTPTimestampParser.DEFAULT_SDF); + config.setRecentDateFormatStr(FTPTimestampParser.DEFAULT_RECENT_SDF); + // 2 hours difference + config.setServerTimeZoneId("America/Chicago"); + config.setLenientFutureDates(false); // NET-407 + parser.configure(config); + + final SimpleDateFormat sdf = (SimpleDateFormat) parser.getRecentDateFormat().clone(); + + // assume we're in the US Pacific Time Zone + final TimeZone tzla = TimeZone.getTimeZone("America/Los_Angeles"); + sdf.setTimeZone(tzla); + + // get formatted versions of time in L.A. + final String fmtTimePlusOneHour = sdf.format(anHourFromNow); + final String fmtTimePlusThreeHours = sdf.format(threeHoursFromNow); - // Test Feb 29 for a known non-leap year - should fail - public void testFeb29NonLeapYear(){ - GregorianCalendar server = new GregorianCalendar(1999, Calendar.APRIL, 1, 12, 0); - // Note: we use a known leap year for the target date to avoid rounding up - GregorianCalendar input = new GregorianCalendar(2000, Calendar.FEBRUARY,29); - GregorianCalendar expected = new GregorianCalendar(1999, Calendar.FEBRUARY,29); try { - checkShortParse("Feb 29th 1999", server, input, expected, true); - fail("Should have failed to parse Feb 29th 1999"); - } catch (ParseException pe) { - // expected + final Calendar parsed = parser.parseTimestamp(fmtTimePlusOneHour); + // the only difference should be the two hours + // difference, no rolling back a year should occur. + assertEquals("no.rollback.because.of.time.zones", TWO_HOURS_OF_MILLISECONDS, cal.getTime().getTime() - parsed.getTime().getTime()); + } catch (final ParseException e) { + fail("Unable to parse " + fmtTimePlusOneHour); } + + // but if the file's timestamp is THREE hours ahead of now, that should + // cause a rollover even taking the time zone difference into account. + // Since that time is still later than ours, it is parsed as occurring + // on this date last year. try { - checkShortParse("Feb 29th 1999", server, input, expected, false); - fail("Should have failed to parse Feb 29th 1999"); - } catch (ParseException pe) { - // expected + final Calendar parsed = parser.parseTimestamp(fmtTimePlusThreeHours); + // rollback should occur here. + assertEquals("rollback.even.with.time.zones", 1, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR)); + } catch (final ParseException e) { + fail("Unable to parse" + fmtTimePlusThreeHours); } } - // This test currently fails, because we assume that short dates are +-6months when parsing Feb 29 - public void DISABLEDtestNET446() throws Exception { - GregorianCalendar server = new GregorianCalendar(2001, Calendar.JANUARY, 1, 12, 0); - // Note: we use a known leap year for the target date to avoid rounding up - GregorianCalendar input = new GregorianCalendar(2000, Calendar.FEBRUARY,29); - GregorianCalendar expected = new GregorianCalendar(2000, Calendar.FEBRUARY,29); - checkShortParse("Feb 29th 2000", server, input, expected); - } + public void testParseTimestampWithSlop() { + final Calendar cal = Calendar.getInstance(); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); - public void testParseDec31Lenient() throws Exception { - GregorianCalendar now = new GregorianCalendar(2007, Calendar.DECEMBER, 30, 12, 0); - checkShortParse("2007-12-30",now,now); // should always work - GregorianCalendar target = (GregorianCalendar) now.clone(); - target.add(Calendar.DAY_OF_YEAR, +1); // tomorrow - checkShortParse("2007-12-31",now,target, true); - } + final Calendar caltemp = (Calendar) cal.clone(); + caltemp.add(Calendar.HOUR_OF_DAY, 1); + final Date anHourFromNow = caltemp.getTime(); + caltemp.add(Calendar.DAY_OF_MONTH, 1); + final Date anHourFromNowTomorrow = caltemp.getTime(); - public void testParseJan01Lenient() throws Exception { - GregorianCalendar now = new GregorianCalendar(2007, Calendar.DECEMBER, 31, 12, 0); - checkShortParse("2007-12-31",now,now); // should always work - GregorianCalendar target = (GregorianCalendar) now.clone(); - target.add(Calendar.DAY_OF_YEAR, +1); // tomorrow - checkShortParse("2008-1-1",now,target, true); - } + final FTPTimestampParserImpl parser = new FTPTimestampParserImpl(); - public void testParseJan01() throws Exception { - GregorianCalendar now = new GregorianCalendar(2007, Calendar.JANUARY, 1, 12, 0); - checkShortParse("2007-01-01",now,now); // should always work - GregorianCalendar target = new GregorianCalendar(2006, Calendar.DECEMBER, 31, 12, 0); - checkShortParse("2006-12-31",now,target, true); - checkShortParse("2006-12-31",now,target, false); + // set the "slop" factor on + parser.setLenientFutureDates(true); + + final SimpleDateFormat sdf = new SimpleDateFormat(parser.getRecentDateFormatString()); + try { + String fmtTime = sdf.format(anHourFromNow); + Calendar parsed = parser.parseTimestamp(fmtTime); + // the timestamp is ahead of now (by one hour), but + // that's within range of the "slop" factor. + // so the date is still considered this year. + assertEquals("test.slop.no.roll.back.year", 0, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR)); + + // add a day to get beyond the range of the slop factor. + // this must mean the file's date refers to a year ago. + fmtTime = sdf.format(anHourFromNowTomorrow); + parsed = parser.parseTimestamp(fmtTime); + assertEquals("test.slop.roll.back.year", 1, cal.get(Calendar.YEAR) - parsed.get(Calendar.YEAR)); + + } catch (final ParseException e) { + fail("Unable to parse"); + } } } diff --git a/src/test/java/org/apache/commons/net/ftp/parser/MLSDComparison.java b/src/test/java/org/apache/commons/net/ftp/parser/MLSDComparison.java index 11f7d5f..dd9283e 100644 --- a/src/test/java/org/apache/commons/net/ftp/parser/MLSDComparison.java +++ b/src/test/java/org/apache/commons/net/ftp/parser/MLSDComparison.java @@ -32,7 +32,6 @@ import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPFileFilters; import org.apache.commons.net.ftp.FTPListParseEngine; - import org.junit.Test; /** @@ -42,72 +41,91 @@ import org.junit.Test; */ public class MLSDComparison { - private final Comparator<FTPFile> cmp = new Comparator<FTPFile>() { - @Override - public int compare(FTPFile o1, FTPFile o2) { - String n1 = o1.getName(); - String n2 = o2.getName(); - return n1.compareTo(n2); - } + private final Comparator<FTPFile> cmp = (o1, o2) -> { + final String n1 = o1.getName(); + final String n2 = o2.getName(); + return n1.compareTo(n2); }; - @Test - public void testFile() throws Exception{ - File path = new File(DownloadListings.DOWNLOAD_DIR); - FilenameFilter filter = new FilenameFilter(){ - @Override - public boolean accept(File dir, String name) { - return name.endsWith("_mlsd.txt"); - } + /** + * Compare two instances to see if they are the same, ignoring any uninitialized fields. + * + * @param a first instance + * @param b second instance + * @return true if the initialized fields are the same + * @since 3.0 + */ + public boolean areEquivalent(final FTPFile a, final FTPFile b) { + return a.getName().equals(b.getName()) && areSame(a.getSize(), b.getSize(), -1L) && +// areSame(a.getUser(), b.getUser()) && +// areSame(a.getGroup(), b.getGroup()) && + areSame(a.getTimestamp(), b.getTimestamp()) && +// areSame(a.getType(), b.getType(), UNKNOWN_TYPE) && +// areSame(a.getHardLinkCount(), b.getHardLinkCount(), 0) && +// areSame(a._permissions, b._permissions) + true; + } - }; - for (File mlsd : path.listFiles(filter)){ - System.out.println(mlsd); - InputStream is = new FileInputStream(mlsd); - FTPListParseEngine engine = new FTPListParseEngine(MLSxEntryParser.getInstance()); - engine.readServerList(is, FTP.DEFAULT_CONTROL_ENCODING); - FTPFile [] mlsds = engine.getFiles(FTPFileFilters.ALL); - is.close(); - File list = new File(mlsd.getParentFile(),mlsd.getName().replace("_mlsd", "_list")); - - System.out.println(list); - is = new FileInputStream(list); - FTPClientConfig cfg = new FTPClientConfig(); - cfg.setServerTimeZoneId("GMT"); - UnixFTPEntryParser parser = new UnixFTPEntryParser(cfg); - engine = new FTPListParseEngine(parser); - engine.readServerList(is, FTP.DEFAULT_CONTROL_ENCODING); - FTPFile [] lists = engine.getFiles(FTPFileFilters.ALL); - is.close(); - compareSortedLists(mlsds, lists); - } + private boolean areSame(final Calendar a, final Calendar b) { + return a == null || b == null || areSameDateTime(a, b); + } + + private boolean areSame(final long a, final long b, final long d) { + return a == d || b == d || a == b; + } + + // compare permissions: default is all false, but that is also a possible + // state, so this may miss some differences +// private boolean areSame(boolean[][] a, boolean[][] b) { +// return isDefault(a) || isDefault(b) || Arrays.deepEquals(a, b); +// } + + // Is the array in its default state? +// private boolean isDefault(boolean[][] a) { +// for(boolean[] r : a){ +// for(boolean rc : r){ +// if (rc) { // not default +// return false; +// } +// } +// } +// return true; +// } + + private boolean areSameDateTime(final Calendar a, final Calendar b) { + final TimeZone UTC = TimeZone.getTimeZone("UTC"); + final Calendar ac = Calendar.getInstance(UTC); + ac.setTime(a.getTime()); + final Calendar bc = Calendar.getInstance(UTC); + bc.setTime(b.getTime()); + return isSameDay(ac, bc) && isSameTime(ac, bc); } - private void compareSortedLists(FTPFile[] lst, FTPFile[] mlst){ - Arrays.sort(lst, cmp ); - Arrays.sort(mlst, cmp ); + private void compareSortedLists(final FTPFile[] lst, final FTPFile[] mlst) { + Arrays.sort(lst, cmp); + Arrays.sort(mlst, cmp); FTPFile first, second; - int firstl=lst.length; - int secondl=mlst.length; - int one=0, two=0; + final int firstl = lst.length; + final int secondl = mlst.length; + int one = 0, two = 0; first = lst[one++]; second = mlst[two++]; int cmp; while (one < firstl || two < secondl) { // String fs1 = first.toFormattedString(); // String fs2 = second.toFormattedString(); - String rl1 = first.getRawListing(); - String rl2 = second.getRawListing(); + final String rl1 = first.getRawListing(); + final String rl2 = second.getRawListing(); cmp = first.getName().compareTo(second.getName()); if (cmp == 0) { - if (first.getName().endsWith("HEADER.html")){ + if (first.getName().endsWith("HEADER.html")) { cmp = 0; } - if (!areEquivalent(first, second)){ + if (!areEquivalent(first, second)) { // System.out.println(rl1); // System.out.println(fs1); - long tdiff = first.getTimestamp().getTimeInMillis()-second.getTimestamp().getTimeInMillis(); - System.out.println("Minutes diff "+tdiff/(1000*60)); + final long tdiff = first.getTimestamp().getTimeInMillis() - second.getTimestamp().getTimeInMillis(); + System.out.println("Minutes diff " + tdiff / (1000 * 60)); // System.out.println(fs2); // System.out.println(rl2); // System.out.println(); @@ -121,99 +139,62 @@ public class MLSDComparison { } } else if (cmp < 0) { if (!first.getName().startsWith(".")) { // skip hidden files - System.out.println("1: "+rl1); + System.out.println("1: " + rl1); } if (one < firstl) { first = lst[one++]; } } else { - System.out.println("2: "+rl2); + System.out.println("2: " + rl2); if (two < secondl) { second = mlst[two++]; } } } } - /** - * Compare two instances to see if they are the same, - * ignoring any uninitialised fields. - * @param a first instance - * @param b second instance - * @return true if the initialised fields are the same - * @since 3.0 - */ - public boolean areEquivalent(FTPFile a, FTPFile b) { - return - a.getName().equals(b.getName()) && - areSame(a.getSize(), b.getSize(), -1L) && -// areSame(a.getUser(), b.getUser()) && -// areSame(a.getGroup(), b.getGroup()) && - areSame(a.getTimestamp(), b.getTimestamp()) && -// areSame(a.getType(), b.getType(), UNKNOWN_TYPE) && -// areSame(a.getHardLinkCount(), b.getHardLinkCount(), 0) && -// areSame(a._permissions, b._permissions) - true - ; - } - - // compare permissions: default is all false, but that is also a possible - // state, so this may miss some differences -// private boolean areSame(boolean[][] a, boolean[][] b) { -// return isDefault(a) || isDefault(b) || Arrays.deepEquals(a, b); -// } - // Is the array in its default state? -// private boolean isDefault(boolean[][] a) { -// for(boolean[] r : a){ -// for(boolean rc : r){ -// if (rc) { // not default -// return false; -// } -// } -// } -// return true; -// } - - - private boolean areSame(Calendar a, Calendar b) { - return a == null || b == null || areSameDateTime(a, b); + private boolean isSameDay(final Calendar a, final Calendar b) { + final int ad = a.get(Calendar.DAY_OF_MONTH); + final int bd = b.get(Calendar.DAY_OF_MONTH); + return a.get(Calendar.YEAR) == b.get(Calendar.YEAR) && a.get(Calendar.MONTH) == b.get(Calendar.MONTH) && ad == bd; } - private boolean areSameDateTime(Calendar a, Calendar b) { - TimeZone UTC = TimeZone.getTimeZone("UTC"); - Calendar ac = Calendar.getInstance(UTC); - ac.setTime(a.getTime()); - Calendar bc = Calendar.getInstance(UTC); - bc.setTime(b.getTime()); - return isSameDay(ac, bc) && isSameTime(ac, bc); + private boolean isSameTime(final Calendar a, final Calendar b) { + final int ah = a.get(Calendar.HOUR_OF_DAY); + final int bh = b.get(Calendar.HOUR_OF_DAY); + final int am = a.get(Calendar.MINUTE); + final int bm = b.get(Calendar.MINUTE); + final int as = a.get(Calendar.SECOND); + final int bs = b.get(Calendar.SECOND); + return (ah == 0 && am == 0 && as == 0) || (bh == 0 && bm == 0 && bs == 0) || (ah == bh && am == bm) // ignore seconds + ; } - private boolean isSameDay(Calendar a, Calendar b) { - int ad = a.get(Calendar.DAY_OF_MONTH); - int bd = b.get(Calendar.DAY_OF_MONTH); - return - a.get(Calendar.YEAR) == b.get(Calendar.YEAR) && - a.get(Calendar.MONTH) == b.get(Calendar.MONTH) && - ad == bd - ; - } - private boolean isSameTime(Calendar a, Calendar b) { - int ah = a.get(Calendar.HOUR_OF_DAY); - int bh = b.get(Calendar.HOUR_OF_DAY); - int am = a.get(Calendar.MINUTE); - int bm = b.get(Calendar.MINUTE); - int as = a.get(Calendar.SECOND); - int bs = b.get(Calendar.SECOND); - return - (ah == 0 && am == 0 && as ==0) || - (bh == 0 && bm == 0 && bs ==0) || - (ah == bh && am == bm) // ignore seconds - ; - } - - - private boolean areSame(long a, long b, long d) { - return a == d || b == d || a == b; + @Test + public void testFile() throws Exception { + final File path = new File(DownloadListings.DOWNLOAD_DIR); + final FilenameFilter filter = (dir, name) -> name.endsWith("_mlsd.txt"); + final File[] files = path.listFiles(filter); + if (files != null) { + for (final File mlsd : files) { + System.out.println(mlsd); + FTPListParseEngine engine = new FTPListParseEngine(MLSxEntryParser.getInstance()); + try (final InputStream is = new FileInputStream(mlsd)) { + engine.readServerList(is, FTP.DEFAULT_CONTROL_ENCODING); + } + final FTPFile[] mlsds = engine.getFiles(FTPFileFilters.ALL); + final File listFile = new File(mlsd.getParentFile(), mlsd.getName().replace("_mlsd", "_list")); + try (final InputStream inputStream = new FileInputStream(listFile)) { + final FTPClientConfig cfg = new FTPClientConfig(); + cfg.setServerTimeZoneId("GMT"); + final UnixFTPEntryParser parser = new UnixFTPEntryParser(cfg); + engine = new FTPListParseEngine(parser); + engine.readServerList(inputStream, FTP.DEFAULT_CONTROL_ENCODING); + final FTPFile[] lists = engine.getFiles(FTPFileFilters.ALL); + compareSortedLists(mlsds, lists); + } + } + } } // private boolean areSame(int a, int b, int d) { diff --git a/src/test/java/org/apache/commons/net/ftp/parser/MLSxEntryParserTest.java b/src/test/java/org/apache/commons/net/ftp/parser/MLSxEntryParserTest.java index bdb4e42..01c2fe1 100644 --- a/src/test/java/org/apache/commons/net/ftp/parser/MLSxEntryParserTest.java +++ b/src/test/java/org/apache/commons/net/ftp/parser/MLSxEntryParserTest.java @@ -20,75 +20,61 @@ import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPFileEntryParser; /** - * @version $Id: UnixFTPEntryParserTest.java 1643407 2014-12-05 19:32:00Z sebb $ */ public class MLSxEntryParserTest extends FTPParseTestFramework { - private static final String[] badsamples = { - "Type=cdir;Modify=20141022065101;UNIX.mode=0775;/no/space", // no space between facts and name - "Type=cdir;Modify=20141022065103;UNIX.mode=0775;", // no name or space - "/no/leading/space", - "", //empty - "Type=cdir;Modify=20141022065102;UNIX.mode=0775; ", // no name - "Type=dir;Size; missing =size", - "Type=dir missing-semicolon", - "Type= missing value and semicolon", - " ", // no path - "Modify=2014; Short stamp", - "Type=pdir;Modify=20141205180002Z; /trailing chars in Modify", - "Type=dir;Modify=2014102206510x2.999;UNIX.mode=0775; modify has spurious chars", - }; + private static final String[] badsamples = { "Type=cdir;Modify=20141022065101;UNIX.mode=0775;/no/space", // no space between facts and name + "Type=cdir;Modify=20141022065103;UNIX.mode=0775;", // no name or space + "/no/leading/space", "", // empty + "Type=cdir;Modify=20141022065102;UNIX.mode=0775; ", // no name + "Type=dir;Size; missing =size", "Type=dir missing-semicolon", "Type= missing value and semicolon", " ", // no path + "Modify=2014; Short stamp", "Type=pdir;Modify=20141205180002Z; /trailing chars in Modify", + "Type=dir;Modify=2014102206510x2.999;UNIX.mode=0775; modify has spurious chars", }; - private static final String[] goodsamples = { - "Type=cdir;Modify=20141022065102;UNIX.mode=0775; /commons/net", - "Type=pdir;Modify=20141205180002;UNIX.mode=0775; /commons", - "Type=file;Size=431;Modify=20130303210732;UNIX.mode=0664; HEADER.html", - "Type=file;Size=1880;Modify=20130611172748;UNIX.mode=0664; README.html", - "Type=file;Size=2364;Modify=20130611170131;UNIX.mode=0664; RELEASE-NOTES.txt", - "Type=dir;Modify=20141022065102;UNIX.mode=0775; binaries", - "Type=dir;Modify=20141022065102.999;UNIX.mode=0775; source", - " /no/facts", // no facts - "Type=; /empty/fact", - "Size=; /empty/size", - " Type=cdir;Modify=20141022065102;UNIX.mode=0775; /leading/space", // leading space before facts => it's a file name! - " ", // pathname of space + private static final String[] goodsamples = { "Type=cdir;Modify=20141022065102;UNIX.mode=0775; /commons/net", + "Type=pdir;Modify=20141205180002;UNIX.mode=0775; /commons", "Type=file;Size=431;Modify=20130303210732;UNIX.mode=0664; HEADER.html", + "Type=file;Size=1880;Modify=20130611172748;UNIX.mode=0664; README.html", + "Type=file;Size=2364;Modify=20130611170131;UNIX.mode=0664; RELEASE-NOTES.txt", "Type=dir;Modify=20141022065102;UNIX.mode=0775; binaries", + "Type=dir;Modify=20141022065102.999;UNIX.mode=0775; source", " /no/facts", // no facts + "Type=; /empty/fact", "Size=; /empty/size", " Type=cdir;Modify=20141022065102;UNIX.mode=0775; /leading/space", // leading space before facts => it's + // a file name! + " ", // pathname of space }; - public MLSxEntryParserTest(String name) { + public MLSxEntryParserTest(final String name) { super(name); } @Override protected String[] getBadListing() { - return (badsamples); + return badsamples; } @Override protected String[] getGoodListing() { - return (goodsamples); + return goodsamples; } - @Override protected FTPFileEntryParser getParser() { - return (MLSxEntryParser.getInstance()); + return MLSxEntryParser.getInstance(); } /** - * Check if FTPFile entry parsing failed; i.e. if entry is null. - * We override parent check, as a null timestamp is not acceptable - * for these tests. + * Check if FTPFile entry parsing failed; i.e. if entry is null. We override parent check, as a null timestamp is not acceptable for these tests. * * @param f FTPFile entry - may be null * @return null if f is null */ @Override - protected FTPFile nullFileOrNullDate(FTPFile f) { + protected FTPFile nullFileOrNullDate(final FTPFile f) { return f; } @Override - public void testParseFieldsOnFile() throws Exception { + public void testDefaultPrecision() { + testPrecision("Type=dir;Modify=20141022065102;UNIX.mode=0775; source", CalendarUnit.SECOND); + } @Override @@ -96,9 +82,7 @@ public class MLSxEntryParserTest extends FTPParseTestFramework { } @Override - public void testDefaultPrecision() { - testPrecision("Type=dir;Modify=20141022065102;UNIX.mode=0775; source", CalendarUnit.SECOND); - + public void testParseFieldsOnFile() throws Exception { } @Override diff --git a/src/test/java/org/apache/commons/net/ftp/parser/MVSFTPEntryParserTest.java b/src/test/java/org/apache/commons/net/ftp/parser/MVSFTPEntryParserTest.java index 587d7c1..5cd868d 100644 --- a/src/test/java/org/apache/commons/net/ftp/parser/MVSFTPEntryParserTest.java +++ b/src/test/java/org/apache/commons/net/ftp/parser/MVSFTPEntryParserTest.java @@ -17,6 +17,7 @@ package org.apache.commons.net.ftp.parser; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.apache.commons.net.ftp.FTPFile; @@ -29,53 +30,68 @@ public class MVSFTPEntryParserTest extends FTPParseTestFramework { private static final String[] goodsamplesDatasetList = { /* Note, if the string begins with SAVE, the parsed entry is stored in the List saveftpfiles */ - // "Volume Unit Referred Ext Used Recfm Lrecl BlkSz Dsorg Dsname", + // "Volume Unit Referred Ext Used Recfm Lrecl BlkSz Dsorg Dsname", "SAVE00 3390 2004/06/23 1 1 FB 128 6144 PS INCOMING.RPTBM023.D061704", "SAVE01 3390 2004/06/23 1 1 FB 128 6144 PO INCOMING.RPTBM024.D061704", "SAVE02 3390 2004/06/23 1 1 FB 128 6144 PO-E INCOMING.RPTBM025.D061704", - "PSMLC1 3390 2005/04/04 1 1 VB 27994 27998 PS file3.I", - "PSMLB9 3390 2005/04/04 1 1 VB 27994 27998 PS file4.I.BU", - "PSMLB6 3390 2005/04/05 1 1 VB 27994 27998 PS file3.I.BU", - "PSMLC6 3390 2005/04/05 1 1 VB 27994 27998 PS file6.I", - "PSMLB7 3390 2005/04/04 1 1 VB 27994 27998 PS file7.O", - "PSMLC6 3390 2005/04/05 1 1 VB 27994 27998 PS file7.O.BU", + "PSMLC1 3390 2005/04/04 1 1 VB 27994 27998 PS file3.I", "PSMLB9 3390 2005/04/04 1 1 VB 27994 27998 PS file4.I.BU", + "PSMLB6 3390 2005/04/05 1 1 VB 27994 27998 PS file3.I.BU", "PSMLC6 3390 2005/04/05 1 1 VB 27994 27998 PS file6.I", + "PSMLB7 3390 2005/04/04 1 1 VB 27994 27998 PS file7.O", "PSMLC6 3390 2005/04/05 1 1 VB 27994 27998 PS file7.O.BU", "FPFS49 3390 2004/06/23 1 1 FB 128 6144 PO-E INCOMING.RPTBM026.D061704", "FPFS41 3390 2004/06/23 1 1 FB 128 6144 PS INCOMING.RPTBM056.D061704", - "FPFS25 3390 2004/06/23 1 1 FB 128 6144 PS INCOMING.WTM204.D061704", }; + "FPFS25 3390 2004/06/23 1 1 FB 128 6144 PS INCOMING.WTM204.D061704", + "PEX26F 3390 2017/07/03 115807 FB 29600 29600 PS INCOMING.FIN.D170630.T160630", + "VVVVVV 3390 2020/04/18 1 60 U 32760 32760 PO NAME" }; private static final String[] goodsamplesMemberList = { /* Note, if the string begins with SAVE, the parsed entry is stored in the List saveftpfiles */ - "Name VV.MM Created Changed Size Init Mod Id", - "SAVE03 01.03 2002/09/12 2002/10/11 09:37 11 11 0 KIL001", + "Name VV.MM Created Changed Size Init Mod Id", "SAVE03 01.03 2002/09/12 2002/10/11 09:37 11 11 0 KIL001", "SAVE04 ", // no statistics - "TBSHELF1 01.03 2002/09/12 2002/10/11 09:37 11 11 0 KIL001", - "TBSHELF2 01.03 2002/09/12 2002/10/11 09:37 11 11 0 KIL001", - "TBSHELF3 01.03 2002/09/12 2002/10/11 09:37 11 11 0 KIL001", - "TBSHELF4 01.03 2002/09/12 2002/10/11 09:37 11 11 0 KIL001", }; + "TBSHELF1 01.03 2002/09/12 2002/10/11 09:37 11 11 0 KIL001", "TBSHELF2 01.03 2002/09/12 2002/10/11 09:37 11 11 0 KIL001", + "TBSHELF3 01.03 2002/09/12 2002/10/11 09:37 11 11 0 KIL001", "TBSHELF4 01.03 2002/09/12 2002/10/11 09:37 11 11 0 KIL001", }; private static final String[] goodsamplesJES1List = { /* no header for JES1 (JES Interface level 1) */ - /* Note, if the string begins with SAVE, the parsed entry is stored in the List saveftpfiles */ - "IBMUSER1 JOB01906 OUTPUT 3 Spool Files", }; + /* Note, if the string begins with SAVE, the parsed entry is stored in the List saveftpfiles */ + "IBMUSER1 JOB01906 OUTPUT 3 Spool Files", }; private static final String[] goodsamplesJES2List = { /* JES2 (JES Interface level 2) */ /* Note, if the string begins with SAVE, the parsed entry is stored in the List saveftpfiles */ - //"JOBNAME JOBID OWNER STATUS CLASS", - "IBMUSER2 JOB01906 IBMUSER OUTPUT A RC=0000 3 spool files", - "IBMUSER TSU01830 IBMUSER OUTPUT TSU ABEND=522 3 spool files", }; + // "JOBNAME JOBID OWNER STATUS CLASS", + "IBMUSER2 JOB01906 IBMUSER OUTPUT A RC=0000 3 spool files", "IBMUSER TSU01830 IBMUSER OUTPUT TSU ABEND=522 3 spool files", }; + + private static final String[] goodsamplesUnixList = { "total 1234", "-rwxr-xr-x 2 root root 4096 Mar 2 15:13 zxbox", + "drwxr-xr-x 2 root root 4096 Aug 24 2001 zxjdbc", }; - private static final String[] badsamples = { - "MigratedP201.$FTXPBI1.$CF2ITB.$AAB0402.I", - "PSMLC133902005/04/041VB2799427998PSfile1.I", "file2.O", }; + private static final String[] badsamples = { "MigratedP201.$FTXPBI1.$CF2ITB.$AAB0402.I", "PSMLC133902005/04/041VB2799427998PSfile1.I", "file2.O", }; /** * @see junit.framework.TestCase#TestCase(String) */ - public MVSFTPEntryParserTest(String name) { + public MVSFTPEntryParserTest(final String name) { super(name); } - /* (non-Javadoc) + @Override + public void doAdditionalGoodTests(final String test, final FTPFile f) { + assertNotNull("Could not parse raw listing in " + test, f.getRawListing()); + assertNotNull("Could not parse name in " + test, f.getName()); + // some tests don't produce any further details + } + + protected List<String[]> getAllGoodListings() { + final List<String[]> l = new ArrayList<>(); + l.add(goodsamplesDatasetList); + l.add(goodsamplesMemberList); + l.add(goodsamplesJES1List); + l.add(goodsamplesJES2List); + l.add(goodsamplesUnixList); + return l; + } + + /* + * (non-Javadoc) + * * @see org.apache.commons.net.ftp.parser.CompositeFTPParseTestFramework#getBadListings() */ @Override @@ -83,7 +99,9 @@ public class MVSFTPEntryParserTest extends FTPParseTestFramework { return badsamples; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.apache.commons.net.ftp.parser.CompositeFTPParseTestFramework#getGoodListings() */ @Override @@ -91,17 +109,6 @@ public class MVSFTPEntryParserTest extends FTPParseTestFramework { return goodsamplesDatasetList; } - protected List<String[]> getAllGoodListings() { - List<String[]> l = new ArrayList<String[]>(); - l.add(goodsamplesDatasetList); - l.add(goodsamplesMemberList); - l.add(goodsamplesJES1List); - l.add(goodsamplesJES2List); - - return l; - } - - /** * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#getParser() */ @@ -110,54 +117,59 @@ public class MVSFTPEntryParserTest extends FTPParseTestFramework { return new MVSFTPEntryParser(); } + @Override + public void testDefaultPrecision() { + // TODO Not sure what dates are parsed + } + /* - * note the testGoodListing has to be the first test invoked, because - * some FTPFile entries are saved for the later tests + * note the testGoodListing has to be the first test invoked, because some FTPFile entries are saved for the later tests * * (non-Javadoc) + * * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#testGoodListing() */ @Override - public void testGoodListing() throws Exception { - String[] goodsamples = getGoodListing(); - MVSFTPEntryParser parser = new MVSFTPEntryParser(); + public void testGoodListing() { + final String[] goodsamples = getGoodListing(); + final MVSFTPEntryParser parser = new MVSFTPEntryParser(); parser.setType(MVSFTPEntryParser.FILE_LIST_TYPE); parser.setRegex(MVSFTPEntryParser.FILE_LIST_REGEX); - for (String test : goodsamples) { - FTPFile f = parser.parseFTPEntry(test); - assertNotNull("Failed to parse " + test, f); - doAdditionalGoodTests(test, f); - } - } - - public void testMemberListing() throws Exception { - MVSFTPEntryParser parser = new MVSFTPEntryParser(); - parser.setType(MVSFTPEntryParser.MEMBER_LIST_TYPE); - parser.setRegex(MVSFTPEntryParser.MEMBER_LIST_REGEX); - for (String test : goodsamplesMemberList) { - FTPFile f = parser.parseFTPEntry(test); + for (final String test : goodsamples) { + final FTPFile f = parser.parseFTPEntry(test); assertNotNull("Failed to parse " + test, f); doAdditionalGoodTests(test, f); } } public void testJesLevel1Listing() { - MVSFTPEntryParser parser = new MVSFTPEntryParser(); + final MVSFTPEntryParser parser = new MVSFTPEntryParser(); parser.setType(MVSFTPEntryParser.JES_LEVEL_1_LIST_TYPE); parser.setRegex(MVSFTPEntryParser.JES_LEVEL_1_LIST_REGEX); - for (String test : goodsamplesJES1List) { - FTPFile f = parser.parseFTPEntry(test); + for (final String test : goodsamplesJES1List) { + final FTPFile f = parser.parseFTPEntry(test); assertNotNull("Failed to parse " + test, f); doAdditionalGoodTests(test, f); } } public void testJesLevel2Listing() { - MVSFTPEntryParser parser = new MVSFTPEntryParser(); + final MVSFTPEntryParser parser = new MVSFTPEntryParser(); parser.setType(MVSFTPEntryParser.JES_LEVEL_2_LIST_TYPE); parser.setRegex(MVSFTPEntryParser.JES_LEVEL_2_LIST_REGEX); - for (String test : goodsamplesJES2List) { - FTPFile f = parser.parseFTPEntry(test); + for (final String test : goodsamplesJES2List) { + final FTPFile f = parser.parseFTPEntry(test); + assertNotNull("Failed to parse " + test, f); + doAdditionalGoodTests(test, f); + } + } + + public void testMemberListing() { + final MVSFTPEntryParser parser = new MVSFTPEntryParser(); + parser.setType(MVSFTPEntryParser.MEMBER_LIST_TYPE); + parser.setRegex(MVSFTPEntryParser.MEMBER_LIST_REGEX); + for (final String test : goodsamplesMemberList) { + final FTPFile f = parser.parseFTPEntry(test); assertNotNull("Failed to parse " + test, f); doAdditionalGoodTests(test, f); } @@ -165,32 +177,32 @@ public class MVSFTPEntryParserTest extends FTPParseTestFramework { @Override public void testParseFieldsOnDirectory() throws Exception { - MVSFTPEntryParser parser = new MVSFTPEntryParser(); + final MVSFTPEntryParser parser = new MVSFTPEntryParser(); parser.setType(MVSFTPEntryParser.FILE_LIST_TYPE); parser.setRegex(MVSFTPEntryParser.FILE_LIST_REGEX); - FTPFile file = parser - .parseFTPEntry("SAVE01 3390 2004/06/23 1 1 FB 128 6144 PO INCOMING.RPTBM024.D061704"); + FTPFile file = parser.parseFTPEntry("SAVE01 3390 2004/06/23 1 1 FB 128 6144 PO INCOMING.RPTBM024.D061704"); assertNotNull("Could not parse entry.", file); assertTrue("Should have been a directory.", file.isDirectory()); assertEquals("INCOMING.RPTBM024.D061704", file.getName()); - file = parser - .parseFTPEntry("SAVE02 3390 2004/06/23 1 1 FB 128 6144 PO-E INCOMING.RPTBM025.D061704"); + file = parser.parseFTPEntry("SAVE02 3390 2004/06/23 1 1 FB 128 6144 PO-E INCOMING.RPTBM025.D061704"); assertNotNull("Could not parse entry.", file); assertTrue("Should have been a directory.", file.isDirectory()); assertEquals("INCOMING.RPTBM025.D061704", file.getName()); } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#testParseFieldsOnFile() */ @Override public void testParseFieldsOnFile() throws Exception { - FTPFile file = null; + FTPFile file; - MVSFTPEntryParser parser = new MVSFTPEntryParser(); + final MVSFTPEntryParser parser = new MVSFTPEntryParser(); parser.setRegex(MVSFTPEntryParser.FILE_LIST_REGEX); parser.setType(MVSFTPEntryParser.FILE_LIST_TYPE); @@ -218,13 +230,22 @@ public class MVSFTPEntryParserTest extends FTPParseTestFramework { } - @Override - public void testDefaultPrecision() { - // TODO Not sure what dates are parsed - } - @Override public void testRecentPrecision() { // TODO Auto-generated method stub } + + public void testUnixListings() { + final MVSFTPEntryParser parser = new MVSFTPEntryParser(); + final List<String> list = new ArrayList<>(); + Collections.addAll(list, goodsamplesUnixList); + parser.preParse(list); + for (final String test : list) { + final FTPFile f = parser.parseFTPEntry(test); + assertNotNull("Failed to parse " + test, f); + assertNotNull("Failed to parse name " + test, f.getName()); + assertNotNull("Failed to parse group " + test, f.getGroup()); + assertNotNull("Failed to parse user " + test, f.getUser()); + } + } } diff --git a/src/test/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParserTest.java b/src/test/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParserTest.java index 8160e42..6aa6a4d 100644 --- a/src/test/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParserTest.java +++ b/src/test/java/org/apache/commons/net/ftp/parser/MacOsPeterFTPEntryParserTest.java @@ -23,48 +23,63 @@ import org.apache.commons.net.ftp.FTPFileEntryParser; public class MacOsPeterFTPEntryParserTest extends FTPParseTestFramework { - private static final String[] badsamples = { - "drwxr-xr-x 123 folder 0 Jan 4 14:49 Steak", - }; - - private static final String[] goodsamples = - { - "-rw-r--r-- 54149 27826 81975 Jul 22 2010 09.jpg", - "drwxr-xr-x folder 0 Jan 4 14:51 Alias_to_Steak", - "-rw-r--r-- 78440 49231 127671 Jul 22 2010 Filename with whitespace.jpg", - "-rw-r--r-- 78440 49231 127671 Jul 22 14:51 Filename with whitespace.jpg", - "-rw-r--r-- 0 108767 108767 Jul 22 2010 presentation03.jpg", - "-rw-r--r-- 58679 60393 119072 Jul 22 2010 presentation04.jpg", - "-rw-r--r-- 82543 51433 133976 Jul 22 2010 presentation06.jpg", - "-rw-r--r-- 83616 1430976 1514592 Jul 22 2010 presentation10.jpg", - "-rw-r--r-- 0 66990 66990 Jul 22 2010 presentation11.jpg", - "drwxr-xr-x folder 0 Jan 4 14:49 Steak", - "-rwx------ 0 12713 12713 Jul 8 2009 Twitter_Avatar.png", - }; - - public MacOsPeterFTPEntryParserTest(String name) { + private static final String[] badsamples = { "drwxr-xr-x 123 folder 0 Jan 4 14:49 Steak", }; + + private static final String[] goodsamples = { "-rw-r--r-- 54149 27826 81975 Jul 22 2010 09.jpg", + "drwxr-xr-x folder 0 Jan 4 14:51 Alias_to_Steak", + "-rw-r--r-- 78440 49231 127671 Jul 22 2010 Filename with whitespace.jpg", + "-rw-r--r-- 78440 49231 127671 Jul 22 14:51 Filename with whitespace.jpg", + "-rw-r--r-- 0 108767 108767 Jul 22 2010 presentation03.jpg", + "-rw-r--r-- 58679 60393 119072 Jul 22 2010 presentation04.jpg", + "-rw-r--r-- 82543 51433 133976 Jul 22 2010 presentation06.jpg", + "-rw-r--r-- 83616 1430976 1514592 Jul 22 2010 presentation10.jpg", + "-rw-r--r-- 0 66990 66990 Jul 22 2010 presentation11.jpg", "drwxr-xr-x folder 0 Jan 4 14:49 Steak", + "-rwx------ 0 12713 12713 Jul 8 2009 Twitter_Avatar.png", }; + + public MacOsPeterFTPEntryParserTest(final String name) { super(name); } + /** + * Method checkPermissions. Verify that the persmissions were properly set. + * + * @param f + */ + private void checkPermissions(final FTPFile f) { + assertTrue("Should have user read permission.", f.hasPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION)); + assertTrue("Should have user write permission.", f.hasPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION)); + assertTrue("Should have user execute permission.", f.hasPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION)); + assertTrue("Should have group read permission.", f.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.READ_PERMISSION)); + assertFalse("Should NOT have group write permission.", f.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.WRITE_PERMISSION)); + assertTrue("Should have group execute permission.", f.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.EXECUTE_PERMISSION)); + assertTrue("Should have world read permission.", f.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.READ_PERMISSION)); + assertFalse("Should NOT have world write permission.", f.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.WRITE_PERMISSION)); + assertTrue("Should have world execute permission.", f.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.EXECUTE_PERMISSION)); + } + @Override protected String[] getBadListing() { - return (badsamples); + return badsamples; } @Override protected String[] getGoodListing() { - return (goodsamples); + return goodsamples; } @Override protected FTPFileEntryParser getParser() { - return (new MacOsPeterFTPEntryParser()); + return new MacOsPeterFTPEntryParser(); + } + + @Override + public void testDefaultPrecision() { + testPrecision("-rw-r--r-- 78440 49231 127671 Jul 22 2010 Filename with whitespace.jpg", CalendarUnit.DAY_OF_MONTH); } @Override public void testParseFieldsOnDirectory() throws Exception { - FTPFile f = getParser().parseFTPEntry( - "drwxr-xr-x folder 0 Mar 2 15:13 Alias_to_Steak"); + final FTPFile f = getParser().parseFTPEntry("drwxr-xr-x folder 0 Mar 2 15:13 Alias_to_Steak"); assertNotNull("Could not parse entry.", f); assertTrue("Should have been a directory.", f.isDirectory()); checkPermissions(f); @@ -74,7 +89,7 @@ public class MacOsPeterFTPEntryParserTest extends FTPParseTestFramework { assertEquals(0, f.getSize()); assertEquals("Alias_to_Steak", f.getName()); - Calendar cal = Calendar.getInstance(); + final Calendar cal = Calendar.getInstance(); cal.set(Calendar.MONTH, Calendar.MARCH); cal.set(Calendar.DAY_OF_MONTH, 1); @@ -88,15 +103,12 @@ public class MacOsPeterFTPEntryParserTest extends FTPParseTestFramework { cal.set(Calendar.HOUR_OF_DAY, 15); cal.set(Calendar.MINUTE, 13); - assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp() - .getTime())); + assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp().getTime())); } @Override public void testParseFieldsOnFile() throws Exception { - FTPFile f = getParser().parseFTPEntry( - "-rwxr-xr-x 78440 49231 127671 Jul 2 14:51 Filename with whitespace.jpg" - ); + final FTPFile f = getParser().parseFTPEntry("-rwxr-xr-x 78440 49231 127671 Jul 2 14:51 Filename with whitespace.jpg"); assertNotNull("Could not parse entry.", f); assertTrue("Should have been a file.", f.isFile()); checkPermissions(f); @@ -106,7 +118,7 @@ public class MacOsPeterFTPEntryParserTest extends FTPParseTestFramework { assertEquals("Filename with whitespace.jpg", f.getName()); assertEquals(127671L, f.getSize()); - Calendar cal = Calendar.getInstance(); + final Calendar cal = Calendar.getInstance(); cal.set(Calendar.MONTH, Calendar.JULY); cal.set(Calendar.DAY_OF_MONTH, 1); @@ -122,42 +134,9 @@ public class MacOsPeterFTPEntryParserTest extends FTPParseTestFramework { assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp().getTime())); } - /** - * Method checkPermissions. - * Verify that the persmissions were properly set. - * @param f - */ - private void checkPermissions(FTPFile f) { - assertTrue("Should have user read permission.", f.hasPermission( - FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION)); - assertTrue("Should have user write permission.", f.hasPermission( - FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION)); - assertTrue("Should have user execute permission.", f.hasPermission( - FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION)); - assertTrue("Should have group read permission.", f.hasPermission( - FTPFile.GROUP_ACCESS, FTPFile.READ_PERMISSION)); - assertTrue("Should NOT have group write permission.", !f.hasPermission( - FTPFile.GROUP_ACCESS, FTPFile.WRITE_PERMISSION)); - assertTrue("Should have group execute permission.", f.hasPermission( - FTPFile.GROUP_ACCESS, FTPFile.EXECUTE_PERMISSION)); - assertTrue("Should have world read permission.", f.hasPermission( - FTPFile.WORLD_ACCESS, FTPFile.READ_PERMISSION)); - assertTrue("Should NOT have world write permission.", !f.hasPermission( - FTPFile.WORLD_ACCESS, FTPFile.WRITE_PERMISSION)); - assertTrue("Should have world execute permission.", f.hasPermission( - FTPFile.WORLD_ACCESS, FTPFile.EXECUTE_PERMISSION)); - } - - @Override - public void testDefaultPrecision() { - testPrecision( - "-rw-r--r-- 78440 49231 127671 Jul 22 2010 Filename with whitespace.jpg", CalendarUnit.DAY_OF_MONTH); - } - @Override public void testRecentPrecision() { - testPrecision( - "-rw-r--r-- 78440 49231 127671 Jul 22 14:51 Filename with whitespace.jpg", CalendarUnit.MINUTE); + testPrecision("-rw-r--r-- 78440 49231 127671 Jul 22 14:51 Filename with whitespace.jpg", CalendarUnit.MINUTE); } } diff --git a/src/test/java/org/apache/commons/net/ftp/parser/NTFTPEntryParserTest.java b/src/test/java/org/apache/commons/net/ftp/parser/NTFTPEntryParserTest.java index 4e08025..5db1de1 100644 --- a/src/test/java/org/apache/commons/net/ftp/parser/NTFTPEntryParserTest.java +++ b/src/test/java/org/apache/commons/net/ftp/parser/NTFTPEntryParserTest.java @@ -24,213 +24,131 @@ import org.apache.commons.net.ftp.FTPFileEntryParser; import org.apache.commons.net.ftp.FTPListParseEngine; /** - * @version $Id: NTFTPEntryParserTest.java 1697293 2015-08-24 01:01:00Z sebb $ */ -public class NTFTPEntryParserTest extends CompositeFTPParseTestFramework -{ - - private static final String [][] goodsamples = { - { // DOS-style tests - "05-26-95 10:57AM 143712 $LDR$", - "05-20-97 03:31PM 681 .bash_history", - "12-05-96 05:03PM <DIR> absoft2", - "11-14-97 04:21PM 953 AUDITOR3.INI", - "05-22-97 08:08AM 828 AUTOEXEC.BAK", - "01-22-98 01:52PM 795 AUTOEXEC.BAT", - "05-13-97 01:46PM 828 AUTOEXEC.DOS", - "12-03-96 06:38AM 403 AUTOTOOL.LOG", - "12-03-96 06:38AM <DIR> 123xyz", - "01-20-97 03:48PM <DIR> bin", - "05-26-1995 10:57AM 143712 $LDR$", +public class NTFTPEntryParserTest extends CompositeFTPParseTestFramework { + + private static final String[][] goodsamples = { { // DOS-style tests + "05-26-95 10:57AM 143712 $LDR$", "05-20-97 03:31PM 681 .bash_history", + "12-05-96 05:03PM <DIR> absoft2", "11-14-97 04:21PM 953 AUDITOR3.INI", + "05-22-97 08:08AM 828 AUTOEXEC.BAK", "01-22-98 01:52PM 795 AUTOEXEC.BAT", + "05-13-97 01:46PM 828 AUTOEXEC.DOS", "12-03-96 06:38AM 403 AUTOTOOL.LOG", + "12-03-96 06:38AM <DIR> 123xyz", "01-20-97 03:48PM <DIR> bin", "05-26-1995 10:57AM 143712 $LDR$", // 24hr clock as used on Windows_CE - "12-05-96 17:03 <DIR> absoft2", - "05-22-97 08:08 828 AUTOEXEC.BAK", - "01-01-98 05:00 <DIR> Network", - "01-01-98 05:00 <DIR> StorageCard", - "09-13-10 20:08 <DIR> Recycled", - "09-06-06 19:00 69 desktop.ini", - "09-13-10 13:08 23 Control Panel.lnk", - "09-13-10 13:08 <DIR> My Documents", - "09-13-10 13:08 <DIR> Program Files", - "09-13-10 13:08 <DIR> Temp", - "09-13-10 13:08 <DIR> Windows", - }, - { // Unix-style tests - "-rw-r--r-- 1 root root 111325 Apr 27 2001 zxJDBC-2.0.1b1.tar.gz", - "-rw-r--r-- 1 root root 190144 Apr 27 2001 zxJDBC-2.0.1b1.zip", - "-rwxr-xr-x 2 500 500 166 Nov 2 2001 73131-testtes1.afp", - "-rw-r--r-- 1 500 500 166 Nov 9 2001 73131-testtes1.AFP", - "drwx------ 4 maxm Domain Users 512 Oct 2 10:59 .metadata", - } - }; - - private static final String[][] badsamples = - { - { // DOS-style tests - "20-05-97 03:31PM 681 .bash_history", - " 0 DIR 05-19-97 12:56 local", - " 0 DIR 05-12-97 16:52 Maintenance Desktop", - }, + "12-05-96 17:03 <DIR> absoft2", "05-22-97 08:08 828 AUTOEXEC.BAK", + "01-01-98 05:00 <DIR> Network", "01-01-98 05:00 <DIR> StorageCard", "09-13-10 20:08 <DIR> Recycled", + "09-06-06 19:00 69 desktop.ini", "09-13-10 13:08 23 Control Panel.lnk", + "09-13-10 13:08 <DIR> My Documents", "09-13-10 13:08 <DIR> Program Files", + "09-13-10 13:08 <DIR> Temp", "09-13-10 13:08 <DIR> Windows", }, + { // Unix-style tests + "-rw-r--r-- 1 root root 111325 Apr 27 2001 zxJDBC-2.0.1b1.tar.gz", + "-rw-r--r-- 1 root root 190144 Apr 27 2001 zxJDBC-2.0.1b1.zip", + "-rwxr-xr-x 2 500 500 166 Nov 2 2001 73131-testtes1.afp", + "-rw-r--r-- 1 500 500 166 Nov 9 2001 73131-testtes1.AFP", + "drwx------ 4 maxm Domain Users 512 Oct 2 10:59 .metadata", } }; + + private static final String[][] badsamples = { { // DOS-style tests + "20-05-97 03:31PM 681 .bash_history", " 0 DIR 05-19-97 12:56 local", + " 0 DIR 05-12-97 16:52 Maintenance Desktop", }, { // Unix-style tests - "drwxr-xr-x 2 root 99 4096Feb 23 30:01 zzplayer", - } - }; + "drwxr-xr-x 2 root 99 4096Feb 23 30:01 zzplayer", } }; - private static final String directoryBeginningWithNumber = - "12-03-96 06:38AM <DIR> 123xyz"; + private static final String directoryBeginningWithNumber = "12-03-96 06:38AM <DIR> 123xyz"; + // byte -123 when read using ISO-8859-1 encoding becomes 0X85 line terminator + private static final byte[] listFilesByteTrace = { 48, 57, 45, 48, 52, 45, 49, 51, 32, 32, 48, 53, 58, 53, 49, 80, 77, 32, 32, 32, 32, 32, 32, 32, 60, 68, + 73, 82, 62, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 97, 115, 112, 110, 101, 116, 95, 99, 108, 105, 101, 110, 116, 13, 10, // 1 + 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 50, 58, 53, 52, 80, 77, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 50, 32, 65, 95, 113, 117, 105, 99, 107, 95, 98, 114, 111, 119, 110, 95, 102, 111, 120, 95, 106, 117, 109, 112, 115, 95, 111, 118, 101, 114, 95, 116, + 104, 101, 95, 108, 97, 122, 121, 95, 100, 111, 103, 13, 10, // 2 + 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 50, 58, 49, 55, 80, 77, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 51, 32, 120, -127, -123, 121, 13, 10, // 3 + 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 49, 58, 52, 57, 80, 77, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 52, 32, -126, -28, -126, -83, -119, -51, -126, -52, -105, -84, -126, -22, -126, -51, -112, -30, -126, -90, -126, -72, -126, -75, -126, -60, -127, + 65, -126, -75, -126, -87, -126, -32, -126, -32, -126, -58, -126, -52, -112, -123, -126, -55, -126, -96, -126, -25, -126, -72, 46, 116, 120, 116, 13, + 10, // 4 + 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 50, 58, 52, 54, 80, 77, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 53, 32, -125, 76, -125, -125, -125, 98, -125, 86, -125, 116, -125, -115, -127, 91, -116, 118, -114, 90, -113, -111, 13, 10, // 5 + 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 50, 58, 52, 54, 80, 77, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 54, 32, -125, 76, -125, -125, -125, 98, -125, 86, -125, -123, -125, 116, -125, -115, -127, 91, -116, 118, -114, 90, -113, -111, 13, 10, // 6 + 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 49, 58, 52, 57, 80, 77, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 55, 32, -114, 79, -116, -38, -126, -52, -105, -25, 46, 116, 120, 116, 13, 10, // 7 + 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 49, 58, 52, 57, 80, 77, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 56, 32, -111, -66, -116, -10, -106, 93, 46, 116, 120, 116, 13, 10, // 8 + 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 50, 58, 53, 52, 80, 77, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 57, 32, -113, -84, -106, -20, -106, -123, -114, 113, 13, 10, // 9 + 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 49, 58, 52, 57, 80, 77, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 49, + 48, 32, -119, -28, -109, 99, -118, -108, -114, -82, -119, -17, -114, -48, -120, -8, -112, -123, -108, 95, -117, -58, 46, 80, 68, 70, 13, 10, // 10 + 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 50, 58, 49, 49, 80, 77, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 49, + 49, 32, -112, -124, -99, -56, 46, 116, 120, 116, 13, 10, // 11 + 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 50, 58, 52, 51, 80, 77, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 49, + 50, 32, -117, -76, -116, -123, 13, 10, // 12 + 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 50, 58, 49, 50, 80, 77, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 49, + 51, 32, -114, -107, -111, -123, -108, 94, -104, 82, 13, 10, // 13 + 48, 55, 45, 48, 51, 45, 49, 51, 32, 32, 48, 50, 58, 51, 53, 80, 77, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 49, + 52, 32, -112, -123, -117, -101, -126, -52, -116, -16, -126, -19, -126, -24, 46, 116, 120, 116, 13, 10, // 14 + 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 50, 58, 49, 50, 80, 77, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 49, + 53, 32, -114, -123, -117, -101, -112, -20, 13, 10, // 15 + 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 49, 58, 52, 57, 80, 77, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 49, + 54, 32, -107, -94, -112, -123, -106, 126, -126, -55, -107, -44, -126, -25, -126, -72, 46, 116, 120, 116, 13, 10 // 16 + }; + + private static final int LISTFILE_COUNT = 16; /** * @see junit.framework.TestCase#TestCase(String) */ - public NTFTPEntryParserTest (String name) - { + public NTFTPEntryParserTest(final String name) { super(name); } - /** - * @see org.apache.commons.net.ftp.parser.CompositeFTPParseTestFramework#getGoodListings() - */ @Override - protected String[][] getGoodListings() - { - return goodsamples; + protected void doAdditionalGoodTests(final String test, final FTPFile f) { + if (test.indexOf("<DIR>") >= 0) { + assertEquals("directory.type", FTPFile.DIRECTORY_TYPE, f.getType()); + } } /** * @see org.apache.commons.net.ftp.parser.CompositeFTPParseTestFramework#getBadListings() */ @Override - protected String[][] getBadListings() - { + protected String[][] getBadListings() { return badsamples; } /** - * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#getParser() - */ - @Override - protected FTPFileEntryParser getParser() - { - return new CompositeFileEntryParser(new FTPFileEntryParser[] - { - new NTFTPEntryParser(), - new UnixFTPEntryParser() - - }); - } - - /** - * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#testParseFieldsOnDirectory() + * @see org.apache.commons.net.ftp.parser.CompositeFTPParseTestFramework#getGoodListings() */ @Override - public void testParseFieldsOnDirectory() throws Exception - { - FTPFile dir = getParser().parseFTPEntry("12-05-96 05:03PM <DIR> absoft2"); - assertNotNull("Could not parse entry.", dir); - assertEquals("Thu Dec 05 17:03:00 1996", - df.format(dir.getTimestamp().getTime())); - assertTrue("Should have been a directory.", - dir.isDirectory()); - assertEquals("absoft2", dir.getName()); - assertEquals(0, dir.getSize()); - - dir = getParser().parseFTPEntry("12-03-96 06:38AM <DIR> 123456"); - assertNotNull("Could not parse entry.", dir); - assertTrue("Should have been a directory.", - dir.isDirectory()); - assertEquals("123456", dir.getName()); - assertEquals(0, dir.getSize()); - - } - - public void testParseLeadingDigits() { - FTPFile file = getParser().parseFTPEntry("05-22-97 12:08AM 5000000000 10 years and under"); - assertNotNull("Could not parse entry", file); - assertEquals("10 years and under", file.getName()); - assertEquals(5000000000L, file.getSize()); - Calendar timestamp = file.getTimestamp(); - assertNotNull("Could not parse time",timestamp); - assertEquals("Thu May 22 00:08:00 1997",df.format(timestamp.getTime())); - - FTPFile dir = getParser().parseFTPEntry("12-03-96 06:38PM <DIR> 10 years and under"); - assertNotNull("Could not parse entry", dir); - assertEquals("10 years and under", dir.getName()); - timestamp = dir.getTimestamp(); - assertNotNull("Could not parse time",timestamp); - assertEquals("Tue Dec 03 18:38:00 1996",df.format(timestamp.getTime())); + protected String[][] getGoodListings() { + return goodsamples; } - public void testNET339() { - FTPFile file = getParser().parseFTPEntry("05-22-97 12:08 5000000000 10 years and under"); - assertNotNull("Could not parse entry", file); - assertEquals("10 years and under", file.getName()); - assertEquals(5000000000L, file.getSize()); - Calendar timestamp = file.getTimestamp(); - assertNotNull("Could not parse time",timestamp); - assertEquals("Thu May 22 12:08:00 1997",df.format(timestamp.getTime())); - - FTPFile dir = getParser().parseFTPEntry("12-03-96 06:38 <DIR> 10 years and under"); - assertNotNull("Could not parse entry", dir); - assertEquals("10 years and under", dir.getName()); - timestamp = dir.getTimestamp(); - assertNotNull("Could not parse time",timestamp); - assertEquals("Tue Dec 03 06:38:00 1996",df.format(timestamp.getTime())); -} - /** - * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#testParseFieldsOnFile() + * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#getParser() */ @Override - public void testParseFieldsOnFile() throws Exception - { - FTPFile f = getParser().parseFTPEntry("05-22-97 12:08AM 5000000000 AUTOEXEC.BAK"); - assertNotNull("Could not parse entry.", f); - assertEquals("Thu May 22 00:08:00 1997", - df.format(f.getTimestamp().getTime())); - assertTrue("Should have been a file.", - f.isFile()); - assertEquals("AUTOEXEC.BAK", f.getName()); - assertEquals(5000000000L, f.getSize()); - - // test an NT-unix style listing that does NOT have a leading zero - // on the hour. + protected FTPFileEntryParser getParser() { + return new CompositeFileEntryParser(new FTPFileEntryParser[] { new NTFTPEntryParser(), new UnixFTPEntryParser() - f = getParser().parseFTPEntry( - "-rw-rw-r-- 1 mqm mqm 17707 Mar 12 3:33 killmq.sh.log"); - assertNotNull("Could not parse entry.", f); - Calendar cal = Calendar.getInstance(); - cal.setTime(f.getTimestamp().getTime()); - assertEquals("hour", 3, cal.get(Calendar.HOUR)); - assertTrue("Should have been a file.", - f.isFile()); - assertEquals(17707, f.getSize()); + }); } - @Override - protected void doAdditionalGoodTests(String test, FTPFile f) - { - if (test.indexOf("<DIR>") >= 0) - { - assertEquals("directory.type", - FTPFile.DIRECTORY_TYPE, f.getType()); - } + public void testDefaultPrecision() { + testPrecision("05-26-1995 10:57AM 143712 $LDR$", CalendarUnit.MINUTE); + testPrecision("05-22-97 08:08 828 AUTOEXEC.BAK", CalendarUnit.MINUTE); } /* - * test condition reported as bug 20259 - now NET-106. - * directory with name beginning with a numeric character - * was not parsing correctly + * test condition reported as bug 20259 - now NET-106. directory with name beginning with a numeric character was not parsing correctly */ - public void testDirectoryBeginningWithNumber() throws Exception - { - FTPFile f = getParser().parseFTPEntry(directoryBeginningWithNumber); + public void testDirectoryBeginningWithNumber() { + final FTPFile f = getParser().parseFTPEntry(directoryBeginningWithNumber); assertEquals("name", "123xyz", f.getName()); } - public void testDirectoryBeginningWithNumberFollowedBySpaces() throws Exception - { + public void testDirectoryBeginningWithNumberFollowedBySpaces() { FTPFile f = getParser().parseFTPEntry("12-03-96 06:38AM <DIR> 123 xyz"); assertEquals("name", "123 xyz", f.getName()); f = getParser().parseFTPEntry("12-03-96 06:38AM <DIR> 123 abc xyz"); @@ -243,106 +161,105 @@ public class NTFTPEntryParserTest extends CompositeFTPParseTestFramework * */ public void testGroupNameWithSpaces() { - FTPFile f = getParser().parseFTPEntry("drwx------ 4 maxm Domain Users 512 Oct 2 10:59 .metadata"); + final FTPFile f = getParser().parseFTPEntry("drwx------ 4 maxm Domain Users 512 Oct 2 10:59 .metadata"); assertNotNull(f); assertEquals("maxm", f.getUser()); assertEquals("Domain Users", f.getGroup()); } - // byte -123 when read using ISO-8859-1 encoding becomes 0X85 line terminator - private static final byte[] listFilesByteTrace = { - 48, 57, 45, 48, 52, 45, 49, 51, 32, 32, 48, 53, 58, 53, 49, 80, 77, - 32, 32, 32, 32, 32, 32, 32, 60, 68, 73, 82, 62, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 97, 115, 112, 110, 101, 116, 95, 99, 108, 105, 101, 110, 116, - 13, 10, // 1 - 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 50, 58, 53, 52, 80, 77, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 50, 32, - 65, 95, 113, 117, 105, 99, 107, 95, 98, 114, 111, 119, 110, 95, 102, 111, 120, 95, 106, 117, 109, 112, 115, - 95, 111, 118, 101, 114, 95, 116, 104, 101, 95, 108, 97, 122, 121, 95, 100, 111, 103, - 13, 10, // 2 - 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 50, 58, 49, 55, 80, 77, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 51, 32, - 120, -127, -123, 121, - 13, 10, // 3 - 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 49, 58, 52, 57, 80, 77, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 52, 32, - -126, -28, -126, -83, -119, -51, -126, -52, -105, -84, -126, -22, -126, -51, - -112, -30, -126, -90, -126, -72, -126, -75, -126, -60, -127, 65, -126, -75, -126, -87, -126, -32, -126, - -32, -126, -58, -126, -52, -112, -123, -126, -55, -126, -96, -126, -25, -126, -72, 46, 116, 120, 116, - 13, 10, // 4 - 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 50, 58, 52, 54, 80, 77, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 53, 32, - -125, 76, -125, -125, -125, 98, -125, 86, -125, 116, -125, -115, -127, 91, -116, 118, -114, 90, -113, -111, - 13, 10, // 5 - 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 50, 58, 52, 54, 80, 77, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 54, 32, - -125, 76, -125, -125, -125, 98, -125, 86, -125, -123, -125, 116, -125, -115, -127, 91, -116, 118, -114, 90, -113, -111, - 13, 10, // 6 - 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 49, 58, 52, 57, 80, 77, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 55, 32, - -114, 79, -116, -38, -126, -52, -105, -25, 46, 116, 120, 116, - 13, 10, // 7 - 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 49, 58, 52, 57, 80, 77, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 56, 32, - -111, -66, -116, -10, -106, 93, 46, 116, 120, 116, - 13, 10, // 8 - 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 50, 58, 53, 52, 80, 77, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 57, 32, - -113, -84, -106, -20, -106, -123, -114, 113, - 13, 10, // 9 - 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 49, 58, 52, 57, 80, 77, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 49, 48, 32, - -119, -28, -109, 99, -118, -108, -114, -82, -119, -17, -114, -48, -120, -8, -112, -123, -108, 95, -117, -58, 46, 80, 68, 70, - 13, 10, // 10 - 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 50, 58, 49, 49, 80, 77, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 49, 49, 32, - -112, -124, -99, -56, 46, 116, 120, 116, - 13, 10, // 11 - 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 50, 58, 52, 51, 80, 77, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 49, 50, 32, - -117, -76, -116, -123, - 13, 10, // 12 - 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 50, 58, 49, 50, 80, 77, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 49, 51, 32, - -114, -107, -111, -123, -108, 94, -104, 82, - 13, 10, //13 - 48, 55, 45, 48, 51, 45, 49, 51, 32, 32, 48, 50, 58, 51, 53, 80, 77, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 49, 52, 32, - -112, -123, -117, -101, -126, -52, -116, -16, -126, -19, -126, -24, 46, 116, 120, 116, - 13, 10, // 14 - 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 50, 58, 49, 50, 80, 77, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 49, 53, 32, - -114, -123, -117, -101, -112, -20, - 13, 10, //15 - 48, 55, 45, 49, 55, 45, 49, 51, 32, 32, 48, 49, 58, 52, 57, 80, 77, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 49, 54, 32, - -107, -94, -112, -123, -106, 126, -126, -55, -107, -44, -126, -25, -126, -72, 46, 116, 120, 116, - 13, 10 // 16 - }; - - private static final int LISTFILE_COUNT = 16; + public void testNET339() { + final FTPFile file = getParser().parseFTPEntry("05-22-97 12:08 5000000000 10 years and under"); + assertNotNull("Could not parse entry", file); + assertEquals("10 years and under", file.getName()); + assertEquals(5000000000L, file.getSize()); + Calendar timestamp = file.getTimestamp(); + assertNotNull("Could not parse time", timestamp); + assertEquals("Thu May 22 12:08:00 1997", df.format(timestamp.getTime())); - private int testNET516(String charset) throws Exception { - FTPFileEntryParser parser = new NTFTPEntryParser(); - FTPListParseEngine engine = new FTPListParseEngine(parser ); - engine.readServerList(new ByteArrayInputStream(listFilesByteTrace),charset); - FTPFile[] ftpfiles = engine.getFiles(); - return ftpfiles.length; + final FTPFile dir = getParser().parseFTPEntry("12-03-96 06:38 <DIR> 10 years and under"); + assertNotNull("Could not parse entry", dir); + assertEquals("10 years and under", dir.getName()); + timestamp = dir.getTimestamp(); + assertNotNull("Could not parse time", timestamp); + assertEquals("Tue Dec 03 06:38:00 1996", df.format(timestamp.getTime())); } public void testNET516() throws Exception { // problem where part of a multi-byte char gets converted to 0x85 = line term - int utf = testNET516("UTF-8"); + final int utf = testNET516("UTF-8"); assertEquals(LISTFILE_COUNT, utf); - int ascii = testNET516("ASCII"); + final int ascii = testNET516("ASCII"); assertEquals(LISTFILE_COUNT, ascii); - int iso8859_1 = testNET516("ISO-8859-1"); + final int iso8859_1 = testNET516("ISO-8859-1"); assertEquals(LISTFILE_COUNT, iso8859_1); } + private int testNET516(final String charset) throws Exception { + final FTPFileEntryParser parser = new NTFTPEntryParser(); + final FTPListParseEngine engine = new FTPListParseEngine(parser); + engine.readServerList(new ByteArrayInputStream(listFilesByteTrace), charset); + final FTPFile[] ftpfiles = engine.getFiles(); + return ftpfiles.length; + } + + /** + * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#testParseFieldsOnDirectory() + */ @Override - public void testDefaultPrecision() { - testPrecision("05-26-1995 10:57AM 143712 $LDR$", CalendarUnit.MINUTE); - testPrecision("05-22-97 08:08 828 AUTOEXEC.BAK", CalendarUnit.MINUTE); + public void testParseFieldsOnDirectory() throws Exception { + FTPFile dir = getParser().parseFTPEntry("12-05-96 05:03PM <DIR> absoft2"); + assertNotNull("Could not parse entry.", dir); + assertEquals("Thu Dec 05 17:03:00 1996", df.format(dir.getTimestamp().getTime())); + assertTrue("Should have been a directory.", dir.isDirectory()); + assertEquals("absoft2", dir.getName()); + assertEquals(0, dir.getSize()); + + dir = getParser().parseFTPEntry("12-03-96 06:38AM <DIR> 123456"); + assertNotNull("Could not parse entry.", dir); + assertTrue("Should have been a directory.", dir.isDirectory()); + assertEquals("123456", dir.getName()); + assertEquals(0, dir.getSize()); + + } + + /** + * @see org.apache.commons.net.ftp.parser.FTPParseTestFramework#testParseFieldsOnFile() + */ + @Override + public void testParseFieldsOnFile() throws Exception { + FTPFile f = getParser().parseFTPEntry("05-22-97 12:08AM 5000000000 AUTOEXEC.BAK"); + assertNotNull("Could not parse entry.", f); + assertEquals("Thu May 22 00:08:00 1997", df.format(f.getTimestamp().getTime())); + assertTrue("Should have been a file.", f.isFile()); + assertEquals("AUTOEXEC.BAK", f.getName()); + assertEquals(5000000000L, f.getSize()); + + // test an NT-unix style listing that does NOT have a leading zero + // on the hour. + + f = getParser().parseFTPEntry("-rw-rw-r-- 1 mqm mqm 17707 Mar 12 3:33 killmq.sh.log"); + assertNotNull("Could not parse entry.", f); + final Calendar cal = Calendar.getInstance(); + cal.setTime(f.getTimestamp().getTime()); + assertEquals("hour", 3, cal.get(Calendar.HOUR)); + assertTrue("Should have been a file.", f.isFile()); + assertEquals(17707, f.getSize()); + } + + public void testParseLeadingDigits() { + final FTPFile file = getParser().parseFTPEntry("05-22-97 12:08AM 5000000000 10 years and under"); + assertNotNull("Could not parse entry", file); + assertEquals("10 years and under", file.getName()); + assertEquals(5000000000L, file.getSize()); + Calendar timestamp = file.getTimestamp(); + assertNotNull("Could not parse time", timestamp); + assertEquals("Thu May 22 00:08:00 1997", df.format(timestamp.getTime())); + + final FTPFile dir = getParser().parseFTPEntry("12-03-96 06:38PM <DIR> 10 years and under"); + assertNotNull("Could not parse entry", dir); + assertEquals("10 years and under", dir.getName()); + timestamp = dir.getTimestamp(); + assertNotNull("Could not parse time", timestamp); + assertEquals("Tue Dec 03 18:38:00 1996", df.format(timestamp.getTime())); } @Override diff --git a/src/test/java/org/apache/commons/net/ftp/parser/NetwareFTPEntryParserTest.java b/src/test/java/org/apache/commons/net/ftp/parser/NetwareFTPEntryParserTest.java index 65523ce..a41dfc6 100644 --- a/src/test/java/org/apache/commons/net/ftp/parser/NetwareFTPEntryParserTest.java +++ b/src/test/java/org/apache/commons/net/ftp/parser/NetwareFTPEntryParserTest.java @@ -25,42 +25,42 @@ import org.apache.commons.net.ftp.FTPFileEntryParser; */ public class NetwareFTPEntryParserTest extends FTPParseTestFramework { - private static final String[] badsamples = { - "a [-----F--] SCION_SYS 512 Apr 13 23:52 SYS", - "d [----AF--] 0 512 10-04-2001 _ADMIN" - }; - - private static final String [] goodsamples = { - "d [-----F--] SCION_SYS 512 Apr 13 23:52 SYS", - "d [----AF--] 0 512 Feb 22 17:32 _ADMIN", - "d [-W---F--] SCION_VOL2 512 Apr 13 23:12 VOL2", - "- [RWCEAFMS] rwinston 19968 Mar 12 15:20 Executive Summary.doc", - "d [RWCEAFMS] rwinston 512 Nov 24 2005 Favorites" - }; - - public NetwareFTPEntryParserTest(String name) { + private static final String[] badsamples = { "a [-----F--] SCION_SYS 512 Apr 13 23:52 SYS", + "d [----AF--] 0 512 10-04-2001 _ADMIN" }; + + private static final String[] goodsamples = { "d [-----F--] SCION_SYS 512 Apr 13 23:52 SYS", + "d [----AF--] 0 512 Feb 22 17:32 _ADMIN", "d [-W---F--] SCION_VOL2 512 Apr 13 23:12 VOL2", + "- [RWCEAFMS] rwinston 19968 Mar 12 15:20 Executive Summary.doc", + "d [RWCEAFMS] rwinston 512 Nov 24 2005 Favorites" }; + + public NetwareFTPEntryParserTest(final String name) { super(name); } @Override protected String[] getBadListing() { - return (badsamples); + return badsamples; } @Override protected String[] getGoodListing() { - return (goodsamples); + return goodsamples; } @Override protected FTPFileEntryParser getParser() { - return (new NetwareFTPEntryParser()); + return new NetwareFTPEntryParser(); + } + + @Override + public void testDefaultPrecision() { + testPrecision("d [RWCEAFMS] rwinston 512 Nov 24 2005 Favorites", CalendarUnit.DAY_OF_MONTH); } @Override public void testParseFieldsOnDirectory() throws Exception { - String reply = "d [-W---F--] testUser 512 Apr 13 23:12 testFile"; - FTPFile f = getParser().parseFTPEntry(reply); + final String reply = "d [-W---F--] testUser 512 Apr 13 23:12 testFile"; + final FTPFile f = getParser().parseFTPEntry(reply); assertNotNull("Could not parse file", f); assertEquals("testFile", f.getName()); @@ -68,7 +68,7 @@ public class NetwareFTPEntryParserTest extends FTPParseTestFramework { assertEquals("testUser", f.getUser()); assertTrue("Directory flag is not set!", f.isDirectory()); - Calendar cal = Calendar.getInstance(); + final Calendar cal = Calendar.getInstance(); cal.set(Calendar.MONTH, 3); cal.set(Calendar.DAY_OF_MONTH, 13); cal.set(Calendar.HOUR_OF_DAY, 23); @@ -77,17 +77,15 @@ public class NetwareFTPEntryParserTest extends FTPParseTestFramework { cal.set(Calendar.MILLISECOND, 0); cal.set(Calendar.YEAR, f.getTimestamp().get(Calendar.YEAR)); - assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp() - .getTime())); + assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp().getTime())); } - @Override public void testParseFieldsOnFile() throws Exception { - String reply = "- [R-CEAFMS] rwinston 19968 Mar 12 15:20 Document name with spaces.doc"; + final String reply = "- [R-CEAFMS] rwinston 19968 Mar 12 15:20 Document name with spaces.doc"; - FTPFile f = getParser().parseFTPEntry(reply); + final FTPFile f = getParser().parseFTPEntry(reply); assertNotNull("Could not parse file", f); assertEquals("Document name with spaces.doc", f.getName()); @@ -99,16 +97,9 @@ public class NetwareFTPEntryParserTest extends FTPParseTestFramework { assertFalse(f.hasPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION)); } - @Override - public void testDefaultPrecision() { - testPrecision("d [RWCEAFMS] rwinston 512 Nov 24 2005 Favorites", CalendarUnit.DAY_OF_MONTH); - } - @Override public void testRecentPrecision() { testPrecision("- [RWCEAFMS] rwinston 19968 Mar 12 15:20 Executive Summary.doc", CalendarUnit.MINUTE); } } - - diff --git a/src/test/java/org/apache/commons/net/ftp/parser/OS2FTPEntryParserTest.java b/src/test/java/org/apache/commons/net/ftp/parser/OS2FTPEntryParserTest.java index a954125..af3bf2e 100644 --- a/src/test/java/org/apache/commons/net/ftp/parser/OS2FTPEntryParserTest.java +++ b/src/test/java/org/apache/commons/net/ftp/parser/OS2FTPEntryParserTest.java @@ -19,86 +19,39 @@ package org.apache.commons.net.ftp.parser; import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPFileEntryParser; -public class OS2FTPEntryParserTest extends FTPParseTestFramework -{ +public class OS2FTPEntryParserTest extends FTPParseTestFramework { - private static final String[] badsamples = - { - " DIR 12-30-97 12:32 jbrekke", - " 0 rsa DIR 11-25-97 09:42 junk", - " 0 dir 05-12-97 16:44 LANGUAGE", - " 0 DIR 13-05-97 25:49 MPTN", - "587823 RSA DIR Jan-08-97 13:58 OS2KRNL", - " 33280 A 1997-02-03 13:49 OS2LDR", - "12-05-96 05:03PM <DIR> absoft2", - "11-14-97 04:21PM 953 AUDITOR3.INI" - }; - private static final String[] goodsamples = - { - " 0 DIR 12-30-97 12:32 jbrekke", - " 0 DIR 11-25-97 09:42 junk", - " 0 DIR 05-12-97 16:44 LANGUAGE", - " 0 DIR 05-19-97 12:56 local", - " 0 DIR 05-12-97 16:52 Maintenance Desktop", - " 0 DIR 05-13-97 10:49 MPTN", - "587823 RSA DIR 01-08-97 13:58 OS2KRNL", - " 33280 A 02-09-97 13:49 OS2LDR", - " 0 DIR 11-28-97 09:42 PC", - "149473 A 11-17-98 16:07 POPUPLOG.OS2", - " 0 DIR 05-12-97 16:44 PSFONTS", - " 0 DIR 05-19-2000 12:56 local", - }; + private static final String[] badsamples = { " DIR 12-30-97 12:32 jbrekke", " 0 rsa DIR 11-25-97 09:42 junk", + " 0 dir 05-12-97 16:44 LANGUAGE", " 0 DIR 13-05-97 25:49 MPTN", + "587823 RSA DIR Jan-08-97 13:58 OS2KRNL", " 33280 A 1997-02-03 13:49 OS2LDR", + "12-05-96 05:03PM <DIR> absoft2", "11-14-97 04:21PM 953 AUDITOR3.INI" }; - public OS2FTPEntryParserTest(String name) - { - super(name); - } - - @Override - public void testParseFieldsOnDirectory() throws Exception - { - FTPFile dir = getParser().parseFTPEntry(" 0 DIR 11-28-97 09:42 PC"); - assertNotNull("Could not parse entry.", dir); - assertTrue("Should have been a directory.", - dir.isDirectory()); - assertEquals(0,dir.getSize()); - assertEquals("PC", dir.getName()); - assertEquals("Fri Nov 28 09:42:00 1997", - df.format(dir.getTimestamp().getTime())); - } + private static final String[] goodsamples = { " 0 DIR 12-30-97 12:32 jbrekke", " 0 DIR 11-25-97 09:42 junk", + " 0 DIR 05-12-97 16:44 LANGUAGE", " 0 DIR 05-19-97 12:56 local", + " 0 DIR 05-12-97 16:52 Maintenance Desktop", " 0 DIR 05-13-97 10:49 MPTN", + "587823 RSA DIR 01-08-97 13:58 OS2KRNL", " 33280 A 02-09-97 13:49 OS2LDR", + " 0 DIR 11-28-97 09:42 PC", "149473 A 11-17-98 16:07 POPUPLOG.OS2", + " 0 DIR 05-12-97 16:44 PSFONTS", " 0 DIR 05-19-2000 12:56 local", }; - @Override - public void testParseFieldsOnFile() throws Exception - { - FTPFile file = getParser().parseFTPEntry("5000000000 A 11-17-98 16:07 POPUPLOG.OS2"); - assertNotNull("Could not parse entry.", file); - assertTrue("Should have been a file.", - file.isFile()); - assertEquals(5000000000L, file.getSize()); - assertEquals("POPUPLOG.OS2", file.getName()); - assertEquals("Tue Nov 17 16:07:00 1998", - df.format(file.getTimestamp().getTime())); + public OS2FTPEntryParserTest(final String name) { + super(name); } @Override - protected String[] getBadListing() - { + protected String[] getBadListing() { - return (badsamples); + return badsamples; } @Override - protected String[] getGoodListing() - { + protected String[] getGoodListing() { - return (goodsamples); + return goodsamples; } @Override - protected FTPFileEntryParser getParser() - { - ConfigurableFTPFileEntryParserImpl parser = - new OS2FTPEntryParser(); + protected FTPFileEntryParser getParser() { + final ConfigurableFTPFileEntryParserImpl parser = new OS2FTPEntryParser(); parser.configure(null); return parser; } @@ -109,6 +62,26 @@ public class OS2FTPEntryParserTest extends FTPParseTestFramework testPrecision(" 0 DIR 05-19-2000 12:56 local", CalendarUnit.MINUTE); } + @Override + public void testParseFieldsOnDirectory() throws Exception { + final FTPFile dir = getParser().parseFTPEntry(" 0 DIR 11-28-97 09:42 PC"); + assertNotNull("Could not parse entry.", dir); + assertTrue("Should have been a directory.", dir.isDirectory()); + assertEquals(0, dir.getSize()); + assertEquals("PC", dir.getName()); + assertEquals("Fri Nov 28 09:42:00 1997", df.format(dir.getTimestamp().getTime())); + } + + @Override + public void testParseFieldsOnFile() throws Exception { + final FTPFile file = getParser().parseFTPEntry("5000000000 A 11-17-98 16:07 POPUPLOG.OS2"); + assertNotNull("Could not parse entry.", file); + assertTrue("Should have been a file.", file.isFile()); + assertEquals(5000000000L, file.getSize()); + assertEquals("POPUPLOG.OS2", file.getName()); + assertEquals("Tue Nov 17 16:07:00 1998", df.format(file.getTimestamp().getTime())); + } + @Override public void testRecentPrecision() { // Not needed diff --git a/src/test/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParserAdditionalTest.java b/src/test/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParserAdditionalTest.java index c0f4fcf..68af2a6 100644 --- a/src/test/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParserAdditionalTest.java +++ b/src/test/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParserAdditionalTest.java @@ -16,85 +16,67 @@ */ package org.apache.commons.net.ftp.parser; +import java.util.Calendar; + import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPFileEntryParser; -import java.util.Calendar; - /** - * @version $Id: OS400FTPEntryParserAdditionalTest.java 1644697 2014-12-11 17:00:57Z sebb $ */ -public class OS400FTPEntryParserAdditionalTest extends CompositeFTPParseTestFramework -{ - private static final String[][] badsamples = -{ - { - "QPGMR 135168 04/03/18 13:18:19 *FILE", - "QPGMR 135168 03/24 13:18:19 *FILE", - "QPGMR 135168 04/03/18 30:06:29 *FILE", - "QPGMR 04/03/18 13:18:19 *FILE RPGUNITC1.FILE", - "QPGMR 135168 03/24 13:18:19 *FILE RPGUNITC1.FILE", - "QPGMR 135168 04/03/18 30:06:29 *FILE RPGUNITC1.FILE", - "QPGMR *MEM ", - "QPGMR 135168 04/03/18 13:18:19 *MEM RPGUNITC1.FILE/RUCALLTST.MBR", - "QPGMR 135168 *MEM RPGUNITC1.FILE/RUCALLTST.MBR", - "QPGMR 04/03/18 13:18:19 *MEM RPGUNITC1.FILE/RUCALLTST.MBR", - "QPGMR USR *MEM RPGUNITC1.FILE/RUCALLTST.MBR" - } - }; - - private static final String[][] goodsamples = - { - { - "QPGMR *MEM RPGUNITC1.FILE/RUCALLTST.MBR", - "QPGMR 16347136 29.06.13 15:45:09 *FILE RPGUNIT.SAVF" - } - }; - - public OS400FTPEntryParserAdditionalTest(String name) - { +public class OS400FTPEntryParserAdditionalTest extends CompositeFTPParseTestFramework { + private static final String[][] badsamples = { { "QPGMR 135168 04/03/18 13:18:19 *FILE", "QPGMR 135168 03/24 13:18:19 *FILE", + "QPGMR 135168 04/03/18 30:06:29 *FILE", "QPGMR 04/03/18 13:18:19 *FILE RPGUNITC1.FILE", + "QPGMR 135168 03/24 13:18:19 *FILE RPGUNITC1.FILE", "QPGMR 135168 04/03/18 30:06:29 *FILE RPGUNITC1.FILE", + "QPGMR *MEM ", "QPGMR 135168 04/03/18 13:18:19 *MEM RPGUNITC1.FILE/RUCALLTST.MBR", + "QPGMR 135168 *MEM RPGUNITC1.FILE/RUCALLTST.MBR", + "QPGMR 04/03/18 13:18:19 *MEM RPGUNITC1.FILE/RUCALLTST.MBR", + "QPGMR USR *MEM RPGUNITC1.FILE/RUCALLTST.MBR" } }; + + private static final String[][] goodsamples = { { "QPGMR *MEM RPGUNITC1.FILE/RUCALLTST.MBR", + "QPGMR 16347136 29.06.13 15:45:09 *FILE RPGUNIT.SAVF" } }; + + public OS400FTPEntryParserAdditionalTest(final String name) { super(name); } @Override - protected String[][] getBadListings() - { + protected void doAdditionalGoodTests(final String test, final FTPFile f) { + if (test.startsWith("d")) { + assertEquals("directory.type", FTPFile.DIRECTORY_TYPE, f.getType()); + } + } + + @Override + protected String[][] getBadListings() { return badsamples; } @Override - protected String[][] getGoodListings() - { + protected String[][] getGoodListings() { return goodsamples; } @Override - protected FTPFileEntryParser getParser() - { - return new CompositeFileEntryParser(new FTPFileEntryParser[] - { - new OS400FTPEntryParser(), - new UnixFTPEntryParser() - }); + protected FTPFileEntryParser getParser() { + return new CompositeFileEntryParser(new FTPFileEntryParser[] { new OS400FTPEntryParser(), new UnixFTPEntryParser() }); } @Override - public void testParseFieldsOnDirectory() throws Exception - { - FTPFile f = getParser().parseFTPEntry("PEP 36864 04/03/24 14:06:34 *DIR dir1/"); - assertNotNull("Could not parse entry.", - f); - assertTrue("Should have been a directory.", - f.isDirectory()); - assertEquals("PEP", - f.getUser()); - assertEquals("dir1", - f.getName()); - assertEquals(36864, - f.getSize()); - - Calendar cal = Calendar.getInstance(); + public void testDefaultPrecision() { + // Done in other class + } + + @Override + public void testParseFieldsOnDirectory() throws Exception { + final FTPFile f = getParser().parseFTPEntry("PEP 36864 04/03/24 14:06:34 *DIR dir1/"); + assertNotNull("Could not parse entry.", f); + assertTrue("Should have been a directory.", f.isDirectory()); + assertEquals("PEP", f.getUser()); + assertEquals("dir1", f.getName()); + assertEquals(36864, f.getSize()); + + final Calendar cal = Calendar.getInstance(); cal.set(Calendar.MONTH, Calendar.MARCH); cal.set(Calendar.YEAR, 2004); @@ -103,36 +85,19 @@ public class OS400FTPEntryParserAdditionalTest extends CompositeFTPParseTestFram cal.set(Calendar.MINUTE, 6); cal.set(Calendar.SECOND, 34); - assertEquals(df.format(cal.getTime()), - df.format(f.getTimestamp().getTime())); + assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp().getTime())); } @Override - protected void doAdditionalGoodTests(String test, FTPFile f) - { - if (test.startsWith("d")) - { - assertEquals("directory.type", - FTPFile.DIRECTORY_TYPE, f.getType()); - } - } + public void testParseFieldsOnFile() throws Exception { + final FTPFile f = getParser().parseFTPEntry("PEP 5000000000 04/03/24 14:06:29 *STMF build.xml"); + assertNotNull("Could not parse entry.", f); + assertTrue("Should have been a file.", f.isFile()); + assertEquals("PEP", f.getUser()); + assertEquals("build.xml", f.getName()); + assertEquals(5000000000L, f.getSize()); - @Override - public void testParseFieldsOnFile() throws Exception - { - FTPFile f = getParser().parseFTPEntry("PEP 5000000000 04/03/24 14:06:29 *STMF build.xml"); - assertNotNull("Could not parse entry.", - f); - assertTrue("Should have been a file.", - f.isFile()); - assertEquals("PEP", - f.getUser()); - assertEquals("build.xml", - f.getName()); - assertEquals(5000000000L, - f.getSize()); - - Calendar cal = Calendar.getInstance(); + final Calendar cal = Calendar.getInstance(); cal.set(Calendar.DAY_OF_MONTH, 24); cal.set(Calendar.MONTH, Calendar.MARCH); @@ -140,13 +105,7 @@ public class OS400FTPEntryParserAdditionalTest extends CompositeFTPParseTestFram cal.set(Calendar.HOUR_OF_DAY, 14); cal.set(Calendar.MINUTE, 6); cal.set(Calendar.SECOND, 29); - assertEquals(df.format(cal.getTime()), - df.format(f.getTimestamp().getTime())); - } - - @Override - public void testDefaultPrecision() { - // Done in other class + assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp().getTime())); } @Override diff --git a/src/test/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParserTest.java b/src/test/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParserTest.java index 043078e..33dd780 100644 --- a/src/test/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParserTest.java +++ b/src/test/java/org/apache/commons/net/ftp/parser/OS400FTPEntryParserTest.java @@ -16,68 +16,51 @@ */ package org.apache.commons.net.ftp.parser; +import java.util.Calendar; + import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPFileEntryParser; -import java.util.Calendar; - /** - * @version $Id: OS400FTPEntryParserTest.java 1697289 2015-08-24 00:32:19Z sebb $ */ -public class OS400FTPEntryParserTest extends CompositeFTPParseTestFramework -{ - private static final String[][] badsamples = -{ - { - "PEP 4019 04/03/18 18:58:16 STMF einladung.zip", - "PEP 422 03/24 14:06:26 *STMF readme", - "PEP 6409 04/03/24 30:06:29 *STMF build.xml", - "PEP USR 36864 04/03/24 14:06:34 *DIR dir1/", - "PEP 3686404/03/24 14:06:47 *DIR zdir2/" - }, - - { - "----rwxr-x 1PEP 0 4019 Mar 18 18:58 einladung.zip", - "----rwxr-x 1 PEP 0 xx 422 Mar 24 14:06 readme", - "----rwxr-x 1 PEP 0 8492 Apr 07 30:13 build.xml", - "d---rwxr-x 2 PEP 0 45056Mar 24 14:06 zdir2" - } - }; - - private static final String[][] goodsamples = - { - { - "PEP 4019 04/03/18 18:58:16 *STMF einladung.zip", - "PEP 422 04/03/24 14:06:26 *STMF readme", - "PEP 6409 04/03/24 14:06:29 *STMF build.xml", - "PEP 36864 04/03/24 14:06:34 *DIR dir1/", - "PEP 36864 04/03/24 14:06:47 *DIR zdir2/" - }, - { - "----rwxr-x 1 PEP 0 4019 Mar 18 18:58 einladung.zip", - "----rwxr-x 1 PEP 0 422 Mar 24 14:06 readme", - "----rwxr-x 1 PEP 0 8492 Apr 07 07:13 build.xml", - "d---rwxr-x 2 PEP 0 45056 Mar 24 14:06 dir1", - "d---rwxr-x 2 PEP 0 45056 Mar 24 14:06 zdir2" - } - }; +public class OS400FTPEntryParserTest extends CompositeFTPParseTestFramework { + private static final String[][] badsamples = { + { "PEP 4019 04/03/18 18:58:16 STMF einladung.zip", "PEP 422 03/24 14:06:26 *STMF readme", + "PEP 6409 04/03/24 30:06:29 *STMF build.xml", "PEP USR 36864 04/03/24 14:06:34 *DIR dir1/", + "PEP 3686404/03/24 14:06:47 *DIR zdir2/" }, + + { "----rwxr-x 1PEP 0 4019 Mar 18 18:58 einladung.zip", "----rwxr-x 1 PEP 0 xx 422 Mar 24 14:06 readme", + "----rwxr-x 1 PEP 0 8492 Apr 07 30:13 build.xml", "d---rwxr-x 2 PEP 0 45056Mar 24 14:06 zdir2" } }; + + private static final String[][] goodsamples = { + { "PEP 4019 04/03/18 18:58:16 *STMF einladung.zip", "PEP 422 04/03/24 14:06:26 *STMF readme", + "PEP 6409 04/03/24 14:06:29 *STMF build.xml", "PEP 36864 04/03/24 14:06:34 *DIR dir1/", + "PEP 36864 04/03/24 14:06:47 *DIR zdir2/" }, + { "----rwxr-x 1 PEP 0 4019 Mar 18 18:58 einladung.zip", "----rwxr-x 1 PEP 0 422 Mar 24 14:06 readme", + "----rwxr-x 1 PEP 0 8492 Apr 07 07:13 build.xml", "d---rwxr-x 2 PEP 0 45056 Mar 24 14:06 dir1", + "d---rwxr-x 2 PEP 0 45056 Mar 24 14:06 zdir2" } }; /** * @see junit.framework.TestCase#TestCase(String) */ - public OS400FTPEntryParserTest(String name) - { + public OS400FTPEntryParserTest(final String name) { super(name); } + @Override + protected void doAdditionalGoodTests(final String test, final FTPFile f) { + if (test.startsWith("d")) { + assertEquals("directory.type", FTPFile.DIRECTORY_TYPE, f.getType()); + } + } + /** * @see FTPParseTestFramework#getBadListing() */ @Override - protected String[][] getBadListings() - { + protected String[][] getBadListings() { return badsamples; } @@ -85,8 +68,7 @@ public class OS400FTPEntryParserTest extends CompositeFTPParseTestFramework * @see FTPParseTestFramework#getGoodListing() */ @Override - protected String[][] getGoodListings() - { + protected String[][] getGoodListings() { return goodsamples; } @@ -94,34 +76,52 @@ public class OS400FTPEntryParserTest extends CompositeFTPParseTestFramework * @see FTPParseTestFramework#getParser() */ @Override - protected FTPFileEntryParser getParser() - { - return new CompositeFileEntryParser(new FTPFileEntryParser[] - { - new OS400FTPEntryParser(), - new UnixFTPEntryParser() - }); + protected FTPFileEntryParser getParser() { + return new CompositeFileEntryParser(new FTPFileEntryParser[] { new OS400FTPEntryParser(), new UnixFTPEntryParser() }); + } + + @Override + public void testDefaultPrecision() { + testPrecision("PEP 4019 04/03/18 18:58:16 *STMF einladung.zip", CalendarUnit.SECOND); + } + + public void testNET573() { + final FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_AS400); + conf.setDefaultDateFormatStr("MM/dd/yy HH:mm:ss"); + final FTPFileEntryParser parser = new OS400FTPEntryParser(conf); + + final FTPFile f = parser.parseFTPEntry("ZFTPDEV 9069 05/20/15 15:36:52 *STMF /DRV/AUDWRKSHET/AUDWRK0204232015114625.PDF"); + assertNotNull("Could not parse entry.", f); + assertNotNull("Could not parse timestamp.", f.getTimestamp()); + assertFalse("Should not have been a directory.", f.isDirectory()); + assertEquals("ZFTPDEV", f.getUser()); + assertEquals("AUDWRK0204232015114625.PDF", f.getName()); + assertEquals(9069, f.getSize()); + + final Calendar cal = Calendar.getInstance(); + cal.set(Calendar.YEAR, 2015); + cal.set(Calendar.MONTH, Calendar.MAY); + cal.set(Calendar.DAY_OF_MONTH, 20); + cal.set(Calendar.HOUR_OF_DAY, 15); + cal.set(Calendar.MINUTE, 36); + cal.set(Calendar.SECOND, 52); + + assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp().getTime())); } /** * @see FTPParseTestFramework#testParseFieldsOnDirectory() */ @Override - public void testParseFieldsOnDirectory() throws Exception - { - FTPFile f = getParser().parseFTPEntry("PEP 36864 04/03/24 14:06:34 *DIR dir1/"); - assertNotNull("Could not parse entry.", - f); - assertTrue("Should have been a directory.", - f.isDirectory()); - assertEquals("PEP", - f.getUser()); - assertEquals("dir1", - f.getName()); - assertEquals(36864, - f.getSize()); - - Calendar cal = Calendar.getInstance(); + public void testParseFieldsOnDirectory() throws Exception { + final FTPFile f = getParser().parseFTPEntry("PEP 36864 04/03/24 14:06:34 *DIR dir1/"); + assertNotNull("Could not parse entry.", f); + assertTrue("Should have been a directory.", f.isDirectory()); + assertEquals("PEP", f.getUser()); + assertEquals("dir1", f.getName()); + assertEquals(36864, f.getSize()); + + final Calendar cal = Calendar.getInstance(); cal.set(Calendar.MONTH, Calendar.MARCH); cal.set(Calendar.YEAR, 2004); @@ -130,39 +130,22 @@ public class OS400FTPEntryParserTest extends CompositeFTPParseTestFramework cal.set(Calendar.MINUTE, 6); cal.set(Calendar.SECOND, 34); - assertEquals(df.format(cal.getTime()), - df.format(f.getTimestamp().getTime())); - } - - @Override - protected void doAdditionalGoodTests(String test, FTPFile f) - { - if (test.startsWith("d")) - { - assertEquals("directory.type", - FTPFile.DIRECTORY_TYPE, f.getType()); - } + assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp().getTime())); } - + /** * @see FTPParseTestFramework#testParseFieldsOnFile() */ @Override - public void testParseFieldsOnFile() throws Exception - { - FTPFile f = getParser().parseFTPEntry("PEP 5000000000 04/03/24 14:06:29 *STMF build.xml"); - assertNotNull("Could not parse entry.", - f); - assertTrue("Should have been a file.", - f.isFile()); - assertEquals("PEP", - f.getUser()); - assertEquals("build.xml", - f.getName()); - assertEquals(5000000000L, - f.getSize()); - - Calendar cal = Calendar.getInstance(); + public void testParseFieldsOnFile() throws Exception { + final FTPFile f = getParser().parseFTPEntry("PEP 5000000000 04/03/24 14:06:29 *STMF build.xml"); + assertNotNull("Could not parse entry.", f); + assertTrue("Should have been a file.", f.isFile()); + assertEquals("PEP", f.getUser()); + assertEquals("build.xml", f.getName()); + assertEquals(5000000000L, f.getSize()); + + final Calendar cal = Calendar.getInstance(); cal.set(Calendar.DAY_OF_MONTH, 24); cal.set(Calendar.MONTH, Calendar.MARCH); @@ -170,13 +153,17 @@ public class OS400FTPEntryParserTest extends CompositeFTPParseTestFramework cal.set(Calendar.HOUR_OF_DAY, 14); cal.set(Calendar.MINUTE, 6); cal.set(Calendar.SECOND, 29); - assertEquals(df.format(cal.getTime()), - df.format(f.getTimestamp().getTime())); + assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp().getTime())); } - @Override - public void testDefaultPrecision() { - testPrecision("PEP 4019 04/03/18 18:58:16 *STMF einladung.zip", CalendarUnit.SECOND); + /** + * Test file names with spaces. + */ + public void testParseFileNameWithSpaces() { + final FTPFile f = getParser().parseFTPEntry("MYUSER 3 06/12/21 12:00:00 *STMF file with space.txt"); + assertNotNull("Could not parse entry.", f); + assertTrue("Should have been a file.", f.isFile()); + assertEquals("file with space.txt", f.getName()); } @Override @@ -184,29 +171,4 @@ public class OS400FTPEntryParserTest extends CompositeFTPParseTestFramework testPrecision("----rwxr-x 1 PEP 0 4019 Mar 18 18:58 einladung.zip", CalendarUnit.MINUTE); } - public void testNET573() throws Exception - { - final FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_AS400); - conf.setDefaultDateFormatStr("MM/dd/yy HH:mm:ss"); - final FTPFileEntryParser parser = new OS400FTPEntryParser(conf); - - FTPFile f = parser.parseFTPEntry("ZFTPDEV 9069 05/20/15 15:36:52 *STMF /DRV/AUDWRKSHET/AUDWRK0204232015114625.PDF"); - assertNotNull("Could not parse entry.", f); - assertNotNull("Could not parse timestamp.", f.getTimestamp()); - assertFalse("Should not have been a directory.", f.isDirectory()); - assertEquals("ZFTPDEV", f.getUser()); - assertEquals("AUDWRK0204232015114625.PDF", f.getName()); - assertEquals(9069, f.getSize()); - - Calendar cal = Calendar.getInstance(); - cal.set(Calendar.YEAR, 2015); - cal.set(Calendar.MONTH, Calendar.MAY); - cal.set(Calendar.DAY_OF_MONTH, 20); - cal.set(Calendar.HOUR_OF_DAY, 15); - cal.set(Calendar.MINUTE, 36); - cal.set(Calendar.SECOND, 52); - - assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp().getTime())); - } - } diff --git a/src/test/java/org/apache/commons/net/ftp/parser/UnixFTPEntryParserTest.java b/src/test/java/org/apache/commons/net/ftp/parser/UnixFTPEntryParserTest.java index 90e2908..da28846 100644 --- a/src/test/java/org/apache/commons/net/ftp/parser/UnixFTPEntryParserTest.java +++ b/src/test/java/org/apache/commons/net/ftp/parser/UnixFTPEntryParserTest.java @@ -22,32 +22,22 @@ import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPFileEntryParser; /** - * @version $Id: UnixFTPEntryParserTest.java 1752661 2016-07-14 13:46:19Z sebb $ */ public class UnixFTPEntryParserTest extends FTPParseTestFramework { - private static final String[] badsamples = { - "zrwxr-xr-x 2 root root 4096 Mar 2 15:13 zxbox", - "dxrwr-xr-x 2 root root 4096 Aug 24 2001 zxjdbc", - "drwxr-xr-x 2 root root 4096 Jam 4 00:03 zziplib", - "drwxr-xr-x 2 root 99 4096 Feb 23 30:01 zzplayer", - "drwxr-xr-x 2 root root 4096 Aug 36 2001 zztpp", - "-rw-r--r-- 1 14 staff 80284 Aug 22 zxJDBC-1.2.3.tar.gz", - "-rw-r--r-- 1 14 staff 119:26 Aug 22 2000 zxJDBC-1.2.3.zip", - /*"-rw-r--r-- 1 ftp no group 83853 Jan 22 2001 zxJDBC-1.2.4.tar.gz",*/ + private static final String[] badsamples = { "zrwxr-xr-x 2 root root 4096 Mar 2 15:13 zxbox", + "dxrwr-xr-x 2 root root 4096 Aug 24 2001 zxjdbc", "drwxr-xr-x 2 root root 4096 Jam 4 00:03 zziplib", + "drwxr-xr-x 2 root 99 4096 Feb 23 30:01 zzplayer", "drwxr-xr-x 2 root root 4096 Aug 36 2001 zztpp", + "-rw-r--r-- 1 14 staff 80284 Aug 22 zxJDBC-1.2.3.tar.gz", "-rw-r--r-- 1 14 staff 119:26 Aug 22 2000 zxJDBC-1.2.3.zip", + /* "-rw-r--r-- 1 ftp no group 83853 Jan 22 2001 zxJDBC-1.2.4.tar.gz", */ "-rw-r--r-- 1ftp nogroup 126552 Jan 22 2001 zxJDBC-1.2.4.zip", "-rw-r--r-- 1 root root 190144 2001-04-27 zxJDBC-2.0.1b1.zip", "-rw-r--r-- 1 root root 111325 Apr -7 18:79 zxJDBC-2.0.1b1.tar.gz" }; - private static final String[] goodsamples = - { - "-rw-r--r-- 1 500 500 21 Aug 8 14:14 JB3-TES1.gz", - "-rwxr-xr-x 2 root root 4096 Mar 2 15:13 zxbox", - "drwxr-xr-x 2 root root 4096 Aug 24 2001 zxjdbc", - "drwxr-xr-x 2 root root 4096 Jan 4 00:03 zziplib", - "drwxr-xr-x 2 root 99 4096 Feb 23 2001 zzplayer", - "drwxr-xr-x 2 root root 4096 Aug 6 2001 zztpp", - "drwxr-xr-x 1 usernameftp 512 Jan 29 23:32 prog", + private static final String[] goodsamples = { "-rw-r--r-- 1 500 500 21 Aug 8 14:14 JB3-TES1.gz", + "-rwxr-xr-x 2 root root 4096 Mar 2 15:13 zxbox", "drwxr-xr-x 2 root root 4096 Aug 24 2001 zxjdbc", + "drwxr-xr-x 2 root root 4096 Jan 4 00:03 zziplib", "drwxr-xr-x 2 root 99 4096 Feb 23 2001 zzplayer", + "drwxr-xr-x 2 root root 4096 Aug 6 2001 zztpp", "drwxr-xr-x 1 usernameftp 512 Jan 29 23:32 prog", "lrw-r--r-- 1 14 14 80284 Aug 22 2000 zxJDBC-1.2.3.tar.gz", "frw-r--r-- 1 14 staff 119926 Aug 22 2000 zxJDBC-1.2.3.zip", "crw-r--r-- 1 ftp nogroup 83853 Jan 22 2001 zxJDBC-1.2.4.tar.gz", @@ -60,144 +50,173 @@ public class UnixFTPEntryParserTest extends FTPParseTestFramework { "-rw-r--r-- 1 500 500 166 Nov 12 2001 73131-testtes2.AFP", "-rw-r--r-- 1 500 500 2040000 Aug 5 07:35 testRemoteUPCopyNIX", "-rw-r--r-- 1 500 500 2040000 Aug 5 07:31 testRemoteUPDCopyNIX", - "-rw-r--r-- 1 500 500 2040000 Aug 5 07:31 testRemoteUPVCopyNIX", - "-rw-r--r-T 1 500 500 0 Mar 25 08:20 testSticky", - "-rwxr-xr-t 1 500 500 0 Mar 25 08:21 testStickyExec", - "-rwSr-Sr-- 1 500 500 0 Mar 25 08:22 testSuid", - "-rwsr-sr-- 1 500 500 0 Mar 25 08:23 testSuidExec", - "-rwsr-sr-- 1 500 500 0 Mar 25 0:23 testSuidExec2", - "drwxrwx---+ 23 500 500 0 Jan 10 13:09 testACL", - "-rw-r--r-- 1 1 3518644 May 25 12:12 std", + "-rw-r--r-- 1 500 500 2040000 Aug 5 07:31 testRemoteUPVCopyNIX", "-rw-r--r-T 1 500 500 0 Mar 25 08:20 testSticky", + "-rwxr-xr-t 1 500 500 0 Mar 25 08:21 testStickyExec", "-rwSr-Sr-- 1 500 500 0 Mar 25 08:22 testSuid", + "-rwsr-sr-- 1 500 500 0 Mar 25 08:23 testSuidExec", "-rwsr-sr-- 1 500 500 0 Mar 25 0:23 testSuidExec2", + "drwxrwx---+ 23 500 500 0 Jan 10 13:09 testACL", "-rw-r--r-- 1 1 3518644 May 25 12:12 std", "lrwxrwxrwx 1 neeme neeme 23 Mar 2 18:06 macros -> ./../../global/macros/.", - "-rw-r--r-- 1 ftp group with spaces in it as allowed in cygwin see bug 38634" + - " 83853 Jan 22 2001 zxJDBC-1.2.4.tar.gz", - // Bug 38634 => NET-16 - "crw-r----- 1 root kmem 0, 27 Jan 30 11:42 kmem", //FreeBSD device - "crw------- 1 root sys 109,767 Jul 2 2004 pci@1c,600000:devctl", //Solaris device + "-rw-r--r-- 1 ftp group with spaces in it as allowed in cygwin see bug 38634" + " 83853 Jan 22 2001 zxJDBC-1.2.4.tar.gz", + // Bug 38634 => NET-16 + "crw-r----- 1 root kmem 0, 27 Jan 30 11:42 kmem", // FreeBSD device + "crw------- 1 root sys 109,767 Jul 2 2004 pci@1c,600000:devctl", // Solaris device "-rwxrwx--- 1 ftp ftp-admin 816026400 Oct 5 2008 bloplab 7 cd1.img", // NET-294 - // http://mail-archives.apache.org/mod_mbox/commons-dev/200408.mbox/%3c4122F3C1.9090402@tanukisoftware.com%3e - "-rw-r--r-- 1 1 3518644 May 25 12:12 std", - "-rw-rw---- 1 ep1adm sapsys 0 6\u6708 3\u65e5 2003\u5e74 \u8a66\u9a13\u30d5\u30a1\u30a4\u30eb.csv", + // https://mail-archives.apache.org/mod_mbox/commons-dev/200408.mbox/%3c4122F3C1.9090402@tanukisoftware.com%3e + "-rw-r--r-- 1 1 3518644 May 25 12:12 std", "-rw-rw---- 1 ep1adm sapsys 0 6\u6708 3\u65e5 2003\u5e74 \u8a66\u9a13\u30d5\u30a1\u30a4\u30eb.csv", "-rw-rw---- 1 ep1adm sapsys 0 8\u6708 17\u65e5 20:10 caerrinf", }; - public UnixFTPEntryParserTest(String name) { + public UnixFTPEntryParserTest(final String name) { super(name); } - @Override - protected String[] getBadListing() { - return (badsamples); + private void checkPermissions(final FTPFile f) { + assertTrue("Should have user read permission.", f.hasPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION)); + assertTrue("Should have user write permission.", f.hasPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION)); + assertTrue("Should have user execute permission.", f.hasPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION)); + assertTrue("Should have group read permission.", f.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.READ_PERMISSION)); + assertFalse("Should NOT have group write permission.", f.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.WRITE_PERMISSION)); + assertTrue("Should have group execute permission.", f.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.EXECUTE_PERMISSION)); + assertTrue("Should have world read permission.", f.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.READ_PERMISSION)); + assertFalse("Should NOT have world write permission.", f.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.WRITE_PERMISSION)); + assertTrue("Should have world execute permission.", f.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.EXECUTE_PERMISSION)); } @Override - protected String[] getGoodListing() { - return (goodsamples); - } - - public void testNumericDateFormat() - { - String testNumericDF = - "-rw-r----- 1 neeme neeme 346 2005-04-08 11:22 services.vsp"; - String testNumericDF2 = - "lrwxrwxrwx 1 neeme neeme 23 2005-03-02 18:06 macros -> ./../../global/macros/."; - - UnixFTPEntryParser parser = - new UnixFTPEntryParser(UnixFTPEntryParser.NUMERIC_DATE_CONFIG); - - FTPFile f = parser.parseFTPEntry(testNumericDF); - assertNotNull("Failed to parse " + testNumericDF, f); - + protected void doAdditionalGoodTests(final String test, final FTPFile f) { + final String link = f.getLink(); + if (null != link) { + final int linklen = link.length(); + if (linklen > 0) { + assertEquals(link, test.substring(test.length() - linklen)); + assertEquals(f.getType(), FTPFile.SYMBOLIC_LINK_TYPE); + } + } + final int type = f.getType(); + switch (test.charAt(0)) { + case 'd': + assertEquals("Type of " + test, type, FTPFile.DIRECTORY_TYPE); + break; + case 'l': + assertEquals("Type of " + test, type, FTPFile.SYMBOLIC_LINK_TYPE); + break; + case 'b': + case 'c': + assertEquals(0, f.getHardLinkCount()); + //$FALL-THROUGH$ TODO this needs to be fixed if a device type is introduced + case 'f': + case '-': + assertEquals("Type of " + test, type, FTPFile.FILE_TYPE); + break; + default: + assertEquals("Type of " + test, type, FTPFile.UNKNOWN_TYPE); + } - Calendar cal = Calendar.getInstance(); - cal.clear(); - cal.set(Calendar.YEAR, 2005); - cal.set(Calendar.MONTH, Calendar.APRIL); + for (int access = FTPFile.USER_ACCESS; access <= FTPFile.WORLD_ACCESS; access++) { + for (int perm = FTPFile.READ_PERMISSION; perm <= FTPFile.EXECUTE_PERMISSION; perm++) { + final int pos = 3 * access + perm + 1; + final char permchar = test.charAt(pos); + assertEquals("Permission " + test.substring(1, 10), Boolean.valueOf(f.hasPermission(access, perm)), + Boolean.valueOf(permchar != '-' && !Character.isUpperCase(permchar))); + } + } - cal.set(Calendar.DAY_OF_MONTH, 8); - cal.set(Calendar.HOUR_OF_DAY, 11); - cal.set(Calendar.MINUTE, 22); - assertEquals(cal.getTime(), f.getTimestamp().getTime()); + assertNotNull("Expected to find a timestamp", f.getTimestamp()); +// Perhaps check date range (need to ensure all good examples qualify) +// assertTrue(test,f.getTimestamp().get(Calendar.YEAR)>=2000); + } - FTPFile f2 = parser.parseFTPEntry(testNumericDF2); - assertNotNull("Failed to parse " + testNumericDF2, f2); - assertEquals("symbolic link", "./../../global/macros/.", f2.getLink()); + @Override + protected String[] getBadListing() { + return badsamples; + } + @Override + protected String[] getGoodListing() { + return goodsamples; } @Override protected FTPFileEntryParser getParser() { - return (new UnixFTPEntryParser()); + return new UnixFTPEntryParser(); } - public void testOwnerNameWithSpaces() { - FTPFile f = getParser().parseFTPEntry("drwxr-xr-x 2 john smith group 4096 Mar 2 15:13 zxbox"); + public void testCorrectGroupNameParsing() { + final FTPFile f = getParser().parseFTPEntry("-rw-r--r-- 1 ftpuser ftpusers 12414535 Mar 17 11:07 test 1999 abc.pdf"); assertNotNull(f); - assertEquals("john smith", f.getUser()); + assertEquals(1, f.getHardLinkCount()); + assertEquals("ftpuser", f.getUser()); + assertEquals("ftpusers", f.getGroup()); + assertEquals(12414535, f.getSize()); + assertEquals("test 1999 abc.pdf", f.getName()); + + final Calendar cal = Calendar.getInstance(); + cal.set(Calendar.MONTH, Calendar.MARCH); + cal.set(Calendar.DAY_OF_MONTH, 17); + cal.set(Calendar.HOUR_OF_DAY, 11); + cal.set(Calendar.MINUTE, 7); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + + assertEquals(f.getTimestamp().get(Calendar.MONTH), cal.get(Calendar.MONTH)); + assertEquals(f.getTimestamp().get(Calendar.DAY_OF_MONTH), cal.get(Calendar.DAY_OF_MONTH)); + assertEquals(f.getTimestamp().get(Calendar.HOUR_OF_DAY), cal.get(Calendar.HOUR_OF_DAY)); + assertEquals(f.getTimestamp().get(Calendar.MINUTE), cal.get(Calendar.MINUTE)); + assertEquals(f.getTimestamp().get(Calendar.SECOND), cal.get(Calendar.SECOND)); } - public void testOwnerAndGroupNameWithSpaces() { - FTPFile f = getParser().parseFTPEntry("drwxr-xr-x 2 john smith test group 4096 Mar 2 15:13 zxbox"); - assertNotNull(f); - assertEquals("john smith", f.getUser()); - assertEquals("test group", f.getGroup()); + @Override + public void testDefaultPrecision() { + testPrecision("drwxr-xr-x 2 user group 4096 Mar 2 2014 zxbox", CalendarUnit.DAY_OF_MONTH); } - public void testNET294() { - FTPFile f = getParser().parseFTPEntry( - "-rwxrwx--- 1 ftp ftp-admin 816026400 Oct 5 2008 bloplab 7 cd1.img"); - assertNotNull(f); - assertEquals("ftp", f.getUser()); - assertEquals("ftp-admin", f.getGroup()); - assertEquals(816026400L,f.getSize()); - assertNotNull("Timestamp should not be null",f.getTimestamp()); - assertEquals(2008,f.getTimestamp().get(Calendar.YEAR)); - assertEquals("bloplab 7 cd1.img",f.getName()); + public void testFilenamesWithEmbeddedNumbers() { + final FTPFile f = getParser().parseFTPEntry("-rw-rw-rw- 1 user group 5840 Mar 19 09:34 123 456 abc.csv"); + assertEquals("123 456 abc.csv", f.getName()); + assertEquals(5840, f.getSize()); + assertEquals("user", f.getUser()); + assertEquals("group", f.getGroup()); } public void testGroupNameWithSpaces() { - FTPFile f = getParser().parseFTPEntry("drwx------ 4 maxm Domain Users 512 Oct 2 10:59 .metadata"); + final FTPFile f = getParser().parseFTPEntry("drwx------ 4 maxm Domain Users 512 Oct 2 10:59 .metadata"); assertNotNull(f); assertEquals("maxm", f.getUser()); assertEquals("Domain Users", f.getGroup()); } - public void testTrailingSpaces() { - FTPFile f = getParser().parseFTPEntry("drwxr-xr-x 2 john smith group 4096 Mar 2 15:13 zxbox "); - assertNotNull(f); - assertEquals("zxbox ", f.getName()); - } - public void testLeadingSpacesDefault() { // the default has been changed to keep spaces - FTPFile f = getParser().parseFTPEntry("drwxr-xr-x 2 john smith group 4096 Mar 2 15:13 zxbox"); + final FTPFile f = getParser().parseFTPEntry("drwxr-xr-x 2 john smith group 4096 Mar 2 15:13 zxbox"); assertNotNull(f); - assertEquals(" zxbox", f.getName() ); // leading spaces retained + assertEquals(" zxbox", f.getName()); // leading spaces retained } - public void testLeadingSpacesNET566() { // check new behaviour - FTPFile f = new UnixFTPEntryParser(null, false).parseFTPEntry( - "drwxr-xr-x 2 john smith group 4096 Mar 2 15:13 zxbox"); + public void testLeadingSpacesNET566() { // check new behavior + final FTPFile f = new UnixFTPEntryParser(null, false).parseFTPEntry("drwxr-xr-x 2 john smith group 4096 Mar 2 15:13 zxbox"); assertNotNull(f); - assertEquals(" zxbox", f.getName() ); // leading spaces retained + assertEquals(" zxbox", f.getName()); // leading spaces retained } - public void testTrimLeadingSpacesNET566() { // check can trim spaces as before - FTPFile f = new UnixFTPEntryParser(null, true).parseFTPEntry( - "drwxr-xr-x 2 john smith group 4096 Mar 2 15:13 zxbox"); + public void testNameWIthPunctuation() { + final FTPFile f = getParser().parseFTPEntry("drwx------ 4 maxm Domain Users 512 Oct 2 10:59 abc(test)123.pdf"); assertNotNull(f); - assertEquals("zxbox", f.getName() ); // leading spaces trimmed + assertEquals("abc(test)123.pdf", f.getName()); } - public void testNameWIthPunctuation() { - FTPFile f = getParser().parseFTPEntry("drwx------ 4 maxm Domain Users 512 Oct 2 10:59 abc(test)123.pdf"); + public void testNET294() { + final FTPFile f = getParser().parseFTPEntry("-rwxrwx--- 1 ftp ftp-admin 816026400 Oct 5 2008 bloplab 7 cd1.img"); assertNotNull(f); - assertEquals("abc(test)123.pdf", f.getName()); + assertEquals("ftp", f.getUser()); + assertEquals("ftp-admin", f.getGroup()); + assertEquals(816026400L, f.getSize()); + assertNotNull("Timestamp should not be null", f.getTimestamp()); + assertEquals(2008, f.getTimestamp().get(Calendar.YEAR)); + assertEquals("bloplab 7 cd1.img", f.getName()); } public void testNoSpacesBeforeFileSize() { - FTPFile f = getParser().parseFTPEntry("drwxr-x---+1464 chrism chrism 41472 Feb 25 13:17 20090225"); + final FTPFile f = getParser().parseFTPEntry("drwxr-x---+1464 chrism chrism 41472 Feb 25 13:17 20090225"); assertNotNull(f); assertEquals(41472, f.getSize()); assertEquals(f.getType(), FTPFile.DIRECTORY_TYPE); @@ -206,41 +225,47 @@ public class UnixFTPEntryParserTest extends FTPParseTestFramework { assertEquals(1464, f.getHardLinkCount()); } - public void testCorrectGroupNameParsing() { - FTPFile f = getParser().parseFTPEntry("-rw-r--r-- 1 ftpuser ftpusers 12414535 Mar 17 11:07 test 1999 abc.pdf"); - assertNotNull(f); - assertEquals(1, f.getHardLinkCount()); - assertEquals("ftpuser", f.getUser()); - assertEquals("ftpusers", f.getGroup()); - assertEquals(12414535, f.getSize()); - assertEquals("test 1999 abc.pdf", f.getName()); + public void testNumericDateFormat() { + final String testNumericDF = "-rw-r----- 1 neeme neeme 346 2005-04-08 11:22 services.vsp"; + final String testNumericDF2 = "lrwxrwxrwx 1 neeme neeme 23 2005-03-02 18:06 macros -> ./../../global/macros/."; - Calendar cal = Calendar.getInstance(); - cal.set(Calendar.MONTH, Calendar.MARCH); - cal.set(Calendar.DAY_OF_MONTH, 17); + final UnixFTPEntryParser parser = new UnixFTPEntryParser(UnixFTPEntryParser.NUMERIC_DATE_CONFIG); + + final FTPFile f = parser.parseFTPEntry(testNumericDF); + assertNotNull("Failed to parse " + testNumericDF, f); + + final Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.set(Calendar.YEAR, 2005); + cal.set(Calendar.MONTH, Calendar.APRIL); + + cal.set(Calendar.DAY_OF_MONTH, 8); cal.set(Calendar.HOUR_OF_DAY, 11); - cal.set(Calendar.MINUTE, 7); - cal.set(Calendar.SECOND, 0); - cal.set(Calendar.MILLISECOND, 0); + cal.set(Calendar.MINUTE, 22); + assertEquals(cal.getTime(), f.getTimestamp().getTime()); + + final FTPFile f2 = parser.parseFTPEntry(testNumericDF2); + assertNotNull("Failed to parse " + testNumericDF2, f2); + assertEquals("symbolic link", "./../../global/macros/.", f2.getLink()); - assertEquals(f.getTimestamp().get(Calendar.MONTH), cal.get(Calendar.MONTH)); - assertEquals(f.getTimestamp().get(Calendar.DAY_OF_MONTH), cal.get(Calendar.DAY_OF_MONTH)); - assertEquals(f.getTimestamp().get(Calendar.HOUR_OF_DAY), cal.get(Calendar.HOUR_OF_DAY)); - assertEquals(f.getTimestamp().get(Calendar.MINUTE), cal.get(Calendar.MINUTE)); - assertEquals(f.getTimestamp().get(Calendar.SECOND), cal.get(Calendar.SECOND)); } - public void testFilenamesWithEmbeddedNumbers() { - FTPFile f = getParser().parseFTPEntry("-rw-rw-rw- 1 user group 5840 Mar 19 09:34 123 456 abc.csv"); - assertEquals("123 456 abc.csv", f.getName()); - assertEquals(5840, f.getSize()); - assertEquals("user", f.getUser()); - assertEquals("group", f.getGroup()); + public void testOwnerAndGroupNameWithSpaces() { + final FTPFile f = getParser().parseFTPEntry("drwxr-xr-x 2 john smith test group 4096 Mar 2 15:13 zxbox"); + assertNotNull(f); + assertEquals("john smith", f.getUser()); + assertEquals("test group", f.getGroup()); + } + + public void testOwnerNameWithSpaces() { + final FTPFile f = getParser().parseFTPEntry("drwxr-xr-x 2 john smith group 4096 Mar 2 15:13 zxbox"); + assertNotNull(f); + assertEquals("john smith", f.getUser()); } @Override public void testParseFieldsOnDirectory() throws Exception { - FTPFile f = getParser().parseFTPEntry("drwxr-xr-x 2 user group 4096 Mar 2 15:13 zxbox"); + final FTPFile f = getParser().parseFTPEntry("drwxr-xr-x 2 user group 4096 Mar 2 15:13 zxbox"); assertNotNull("Could not parse entry.", f); assertTrue("Should have been a directory.", f.isDirectory()); checkPermissions(f); @@ -250,7 +275,7 @@ public class UnixFTPEntryParserTest extends FTPParseTestFramework { assertEquals("zxbox", f.getName()); assertEquals(4096, f.getSize()); - Calendar cal = Calendar.getInstance(); + final Calendar cal = Calendar.getInstance(); cal.set(Calendar.MONTH, Calendar.MARCH); cal.set(Calendar.DAY_OF_MONTH, 1); @@ -267,43 +292,9 @@ public class UnixFTPEntryParserTest extends FTPParseTestFramework { assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp().getTime())); } - - @Override - public void testRecentPrecision() { - testPrecision("drwxr-xr-x 2 user group 4096 Mar 2 15:13 zxbox", CalendarUnit.MINUTE); - } - - @Override - public void testDefaultPrecision() { - testPrecision("drwxr-xr-x 2 user group 4096 Mar 2 2014 zxbox", CalendarUnit.DAY_OF_MONTH); - } - - private void checkPermissions(FTPFile f) { - assertTrue("Should have user read permission.", f.hasPermission( - FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION)); - assertTrue("Should have user write permission.", f.hasPermission( - FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION)); - assertTrue("Should have user execute permission.", f.hasPermission( - FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION)); - assertTrue("Should have group read permission.", f.hasPermission( - FTPFile.GROUP_ACCESS, FTPFile.READ_PERMISSION)); - assertTrue("Should NOT have group write permission.", !f.hasPermission( - FTPFile.GROUP_ACCESS, FTPFile.WRITE_PERMISSION)); - assertTrue("Should have group execute permission.", f.hasPermission( - FTPFile.GROUP_ACCESS, FTPFile.EXECUTE_PERMISSION)); - assertTrue("Should have world read permission.", f.hasPermission( - FTPFile.WORLD_ACCESS, FTPFile.READ_PERMISSION)); - assertTrue("Should NOT have world write permission.", !f.hasPermission( - FTPFile.WORLD_ACCESS, FTPFile.WRITE_PERMISSION)); - assertTrue("Should have world execute permission.", f.hasPermission( - FTPFile.WORLD_ACCESS, FTPFile.EXECUTE_PERMISSION)); - } - @Override public void testParseFieldsOnFile() throws Exception { - FTPFile f = getParser() - .parseFTPEntry( - "-rwxr-xr-x 2 user my group 500 5000000000 Mar 2 15:13 zxbox"); + final FTPFile f = getParser().parseFTPEntry("-rwxr-xr-x 2 user my group 500 5000000000 Mar 2 15:13 zxbox"); assertNotNull("Could not parse entry.", f); assertTrue("Should have been a file.", f.isFile()); checkPermissions(f); @@ -313,7 +304,7 @@ public class UnixFTPEntryParserTest extends FTPParseTestFramework { assertEquals("zxbox", f.getName()); assertEquals(5000000000L, f.getSize()); - Calendar cal = Calendar.getInstance(); + final Calendar cal = Calendar.getInstance(); cal.set(Calendar.MONTH, Calendar.MARCH); cal.set(Calendar.DAY_OF_MONTH, 1); @@ -329,10 +320,9 @@ public class UnixFTPEntryParserTest extends FTPParseTestFramework { assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp().getTime())); } - // http://mail-archives.apache.org/mod_mbox/commons-dev/200408.mbox/%3c4122F3C1.9090402@tanukisoftware.com%3e - public void testParseFieldsOnFileJapaneseTime() throws Exception - { - FTPFile f = getParser().parseFTPEntry("-rwxr-xr-x 2 user group 4096 3\u6708 2\u65e5 15:13 zxbox"); + // https://mail-archives.apache.org/mod_mbox/commons-dev/200408.mbox/%3c4122F3C1.9090402@tanukisoftware.com%3e + public void testParseFieldsOnFileJapaneseTime() { + final FTPFile f = getParser().parseFTPEntry("-rwxr-xr-x 2 user group 4096 3\u6708 2\u65e5 15:13 zxbox"); assertNotNull("Could not parse entry.", f); assertTrue("Should have been a file.", f.isFile()); checkPermissions(f); @@ -343,25 +333,24 @@ public class UnixFTPEntryParserTest extends FTPParseTestFramework { assertEquals(4096, f.getSize()); assertNotNull("Timestamp not null", f.getTimestamp()); - Calendar cal = Calendar.getInstance(); + final Calendar cal = Calendar.getInstance(); cal.set(Calendar.MONTH, Calendar.MARCH); - cal.set(Calendar.DATE,1); + cal.set(Calendar.DATE, 1); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); if (f.getTimestamp().getTime().before(cal.getTime())) { cal.add(Calendar.YEAR, -1); } - cal.set(Calendar.DATE,2); + cal.set(Calendar.DATE, 2); cal.set(Calendar.HOUR_OF_DAY, 15); cal.set(Calendar.MINUTE, 13); assertEquals(df.format(cal.getTime()), df.format(f.getTimestamp().getTime())); } - // http://mail-archives.apache.org/mod_mbox/commons-dev/200408.mbox/%3c4122F3C1.9090402@tanukisoftware.com%3e - public void testParseFieldsOnFileJapaneseYear() throws Exception { - FTPFile f = getParser().parseFTPEntry( - "-rwxr-xr-x 2 user group 4096 3\u6708 2\u65e5 2003\u5e74 \u8a66\u9a13\u30d5\u30a1\u30a4\u30eb.csv"); + // https://mail-archives.apache.org/mod_mbox/commons-dev/200408.mbox/%3c4122F3C1.9090402@tanukisoftware.com%3e + public void testParseFieldsOnFileJapaneseYear() { + final FTPFile f = getParser().parseFTPEntry("-rwxr-xr-x 2 user group 4096 3\u6708 2\u65e5 2003\u5e74 \u8a66\u9a13\u30d5\u30a1\u30a4\u30eb.csv"); assertNotNull("Could not parse entry.", f); assertTrue("Should have been a file.", f.isFile()); checkPermissions(f); @@ -372,7 +361,7 @@ public class UnixFTPEntryParserTest extends FTPParseTestFramework { assertEquals(4096, f.getSize()); assertNotNull("Timestamp not null", f.getTimestamp()); - Calendar cal = Calendar.getInstance(); + final Calendar cal = Calendar.getInstance(); cal.set(Calendar.YEAR, 2003); cal.set(Calendar.MONTH, Calendar.MARCH); cal.set(Calendar.DATE, 2); @@ -383,52 +372,19 @@ public class UnixFTPEntryParserTest extends FTPParseTestFramework { } @Override - protected void doAdditionalGoodTests(String test, FTPFile f) { - String link = f.getLink(); - if (null != link) { - int linklen = link.length(); - if (linklen > 0) { - assertEquals(link, test.substring(test.length() - linklen)); - assertEquals(f.getType(), FTPFile.SYMBOLIC_LINK_TYPE); - } - } - int type = f.getType(); - switch (test.charAt(0)) - { - case 'd': - assertEquals("Type of "+ test, type, FTPFile.DIRECTORY_TYPE); - break; - case 'l': - assertEquals("Type of "+ test, type, FTPFile.SYMBOLIC_LINK_TYPE); - break; - case 'b': - case 'c': - assertEquals(0, f.getHardLinkCount()); - //$FALL-THROUGH$ TODO this needs to be fixed if a device type is introduced - case 'f': - case '-': - assertEquals("Type of "+ test, type, FTPFile.FILE_TYPE); - break; - default: - assertEquals("Type of "+ test, type, FTPFile.UNKNOWN_TYPE); - } + public void testRecentPrecision() { + testPrecision("drwxr-xr-x 2 user group 4096 Mar 2 15:13 zxbox", CalendarUnit.MINUTE); + } - for (int access = FTPFile.USER_ACCESS; - access <= FTPFile.WORLD_ACCESS; access++) - { - for (int perm = FTPFile.READ_PERMISSION; - perm <= FTPFile.EXECUTE_PERMISSION; perm++) - { - int pos = 3*access + perm + 1; - char permchar = test.charAt(pos); - assertEquals("Permission " + test.substring(1,10), - Boolean.valueOf(f.hasPermission(access, perm)), - Boolean.valueOf(permchar != '-' && !Character.isUpperCase(permchar))); - } - } + public void testTrailingSpaces() { + final FTPFile f = getParser().parseFTPEntry("drwxr-xr-x 2 john smith group 4096 Mar 2 15:13 zxbox "); + assertNotNull(f); + assertEquals("zxbox ", f.getName()); + } - assertNotNull("Expected to find a timestamp",f.getTimestamp()); -// Perhaps check date range (need to ensure all good examples qualify) -// assertTrue(test,f.getTimestamp().get(Calendar.YEAR)>=2000); + public void testTrimLeadingSpacesNET566() { // check can trim spaces as before + final FTPFile f = new UnixFTPEntryParser(null, true).parseFTPEntry("drwxr-xr-x 2 john smith group 4096 Mar 2 15:13 zxbox"); + assertNotNull(f); + assertEquals("zxbox", f.getName()); // leading spaces trimmed } } diff --git a/src/test/java/org/apache/commons/net/ftp/parser/VMSFTPEntryParserTest.java b/src/test/java/org/apache/commons/net/ftp/parser/VMSFTPEntryParserTest.java index 0ba6901..e3afde2 100644 --- a/src/test/java/org/apache/commons/net/ftp/parser/VMSFTPEntryParserTest.java +++ b/src/test/java/org/apache/commons/net/ftp/parser/VMSFTPEntryParserTest.java @@ -24,275 +24,203 @@ import org.apache.commons.net.ftp.FTPFileEntryParser; import org.apache.commons.net.ftp.FTPListParseEngine; /** - * @version $Id: VMSFTPEntryParserTest.java 1738428 2016-04-10 12:49:32Z sebb $ */ -public class VMSFTPEntryParserTest extends FTPParseTestFramework -{ - private static final String[] badsamples = - { - - "1-JUN.LIS;2 9/9 JUN-2-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,)", - "1-JUN.LIS;2 a/9 2-JUN-98 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,)", - "DATA.DIR; 1 1/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (,RWED,RWED,RE)", - "120196.TXT;1 118/126 14-APR-1997 12:45:27 PM [GROUP,OWNER] (RWED,,RWED,RE)", - "30CHARBAR.TXT;1 11/18 2-JUN-1998 08:38:42 [GROUP-1,OWNER] (RWED,RWED,RWED,RE)", - "A.;2 18/18 1-JUL-1998 08:43:20 [GROUP,OWNER] (RWED2,RWED,RWED,RE)", - "AA.;2 152/153 13-FED-1997 08:13:43 [GROUP,OWNER] (RWED,RWED,RWED,RE)", - "Directory USER1:[TEMP]\r\n\r\n", - "\r\nTotal 14 files" - }; +public class VMSFTPEntryParserTest extends FTPParseTestFramework { + private static final String[] BAD_SAMPLES = { + + "1-JUN.LIS;2 9/9 JUN-2-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,)", + "1-JUN.LIS;2 a/9 2-JUN-98 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,)", + "DATA.DIR; 1 1/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (,RWED,RWED,RE)", + "120196.TXT;1 118/126 14-APR-1997 12:45:27 PM [GROUP,OWNER] (RWED,,RWED,RE)", + "30CHARBAR.TXT;1 11/18 2-JUN-1998 08:38:42 [GROUP-1,OWNER] (RWED,RWED,RWED,RE)", + "A.;2 18/18 1-JUL-1998 08:43:20 [GROUP,OWNER] (RWED2,RWED,RWED,RE)", + "AA.;2 152/153 13-FED-1997 08:13:43 [GROUP,OWNER] (RWED,RWED,RWED,RE)", "Directory USER1:[TEMP]\r\n\r\n", + "\r\nTotal 14 files" }; // CHECKSTYLE:OFF (long lines) - private static final String[] goodsamples = - { - "1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", - "1-JUN.LIS;3 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,)", - "1-JUN.LIS;2 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,)", - "DATA.DIR;1 1/9 2-JUN-1998 07:32:04 [TRANSLATED] (,RWED,RWED,RE)", - "120196.TXT;1 118/126 14-APR-1997 12:45:27 [GROUP,OWNER] (RWED,,RWED,RE)", - "30CHARBAR.TXT;1 11/18 2-JUN-1998 08:38:42 [GROUP,OWNER] (RWED,RWED,RWED,RE)", - "A.;2 18/18 1-JUL-1998 08:43:20 [GROUP,OWNER] (RWED,RWED,RWED,RE)", - "AA.;2 152/153 13-FEB-1997 08:13:43 [GROUP,OWNER] (RWED,RWED,RWED,RE)", - "UCX$REXECD_STARTUP.LOG;1098\r\n"+ - " 4/15 24-FEB-2003 13:17:24 [POSTWARE,LP] (RWED,RWED,RE,)", - "UNARCHIVE.COM;1 2/15 7-JUL-1997 16:37:45 [POSTWARE,LP] (RWE,RWE,RWE,RE)", - "UNXMERGE.COM;15 1/15 20-AUG-1996 13:59:50 [POSTWARE,LP] (RWE,RWE,RWE,RE)", - "UNXTEMP.COM;7 1/15 15-AUG-1996 14:10:38 [POSTWARE,LP] (RWE,RWE,RWE,RE)", - "UNZIP_AND_ATTACH_FILES.COM;12\r\n"+ - " 14/15 24-JUL-2002 14:35:40 [TRANSLATED] (RWE,RWE,RWE,RE)", - "UNZIP_AND_ATTACH_FILES.SAV;1\r\n"+ - " 14/15 17-JAN-2002 11:13:53 [POSTWARE,LP] (RWE,RWED,RWE,RE)", - "FREEWARE40.DIR;1 27/36"+ - " 16-FEB-1999 10:01:46 [AP_HTTPD,APACHE$WWW (RWE,RWE,RE,RE)", - "1-JUN.LIS;1 9/9 2-jun-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", - }; + private static final String[] GOOD_SAMPLES = { "1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", + "1-JUN.LIS;3 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,)", + "1-JUN.LIS;2 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,)", + "DATA.DIR;1 1/9 2-JUN-1998 07:32:04 [TRANSLATED] (,RWED,RWED,RE)", + "120196.TXT;1 118/126 14-APR-1997 12:45:27 [GROUP,OWNER] (RWED,,RWED,RE)", + "30CHARBAR.TXT;1 11/18 2-JUN-1998 08:38:42 [GROUP,OWNER] (RWED,RWED,RWED,RE)", + "A.;2 18/18 1-JUL-1998 08:43:20 [GROUP,OWNER] (RWED,RWED,RWED,RE)", + "AA.;2 152/153 13-FEB-1997 08:13:43 [GROUP,OWNER] (RWED,RWED,RWED,RE)", + "UCX$REXECD_STARTUP.LOG;1098\r\n" + " 4/15 24-FEB-2003 13:17:24 [POSTWARE,LP] (RWED,RWED,RE,)", + "UNARCHIVE.COM;1 2/15 7-JUL-1997 16:37:45 [POSTWARE,LP] (RWE,RWE,RWE,RE)", + "UNXMERGE.COM;15 1/15 20-AUG-1996 13:59:50 [POSTWARE,LP] (RWE,RWE,RWE,RE)", + "UNXTEMP.COM;7 1/15 15-AUG-1996 14:10:38 [POSTWARE,LP] (RWE,RWE,RWE,RE)", + "UNZIP_AND_ATTACH_FILES.COM;12\r\n" + " 14/15 24-JUL-2002 14:35:40 [TRANSLATED] (RWE,RWE,RWE,RE)", + "UNZIP_AND_ATTACH_FILES.SAV;1\r\n" + " 14/15 17-JAN-2002 11:13:53 [POSTWARE,LP] (RWE,RWED,RWE,RE)", + "FREEWARE40.DIR;1 27/36" + " 16-FEB-1999 10:01:46 [AP_HTTPD,APACHE$WWW (RWE,RWE,RE,RE)", + "1-JUN.LIS;1 9/9 2-jun-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)", + "ALLOCMISS.COM;1 1 15-AUG-1996 14:10:38 [POSTWARE,LP] (RWE,RWE,RWE,RE)" }; // CHECKSTYLE:ON - private static final String fullListing = "Directory USER1:[TEMP]\r\n\r\n"+ - "1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)\r\n"+ - "2-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,)\r\n"+ - "3-JUN.LIS;1 9/9 3-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,)\r\n"+ - "3-JUN.LIS;4 9/9 7-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,)\r\n"+ - "3-JUN.LIS;2 9/9 4-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,)\r\n"+ - "3-JUN.LIS;3 9/9 6-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,)\r\n"+ - "\r\nTotal 6 files"; + private static final String FULL_LISTING = "Directory USER1:[TEMP]\r\n\r\n" + + "1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)\r\n" + + "2-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,)\r\n" + + "3-JUN.LIS;1 9/9 3-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,)\r\n" + + "3-JUN.LIS;4 9/9 7-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,)\r\n" + + "3-JUN.LIS;2 9/9 4-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,)\r\n" + + "3-JUN.LIS;3 9/9 6-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,)\r\n" + "\r\nTotal 6 files"; /** * @see junit.framework.TestCase#TestCase(String) */ - public VMSFTPEntryParserTest(String name) - { + public VMSFTPEntryParserTest(final String name) { super(name); } - public void testWholeListParse() throws IOException - { - VMSFTPEntryParser parser = new VMSFTPEntryParser(); - parser.configure(null); - FTPListParseEngine engine = new FTPListParseEngine(parser); - engine.readServerList( - new ByteArrayInputStream(fullListing.getBytes()), null); // use default encoding - FTPFile[] files = engine.getFiles(); - assertEquals(6, files.length); - assertFileInListing(files, "2-JUN.LIS"); - assertFileInListing(files, "3-JUN.LIS"); - assertFileInListing(files, "1-JUN.LIS"); - assertFileNotInListing(files, "1-JUN.LIS;1"); - - } - - public void testWholeListParseWithVersioning() throws IOException - { - - VMSFTPEntryParser parser = new VMSVersioningFTPEntryParser(); - parser.configure(null); - FTPListParseEngine engine = new FTPListParseEngine(parser); - engine.readServerList( - new ByteArrayInputStream(fullListing.getBytes()), null); // use default encoding - FTPFile[] files = engine.getFiles(); - assertEquals(3, files.length); - assertFileInListing(files, "1-JUN.LIS;1"); - assertFileInListing(files, "2-JUN.LIS;1"); - assertFileInListing(files, "3-JUN.LIS;4"); - assertFileNotInListing(files, "3-JUN.LIS;1"); - assertFileNotInListing(files, "3-JUN.LIS"); - - } - - public void assertFileInListing(FTPFile[] listing, String name) { - for (FTPFile element : listing) - { + public void assertFileInListing(final FTPFile[] listing, final String name) { + for (final FTPFile element : listing) { if (name.equals(element.getName())) { return; } } fail("File " + name + " not found in supplied listing"); } - public void assertFileNotInListing(FTPFile[] listing, String name) { - for (FTPFile element : listing) - { + + public void assertFileNotInListing(final FTPFile[] listing, final String name) { + for (final FTPFile element : listing) { if (name.equals(element.getName())) { fail("Unexpected File " + name + " found in supplied listing"); } } } + /* + * Verify that the VMS parser does NOT set the permissions. + */ + private void checkPermisions(final FTPFile dir, final int octalPerm) { + int permMask = 1 << 8; + assertEquals("Owner should not have read permission.", (permMask & octalPerm) != 0, dir.hasPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION)); + permMask >>= 1; + assertEquals("Owner should not have write permission.", (permMask & octalPerm) != 0, dir.hasPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION)); + permMask >>= 1; + assertEquals("Owner should not have execute permission.", (permMask & octalPerm) != 0, + dir.hasPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION)); + permMask >>= 1; + assertEquals("Group should not have read permission.", (permMask & octalPerm) != 0, dir.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.READ_PERMISSION)); + permMask >>= 1; + assertEquals("Group should not have write permission.", (permMask & octalPerm) != 0, dir.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.WRITE_PERMISSION)); + permMask >>= 1; + assertEquals("Group should not have execute permission.", (permMask & octalPerm) != 0, + dir.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.EXECUTE_PERMISSION)); + permMask >>= 1; + assertEquals("World should not have read permission.", (permMask & octalPerm) != 0, dir.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.READ_PERMISSION)); + permMask >>= 1; + assertEquals("World should not have write permission.", (permMask & octalPerm) != 0, dir.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.WRITE_PERMISSION)); + permMask >>= 1; + assertEquals("World should not have execute permission.", (permMask & octalPerm) != 0, + dir.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.EXECUTE_PERMISSION)); + } + @Override - public void testParseFieldsOnDirectory() throws Exception - { + protected String[] getBadListing() { - FTPFile dir = getParser().parseFTPEntry( - "DATA.DIR;1 1/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)"); - assertTrue("Should be a directory.", - dir.isDirectory()); - assertEquals("DATA.DIR", - dir.getName()); - assertEquals(512, - dir.getSize()); - assertEquals("Tue Jun 02 07:32:04 1998", - df.format(dir.getTimestamp().getTime())); - assertEquals("GROUP", - dir.getGroup()); - assertEquals("OWNER", - dir.getUser()); - checkPermisions(dir, 0775); + return BAD_SAMPLES; + } + @Override + protected String[] getGoodListing() { - dir = getParser().parseFTPEntry( - "DATA.DIR;1 1/9 2-JUN-1998 07:32:04 [TRANSLATED] (RWED,RWED,,RE)"); - assertTrue("Should be a directory.", - dir.isDirectory()); - assertEquals("DATA.DIR", - dir.getName()); - assertEquals(512, - dir.getSize()); - assertEquals("Tue Jun 02 07:32:04 1998", - df.format(dir.getTimestamp().getTime())); - assertEquals(null, - dir.getGroup()); - assertEquals("TRANSLATED", - dir.getUser()); - checkPermisions(dir, 0705); + return GOOD_SAMPLES; } @Override - public void testParseFieldsOnFile() throws Exception - { - FTPFile file = getParser().parseFTPEntry( - "1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RW,R)"); - assertTrue("Should be a file.", - file.isFile()); - assertEquals("1-JUN.LIS", - file.getName()); - assertEquals(9 * 512, - file.getSize()); - assertEquals("Tue Jun 02 07:32:04 1998", - df.format(file.getTimestamp().getTime())); - assertEquals("GROUP", - file.getGroup()); - assertEquals("OWNER", - file.getUser()); - checkPermisions(file, 0764); - + protected FTPFileEntryParser getParser() { + final ConfigurableFTPFileEntryParserImpl parser = new VMSFTPEntryParser(); + parser.configure(null); + return parser; + } - file = getParser().parseFTPEntry("1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [TRANSLATED] (RWED,RD,,)"); - assertTrue("Should be a file.", - file.isFile()); - assertEquals("1-JUN.LIS", - file.getName()); - assertEquals(9 * 512, - file.getSize()); - assertEquals("Tue Jun 02 07:32:04 1998", - df.format(file.getTimestamp().getTime())); - assertEquals(null, - file.getGroup()); - assertEquals("TRANSLATED", - file.getUser()); - checkPermisions(file, 0400); + protected FTPFileEntryParser getVersioningParser() { + final ConfigurableFTPFileEntryParserImpl parser = new VMSVersioningFTPEntryParser(); + parser.configure(null); + return parser; } @Override public void testDefaultPrecision() { - testPrecision( - "1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [TRANSLATED] (RWED,RD,,)", CalendarUnit.SECOND); + testPrecision("1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [TRANSLATED] (RWED,RD,,)", CalendarUnit.SECOND); } @Override - public void testRecentPrecision() { - // Not used + public void testParseFieldsOnDirectory() throws Exception { + + FTPFile dir = getParser().parseFTPEntry("DATA.DIR;1 1/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RWED,RE)"); + assertTrue("Should be a directory.", dir.isDirectory()); + assertEquals("DATA.DIR", dir.getName()); + assertEquals(512, dir.getSize()); + assertEquals("Tue Jun 02 07:32:04 1998", df.format(dir.getTimestamp().getTime())); + assertEquals("GROUP", dir.getGroup()); + assertEquals("OWNER", dir.getUser()); + checkPermisions(dir, 0775); + + dir = getParser().parseFTPEntry("DATA.DIR;1 1/9 2-JUN-1998 07:32:04 [TRANSLATED] (RWED,RWED,,RE)"); + assertTrue("Should be a directory.", dir.isDirectory()); + assertEquals("DATA.DIR", dir.getName()); + assertEquals(512, dir.getSize()); + assertEquals("Tue Jun 02 07:32:04 1998", df.format(dir.getTimestamp().getTime())); + assertNull(dir.getGroup()); + assertEquals("TRANSLATED", dir.getUser()); + checkPermisions(dir, 0705); } @Override - protected String[] getBadListing() - { + public void testParseFieldsOnFile() throws Exception { + FTPFile file = getParser().parseFTPEntry("1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [GROUP,OWNER] (RWED,RWED,RW,R)"); + assertTrue("Should be a file.", file.isFile()); + assertEquals("1-JUN.LIS", file.getName()); + assertEquals(9 * 512, file.getSize()); + assertEquals("Tue Jun 02 07:32:04 1998", df.format(file.getTimestamp().getTime())); + assertEquals("GROUP", file.getGroup()); + assertEquals("OWNER", file.getUser()); + checkPermisions(file, 0764); - return (badsamples); + file = getParser().parseFTPEntry("1-JUN.LIS;1 9/9 2-JUN-1998 07:32:04 [TRANSLATED] (RWED,RD,,)"); + assertTrue("Should be a file.", file.isFile()); + assertEquals("1-JUN.LIS", file.getName()); + assertEquals(9 * 512, file.getSize()); + assertEquals("Tue Jun 02 07:32:04 1998", df.format(file.getTimestamp().getTime())); + assertNull(file.getGroup()); + assertEquals("TRANSLATED", file.getUser()); + checkPermisions(file, 0400); } @Override - protected String[] getGoodListing() - { - - return (goodsamples); + public void testRecentPrecision() { + // Not used } - @Override - protected FTPFileEntryParser getParser() - { - ConfigurableFTPFileEntryParserImpl parser = - new VMSFTPEntryParser(); + public void testWholeListParse() throws IOException { + final VMSFTPEntryParser parser = new VMSFTPEntryParser(); parser.configure(null); - return parser; + final FTPListParseEngine engine = new FTPListParseEngine(parser); + engine.readServerList(new ByteArrayInputStream(FULL_LISTING.getBytes()), null); // use default encoding + final FTPFile[] files = engine.getFiles(); + assertEquals(6, files.length); + assertFileInListing(files, "2-JUN.LIS"); + assertFileInListing(files, "3-JUN.LIS"); + assertFileInListing(files, "1-JUN.LIS"); + assertFileNotInListing(files, "1-JUN.LIS;1"); + } - protected FTPFileEntryParser getVersioningParser() - { - ConfigurableFTPFileEntryParserImpl parser = - new VMSVersioningFTPEntryParser(); + public void testWholeListParseWithVersioning() throws IOException { + + final VMSFTPEntryParser parser = new VMSVersioningFTPEntryParser(); parser.configure(null); - return parser; - } + final FTPListParseEngine engine = new FTPListParseEngine(parser); + engine.readServerList(new ByteArrayInputStream(FULL_LISTING.getBytes()), null); // use default encoding + final FTPFile[] files = engine.getFiles(); + assertEquals(3, files.length); + assertFileInListing(files, "1-JUN.LIS;1"); + assertFileInListing(files, "2-JUN.LIS;1"); + assertFileInListing(files, "3-JUN.LIS;4"); + assertFileNotInListing(files, "3-JUN.LIS;1"); + assertFileNotInListing(files, "3-JUN.LIS"); - /* - * Verify that the VMS parser does NOT set the permissions. - */ - private void checkPermisions(FTPFile dir, int octalPerm) - { - int permMask = 1<<8; - assertTrue("Owner should not have read permission.", - ((permMask & octalPerm) != 0) == dir.hasPermission(FTPFile.USER_ACCESS, - FTPFile.READ_PERMISSION)); - permMask >>= 1; - assertTrue("Owner should not have write permission.", - ((permMask & octalPerm) != 0) == dir.hasPermission(FTPFile.USER_ACCESS, - FTPFile.WRITE_PERMISSION)); - permMask >>= 1; - assertTrue("Owner should not have execute permission.", - ((permMask & octalPerm) != 0) == dir.hasPermission(FTPFile.USER_ACCESS, - FTPFile.EXECUTE_PERMISSION)); - permMask >>= 1; - assertTrue("Group should not have read permission.", - ((permMask & octalPerm) != 0) == dir.hasPermission(FTPFile.GROUP_ACCESS, - FTPFile.READ_PERMISSION)); - permMask >>= 1; - assertTrue("Group should not have write permission.", - ((permMask & octalPerm) != 0) == dir.hasPermission(FTPFile.GROUP_ACCESS, - FTPFile.WRITE_PERMISSION)); - permMask >>= 1; - assertTrue("Group should not have execute permission.", - ((permMask & octalPerm) != 0) == dir.hasPermission(FTPFile.GROUP_ACCESS, - FTPFile.EXECUTE_PERMISSION)); - permMask >>= 1; - assertTrue("World should not have read permission.", - ((permMask & octalPerm) != 0) == dir.hasPermission(FTPFile.WORLD_ACCESS, - FTPFile.READ_PERMISSION)); - permMask >>= 1; - assertTrue("World should not have write permission.", - ((permMask & octalPerm) != 0) == dir.hasPermission(FTPFile.WORLD_ACCESS, - FTPFile.WRITE_PERMISSION)); - permMask >>= 1; - assertTrue("World should not have execute permission.", - ((permMask & octalPerm) != 0) == dir.hasPermission(FTPFile.WORLD_ACCESS, - FTPFile.EXECUTE_PERMISSION)); } } diff --git a/src/test/java/org/apache/commons/net/imap/IMAPTest.java b/src/test/java/org/apache/commons/net/imap/IMAPTest.java index fdf077b..50992aa 100644 --- a/src/test/java/org/apache/commons/net/imap/IMAPTest.java +++ b/src/test/java/org/apache/commons/net/imap/IMAPTest.java @@ -19,10 +19,8 @@ package org.apache.commons.net.imap; import org.junit.Assert; - import org.junit.Test; - public class IMAPTest { @Test @@ -30,17 +28,17 @@ public class IMAPTest { // This test assumes: // - 26 letters in the generator alphabet // - the generator uses a fixed size tag - IMAP imap = new IMAP(); - String initial = imap.generateCommandID(); + final IMAP imap = new IMAP(); + final String initial = imap.generateCommandID(); int expected = 1; - for(int j=0; j < initial.length(); j++) { + for (int j = 0; j < initial.length(); j++) { expected *= 26; // letters in alphabet } - int i=0; - boolean matched=false; - while(i <= expected+10) { // don't loop forever, but allow it to pass go! + int i = 0; + boolean matched = false; + while (i <= expected + 10) { // don't loop forever, but allow it to pass go! i++; - String s = imap.generateCommandID(); + final String s = imap.generateCommandID(); matched = initial.equals(s); if (matched) { // we've wrapped around completely break; diff --git a/src/test/java/org/apache/commons/net/io/DotTerminatedMessageReaderTest.java b/src/test/java/org/apache/commons/net/io/DotTerminatedMessageReaderTest.java index cecc368..c74a129 100644 --- a/src/test/java/org/apache/commons/net/io/DotTerminatedMessageReaderTest.java +++ b/src/test/java/org/apache/commons/net/io/DotTerminatedMessageReaderTest.java @@ -24,15 +24,15 @@ import junit.framework.TestCase; public class DotTerminatedMessageReaderTest extends TestCase { + private static final String CRLF = "\r\n"; + private static final String DOT = "."; + private static final String EOM = CRLF + DOT + CRLF; private DotTerminatedMessageReader reader; private final StringBuilder str = new StringBuilder(); private final char[] buf = new char[64]; - private static final String CRLF = "\r\n"; - private static final String DOT = "."; - private static final String EOM = CRLF+DOT+CRLF; - public void testReadSimpleStringCrLfLineEnding() throws IOException { - final String test = "Hello World!"+EOM; + public void testDoubleCrBeforeDot() throws IOException { + final String test = "Hello World!\r" + EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); int read = 0; @@ -40,11 +40,11 @@ public class DotTerminatedMessageReaderTest extends TestCase { str.append(buf, 0, read); } - assertEquals("Hello World!"+CRLF, str.toString()); + assertEquals("Hello World!\r" + CRLF, str.toString()); } - public void testReadSimpleStringLfLineEnding() throws IOException { - final String test = "Hello World!"+EOM; + public void testEmbeddedDot1() throws IOException { + final String test = "Hello . World!" + EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); int read = 0; @@ -52,11 +52,11 @@ public class DotTerminatedMessageReaderTest extends TestCase { str.append(buf, 0, read); } - assertEquals("Hello World!"+CRLF, str.toString()); + assertEquals("Hello . World!" + CRLF, str.toString()); } - public void testEmbeddedNewlines() throws IOException { - final String test = "Hello"+CRLF+"World\nA\rB"+EOM; + public void testEmbeddedDot2() throws IOException { + final String test = "Hello .. World!" + EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); int read = 0; @@ -64,11 +64,11 @@ public class DotTerminatedMessageReaderTest extends TestCase { str.append(buf, 0, read); } - assertEquals(str.toString(), "Hello" + CRLF +"World\nA\rB" + CRLF); + assertEquals("Hello .. World!" + CRLF, str.toString()); } - public void testDoubleCrBeforeDot() throws IOException { - final String test = "Hello World!\r"+EOM; + public void testEmbeddedDot3() throws IOException { + final String test = "Hello World." + CRLF + "more" + EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); int read = 0; @@ -76,11 +76,11 @@ public class DotTerminatedMessageReaderTest extends TestCase { str.append(buf, 0, read); } - assertEquals("Hello World!\r" + CRLF,str.toString()); + assertEquals("Hello World." + CRLF + "more" + CRLF, str.toString()); } - public void testLeadingDot() throws IOException { - final String test = "Hello World!"+CRLF+"..text"+EOM; + public void testEmbeddedDot4() throws IOException { + final String test = "Hello World\r.\nmore" + EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); int read = 0; @@ -88,11 +88,11 @@ public class DotTerminatedMessageReaderTest extends TestCase { str.append(buf, 0, read); } - assertEquals("Hello World!" + CRLF+".text"+CRLF,str.toString()); + assertEquals("Hello World\r.\nmore" + CRLF, str.toString()); } - public void testEmbeddedDot1() throws IOException { - final String test = "Hello . World!"+EOM; + public void testEmbeddedNewlines() throws IOException { + final String test = "Hello" + CRLF + "World\nA\rB" + EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); int read = 0; @@ -100,11 +100,11 @@ public class DotTerminatedMessageReaderTest extends TestCase { str.append(buf, 0, read); } - assertEquals("Hello . World!" + CRLF,str.toString()); + assertEquals(str.toString(), "Hello" + CRLF + "World\nA\rB" + CRLF); } - public void testEmbeddedDot2() throws IOException { - final String test = "Hello .. World!"+EOM; + public void testLeadingDot() throws IOException { + final String test = "Hello World!" + CRLF + "..text" + EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); int read = 0; @@ -112,35 +112,25 @@ public class DotTerminatedMessageReaderTest extends TestCase { str.append(buf, 0, read); } - assertEquals("Hello .. World!" + CRLF,str.toString()); + assertEquals("Hello World!" + CRLF + ".text" + CRLF, str.toString()); } - public void testEmbeddedDot3() throws IOException { - final String test = "Hello World."+CRLF+"more"+EOM; + public void testReadLine1() throws Exception { + final String test = "Hello World" + CRLF + "more" + EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); - int read = 0; - while ((read = reader.read(buf)) != -1) { - str.append(buf, 0, read); + String line; + while ((line = reader.readLine()) != null) { + str.append(line); + str.append("#"); } - assertEquals("Hello World." + CRLF+"more"+CRLF,str.toString()); - } - - public void testEmbeddedDot4() throws IOException { - final String test = "Hello World\r.\nmore"+EOM; - reader = new DotTerminatedMessageReader(new StringReader(test)); - - int read = 0; - while ((read = reader.read(buf)) != -1) { - str.append(buf, 0, read); - } + assertEquals("Hello World#more#", str.toString()); - assertEquals("Hello World\r.\nmore" + CRLF,str.toString()); } - public void testReadLine1() throws Exception { - final String test = "Hello World"+CRLF+"more"+EOM; + public void testReadLine2() throws Exception { + final String test = "Hello World\r.\nmore" + EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); String line; @@ -149,26 +139,36 @@ public class DotTerminatedMessageReaderTest extends TestCase { str.append("#"); } - assertEquals("Hello World#more#",str.toString()); + assertEquals("Hello World\r.\nmore#", str.toString()); } - public void testReadLine2() throws Exception { - final String test = "Hello World\r.\nmore"+EOM; + public void testReadSimpleStringCrLfLineEnding() throws IOException { + final String test = "Hello World!" + EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); - String line; - while ((line = reader.readLine()) != null) { - str.append(line); - str.append("#"); + int read = 0; + while ((read = reader.read(buf)) != -1) { + str.append(buf, 0, read); } - assertEquals("Hello World\r.\nmore#",str.toString()); + assertEquals("Hello World!" + CRLF, str.toString()); + } + + public void testReadSimpleStringLfLineEnding() throws IOException { + final String test = "Hello World!" + EOM; + reader = new DotTerminatedMessageReader(new StringReader(test)); + + int read = 0; + while ((read = reader.read(buf)) != -1) { + str.append(buf, 0, read); + } + assertEquals("Hello World!" + CRLF, str.toString()); } public void testSingleDotWithTrailingText() throws IOException { - final String test = "Hello World!"+CRLF+".text"+EOM; + final String test = "Hello World!" + CRLF + ".text" + EOM; reader = new DotTerminatedMessageReader(new StringReader(test)); int read = 0; @@ -176,7 +176,7 @@ public class DotTerminatedMessageReaderTest extends TestCase { str.append(buf, 0, read); } - assertEquals("Hello World!"+CRLF+".text"+CRLF,str.toString()); + assertEquals("Hello World!" + CRLF + ".text" + CRLF, str.toString()); } } diff --git a/src/test/java/org/apache/commons/net/io/ToNetASCIIInputStreamTest.java b/src/test/java/org/apache/commons/net/io/ToNetASCIIInputStreamTest.java index 56473a3..ba6bb00 100644 --- a/src/test/java/org/apache/commons/net/io/ToNetASCIIInputStreamTest.java +++ b/src/test/java/org/apache/commons/net/io/ToNetASCIIInputStreamTest.java @@ -21,31 +21,53 @@ package org.apache.commons.net.io; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; + //import java.nio.charset.Charset; import org.junit.Assert; import org.junit.Test; public class ToNetASCIIInputStreamTest { - private static final String ASCII = /*Charset.forName*/("ASCII"); + private void byteTest(final boolean byByte, final String input, final String expect) throws IOException { + final byte[] data = input.getBytes(StandardCharsets.US_ASCII); + final byte[] expected = expect.getBytes(StandardCharsets.US_ASCII); + final InputStream source = new ByteArrayInputStream(data); + try (final ToNetASCIIInputStream toNetASCII = new ToNetASCIIInputStream(source)) { + final byte[] output = new byte[data.length * 2]; // cannot be longer than twice the input - @Test - public void testToNetASCIIInputStream1() throws Exception - { - byteTest(false, "", ""); - byteTest(false, "\r", "\r"); - byteTest(false, "a", "a"); - byteTest(false, "a\nb", "a\r\nb"); - byteTest(false, "a\r\nb", "a\r\nb"); - byteTest(false, "\n", "\r\n"); - byteTest(false, "Hello\nWorld\n", "Hello\r\nWorld\r\n"); - byteTest(false, "Hello\nWorld\r\n", "Hello\r\nWorld\r\n"); - byteTest(false, "Hello\nWorld\n\r", "Hello\r\nWorld\r\n\r"); + final int length = byByte ? getSingleBytes(toNetASCII, output) : getBuffer(toNetASCII, output); + + final byte[] result = new byte[length]; + System.arraycopy(output, 0, result, 0, length); + Assert.assertArrayEquals("Failed converting " + input, expected, result); + } + } + + private int getBuffer(final ToNetASCIIInputStream toNetASCII, final byte[] output) throws IOException { + int length = 0; + int remain = output.length; + int chunk; + int offset = 0; + while (remain > 0 && (chunk = toNetASCII.read(output, offset, remain)) != -1) { + length += chunk; + offset += chunk; + remain -= chunk; + } + return length; + } + + private int getSingleBytes(final ToNetASCIIInputStream toNetASCII, final byte[] output) throws IOException { + int b; + int length = 0; + while ((b = toNetASCII.read()) != -1) { + output[length++] = (byte) b; + } + return length; } @Test - public void testToNetASCIIInputStream_single_bytes() throws Exception - { + public void testToNetASCIIInputStream_single_bytes() throws Exception { byteTest(true, "", ""); byteTest(true, "\r", "\r"); byteTest(true, "\n", "\r\n"); @@ -57,45 +79,17 @@ public class ToNetASCIIInputStreamTest { byteTest(true, "Hello\nWorld\n\r", "Hello\r\nWorld\r\n\r"); } - private void byteTest(boolean byByte, String input, String expect) throws IOException { - byte[] data = input.getBytes(ASCII); - byte[] expected = expect.getBytes(ASCII); - InputStream source = new ByteArrayInputStream(data); - ToNetASCIIInputStream toNetASCII = new ToNetASCIIInputStream(source); - byte[] output = new byte[data.length*2]; // cannot be longer than twice the input - - int length = byByte ? - getSingleBytes(toNetASCII, output) : - getBuffer(toNetASCII, output); - - byte[] result = new byte[length]; - System.arraycopy(output, 0, result, 0, length); - Assert.assertArrayEquals("Failed converting "+input,expected, result); - toNetASCII.close(); - } - - private int getSingleBytes(ToNetASCIIInputStream toNetASCII, byte[] output) - throws IOException { - int b; - int length=0; - while((b=toNetASCII.read()) != -1) { - output[length++]=(byte)b; - } - return length; - } - - private int getBuffer(ToNetASCIIInputStream toNetASCII, byte[] output) - throws IOException { - int length=0; - int remain=output.length; - int chunk; - int offset=0; - while(remain > 0 && (chunk=toNetASCII.read(output,offset,remain)) != -1){ - length+=chunk; - offset+=chunk; - remain-=chunk; - } - return length; + @Test + public void testToNetASCIIInputStream1() throws Exception { + byteTest(false, "", ""); + byteTest(false, "\r", "\r"); + byteTest(false, "a", "a"); + byteTest(false, "a\nb", "a\r\nb"); + byteTest(false, "a\r\nb", "a\r\nb"); + byteTest(false, "\n", "\r\n"); + byteTest(false, "Hello\nWorld\n", "Hello\r\nWorld\r\n"); + byteTest(false, "Hello\nWorld\r\n", "Hello\r\nWorld\r\n"); + byteTest(false, "Hello\nWorld\n\r", "Hello\r\nWorld\r\n\r"); } } diff --git a/src/test/java/org/apache/commons/net/nntp/TestThreader.java b/src/test/java/org/apache/commons/net/nntp/TestThreader.java index 8aaa37b..2100783 100644 --- a/src/test/java/org/apache/commons/net/nntp/TestThreader.java +++ b/src/test/java/org/apache/commons/net/nntp/TestThreader.java @@ -28,50 +28,52 @@ import org.junit.Test; */ public class TestThreader { - @Test + private static final Threadable[] EMPTY_THREADABLE_ARRAY = {}; + @SuppressWarnings("deprecation") // test of deprecated method - public void testNullArray() { // NET-539 - Threader t = new Threader(); - Threadable[] messages=null; + @Test + public void testEmptyArray() { // NET-539 + final Threader t = new Threader(); + final Threadable[] messages = EMPTY_THREADABLE_ARRAY; Assert.assertNull(t.thread(messages)); } @Test - public void testNullList() { - Threader t = new Threader(); - List<Threadable> messages=null; - Assert.assertNull(t.thread(messages)); + public void testEmptyIterable() { // NET-539 + final Threader t = new Threader(); + final Threadable[] messages = EMPTY_THREADABLE_ARRAY; + final Iterable<Threadable> asList = Arrays.asList(messages); + Assert.assertNull(t.thread(asList)); } @Test - public void testNullIterable() { - Threader t = new Threader(); - Iterable<Threadable> messages=null; - Assert.assertNull(t.thread(messages)); + public void testEmptyList() { // NET-539 + final Threader t = new Threader(); + final Threadable[] messages = EMPTY_THREADABLE_ARRAY; + final List<Threadable> asList = Arrays.asList(messages); + Assert.assertNull(t.thread(asList)); } - @SuppressWarnings("deprecation") // test of deprecated method @Test - public void testEmptyArray() { // NET-539 - Threader t = new Threader(); - Threadable[] messages=new Threadable[0]; + @SuppressWarnings("deprecation") // test of deprecated method + public void testNullArray() { // NET-539 + final Threader t = new Threader(); + final Threadable[] messages = null; Assert.assertNull(t.thread(messages)); } @Test - public void testEmptyList() { // NET-539 - Threader t = new Threader(); - Threadable[] messages=new Threadable[0]; - final List<Threadable> asList = Arrays.asList(messages); - Assert.assertNull(t.thread(asList)); + public void testNullIterable() { + final Threader t = new Threader(); + final Iterable<Threadable> messages = null; + Assert.assertNull(t.thread(messages)); } @Test - public void testEmptyIterable() { // NET-539 - Threader t = new Threader(); - Threadable[] messages=new Threadable[0]; - final Iterable<Threadable> asList = Arrays.asList(messages); - Assert.assertNull(t.thread(asList)); + public void testNullList() { + final Threader t = new Threader(); + final List<Threadable> messages = null; + Assert.assertNull(t.thread(messages)); } } diff --git a/src/test/java/org/apache/commons/net/ntp/TestNtpClient.java b/src/test/java/org/apache/commons/net/ntp/TestNtpClient.java index a3cfd41..af894ca 100644 --- a/src/test/java/org/apache/commons/net/ntp/TestNtpClient.java +++ b/src/test/java/org/apache/commons/net/ntp/TestNtpClient.java @@ -16,16 +16,15 @@ */ package org.apache.commons.net.ntp; +import java.io.IOException; +import java.net.InetAddress; + +import org.apache.commons.net.examples.ntp.SimpleNTPServer; import org.junit.AfterClass; -import org.junit.BeforeClass; import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Test; -import examples.ntp.SimpleNTPServer; - -import java.io.IOException; -import java.net.InetAddress; - /** * JUnit test class for NtpClient using SimpleNTPServer */ @@ -34,21 +33,20 @@ public class TestNtpClient { private static SimpleNTPServer server; @BeforeClass - public static void oneTimeSetUp() throws IOException - { + public static void oneTimeSetUp() throws IOException { // one-time initialization code server = new SimpleNTPServer(0); server.connect(); try { server.start(); - } catch (IOException e) { + } catch (final IOException e) { Assert.fail("failed to start NTP server: " + e); } Assert.assertTrue(server.isStarted()); - //System.out.println("XXX: time server started"); + // System.out.println("XXX: time server started"); boolean running = false; - for (int retries=0; retries < 5; retries++) { + for (int retries = 0; retries < 5; retries++) { running = server.isRunning(); if (running) { break; @@ -56,7 +54,7 @@ public class TestNtpClient { // if not running then sleep 2 seconds and try again try { Thread.sleep(2000); - } catch (InterruptedException e) { + } catch (final InterruptedException e) { // ignore } } @@ -74,34 +72,34 @@ public class TestNtpClient { @Test public void testGetTime() throws IOException { - long currentTime = System.currentTimeMillis(); - NTPUDPClient client = new NTPUDPClient(); + final long currentTimeMillis = System.currentTimeMillis(); + final NTPUDPClient client = new NTPUDPClient(); // timeout if response takes longer than 2 seconds client.setDefaultTimeout(2000); try { // Java 1.7: use InetAddress.getLoopbackAddress() instead - InetAddress addr = InetAddress.getByAddress("loopback", new byte[]{127, 0, 0, 1}); - TimeInfo timeInfo = client.getTime(addr, server.getPort()); + final InetAddress addr = InetAddress.getByAddress("loopback", new byte[] { 127, 0, 0, 1 }); + final TimeInfo timeInfo = client.getTime(addr, server.getPort()); Assert.assertNotNull(timeInfo); - Assert.assertTrue(timeInfo.getReturnTime() >= currentTime); - NtpV3Packet message = timeInfo.getMessage(); + Assert.assertTrue(timeInfo.getReturnTime() >= currentTimeMillis); + final NtpV3Packet message = timeInfo.getMessage(); Assert.assertNotNull(message); - TimeStamp rcvTimeStamp = message.getReceiveTimeStamp(); - TimeStamp xmitTimeStamp = message.getTransmitTimeStamp(); + final TimeStamp rcvTimeStamp = message.getReceiveTimeStamp(); + final TimeStamp xmitTimeStamp = message.getTransmitTimeStamp(); Assert.assertTrue(xmitTimeStamp.compareTo(rcvTimeStamp) >= 0); - TimeStamp originateTimeStamp = message.getOriginateTimeStamp(); + final TimeStamp originateTimeStamp = message.getOriginateTimeStamp(); Assert.assertNotNull(originateTimeStamp); - Assert.assertTrue(originateTimeStamp.getTime() >= currentTime); + Assert.assertTrue(originateTimeStamp.getTime() >= currentTimeMillis); Assert.assertEquals(NtpV3Packet.MODE_SERVER, message.getMode()); // following assertions are specific to the SimpleNTPServer - TimeStamp referenceTimeStamp = message.getReferenceTimeStamp(); + final TimeStamp referenceTimeStamp = message.getReferenceTimeStamp(); Assert.assertNotNull(referenceTimeStamp); - Assert.assertTrue(referenceTimeStamp.getTime() >= currentTime); + Assert.assertTrue(referenceTimeStamp.getTime() >= currentTimeMillis); Assert.assertEquals(NtpV3Packet.VERSION_3, message.getVersion()); Assert.assertEquals(1, message.getStratum()); diff --git a/src/test/java/org/apache/commons/net/ntp/TestNtpPacket.java b/src/test/java/org/apache/commons/net/ntp/TestNtpPacket.java index 0657e0e..d1aa75e 100644 --- a/src/test/java/org/apache/commons/net/ntp/TestNtpPacket.java +++ b/src/test/java/org/apache/commons/net/ntp/TestNtpPacket.java @@ -16,31 +16,41 @@ */ package org.apache.commons.net.ntp; -import org.junit.Test; -import org.junit.Assert; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.net.DatagramPacket; +import org.junit.Assert; +import org.junit.Test; + public class TestNtpPacket { // pre-canned NTP packet // [version:3, mode:4, poll:4, refId=0x81531472, precision:-17, delay:100, dispersion(ms):51.605224609375, // id:129.83.20.114, xmitTime:Thu, May 30 2013 17:46:01.295, etc. ] - static final byte[] ntpPacket = hexStringToByteArray( - "1c0304ef0000006400000d3681531472d552447fec1d6000d5524718ac49ba5ed55247194b6d9000d55247194b797000"); + static final byte[] ntpPacket = hexStringToByteArray("1c0304ef0000006400000d3681531472d552447fec1d6000d5524718ac49ba5ed55247194b6d9000d55247194b797000"); + + private static byte[] hexStringToByteArray(final String s) { + final int len = s.length(); + final byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); + } + return data; + } @Test public void testCreate() { - NtpV3Packet message = new NtpV3Impl(); - message.setLeapIndicator(0); // byte 0 [bit numbers 7-6] + final NtpV3Packet message = new NtpV3Impl(); + message.setLeapIndicator(0); // byte 0 [bit numbers 7-6] message.setVersion(NtpV3Packet.VERSION_3); // byte 0 [bit numbers 5-4] - message.setMode(4); // byte 0 [bit numbers 3-0] - message.setStratum(3); // byte 1 - message.setPoll(4); // byte 2 - message.setPrecision(-17); // byte 3 - message.setRootDelay(100); // bytes 4-7 - message.setRootDispersion(3382); // bytes 8-11 - message.setReferenceId(0x81531472); // byte 12-15 + message.setMode(4); // byte 0 [bit numbers 3-0] + message.setStratum(3); // byte 1 + message.setPoll(4); // byte 2 + message.setPrecision(-17); // byte 3 + message.setRootDelay(100); // bytes 4-7 + message.setRootDispersion(3382); // bytes 8-11 + message.setReferenceId(0x81531472); // byte 12-15 message.setReferenceTime(new TimeStamp(0xd552447fec1d6000L)); message.setOriginateTimeStamp(new TimeStamp(0xd5524718ac49ba5eL)); message.setReceiveTimeStamp(new TimeStamp(0xd55247194b6d9000L)); @@ -58,12 +68,12 @@ public class TestNtpPacket { Assert.assertEquals(51, message.getRootDispersionInMillis()); Assert.assertEquals(message.getRootDelay() / 65.536, message.getRootDelayInMillisDouble(), 1e-13); - DatagramPacket dp = message.getDatagramPacket(); // this creates a new datagram + final DatagramPacket dp = message.getDatagramPacket(); // this creates a new datagram Assert.assertNotNull(dp); Assert.assertEquals(48, dp.getLength()); // fixed 48-byte length - NtpV3Packet message2 = new NtpV3Impl(); - DatagramPacket dp2 = new DatagramPacket(ntpPacket, ntpPacket.length); + final NtpV3Packet message2 = new NtpV3Impl(); + final DatagramPacket dp2 = new DatagramPacket(ntpPacket, ntpPacket.length); message2.setDatagramPacket(dp2); Assert.assertEquals(message2, message); @@ -74,7 +84,7 @@ public class TestNtpPacket { @Test public void testCreateAndSetByte0() { // LI + VN + Mode all part of first byte -- make sure set order does not matter - NtpV3Packet message = new NtpV3Impl(); + final NtpV3Packet message = new NtpV3Impl(); message.setLeapIndicator(2); message.setMode(4); @@ -125,9 +135,30 @@ public class TestNtpPacket { Assert.assertEquals(2, message.getLeapIndicator()); } + @Test + public void testCreateFromBadPacket() { + final NtpV3Packet message = new NtpV3Impl(); + final DatagramPacket dp = new DatagramPacket(ntpPacket, ntpPacket.length - 4); // drop 4-bytes from packet + assertThrows(IllegalArgumentException.class, () -> message.setDatagramPacket(dp)); + } + + @Test + public void testCreateFromBytes() { + final NtpV3Packet message = new NtpV3Impl(); + final DatagramPacket dp = new DatagramPacket(ntpPacket, ntpPacket.length); + message.setDatagramPacket(dp); + Assert.assertEquals(4, message.getMode()); + } + + @Test + public void testCreateFromNullPacket() { + final NtpV3Packet message = new NtpV3Impl(); + assertThrows(IllegalArgumentException.class, () -> message.setDatagramPacket(null)); + } + @Test public void testCreateNtpV4() { - NtpV3Packet message = new NtpV3Impl(); + final NtpV3Packet message = new NtpV3Impl(); message.setVersion(NtpV3Packet.VERSION_4); message.setStratum(3); message.setReferenceId(0x81531472); @@ -144,33 +175,12 @@ public class TestNtpPacket { Assert.assertEquals("GPS", message.getReferenceIdString()); } - @Test - public void testCreateFromBytes() { - NtpV3Packet message = new NtpV3Impl(); - DatagramPacket dp = new DatagramPacket(ntpPacket, ntpPacket.length); - message.setDatagramPacket(dp); - Assert.assertEquals(4, message.getMode()); - } - - @Test(expected=IllegalArgumentException.class) - public void testCreateFromBadPacket() { - NtpV3Packet message = new NtpV3Impl(); - DatagramPacket dp = new DatagramPacket(ntpPacket, ntpPacket.length-4); // drop 4-bytes from packet - message.setDatagramPacket(dp); - } - - @Test(expected=IllegalArgumentException.class) - public void testCreateFromNullPacket() { - NtpV3Packet message = new NtpV3Impl(); - message.setDatagramPacket(null); - } - @Test public void testEquals() { - NtpV3Packet message1 = new NtpV3Impl(); - DatagramPacket dp = new DatagramPacket(ntpPacket, ntpPacket.length); + final NtpV3Packet message1 = new NtpV3Impl(); + final DatagramPacket dp = new DatagramPacket(ntpPacket, ntpPacket.length); message1.setDatagramPacket(dp); - NtpV3Packet message2 = new NtpV3Impl(); + final NtpV3Packet message2 = new NtpV3Impl(); message2.setDatagramPacket(dp); Assert.assertEquals("hashCode", message1.hashCode(), message2.hashCode()); Assert.assertEquals(message1, message2); @@ -178,20 +188,10 @@ public class TestNtpPacket { // now change the packet to force equals() => false message2.setMode(2); Assert.assertTrue(message1.getMode() != message2.getMode()); - Assert.assertFalse(message1.equals(message2)); + Assert.assertNotEquals(message1, message2); - NtpV3Packet message3 = null; - Assert.assertFalse(message1.equals(message3)); - } - - private static byte[] hexStringToByteArray(String s) { - int len = s.length(); - byte[] data = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) - + Character.digit(s.charAt(i+1), 16)); - } - return data; + final NtpV3Packet message3 = null; + Assert.assertNotEquals(message3, message1); } } diff --git a/src/test/java/org/apache/commons/net/ntp/TestTimeInfo.java b/src/test/java/org/apache/commons/net/ntp/TestTimeInfo.java index 0092a22..2927ecc 100644 --- a/src/test/java/org/apache/commons/net/ntp/TestTimeInfo.java +++ b/src/test/java/org/apache/commons/net/ntp/TestTimeInfo.java @@ -16,60 +16,54 @@ */ package org.apache.commons.net.ntp; -import org.junit.Test; -import org.junit.Assert; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; +import org.junit.Assert; +import org.junit.Test; + public class TestTimeInfo { @Test - public void testEquals() { - NtpV3Packet packet = new NtpV3Impl(); - final long returnTime = System.currentTimeMillis(); - TimeInfo info = new TimeInfo(packet, returnTime); - info.addComment("this is a comment"); - TimeInfo other = new TimeInfo(packet, returnTime); - other.addComment("this is a comment"); - Assert.assertEquals(info, other); // fails - Assert.assertEquals(info.hashCode(), other.hashCode()); - other.addComment("another comment"); - //Assert.assertFalse(info.equals(other)); // comments not used for equality - - TimeInfo another = new TimeInfo(packet, returnTime, new ArrayList<String>()); - Assert.assertEquals(info, another); + public void testAddress() throws UnknownHostException { + final NtpV3Packet packet = new NtpV3Impl(); + final TimeInfo info = new TimeInfo(packet, System.currentTimeMillis()); + Assert.assertNull(info.getAddress()); + packet.getDatagramPacket().setAddress(InetAddress.getByAddress("loopback", new byte[] { 127, 0, 0, 1 })); + Assert.assertNotNull(info.getAddress()); } @Test public void testComputeDetails() { // if (origTime > returnTime) // assert destTime >= origTime - NtpV3Packet packet = new NtpV3Impl(); - long returnTime = System.currentTimeMillis(); + final NtpV3Packet packet = new NtpV3Impl(); + final long returnTimeMillis = System.currentTimeMillis(); // example // returntime=1370571658178 - // origTime= 1370571659178 + // origTime= 1370571659178 // originate time as defined in RFC-1305 (t1) - packet.setOriginateTimeStamp(TimeStamp.getNtpTime(returnTime + 1000)); + packet.setOriginateTimeStamp(TimeStamp.getNtpTime(returnTimeMillis + 1000)); // Receive Time is time request received by server (t2) packet.setReceiveTimeStamp(packet.getOriginateTimeStamp()); // Transmit time is time reply sent by server (t3) packet.setTransmitTime(packet.getOriginateTimeStamp()); packet.setReferenceTime(packet.getOriginateTimeStamp()); - //long origTime = packet.getOriginateTimeStamp().getTime(); - //System.out.println("returntime=" + returnTime); - //System.out.println("origTime= " + origTime); + // long origTime = packet.getOriginateTimeStamp().getTime(); + // System.out.println("returntime=" + returnTime); + // System.out.println("origTime= " + origTime); - TimeInfo info = new TimeInfo(packet, returnTime); + final TimeInfo info = new TimeInfo(packet, returnTimeMillis); info.computeDetails(); Assert.assertSame(packet, info.getMessage()); - Assert.assertEquals(returnTime, info.getReturnTime()); + Assert.assertEquals(returnTimeMillis, info.getReturnTime()); Assert.assertEquals(Long.valueOf(500), info.getOffset()); Assert.assertEquals(Long.valueOf(-1000), info.getDelay()); @@ -77,60 +71,68 @@ public class TestTimeInfo { Assert.assertEquals(2, info.getComments().size()); } - @Test(expected=IllegalArgumentException.class) - public void testException() { - NtpV3Packet packet = null; - new TimeInfo(packet, 1L); - } - @Test - public void testAddress() throws UnknownHostException { - NtpV3Packet packet = new NtpV3Impl(); - TimeInfo info = new TimeInfo(packet, System.currentTimeMillis()); - Assert.assertNull(info.getAddress()); - packet.getDatagramPacket().setAddress(InetAddress.getByAddress("loopback", new byte[]{127, 0, 0, 1})); - Assert.assertNotNull(info.getAddress()); + public void testEquals() { + final NtpV3Packet packet = new NtpV3Impl(); + final long returnTime = System.currentTimeMillis(); + final TimeInfo info = new TimeInfo(packet, returnTime); + info.addComment("this is a comment"); + final TimeInfo other = new TimeInfo(packet, returnTime); + other.addComment("this is a comment"); + Assert.assertEquals(info, other); // fails + Assert.assertEquals(info.hashCode(), other.hashCode()); + other.addComment("another comment"); + // Assert.assertFalse(info.equals(other)); // comments not used for equality + + final TimeInfo another = new TimeInfo(packet, returnTime, new ArrayList<String>()); + Assert.assertEquals(info, another); } @Test - public void testZeroTime() { - NtpV3Packet packet = new NtpV3Impl(); - TimeInfo info = new TimeInfo(packet, 0); - info.computeDetails(); - Assert.assertNull(info.getDelay()); - Assert.assertNull(info.getOffset()); - Assert.assertEquals(0L, info.getReturnTime()); - // comments: Error: zero orig time -- cannot compute delay/offset - final List<String> comments = info.getComments(); - Assert.assertEquals(1, comments.size()); - Assert.assertTrue(comments.get(0).contains("zero orig time")); + public void testException() { + final NtpV3Packet packet = null; + assertThrows(IllegalArgumentException.class, () -> new TimeInfo(packet, 1L)); } @Test public void testNotEquals() { - NtpV3Packet packet = new NtpV3Impl(); - long returnTime = System.currentTimeMillis(); - TimeInfo info = new TimeInfo(packet, returnTime); + final NtpV3Packet packet = new NtpV3Impl(); + final long returnTime = System.currentTimeMillis(); + final TimeInfo info = new TimeInfo(packet, returnTime); // 1. different return time - NtpV3Packet packet2 = new NtpV3Impl(); + final NtpV3Packet packet2 = new NtpV3Impl(); Assert.assertEquals(packet, packet2); - TimeInfo info2 = new TimeInfo(packet2, returnTime + 1); - Assert.assertFalse(info.equals(info2)); + final TimeInfo info2 = new TimeInfo(packet2, returnTime + 1); + Assert.assertNotEquals(info, info2); // 2. different message / same time packet2.setStratum(3); packet2.setRootDelay(25); - TimeInfo info3 = new TimeInfo(packet2, returnTime); - Assert.assertFalse(info.equals(info3)); + final TimeInfo info3 = new TimeInfo(packet2, returnTime); + Assert.assertNotEquals(info, info3); // 3. different class - Object other = this; - Assert.assertFalse(info.equals(other)); + Object other = this; + Assert.assertNotEquals(info, other); // 4. null comparison other = null; - Assert.assertFalse(info.equals(other)); + Assert.assertNotEquals(info, other); + } + + @Test + public void testZeroTime() { + final NtpV3Packet packet = new NtpV3Impl(); + final TimeInfo info = new TimeInfo(packet, 0); + info.computeDetails(); + Assert.assertNull(info.getDelay()); + Assert.assertNull(info.getOffset()); + Assert.assertEquals(0L, info.getReturnTime()); + // comments: Error: zero orig time -- cannot compute delay/offset + final List<String> comments = info.getComments(); + Assert.assertEquals(1, comments.size()); + Assert.assertTrue(comments.get(0).contains("zero orig time")); } } diff --git a/src/test/java/org/apache/commons/net/ntp/TimeStampTest.java b/src/test/java/org/apache/commons/net/ntp/TimeStampTest.java index 5f342be..07dd4f9 100644 --- a/src/test/java/org/apache/commons/net/ntp/TimeStampTest.java +++ b/src/test/java/org/apache/commons/net/ntp/TimeStampTest.java @@ -16,8 +16,9 @@ */ package org.apache.commons.net.ntp; -import java.util.Date; import java.util.Calendar; +import java.util.Date; + import junit.framework.TestCase; /** @@ -25,16 +26,16 @@ import junit.framework.TestCase; */ public class TimeStampTest extends TestCase { - private static final String TIME1 = "c1a9ae1c.cf6ac48d"; // Tue, Dec 17 2002 14:07:24.810 UTC - private static final String TIME2 = "c1a9ae1c.cf6ac48f"; // Tue, Dec 17 2002 14:07:24.810 UTC - private static final String TIME3 = "c1a9ae1d.cf6ac48e"; // Tue, Dec 17 2002 14:07:25.810 UTC + private static final String TIME1 = "c1a9ae1c.cf6ac48d"; // Tue, Dec 17 2002 14:07:24.810 UTC + private static final String TIME2 = "c1a9ae1c.cf6ac48f"; // Tue, Dec 17 2002 14:07:24.810 UTC + private static final String TIME3 = "c1a9ae1d.cf6ac48e"; // Tue, Dec 17 2002 14:07:25.810 UTC public void testCompare() { - TimeStamp ts1 = new TimeStamp(TIME1); // Tue, Dec 17 2002 14:07:24.810 UTC - TimeStamp ts2 = new TimeStamp(TIME1); - TimeStamp ts3 = new TimeStamp(TIME2); // Tue, Dec 17 2002 14:07:24.810 UTC - TimeStamp ts4 = new TimeStamp(TIME3); // Tue, Dec 17 2002 14:07:25.810 UTC + final TimeStamp ts1 = new TimeStamp(TIME1); // Tue, Dec 17 2002 14:07:24.810 UTC + final TimeStamp ts2 = new TimeStamp(TIME1); + final TimeStamp ts3 = new TimeStamp(TIME2); // Tue, Dec 17 2002 14:07:24.810 UTC + final TimeStamp ts4 = new TimeStamp(TIME3); // Tue, Dec 17 2002 14:07:25.810 UTC // do assertion tests on TimeStamp class assertEquals("equals(1,2)", ts1, ts2); @@ -43,50 +44,50 @@ public class TimeStampTest extends TestCase { assertEquals("hashCode(1,2)", ts1.hashCode(), ts2.hashCode()); assertEquals("ts1==ts1", ts1, ts1); - // timestamps in ts1 (TIME1) and ts3 (TIME2) are only off by the smallest - // fraction of a second (~200 picoseconds) so the times are not equal but - // when converted to Java dates (in milliseconds) they will be equal. - assertTrue("ts1 != ts3", !ts1.equals(ts3)); + // timestamps in ts1 (TIME1) and ts3 (TIME2) are only off by the smallest + // fraction of a second (~200 picoseconds) so the times are not equal but + // when converted to Java dates (in milliseconds) they will be equal. + assertFalse("ts1 != ts3", ts1.equals(ts3)); assertEquals("compareTo(1,3)", -1, ts1.compareTo(ts3)); assertEquals("seconds", ts1.getSeconds(), ts3.getSeconds()); assertTrue("fraction", ts1.getFraction() != ts3.getFraction()); assertTrue("ntpValue(1,3)", ts1.ntpValue() != ts3.ntpValue()); assertTrue("hashCode(1,3)", ts1.hashCode() != ts3.hashCode()); - long time1 = ts1.getTime(); - long time3 = ts3.getTime(); + final long time1 = ts1.getTime(); + final long time3 = ts3.getTime(); assertEquals("equals(time1,3)", time1, time3); // ntpTime1 != ntpTime3 but JavaTime(t1) == JavaTime(t3)... - assertTrue("ts3 != ts4", !ts3.equals(ts4)); + assertFalse("ts3 != ts4", ts3.equals(ts4)); assertTrue("time3 != ts4.time", time3 != ts4.getTime()); } - public void testUTCString() { - TimeStamp ts1 = new TimeStamp(TIME1); // Tue, Dec 17 2002 14:07:24.810 UTC - String actual = ts1.toUTCString(); - assertEquals("Tue, Dec 17 2002 14:07:24.810 UTC", actual); - } - public void testDateConversion() { - // convert current date to NtpTimeStamp then compare Java date - // computed from NTP timestamp with original Java date. - Calendar refCal = Calendar.getInstance(java.util.TimeZone.getTimeZone("UTC")); - Date refDate = refCal.getTime(); - TimeStamp ts = new TimeStamp(refDate); - assertEquals("refDate.getTime()", refDate.getTime(), ts.getTime()); - Date tsDate = ts.getDate(); - assertEquals(refDate, tsDate); + // convert current date to NtpTimeStamp then compare Java date + // computed from NTP timestamp with original Java date. + final Calendar refCal = Calendar.getInstance(java.util.TimeZone.getTimeZone("UTC")); + final Date refDate = refCal.getTime(); + final TimeStamp ts = new TimeStamp(refDate); + assertEquals("refDate.getTime()", refDate.getTime(), ts.getTime()); + final Date tsDate = ts.getDate(); + assertEquals(refDate, tsDate); } public void testNotSame() { - TimeStamp time = TimeStamp.getCurrentTime(); + final TimeStamp time = TimeStamp.getCurrentTime(); Object other = Integer.valueOf(0); - if(time.equals(other)) { - fail("TimeStamp cannot equal Date"); + if (time.equals(other)) { + fail("TimeStamp cannot equal Date"); } other = null; - if(time.equals(other)) { + if (time.equals(other)) { fail("TimeStamp cannot equal null"); } } + public void testUTCString() { + final TimeStamp ts1 = new TimeStamp(TIME1); // Tue, Dec 17 2002 14:07:24.810 UTC + final String actual = ts1.toUTCString(); + assertEquals("Tue, Dec 17 2002 14:07:24.810 UTC", actual); + } + } diff --git a/src/test/java/org/apache/commons/net/pop3/POP3ClientCommandsTest.java b/src/test/java/org/apache/commons/net/pop3/POP3ClientCommandsTest.java index 94ca4ff..72a5c6b 100644 --- a/src/test/java/org/apache/commons/net/pop3/POP3ClientCommandsTest.java +++ b/src/test/java/org/apache/commons/net/pop3/POP3ClientCommandsTest.java @@ -16,513 +16,466 @@ */ package org.apache.commons.net.pop3; -import junit.framework.TestCase; - -import java.net.InetAddress; import java.io.IOException; import java.io.Reader; +import java.net.InetAddress; + +import junit.framework.TestCase; /** * - * The POP3* tests all presume the existence of the following parameters: - * mailserver: localhost (running on the default port 110) - * account: username=test; password=password - * account: username=alwaysempty; password=password. - * mail: At least four emails in the test account and zero emails - * in the alwaysempty account + * The POP3* tests all presume the existence of the following parameters: mailserver: localhost (running on the default port 110) account: username=test; + * password=password account: username=alwaysempty; password=password. mail: At least four emails in the test account and zero emails in the alwaysempty account * - * If this won't work for you, you can change these parameters in the - * TestSetupParameters class. + * If this won't work for you, you can change these parameters in the TestSetupParameters class. * - * The tests were originally run on a default installation of James. - * Your mileage may vary based on the POP3 server you run the tests against. - * Some servers are more standards-compliant than others. + * The tests were originally run on a default installation of James. Your mileage may vary based on the POP3 server you run the tests against. Some servers are + * more standards-compliant than others. */ -public class POP3ClientCommandsTest extends TestCase -{ - POP3Client p = null; +public class POP3ClientCommandsTest extends TestCase { + POP3Client pop3Client; String user = POP3Constants.user; String emptyUser = POP3Constants.emptyuser; String password = POP3Constants.password; String mailhost = POP3Constants.mailhost; - public POP3ClientCommandsTest(String name) - { + public POP3ClientCommandsTest(final String name) { super(name); } - private void reset() throws IOException - { - //Case where this is the first time reset is called - if (p == null) - { - //Do nothing - } - else if (p.isConnected()) - { - p.disconnect(); - } - p = null; - p = new POP3Client(); + private void connect() throws Exception { + pop3Client.connect(InetAddress.getByName(mailhost)); + assertTrue(pop3Client.isConnected()); + assertEquals(POP3.AUTHORIZATION_STATE, pop3Client.getState()); } - private void connect() throws Exception - { - p.connect(InetAddress.getByName(mailhost)); - assertTrue(p.isConnected()); - assertEquals(POP3.AUTHORIZATION_STATE, p.getState()); + private void login() throws Exception { + assertTrue(pop3Client.login(user, password)); + assertEquals(POP3.TRANSACTION_STATE, pop3Client.getState()); } - private void login() throws Exception - { - assertTrue(p.login(user, password)); - assertEquals(POP3.TRANSACTION_STATE, p.getState()); + private void reset() throws IOException { + // Case where this is the first time reset is called + if (pop3Client == null) { + // Do nothing + } else if (pop3Client.isConnected()) { + pop3Client.disconnect(); + } + pop3Client = null; + pop3Client = new POP3Client(); } - public void testNoopCommand() throws Exception - { + public void testDelete() throws Exception { reset(); connect(); + login(); + // Get the original number of messages + POP3MessageInfo[] msg = pop3Client.listMessages(); + final int numMessages = msg.length; + int numDeleted = 0; - //Should fail before authorization - assertFalse(p.noop()); + // Now delete some and logout + for (int i = 0; i < numMessages - 3; i++) { + pop3Client.deleteMessage(i + 1); + numDeleted++; + } + // Check to see that they are marked as deleted + assertEquals(numMessages, numDeleted + 3); - //Should pass in transaction state + // Logout and come back in + pop3Client.logout(); + reset(); + connect(); login(); - assertTrue(p.noop()); - //Should fail in update state - p.setState(POP3.UPDATE_STATE); - assertFalse(p.noop()); + // Get the new number of messages, because of + // reset, new number should match old number + msg = pop3Client.listMessages(); + assertEquals(numMessages - numDeleted, msg.length); } - public void testStatus() throws Exception - { + public void testDeleteWithReset() throws Exception { reset(); connect(); - - //Should fail in authorization state - assertNull(p.status()); - - //Should pass on a mailbox with mail in it login(); - POP3MessageInfo msg = p.status(); - assertTrue(msg.number > 0); - assertTrue(msg.size > 0); - assertNull(msg.identifier); - p.logout(); + // Get the original number of messages + POP3MessageInfo[] msg = pop3Client.listMessages(); + final int numMessages = msg.length; + int numDeleted = 0; - //Should also pass on a mailbox with no mail in it - reset(); - connect(); - assertTrue(p.login(emptyUser, password)); - POP3MessageInfo msg2 = p.status(); - assertEquals(0, msg2.number); - assertEquals(0, msg2.size); - assertNull(msg2.identifier); - p.logout(); + // Now delete some and logout + for (int i = 0; i < numMessages - 1; i++) { + pop3Client.deleteMessage(i + 1); + numDeleted++; + } + // Check to see that they are marked as deleted + assertEquals(numMessages, numDeleted + 1); + + // Now reset to unmark the messages as deleted + pop3Client.reset(); - //Should fail in the 'update' state + // Logout and come back in + pop3Client.logout(); reset(); connect(); login(); - p.setState(POP3.UPDATE_STATE); - assertNull(p.status()); + + // Get the new number of messages, because of + // reset, new number should match old number + msg = pop3Client.listMessages(); + assertEquals(numMessages, msg.length); } - public void testListMessagesOnFullMailbox() throws Exception - { + public void testListMessageOnEmptyMailbox() throws Exception { reset(); connect(); - login(); - - POP3MessageInfo[] msg = p.listMessages(); - assertTrue(msg.length > 0); - - for(int i = 0; i < msg.length; i++) - { - assertNotNull(msg[i]); - assertEquals(i+1, msg[i].number); - assertTrue(msg[i].size > 0); - assertNull(msg[i].identifier); - } + assertTrue(pop3Client.login(emptyUser, password)); - //Now test from the update state - p.setState(POP3.UPDATE_STATE); - msg = p.listMessages(); + // The first message is always at index 1 + final POP3MessageInfo msg = pop3Client.listMessage(1); assertNull(msg); } - public void testListMessageOnFullMailbox() throws Exception - { + public void testListMessageOnFullMailbox() throws Exception { reset(); connect(); login(); - //The first message is always at index 1 - POP3MessageInfo msg = p.listMessage(1); + // The first message is always at index 1 + POP3MessageInfo msg = pop3Client.listMessage(1); assertNotNull(msg); assertEquals(1, msg.number); assertTrue(msg.size > 0); assertNull(msg.identifier); - //Now retrieve a message from index 0 - msg = p.listMessage(0); + // Now retrieve a message from index 0 + msg = pop3Client.listMessage(0); assertNull(msg); - //Now retrieve a msg that is not there - msg = p.listMessage(100000); + // Now retrieve a msg that is not there + msg = pop3Client.listMessage(100000); assertNull(msg); - //Now retrieve a msg with a negative index - msg = p.listMessage(-2); + // Now retrieve a msg with a negative index + msg = pop3Client.listMessage(-2); assertNull(msg); - //Now try to get a valid message from the update state - p.setState(POP3.UPDATE_STATE); - msg = p.listMessage(1); + // Now try to get a valid message from the update state + pop3Client.setState(POP3.UPDATE_STATE); + msg = pop3Client.listMessage(1); assertNull(msg); } - public void testListMessagesOnEmptyMailbox() throws Exception - { + public void testListMessagesOnEmptyMailbox() throws Exception { reset(); connect(); - assertTrue(p.login(emptyUser, password)); + assertTrue(pop3Client.login(emptyUser, password)); - POP3MessageInfo[] msg = p.listMessages(); + POP3MessageInfo[] msg = pop3Client.listMessages(); assertEquals(0, msg.length); - //Now test from the update state - p.setState(POP3.UPDATE_STATE); - msg = p.listMessages(); - assertNull(msg); - } - - public void testListMessageOnEmptyMailbox() throws Exception - { - reset(); - connect(); - assertTrue(p.login(emptyUser, password)); - - //The first message is always at index 1 - POP3MessageInfo msg = p.listMessage(1); + // Now test from the update state + pop3Client.setState(POP3.UPDATE_STATE); + msg = pop3Client.listMessages(); assertNull(msg); } - public void testListUniqueIDsOnFullMailbox() throws Exception - { + public void testListMessagesOnFullMailbox() throws Exception { reset(); connect(); login(); - POP3MessageInfo[] msg = p.listUniqueIdentifiers(); + POP3MessageInfo[] msg = pop3Client.listMessages(); assertTrue(msg.length > 0); - for(int i = 0; i < msg.length; i++) - { + for (int i = 0; i < msg.length; i++) { assertNotNull(msg[i]); assertEquals(i + 1, msg[i].number); - assertNotNull(msg[i].identifier); + assertTrue(msg[i].size > 0); + assertNull(msg[i].identifier); } - //Now test from the update state - p.setState(POP3.UPDATE_STATE); - msg = p.listUniqueIdentifiers(); + // Now test from the update state + pop3Client.setState(POP3.UPDATE_STATE); + msg = pop3Client.listMessages(); + assertNull(msg); + } + + public void testListUniqueIdentifierOnEmptyMailbox() throws Exception { + reset(); + connect(); + assertTrue(pop3Client.login(emptyUser, password)); + + // The first message is always at index 1 + final POP3MessageInfo msg = pop3Client.listUniqueIdentifier(1); assertNull(msg); } - public void testListUniqueIDOnFullMailbox() throws Exception - { + public void testListUniqueIDOnFullMailbox() throws Exception { reset(); connect(); login(); - //The first message is always at index 1 - POP3MessageInfo msg = p.listUniqueIdentifier(1); + // The first message is always at index 1 + POP3MessageInfo msg = pop3Client.listUniqueIdentifier(1); assertNotNull(msg); assertEquals(1, msg.number); assertNotNull(msg.identifier); - //Now retrieve a message from index 0 - msg = p.listUniqueIdentifier(0); + // Now retrieve a message from index 0 + msg = pop3Client.listUniqueIdentifier(0); assertNull(msg); - //Now retrieve a msg that is not there - msg = p.listUniqueIdentifier(100000); + // Now retrieve a msg that is not there + msg = pop3Client.listUniqueIdentifier(100000); assertNull(msg); - //Now retrieve a msg with a negative index - msg = p.listUniqueIdentifier(-2); + // Now retrieve a msg with a negative index + msg = pop3Client.listUniqueIdentifier(-2); assertNull(msg); - //Now try to get a valid message from the update state - p.setState(POP3.UPDATE_STATE); - msg = p.listUniqueIdentifier(1); + // Now try to get a valid message from the update state + pop3Client.setState(POP3.UPDATE_STATE); + msg = pop3Client.listUniqueIdentifier(1); assertNull(msg); } - public void testListUniqueIDsOnEmptyMailbox() throws Exception - { + public void testListUniqueIDsOnEmptyMailbox() throws Exception { reset(); connect(); - assertTrue(p.login(emptyUser, password)); + assertTrue(pop3Client.login(emptyUser, password)); - POP3MessageInfo[] msg = p.listUniqueIdentifiers(); + POP3MessageInfo[] msg = pop3Client.listUniqueIdentifiers(); assertEquals(0, msg.length); - //Now test from the update state - p.setState(POP3.UPDATE_STATE); - msg = p.listUniqueIdentifiers(); + // Now test from the update state + pop3Client.setState(POP3.UPDATE_STATE); + msg = pop3Client.listUniqueIdentifiers(); assertNull(msg); } - public void testListUniqueIdentifierOnEmptyMailbox() throws Exception - { + public void testListUniqueIDsOnFullMailbox() throws Exception { reset(); connect(); - assertTrue(p.login(emptyUser, password)); + login(); + + POP3MessageInfo[] msg = pop3Client.listUniqueIdentifiers(); + assertTrue(msg.length > 0); + + for (int i = 0; i < msg.length; i++) { + assertNotNull(msg[i]); + assertEquals(i + 1, msg[i].number); + assertNotNull(msg[i].identifier); + } - //The first message is always at index 1 - POP3MessageInfo msg = p.listUniqueIdentifier(1); + // Now test from the update state + pop3Client.setState(POP3.UPDATE_STATE); + msg = pop3Client.listUniqueIdentifiers(); assertNull(msg); } - public void testRetrieveMessageOnFullMailbox() throws Exception - { + public void testNoopCommand() throws Exception { + reset(); + connect(); + + // Should fail before authorization + assertFalse(pop3Client.noop()); + + // Should pass in transaction state + login(); + assertTrue(pop3Client.noop()); + + // Should fail in update state + pop3Client.setState(POP3.UPDATE_STATE); + assertFalse(pop3Client.noop()); + } + + public void testResetAndDeleteShouldFails() throws Exception { + reset(); + connect(); + login(); + + pop3Client.setState(POP3.UPDATE_STATE); + assertFalse(pop3Client.reset()); + + assertFalse(pop3Client.deleteMessage(1)); + } + + public void testRetrieveMessageOnEmptyMailbox() throws Exception { + reset(); + connect(); + assertTrue(pop3Client.login(emptyUser, password)); + assertNull(pop3Client.retrieveMessage(1)); + } + + public void testRetrieveMessageOnFullMailbox() throws Exception { reset(); connect(); login(); int reportedSize = 0; int actualSize = 0; - POP3MessageInfo[] msg = p.listMessages(); + final POP3MessageInfo[] msg = pop3Client.listMessages(); assertTrue(msg.length > 0); - for (int i = msg.length; i > 0; i--) - { + for (int i = msg.length; i > 0; i--) { reportedSize = msg[i - 1].size; - Reader r = p.retrieveMessage(i); + final Reader r = pop3Client.retrieveMessage(i); assertNotNull(r); int delaycount = 0; - if (!r.ready()) - { - //Give the reader time to get the message - //from the server + if (!r.ready()) { + // Give the reader time to get the message + // from the server Thread.sleep(500); delaycount++; - //but don't wait too long - if (delaycount == 4) - { + // but don't wait too long + if (delaycount == 4) { break; } } - while(r.ready()) - { + while (r.ready()) { r.read(); actualSize++; } - //Due to variations in line termination - //on different platforms, the actual - //size may vary slightly. On Win2KPro, the - //actual size is 2 bytes larger than the reported - //size. + // Due to variations in line termination + // on different platforms, the actual + // size may vary slightly. On Win2KPro, the + // actual size is 2 bytes larger than the reported + // size. assertTrue(actualSize >= reportedSize); } } - public void testRetrieveMessageOnEmptyMailbox() throws Exception - { - reset(); - connect(); - assertTrue(p.login(emptyUser, password)); - assertNull(p.retrieveMessage(1)); - } - - public void testRetrieveMessageShouldFails() throws Exception - { + public void testRetrieveMessageShouldFails() throws Exception { reset(); connect(); login(); - //Try to get message 0 - assertNull(p.retrieveMessage(0)); + // Try to get message 0 + assertNull(pop3Client.retrieveMessage(0)); - //Try to get a negative message - assertNull(p.retrieveMessage(-2)); + // Try to get a negative message + assertNull(pop3Client.retrieveMessage(-2)); - //Try to get a message that is not there - assertNull(p.retrieveMessage(100000)); + // Try to get a message that is not there + assertNull(pop3Client.retrieveMessage(100000)); - //Change states and try to get a valid message - p.setState(POP3.UPDATE_STATE); - assertNull(p.retrieveMessage(1)); + // Change states and try to get a valid message + pop3Client.setState(POP3.UPDATE_STATE); + assertNull(pop3Client.retrieveMessage(1)); + } + + public void testRetrieveMessageTopOnEmptyMailbox() throws Exception { + reset(); + connect(); + assertTrue(pop3Client.login(emptyUser, password)); + assertNull(pop3Client.retrieveMessageTop(1, 10)); } - public void testRetrieveMessageTopOnFullMailbox() throws Exception - { + public void testRetrieveMessageTopOnFullMailbox() throws Exception { reset(); connect(); login(); - int numLines = 10; + final int numLines = 10; - POP3MessageInfo[] msg = p.listMessages(); + final POP3MessageInfo[] msg = pop3Client.listMessages(); assertTrue(msg.length > 0); - for (int i = 0; i < msg.length; i++) - { - Reader r = p.retrieveMessageTop(i + 1, numLines); + for (int i = 0; i < msg.length; i++) { + Reader r = pop3Client.retrieveMessageTop(i + 1, numLines); assertNotNull(r); r.close(); r = null; } } - public void testRetrieveOverSizedMessageTopOnFullMailbox() throws Exception - { + public void testRetrieveMessageTopShouldFails() throws Exception { + reset(); + connect(); + login(); + + // Try to get message 0 + assertNull(pop3Client.retrieveMessageTop(0, 10)); + + // Try to get a negative message + assertNull(pop3Client.retrieveMessageTop(-2, 10)); + + // Try to get a message that is not there + assertNull(pop3Client.retrieveMessageTop(100000, 10)); + + // Change states and try to get a valid message + pop3Client.setState(POP3.UPDATE_STATE); + assertNull(pop3Client.retrieveMessageTop(1, 10)); + } + + public void testRetrieveOverSizedMessageTopOnFullMailbox() throws Exception { reset(); connect(); login(); - int reportedSize = 0; int actualSize = 0; - POP3MessageInfo msg = p.listMessage(1); - reportedSize = msg.size; + final POP3MessageInfo msg = pop3Client.listMessage(1); + final int reportedSize = msg.size; - //Now try to retrieve more lines than exist in the message - Reader r = p.retrieveMessageTop(1, 100000); + // Now try to retrieve more lines than exist in the message + final Reader r = pop3Client.retrieveMessageTop(1, 100000); assertNotNull(r); int delaycount = 0; - while(!r.ready()) - { - //Give the reader time to get the message - //from the server + while (!r.ready()) { + // Give the reader time to get the message + // from the server Thread.sleep(500); delaycount++; - //but don't wait too long - if (delaycount == 4) - { + // but don't wait too long + if (delaycount == 4) { break; } } - while(r.ready()) - { + while (r.ready()) { r.read(); actualSize++; } - //Due to variations in line termination - //on different platforms, the actual - //size may vary slightly. On Win2KPro, the - //actual size is 2 bytes larger than the reported - //size. + // Due to variations in line termination + // on different platforms, the actual + // size may vary slightly. On Win2KPro, the + // actual size is 2 bytes larger than the reported + // size. assertTrue(actualSize >= reportedSize); } - public void testRetrieveMessageTopOnEmptyMailbox() throws Exception - { + public void testStatus() throws Exception { reset(); connect(); - assertTrue(p.login(emptyUser, password)); - assertNull(p.retrieveMessageTop(1, 10)); - } - public void testRetrieveMessageTopShouldFails() throws Exception - { - reset(); - connect(); - login(); + // Should fail in authorization state + assertNull(pop3Client.status()); - //Try to get message 0 - assertNull(p.retrieveMessageTop(0, 10)); - - //Try to get a negative message - assertNull(p.retrieveMessageTop(-2, 10)); - - //Try to get a message that is not there - assertNull(p.retrieveMessageTop(100000, 10)); - - //Change states and try to get a valid message - p.setState(POP3.UPDATE_STATE); - assertNull(p.retrieveMessageTop(1, 10)); - } - - public void testDeleteWithReset() throws Exception - { - reset(); - connect(); - login(); - //Get the original number of messages - POP3MessageInfo[] msg = p.listMessages(); - int numMessages = msg.length; - int numDeleted = 0; - - //Now delete some and logout - for (int i = 0; i < numMessages - 1; i ++) - { - p.deleteMessage(i + 1); - numDeleted++; - } - //Check to see that they are marked as deleted - assertEquals(numMessages, (numDeleted + 1)); - - //Now reset to unmark the messages as deleted - p.reset(); - - //Logout and come back in - p.logout(); - reset(); - connect(); + // Should pass on a mailbox with mail in it login(); + final POP3MessageInfo msg = pop3Client.status(); + assertTrue(msg.number > 0); + assertTrue(msg.size > 0); + assertNull(msg.identifier); + pop3Client.logout(); - //Get the new number of messages, because of - //reset, new number should match old number - msg = p.listMessages(); - assertEquals(numMessages, msg.length); - } - - public void testDelete() throws Exception - { - reset(); - connect(); - login(); - //Get the original number of messages - POP3MessageInfo[] msg = p.listMessages(); - int numMessages = msg.length; - int numDeleted = 0; - - //Now delete some and logout - for (int i = 0; i < numMessages - 3; i ++) - { - p.deleteMessage(i + 1); - numDeleted++; - } - //Check to see that they are marked as deleted - assertEquals(numMessages, (numDeleted + 3)); - - //Logout and come back in - p.logout(); + // Should also pass on a mailbox with no mail in it reset(); connect(); - login(); - - //Get the new number of messages, because of - //reset, new number should match old number - msg = p.listMessages(); - assertEquals(numMessages - numDeleted, msg.length); - } + assertTrue(pop3Client.login(emptyUser, password)); + final POP3MessageInfo msg2 = pop3Client.status(); + assertEquals(0, msg2.number); + assertEquals(0, msg2.size); + assertNull(msg2.identifier); + pop3Client.logout(); - public void testResetAndDeleteShouldFails() throws Exception - { + // Should fail in the 'update' state reset(); connect(); login(); - - p.setState(POP3.UPDATE_STATE); - assertFalse(p.reset()); - - assertFalse(p.deleteMessage(1)); + pop3Client.setState(POP3.UPDATE_STATE); + assertNull(pop3Client.status()); } } diff --git a/src/test/java/org/apache/commons/net/pop3/POP3ClientTest.java b/src/test/java/org/apache/commons/net/pop3/POP3ClientTest.java index 35ca769..b6ddbae 100644 --- a/src/test/java/org/apache/commons/net/pop3/POP3ClientTest.java +++ b/src/test/java/org/apache/commons/net/pop3/POP3ClientTest.java @@ -16,141 +16,118 @@ */ package org.apache.commons.net.pop3; -import junit.framework.TestCase; - -import java.net.InetAddress; import java.io.IOException; +import java.net.InetAddress; +import junit.framework.TestCase; /** - * @version $Id: POP3ClientTest.java 1697293 2015-08-24 01:01:00Z sebb $ * - * The POP3* tests all presume the existence of the following parameters: - * mailserver: localhost (running on the default port 110) - * account: username=test; password=password - * account: username=alwaysempty; password=password. - * mail: At least four emails in the test account and zero emails - * in the alwaysempty account + * The POP3* tests all presume the existence of the following parameters: mailserver: localhost (running on the default port 110) account: username=test; + * password=password account: username=alwaysempty; password=password. mail: At least four emails in the test account and zero emails in the alwaysempty account * - * If this won't work for you, you can change these parameters in the - * TestSetupParameters class. + * If this won't work for you, you can change these parameters in the TestSetupParameters class. * - * The tests were originally run on a default installation of James. - * Your mileage may vary based on the POP3 server you run the tests against. - * Some servers are more standards-compliant than others. + * The tests were originally run on a default installation of James. Your mileage may vary based on the POP3 server you run the tests against. Some servers are + * more standards-compliant than others. */ -public class POP3ClientTest extends TestCase -{ - POP3Client p = null; +public class POP3ClientTest extends TestCase { + POP3Client p; String user = POP3Constants.user; String emptyUser = POP3Constants.emptyuser; String password = POP3Constants.password; String mailhost = POP3Constants.mailhost; - public POP3ClientTest(String name) - { + public POP3ClientTest(final String name) { super(name); } - private void reset() throws IOException - { - //Case where this is the first time reset is called - if (p == null) - { - //Do nothing - } - else if (p.isConnected()) - { - p.disconnect(); - } - p = null; - p = new POP3Client(); - } - - private void connect() throws Exception - { + private void connect() throws Exception { p.connect(InetAddress.getByName(mailhost)); assertTrue(p.isConnected()); assertEquals(POP3.AUTHORIZATION_STATE, p.getState()); } - private void login() throws Exception - { + private void login() throws Exception { assertTrue(p.login(user, password)); assertEquals(POP3.TRANSACTION_STATE, p.getState()); } - /* - * Simple test to logon to a valid server using a valid - * user name and password. - */ - public void testValidLoginWithNameAndPassword() throws Exception - { - reset(); - connect(); - - //Try with a valid user - login(); + private void reset() throws IOException { + // Case where this is the first time reset is called + if (p == null) { + // Do nothing + } else if (p.isConnected()) { + p.disconnect(); + } + p = null; + p = new POP3Client(); } - public void testInvalidLoginWithBadName() throws Exception - { + public void testInvalidLoginWithBadName() throws Exception { reset(); connect(); - //Try with an invalid user that doesn't exist + // Try with an invalid user that doesn't exist assertFalse(p.login("badusername", password)); } - public void testInvalidLoginWithBadPassword() throws Exception - { + public void testInvalidLoginWithBadPassword() throws Exception { reset(); connect(); - //Try with a bad password + // Try with a bad password assertFalse(p.login(user, "badpassword")); } /* - * Test to try to run the login method from the - * disconnected, transaction and update states + * Test to try to run the login method from the disconnected, transaction and update states */ - public void testLoginFromWrongState() throws Exception - { + public void testLoginFromWrongState() throws Exception { reset(); - //Not currently connected, not in authorization state - //Try to login with good name/password + // Not currently connected, not in authorization state + // Try to login with good name/password assertFalse(p.login(user, password)); - //Now connect and set the state to 'transaction' and try again + // Now connect and set the state to 'transaction' and try again connect(); p.setState(POP3.TRANSACTION_STATE); assertFalse(p.login(user, password)); p.disconnect(); - //Now connect and set the state to 'update' and try again + // Now connect and set the state to 'update' and try again connect(); p.setState(POP3.UPDATE_STATE); assertFalse(p.login(user, password)); p.disconnect(); } - public void testLogoutFromAllStates() throws Exception - { - //From 'transaction' state + public void testLogoutFromAllStates() throws Exception { + // From 'transaction' state reset(); connect(); login(); assertTrue(p.logout()); assertEquals(POP3.UPDATE_STATE, p.getState()); - //From 'update' state + // From 'update' state reset(); connect(); login(); p.setState(POP3.UPDATE_STATE); assertTrue(p.logout()); } + + /* + * Simple test to logon to a valid server using a valid user name and password. + */ + public void testValidLoginWithNameAndPassword() throws Exception { + reset(); + connect(); + + // Try with a valid user + login(); + } } diff --git a/src/test/java/org/apache/commons/net/pop3/POP3Constants.java b/src/test/java/org/apache/commons/net/pop3/POP3Constants.java index 61dc09d..186846f 100644 --- a/src/test/java/org/apache/commons/net/pop3/POP3Constants.java +++ b/src/test/java/org/apache/commons/net/pop3/POP3Constants.java @@ -17,31 +17,23 @@ package org.apache.commons.net.pop3; /** - * @version $Id: POP3Constants.java 1697293 2015-08-24 01:01:00Z sebb $ * - * The POP3* tests all presume the existence of the following parameters: - * mailserver: localhost (running on the default port 110) - * account: username=test; password=password - * account: username=alwaysempty; password=password. - * mail: At least four emails in the test account and zero emails - * in the alwaysempty account + * The POP3* tests all presume the existence of the following parameters: mailserver: localhost (running on the default port 110) account: username=test; + * password=password account: username=alwaysempty; password=password. mail: At least four emails in the test account and zero emails in the alwaysempty account * - * If this won't work for you, you can change these parameters in the - * TestSetupParameters class. + * If this won't work for you, you can change these parameters in the TestSetupParameters class. * - * The tests were originally run on a default installation of James. - * Your mileage may vary based on the POP3 server you run the tests against. - * Some servers are more standards-compliant than others. + * The tests were originally run on a default installation of James. Your mileage may vary based on the POP3 server you run the tests against. Some servers are + * more standards-compliant than others. */ -public class POP3Constants -{ +public class POP3Constants { public static final String user = "test"; public static final String emptyuser = "alwaysempty"; public static final String password = "password"; public static final String mailhost = "localhost"; - //Cannot be instantiated - private POP3Constants() - {} + // Cannot be instantiated + private POP3Constants() { + } } diff --git a/src/test/java/org/apache/commons/net/pop3/POP3ConstructorTest.java b/src/test/java/org/apache/commons/net/pop3/POP3ConstructorTest.java index e688a38..299fa89 100644 --- a/src/test/java/org/apache/commons/net/pop3/POP3ConstructorTest.java +++ b/src/test/java/org/apache/commons/net/pop3/POP3ConstructorTest.java @@ -16,51 +16,42 @@ */ package org.apache.commons.net.pop3; -import junit.framework.TestCase; import java.io.Reader; +import junit.framework.TestCase; + /** - * The POP3* tests all presume the existence of the following parameters: - * mailserver: localhost (running on the default port 110) - * account: username=test; password=password - * account: username=alwaysempty; password=password. - * mail: At least four emails in the test account and zero emails - * in the alwaysempty account + * The POP3* tests all presume the existence of the following parameters: mailserver: localhost (running on the default port 110) account: username=test; + * password=password account: username=alwaysempty; password=password. mail: At least four emails in the test account and zero emails in the alwaysempty account * - * If this won't work for you, you can change these parameters in the - * TestSetupParameters class. + * If this won't work for you, you can change these parameters in the TestSetupParameters class. * - * The tests were originally run on a default installation of James. - * Your mileage may vary based on the POP3 server you run the tests against. - * Some servers are more standards-compliant than others. + * The tests were originally run on a default installation of James. Your mileage may vary based on the POP3 server you run the tests against. Some servers are + * more standards-compliant than others. */ -public class POP3ConstructorTest extends TestCase -{ +public class POP3ConstructorTest extends TestCase { String user = POP3Constants.user; String emptyUser = POP3Constants.emptyuser; String password = POP3Constants.password; String mailhost = POP3Constants.mailhost; - public POP3ConstructorTest(String name) - { + public POP3ConstructorTest(final String name) { super(name); } /* - * This test will ensure that the constants are not inadvertently changed. - * If the constants are changed in org.apache.commons.net.pop3 for some - * reason, this test will have to be updated. + * This test will ensure that the constants are not inadvertently changed. If the constants are changed in org.apache.commons.net.pop3 for some reason, this + * test will have to be updated. */ - public void testConstants() - { - //From POP3 + public void testConstants() { + // From POP3 assertEquals(110, POP3.DEFAULT_PORT); assertEquals(-1, POP3.DISCONNECTED_STATE); assertEquals(0, POP3.AUTHORIZATION_STATE); assertEquals(1, POP3.TRANSACTION_STATE); assertEquals(2, POP3.UPDATE_STATE); - //From POP3Command + // From POP3Command assertEquals(0, POP3Command.USER); assertEquals(1, POP3Command.PASS); assertEquals(2, POP3Command.QUIT); @@ -75,45 +66,33 @@ public class POP3ConstructorTest extends TestCase assertEquals(11, POP3Command.UIDL); } - public void testPOP3DefaultConstructor() - { - POP3 pop = new POP3(); + public void testPOP3ClientStateTransition() throws Exception { + final POP3Client pop = new POP3Client(); + // Initial state assertEquals(110, pop.getDefaultPort()); assertEquals(POP3.DISCONNECTED_STATE, pop.getState()); - assertNull(pop._reader); - assertNotNull(pop._replyLines); - } + assertNull(pop.reader); + assertNotNull(pop.replyLines); - public void testPOP3ClientStateTransition() throws Exception - { - POP3Client pop = new POP3Client(); - - //Initial state - assertEquals(110, pop.getDefaultPort()); - assertEquals(POP3.DISCONNECTED_STATE, pop.getState()); - assertNull(pop._reader); - assertNotNull(pop._replyLines); - - //Now connect + // Now connect pop.connect(mailhost); assertEquals(POP3.AUTHORIZATION_STATE, pop.getState()); - //Now authenticate + // Now authenticate pop.login(user, password); assertEquals(POP3.TRANSACTION_STATE, pop.getState()); - //Now do a series of commands and make sure the state stays as it should + // Now do a series of commands and make sure the state stays as it should pop.noop(); assertEquals(POP3.TRANSACTION_STATE, pop.getState()); pop.status(); assertEquals(POP3.TRANSACTION_STATE, pop.getState()); - //Make sure we have at least one message to test - POP3MessageInfo[] msg = pop.listMessages(); + // Make sure we have at least one message to test + final POP3MessageInfo[] msg = pop.listMessages(); - if (msg.length > 0) - { + if (msg.length > 0) { pop.deleteMessage(1); assertEquals(POP3.TRANSACTION_STATE, pop.getState()); @@ -135,9 +114,8 @@ public class POP3ConstructorTest extends TestCase Reader r = pop.retrieveMessage(1); assertEquals(POP3.TRANSACTION_STATE, pop.getState()); - //Add some sleep here to handle network latency - while(!r.ready()) - { + // Add some sleep here to handle network latency + while (!r.ready()) { Thread.sleep(10); } r.close(); @@ -146,9 +124,8 @@ public class POP3ConstructorTest extends TestCase r = pop.retrieveMessageTop(1, 10); assertEquals(POP3.TRANSACTION_STATE, pop.getState()); - //Add some sleep here to handle network latency - while(!r.ready()) - { + // Add some sleep here to handle network latency + while (!r.ready()) { Thread.sleep(10); } r.close(); @@ -156,8 +133,17 @@ public class POP3ConstructorTest extends TestCase } - //Now logout + // Now logout pop.logout(); assertEquals(POP3.UPDATE_STATE, pop.getState()); } + + public void testPOP3DefaultConstructor() { + final POP3 pop = new POP3(); + + assertEquals(110, pop.getDefaultPort()); + assertEquals(POP3.DISCONNECTED_STATE, pop.getState()); + assertNull(pop.reader); + assertNotNull(pop.replyLines); + } } diff --git a/src/test/java/org/apache/commons/net/smtp/SimpleSMTPHeaderTestCase.java b/src/test/java/org/apache/commons/net/smtp/SimpleSMTPHeaderTestCase.java index f30fb63..0cec314 100644 --- a/src/test/java/org/apache/commons/net/smtp/SimpleSMTPHeaderTestCase.java +++ b/src/test/java/org/apache/commons/net/smtp/SimpleSMTPHeaderTestCase.java @@ -16,7 +16,10 @@ */ package org.apache.commons.net.smtp; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -33,6 +36,42 @@ public class SimpleSMTPHeaderTestCase { private SimpleSMTPHeader header; private Date beforeDate; + // Returns the msg without a date + private String checkDate(final String msg) { + final Pattern pat = Pattern.compile("^(Date: (.+))$", Pattern.MULTILINE); + final Matcher m = pat.matcher(msg); + if (m.find()) { + final String date = m.group(2); + final String pattern = "EEE, dd MMM yyyy HH:mm:ss Z"; // Fri, 21 Nov 1997 09:55:06 -0600 + final SimpleDateFormat format = new SimpleDateFormat(pattern, Locale.ENGLISH); + try { + final Date sentDate = format.parse(date); + // Round to nearest second because the text format does not include ms + final long sentSecs = sentDate.getTime() / 1000; + final long beforeDateSecs = beforeDate.getTime() / 1000; + final Date afterDate = new Date(); + final long afterDateSecs = afterDate.getTime() / 1000; + if (sentSecs < beforeDateSecs) { + fail(sentDate + " should be after " + beforeDate); + } + if (sentSecs > afterDateSecs) { + fail(sentDate + " should be before " + afterDate); + } + } catch (final ParseException e) { + fail("" + e); + } + + final int start = m.start(1); + final int end = m.end(1); + if (start == 0) { + return msg.substring(end + 1); + } + return msg.substring(0, start) + msg.substring(end + 1); + } + fail("Expecting Date header in " + msg); + return null; + } + @Before public void setUp() { beforeDate = new Date(); @@ -46,30 +85,9 @@ public class SimpleSMTPHeaderTestCase { assertEquals("From: from@here.invalid\nTo: to@there.invalid\nSubject: Test email\n\n", checkDate(header.toString())); } - @Test - public void testToStringNoSubject() { - SimpleSMTPHeader hdr = new SimpleSMTPHeader("from@here.invalid", "to@there.invalid", null); - assertNotNull(hdr); - // Note that the DotTerminatedMessageWriter converts LF to CRLF - assertEquals("From: from@here.invalid\nTo: to@there.invalid\n\n", checkDate(hdr.toString())); - } - - @Test(expected=IllegalArgumentException.class) - public void testToStringNoFrom() { - new SimpleSMTPHeader(null, null, null); - } - - @Test - public void testToStringNoTo() { - SimpleSMTPHeader hdr = new SimpleSMTPHeader("from@here.invalid", null, null); - assertNotNull(hdr); - // Note that the DotTerminatedMessageWriter converts LF to CRLF - assertEquals("From: from@here.invalid\n\n", checkDate(hdr.toString())); - } - @Test public void testToStringAddHeader() { - SimpleSMTPHeader hdr = new SimpleSMTPHeader("from@here.invalid", null, null); + final SimpleSMTPHeader hdr = new SimpleSMTPHeader("from@here.invalid", null, null); assertNotNull(hdr); hdr.addHeaderField("X-Header1", "value 1"); hdr.addHeaderField("X-Header2", "value 2"); @@ -79,48 +97,31 @@ public class SimpleSMTPHeaderTestCase { @Test public void testToStringAddHeaderDate() { - SimpleSMTPHeader hdr = new SimpleSMTPHeader("from@here.invalid", null, null); + final SimpleSMTPHeader hdr = new SimpleSMTPHeader("from@here.invalid", null, null); assertNotNull(hdr); hdr.addHeaderField("Date", "dummy date"); // does not replace the Date field assertEquals("Date: dummy date\nFrom: from@here.invalid\n\n", hdr.toString()); } - // Returns the msg without a date - private String checkDate(String msg) { - Pattern pat = Pattern.compile("^(Date: (.+))$", Pattern.MULTILINE); - Matcher m = pat.matcher(msg); - if (m.find()) { - String date = m.group(2); - final String pattern = "EEE, dd MMM yyyy HH:mm:ss Z"; // Fri, 21 Nov 1997 09:55:06 -0600 - final SimpleDateFormat format = new SimpleDateFormat(pattern, Locale.ENGLISH); - try { - final Date sentDate = format.parse(date); - // Round to nearest second because the text format does not include ms - long sentSecs = sentDate.getTime() / 1000; - long beforeDateSecs = beforeDate.getTime() / 1000; - Date afterDate = new Date(); - long afterDateSecs = afterDate.getTime() / 1000; - if (sentSecs < beforeDateSecs) { - fail(sentDate + " should be after "+beforeDate); - } - if (sentSecs > (afterDateSecs)) { - fail(sentDate+" should be before "+afterDate); - } - } catch (ParseException e) { - fail(""+e); - } + @Test + public void testToStringNoFrom() { + assertThrows(IllegalArgumentException.class, () -> new SimpleSMTPHeader(null, null, null)); + } - int start = m.start(1); - int end = m.end(1); - if (start == 0) { - return msg.substring(end+1); - } else { - return msg.substring(0, start)+msg.substring(end+1); - } - } else { - fail("Expecting Date header in "+msg); - } - return null; + @Test + public void testToStringNoSubject() { + final SimpleSMTPHeader hdr = new SimpleSMTPHeader("from@here.invalid", "to@there.invalid", null); + assertNotNull(hdr); + // Note that the DotTerminatedMessageWriter converts LF to CRLF + assertEquals("From: from@here.invalid\nTo: to@there.invalid\n\n", checkDate(hdr.toString())); + } + + @Test + public void testToStringNoTo() { + final SimpleSMTPHeader hdr = new SimpleSMTPHeader("from@here.invalid", null, null); + assertNotNull(hdr); + // Note that the DotTerminatedMessageWriter converts LF to CRLF + assertEquals("From: from@here.invalid\n\n", checkDate(hdr.toString())); } } diff --git a/src/test/java/org/apache/commons/net/telnet/EchoOptionHandlerTest.java b/src/test/java/org/apache/commons/net/telnet/EchoOptionHandlerTest.java index 095e013..be9b368 100644 --- a/src/test/java/org/apache/commons/net/telnet/EchoOptionHandlerTest.java +++ b/src/test/java/org/apache/commons/net/telnet/EchoOptionHandlerTest.java @@ -16,62 +16,51 @@ */ package org.apache.commons.net.telnet; -/*** +/** * JUnit test class for EchoOptionHandler - ***/ -public class EchoOptionHandlerTest extends TelnetOptionHandlerTestAbstract -{ + */ +public class EchoOptionHandlerTest extends TelnetOptionHandlerTestAbstract { - /*** + /** * setUp for the test. - ***/ + */ @Override - protected void setUp() - { + protected void setUp() { opthand1 = new EchoOptionHandler(); opthand2 = new EchoOptionHandler(true, true, true, true); opthand3 = new EchoOptionHandler(false, false, false, false); } - /*** - * test of the constructors. - ***/ + /** + * test of server-driven subnegotiation. Checks that no subnegotiation is made. + */ @Override - public void testConstructors() - { - assertEquals(opthand1.getOptionCode(), TelnetOption.ECHO); - super.testConstructors(); - } + public void testAnswerSubnegotiation() { + final int subn[] = { TelnetCommand.IAC, TelnetCommand.SB, TelnetOption.ECHO, 1, TelnetCommand.IAC, TelnetCommand.SE, }; - /*** - * test of client-driven subnegotiation. - * Checks that no subnegotiation is made. - ***/ - @Override - public void testStartSubnegotiation() - { - int resp1[] = opthand1.startSubnegotiationLocal(); - int resp2[] = opthand1.startSubnegotiationRemote(); + final int resp1[] = opthand1.answerSubnegotiation(subn, subn.length); - assertEquals(resp1, null); - assertEquals(resp2, null); + assertNull(resp1); } - /*** - * test of server-driven subnegotiation. - * Checks that no subnegotiation is made. - ***/ + /** + * test of the constructors. + */ @Override - public void testAnswerSubnegotiation() - { - int subn[] = - { - TelnetCommand.IAC, TelnetCommand.SB, TelnetOption.ECHO, - 1, TelnetCommand.IAC, TelnetCommand.SE, - }; + public void testConstructors() { + assertEquals(opthand1.getOptionCode(), TelnetOption.ECHO); + super.testConstructors(); + } - int resp1[] = opthand1.answerSubnegotiation(subn, subn.length); + /** + * test of client-driven subnegotiation. Checks that no subnegotiation is made. + */ + @Override + public void testStartSubnegotiation() { + final int resp1[] = opthand1.startSubnegotiationLocal(); + final int resp2[] = opthand1.startSubnegotiationRemote(); - assertEquals(resp1, null); + assertNull(resp1); + assertNull(resp2); } } diff --git a/src/test/java/org/apache/commons/net/telnet/InvalidTelnetOptionExceptionTest.java b/src/test/java/org/apache/commons/net/telnet/InvalidTelnetOptionExceptionTest.java index 283ac57..3b052fa 100644 --- a/src/test/java/org/apache/commons/net/telnet/InvalidTelnetOptionExceptionTest.java +++ b/src/test/java/org/apache/commons/net/telnet/InvalidTelnetOptionExceptionTest.java @@ -18,32 +18,29 @@ package org.apache.commons.net.telnet; import junit.framework.TestCase; -/*** +/** * JUnit test class for InvalidTelnetOptionException - ***/ -public class InvalidTelnetOptionExceptionTest extends TestCase -{ + */ +public class InvalidTelnetOptionExceptionTest extends TestCase { private InvalidTelnetOptionException exc1; private String msg1; private int code1; - /*** + /** * setUp for the test. - ***/ + */ @Override - protected void setUp() - { + protected void setUp() { msg1 = "MSG"; code1 = 13; exc1 = new InvalidTelnetOptionException(msg1, code1); } - /*** + /** * test of the constructors. - ***/ - public void testConstructors() - { + */ + public void testConstructors() { assertTrue(exc1.getMessage().indexOf(msg1) >= 0); - assertTrue(exc1.getMessage().indexOf("" +code1) >= 0); + assertTrue(exc1.getMessage().indexOf("" + code1) >= 0); } } \ No newline at end of file diff --git a/src/test/java/org/apache/commons/net/telnet/SimpleOptionHandlerTest.java b/src/test/java/org/apache/commons/net/telnet/SimpleOptionHandlerTest.java index 669ba1d..a559f47 100644 --- a/src/test/java/org/apache/commons/net/telnet/SimpleOptionHandlerTest.java +++ b/src/test/java/org/apache/commons/net/telnet/SimpleOptionHandlerTest.java @@ -16,64 +16,53 @@ */ package org.apache.commons.net.telnet; -/*** +/** * JUnit test class for SimpleOptionHandler - ***/ -public class SimpleOptionHandlerTest extends TelnetOptionHandlerTestAbstract -{ - /*** + */ +public class SimpleOptionHandlerTest extends TelnetOptionHandlerTestAbstract { + /** * setUp for the test. - ***/ + */ @Override - protected void setUp() - { + protected void setUp() { opthand1 = new SimpleOptionHandler(4); opthand2 = new SimpleOptionHandler(8, true, true, true, true); opthand3 = new SimpleOptionHandler(91, false, false, false, false); } - /*** + /** + * test of server-driven subnegotiation. Checks that no subnegotiation is made. + */ + @Override + public void testAnswerSubnegotiation() { + final int subn[] = { TelnetCommand.IAC, TelnetCommand.SB, 4, 1, TelnetCommand.IAC, TelnetCommand.SE, }; + + final int resp1[] = opthand1.answerSubnegotiation(subn, subn.length); + + assertNull(resp1); + } + + /** * test of the constructors. - ***/ + */ @Override - public void testConstructors() - { + public void testConstructors() { assertEquals(opthand1.getOptionCode(), 4); assertEquals(opthand2.getOptionCode(), 8); assertEquals(opthand3.getOptionCode(), 91); super.testConstructors(); } - /*** - * test of client-driven subnegotiation. - * Checks that no subnegotiation is made. - ***/ - @Override - public void testStartSubnegotiation() - { - - int resp1[] = opthand1.startSubnegotiationLocal(); - int resp2[] = opthand1.startSubnegotiationRemote(); - - assertEquals(resp1, null); - assertEquals(resp2, null); - } - - /*** - * test of server-driven subnegotiation. - * Checks that no subnegotiation is made. - ***/ + /** + * test of client-driven subnegotiation. Checks that no subnegotiation is made. + */ @Override - public void testAnswerSubnegotiation() - { - int subn[] = - { - TelnetCommand.IAC, TelnetCommand.SB, 4, - 1, TelnetCommand.IAC, TelnetCommand.SE, - }; + public void testStartSubnegotiation() { - int resp1[] = opthand1.answerSubnegotiation(subn, subn.length); + final int resp1[] = opthand1.startSubnegotiationLocal(); + final int resp2[] = opthand1.startSubnegotiationRemote(); - assertEquals(resp1, null); + assertNull(resp1); + assertNull(resp2); } } diff --git a/src/test/java/org/apache/commons/net/telnet/SuppressGAOptionHandlerTest.java b/src/test/java/org/apache/commons/net/telnet/SuppressGAOptionHandlerTest.java index c158c50..f1b8e72 100644 --- a/src/test/java/org/apache/commons/net/telnet/SuppressGAOptionHandlerTest.java +++ b/src/test/java/org/apache/commons/net/telnet/SuppressGAOptionHandlerTest.java @@ -16,63 +16,52 @@ */ package org.apache.commons.net.telnet; -/*** +/** * JUnit test class for SuppressGAOptionHandler - ***/ -public class SuppressGAOptionHandlerTest extends TelnetOptionHandlerTestAbstract -{ + */ +public class SuppressGAOptionHandlerTest extends TelnetOptionHandlerTestAbstract { - /*** + /** * setUp for the test. - ***/ + */ @Override - protected void setUp() - { + protected void setUp() { opthand1 = new SuppressGAOptionHandler(); opthand2 = new SuppressGAOptionHandler(true, true, true, true); opthand3 = new SuppressGAOptionHandler(false, false, false, false); } - /*** - * test of the constructors. - ***/ + /** + * test of server-driven subnegotiation. Checks that no subnegotiation is made. + */ @Override - public void testConstructors() - { - assertEquals(opthand1.getOptionCode(), TelnetOption.SUPPRESS_GO_AHEAD); - super.testConstructors(); - } + public void testAnswerSubnegotiation() { + final int subn[] = { TelnetCommand.IAC, TelnetCommand.SB, TelnetOption.SUPPRESS_GO_AHEAD, 1, TelnetCommand.IAC, TelnetCommand.SE, }; - /*** - * test of client-driven subnegotiation. - * Checks that no subnegotiation is made. - ***/ - @Override - public void testStartSubnegotiation() - { + final int resp1[] = opthand1.answerSubnegotiation(subn, subn.length); - int resp1[] = opthand1.startSubnegotiationLocal(); - int resp2[] = opthand1.startSubnegotiationRemote(); + assertNull(resp1); + } - assertEquals(resp1, null); - assertEquals(resp2, null); + /** + * test of the constructors. + */ + @Override + public void testConstructors() { + assertEquals(opthand1.getOptionCode(), TelnetOption.SUPPRESS_GO_AHEAD); + super.testConstructors(); } - /*** - * test of server-driven subnegotiation. - * Checks that no subnegotiation is made. - ***/ + /** + * test of client-driven subnegotiation. Checks that no subnegotiation is made. + */ @Override - public void testAnswerSubnegotiation() - { - int subn[] = - { - TelnetCommand.IAC, TelnetCommand.SB, TelnetOption.SUPPRESS_GO_AHEAD, - 1, TelnetCommand.IAC, TelnetCommand.SE, - }; + public void testStartSubnegotiation() { - int resp1[] = opthand1.answerSubnegotiation(subn, subn.length); + final int resp1[] = opthand1.startSubnegotiationLocal(); + final int resp2[] = opthand1.startSubnegotiationRemote(); - assertEquals(resp1, null); + assertNull(resp1); + assertNull(resp2); } } diff --git a/src/test/java/org/apache/commons/net/telnet/TelnetClientFunctionalTest.java b/src/test/java/org/apache/commons/net/telnet/TelnetClientFunctionalTest.java index 84fc570..a759391 100644 --- a/src/test/java/org/apache/commons/net/telnet/TelnetClientFunctionalTest.java +++ b/src/test/java/org/apache/commons/net/telnet/TelnetClientFunctionalTest.java @@ -16,104 +16,79 @@ */ package org.apache.commons.net.telnet; -import junit.framework.TestCase; import java.io.InputStream; import java.io.OutputStream; -/*** - * JUnit functional test for TelnetClient. - * Connects to the weather forecast service - * rainmaker.wunderground.com and asks for Los Angeles forecast. - ***/ -public class TelnetClientFunctionalTest extends TestCase -{ +import junit.framework.TestCase; + +/** + * JUnit functional test for TelnetClient. Connects to the weather forecast service rainmaker.wunderground.com and asks for Los Angeles forecast. + */ +public class TelnetClientFunctionalTest extends TestCase { protected TelnetClient tc1; - /*** + /** * test setUp - ***/ + */ @Override - protected void setUp() - { + protected void setUp() { tc1 = new TelnetClient(); } /* - * Do the functional test: - * - connect to the weather service - * - press return on the first menu - * - send LAX on the second menu - * - send X to exit - ***/ - public void testFunctionalTest() throws Exception - { + * Do the functional test: - connect to the weather service - press return on the first menu - send LAX on the second menu - send X to exit + */ + public void testFunctionalTest() throws Exception { boolean testresult = false; tc1.connect("rainmaker.wunderground.com", 3000); - InputStream is = tc1.getInputStream(); - OutputStream os = tc1.getOutputStream(); + try (final InputStream is = tc1.getInputStream(); final OutputStream os = tc1.getOutputStream()) { - boolean cont = waitForString(is, "Return to continue:", 30000); - if (cont) - { - os.write("\n".getBytes()); - os.flush(); - cont = waitForString(is, "city code--", 30000); - } - if (cont) - { - os.write("LAX\n".getBytes()); - os.flush(); - cont = waitForString(is, "Los Angeles", 30000); - } - if (cont) - { - cont = waitForString(is, "X to exit:", 30000); - } - if (cont) - { - os.write("X\n".getBytes()); - os.flush(); - tc1.disconnect(); - testresult = true; - } + boolean cont = waitForString(is, "Return to continue:", 30000); + if (cont) { + os.write("\n".getBytes()); + os.flush(); + cont = waitForString(is, "city code--", 30000); + } + if (cont) { + os.write("LAX\n".getBytes()); + os.flush(); + cont = waitForString(is, "Los Angeles", 30000); + } + if (cont) { + cont = waitForString(is, "X to exit:", 30000); + } + if (cont) { + os.write("X\n".getBytes()); + os.flush(); + tc1.disconnect(); + testresult = true; + } - assertTrue(testresult); - os.close(); - is.close(); + assertTrue(testresult); + } } - /* * Helper method. waits for a string with timeout */ - public boolean waitForString(InputStream is, String end, long timeout) throws Exception - { - byte buffer[] = new byte[32]; - long starttime = System.currentTimeMillis(); + public boolean waitForString(final InputStream is, final String end, final long timeout) throws Exception { + final byte buffer[] = new byte[32]; + final long starttime = System.currentTimeMillis(); String readbytes = ""; - while((readbytes.indexOf(end) < 0) && - ((System.currentTimeMillis() - starttime) < timeout)) - { - if(is.available() > 0) - { - int ret_read = is.read(buffer); + while ((readbytes.indexOf(end) < 0) && ((System.currentTimeMillis() - starttime) < timeout)) { + if (is.available() > 0) { + final int ret_read = is.read(buffer); readbytes = readbytes + new String(buffer, 0, ret_read); - } - else - { + } else { Thread.sleep(500); } } - if(readbytes.indexOf(end) >= 0) - { + if (readbytes.indexOf(end) >= 0) { return (true); } - else - { - return (false); - } + return (false); } } \ No newline at end of file diff --git a/src/test/java/org/apache/commons/net/telnet/TelnetClientTest.java b/src/test/java/org/apache/commons/net/telnet/TelnetClientTest.java index 068b252..298076e 100644 --- a/src/test/java/org/apache/commons/net/telnet/TelnetClientTest.java +++ b/src/test/java/org/apache/commons/net/telnet/TelnetClientTest.java @@ -15,7 +15,6 @@ * limitations under the License. */ package org.apache.commons.net.telnet; -import junit.framework.TestCase; import java.io.IOException; import java.io.InputStream; @@ -23,33 +22,28 @@ import java.io.OutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; +import junit.framework.TestCase; + /** - * JUnit test class for TelnetClient.s - * Implements protocol compliance tests + * JUnit test class for TelnetClient.s Implements protocol compliance tests */ -public class TelnetClientTest -extends TestCase implements TelnetNotificationHandler -{ +public class TelnetClientTest extends TestCase implements TelnetNotificationHandler { /** - * Handy holder to hold both sides of the connection - * used in testing for clarity. + * Handy holder to hold both sides of the connection used in testing for clarity. */ private class TestConnection { private final TelnetTestSimpleServer server; private final TelnetClient client; private final int port; - TestConnection( - TelnetTestSimpleServer server, - TelnetClient client, - int port) - { + + TestConnection(final TelnetTestSimpleServer server, final TelnetClient client, final int port) { this.server = server; this.client = client; this.port = port; } + protected void close() { - TelnetClientTest.this.closeConnection( - this.server, this.client, this.port); + TelnetClientTest.this.closeConnection(this.server, this.client, this.port); } } @@ -59,78 +53,152 @@ extends TestCase implements TelnetNotificationHandler private TestConnection OPTIONS; private TestConnection ANSI; private TestConnection NOREAD; + private TestConnection SMALL_BUFFER; + + private final int NUM_CONNECTIONS = 5; - private final int NUM_CONNECTIONS = 4; + protected int numdo; + protected int numdont; + protected int numwill; + protected int numwont; + + protected int[] lastSubnegotiation; + protected int lastSubnegotiationLength; + + void closeConnection(final TelnetTestSimpleServer server, final TelnetClient client, final int port) { + if (server != null) { + server.disconnect(); + server.stop(); + } + try { + if (client != null) { + client.disconnect(); + } + } catch (final IOException e) { + System.err.println("failed to close client-server connection on port " + port); + System.err.println("ERROR in closeConnection(), " + e.getMessage()); + } + + } + + /* + * Helper method. compares two arrays of int + */ + protected boolean equalBytes(final byte a1[], final byte a2[]) { + if (a1.length != a2.length) { + return false; + } + boolean result = true; + for (int ii = 0; ii < a1.length; ii++) { + if (a1[ii] != a2[ii]) { + result = false; + } + } + return result; + } - protected int numdo = 0; - protected int numdont = 0; - protected int numwill = 0; - protected int numwont = 0; + /* + * Callback method called when TelnetClient receives an option negotiation command. <p> + * + * @param negotiation_code - type of negotiation command received (RECEIVED_DO, RECEIVED_DONT, RECEIVED_WILL, RECEIVED_WONT) <p> + * + * @param option_code - code of the option negotiated <p> + */ + @Override + public void receivedNegotiation(final int negotiation_code, final int option_code) { + switch (negotiation_code) { + case TelnetNotificationHandler.RECEIVED_DO: + numdo++; + break; + case TelnetNotificationHandler.RECEIVED_DONT: + numdont++; + break; + case TelnetNotificationHandler.RECEIVED_WILL: + numwill++; + break; + case TelnetNotificationHandler.RECEIVED_WONT: + numwont++; + break; + default: + break; + } + } /* * open connections needed for the tests for the test. */ @Override - protected void setUp() throws Exception - { + protected void setUp() throws Exception { + final SimpleOptionHandler subnegotiationSizeHandler = new SimpleOptionHandler(99, false, false, true, false) { + @Override + public int[] answerSubnegotiation(final int[] suboptionData, final int suboptionLength) { + lastSubnegotiation = suboptionData; + lastSubnegotiationLength = suboptionLength; + return null; + } + }; + int socket = 0; super.setUp(); - for (int port = 3333; socket < NUM_CONNECTIONS && port < 4000; port++) - { + for (int port = 3333; socket < NUM_CONNECTIONS && port < 4000; port++) { TelnetTestSimpleServer server = null; TelnetClient client = null; - try { - server = new TelnetTestSimpleServer(port); + try { + server = new TelnetTestSimpleServer(port); switch (socket) { - case 0: - client = new TelnetClient(); - // redundant but makes code clearer. - client.setReaderThread(true); - client.connect("127.0.0.1", port); - STANDARD = new TestConnection(server, client, port); - break; - case 1: - client = new TelnetClient(); - TerminalTypeOptionHandler ttopt = - new TerminalTypeOptionHandler("VT100", false, false, true, false); - EchoOptionHandler echoopt = - new EchoOptionHandler(true, false, true, false); - SuppressGAOptionHandler gaopt = - new SuppressGAOptionHandler(true, true, true, true); - - client.addOptionHandler(ttopt); - client.addOptionHandler(echoopt); - client.addOptionHandler(gaopt); - client.connect("127.0.0.1", port); - OPTIONS = new TestConnection(server, client, port); - break; - case 2: - client = new TelnetClient("ANSI"); - client.connect("127.0.0.1", port); - ANSI = new TestConnection(server, client, port); - break; - case 3: - client = new TelnetClient(); - client.setReaderThread(false); - client.connect("127.0.0.1", port); - NOREAD = new TestConnection(server, client, port); - break; - } - // only increment socket number on success - socket++; - } catch (IOException e) { - closeConnection(server, client, port); - } - } - if (socket < NUM_CONNECTIONS) { - System.err.println("Only created "+socket+" clients; wanted "+NUM_CONNECTIONS); - } - Thread.sleep(1000); + case 0: + client = new TelnetClient(); + // redundant but makes code clearer. + client.setReaderThread(true); + client.addOptionHandler(subnegotiationSizeHandler); + client.connect("127.0.0.1", port); + STANDARD = new TestConnection(server, client, port); + break; + case 1: + client = new TelnetClient(); + final TerminalTypeOptionHandler ttopt = new TerminalTypeOptionHandler("VT100", false, false, true, false); + final EchoOptionHandler echoopt = new EchoOptionHandler(true, false, true, false); + final SuppressGAOptionHandler gaopt = new SuppressGAOptionHandler(true, true, true, true); + + client.addOptionHandler(ttopt); + client.addOptionHandler(echoopt); + client.addOptionHandler(gaopt); + client.connect("127.0.0.1", port); + OPTIONS = new TestConnection(server, client, port); + break; + case 2: + client = new TelnetClient("ANSI"); + client.connect("127.0.0.1", port); + ANSI = new TestConnection(server, client, port); + break; + case 3: + client = new TelnetClient(); + client.setReaderThread(false); + client.connect("127.0.0.1", port); + NOREAD = new TestConnection(server, client, port); + break; + case 4: + client = new TelnetClient(8); + client.addOptionHandler(subnegotiationSizeHandler); + client.connect("127.0.0.1", port); + SMALL_BUFFER = new TestConnection(server, client, port); + break; + } + // only increment socket number on success + socket++; + } catch (final IOException e) { + closeConnection(server, client, port); + } + } + if (socket < NUM_CONNECTIONS) { + System.err.println("Only created " + socket + " clients; wanted " + NUM_CONNECTIONS); + } + Thread.sleep(1000); } /* - * @throws java.lang.Exception + * @throws Exception */ @Override protected void tearDown() throws Exception { @@ -138,84 +206,142 @@ extends TestCase implements TelnetNotificationHandler ANSI.close(); OPTIONS.close(); STANDARD.close(); + SMALL_BUFFER.close(); try { Thread.sleep(1000); - } catch (InterruptedException ie) { - //do nothing + } catch (final InterruptedException ie) { + // do nothing } super.tearDown(); } + /* + * test of AYT functionality + */ + public void testAYT() throws Exception { + boolean ayt_true_ok = false; + boolean ayt_false_ok = false; + + final byte AYT[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.AYT }; + final byte response[] = { (byte) '[', (byte) 'Y', (byte) 'e', (byte) 's', (byte) ']' }; + final String inputs[] = new String[1]; + final String outputs[] = new String[1]; + inputs[0] = new String(AYT); + outputs[0] = new String(response); + final OutputStream os = ANSI.server.getOutputStream(); + final InputStream is = ANSI.server.getInputStream(); + final TelnetTestResponder tr = new TelnetTestResponder(is, os, inputs, outputs, 30000); + assertNotNull(tr); + final boolean res1 = ANSI.client.sendAYT(2000); - void closeConnection(TelnetTestSimpleServer server, TelnetClient client, int port) { - if (server != null) { - server.disconnect(); - server.stop(); + if (res1 == true) { + ayt_true_ok = true; } - try { - if (client != null) { - client.disconnect(); + + Thread.sleep(1000); + is.skip(is.available()); + + final boolean res2 = ANSI.client.sendAYT(2000); + + if (res2 == false) { + ayt_false_ok = true; + } + + assertTrue(ayt_true_ok); + assertTrue(ayt_false_ok); + } + + /* + * protocol compliance test in case of option handler removal + */ + public void testDeleteOptionHandler() throws Exception { + boolean remove_ok = false; + boolean remove_invalid_ok1 = false; + boolean remove_invalid_ok2 = false; + + final byte buffread[] = new byte[6]; + final byte send[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, (byte) TelnetOption.ECHO, (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, + (byte) TelnetOption.SUPPRESS_GO_AHEAD, (byte) TelnetCommand.IAC, (byte) TelnetCommand.WILL, (byte) TelnetOption.SUPPRESS_GO_AHEAD }; + + final byte expected[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT, (byte) TelnetOption.SUPPRESS_GO_AHEAD, (byte) TelnetCommand.IAC, + (byte) TelnetCommand.DONT, (byte) TelnetOption.SUPPRESS_GO_AHEAD }; + + final InputStream is = OPTIONS.server.getInputStream(); + final OutputStream os = OPTIONS.server.getOutputStream(); + Thread.sleep(1000); + is.skip(is.available()); + os.write(send); + os.flush(); + Thread.sleep(1000); + if (is.available() == 0) { + OPTIONS.client.deleteOptionHandler(TelnetOption.SUPPRESS_GO_AHEAD); + Thread.sleep(1000); + if (is.available() == 6) { + is.read(buffread); + if (equalBytes(buffread, expected)) { + remove_ok = true; + } } - } catch (IOException e) { - System.err.println("failed to close client-server connection on port " + port); - System.err.println("ERROR in closeConnection(), "+ e.getMessage()); } + try { + OPTIONS.client.deleteOptionHandler(TelnetOption.SUPPRESS_GO_AHEAD); + } catch (final Exception e) { + remove_invalid_ok1 = true; + } + + try { + OPTIONS.client.deleteOptionHandler(550); + } catch (final Exception e) { + remove_invalid_ok2 = true; + } + + assertTrue(remove_ok); + assertTrue(remove_invalid_ok1); + assertTrue(remove_invalid_ok2); + assertTrue(OPTIONS.client.getLocalOptionState(TelnetOption.ECHO)); + assertFalse(OPTIONS.client.getLocalOptionState(TelnetOption.SUPPRESS_GO_AHEAD)); + assertFalse(OPTIONS.client.getLocalOptionState(TelnetOption.SUPPRESS_GO_AHEAD)); } /* * tests the initial condition of the sessions */ - public void testInitial() throws Exception - { + public void testInitial() throws Exception { boolean connect1_ok = false; boolean connect2_ok = false; boolean connect3_ok = false; boolean init2_ok = false; boolean add_invalid_ok1 = false; boolean add_invalid_ok2 = false; - byte buffread2[] = new byte[9]; - byte expected2[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.WILL, - (byte) TelnetOption.ECHO, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.WILL, - (byte) TelnetOption.SUPPRESS_GO_AHEAD, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, - (byte) TelnetOption.SUPPRESS_GO_AHEAD, - }; + final byte buffread2[] = new byte[9]; + final byte expected2[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.WILL, (byte) TelnetOption.ECHO, (byte) TelnetCommand.IAC, + (byte) TelnetCommand.WILL, (byte) TelnetOption.SUPPRESS_GO_AHEAD, (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, + (byte) TelnetOption.SUPPRESS_GO_AHEAD, }; - SimpleOptionHandler hand = new SimpleOptionHandler(550); - try - { + final SimpleOptionHandler hand = new SimpleOptionHandler(550); + try { STANDARD.client.addOptionHandler(hand); - } - catch (Exception e) - { + } catch (final Exception e) { add_invalid_ok1 = true; } - try - { + try { OPTIONS.client.addOptionHandler(hand); - } - catch (Exception e) - { + } catch (final Exception e) { add_invalid_ok2 = true; } - InputStream is1 = STANDARD.server.getInputStream(); + final InputStream is1 = STANDARD.server.getInputStream(); Thread.sleep(1000); - if(is1.available() == 0) - { + if (is1.available() == 0) { connect1_ok = true; } Thread.sleep(1000); - InputStream is2 = OPTIONS.server.getInputStream(); - if(is2.available() == 9) - { + final InputStream is2 = OPTIONS.server.getInputStream(); + if (is2.available() == 9) { is2.read(buffread2); connect2_ok = true; @@ -224,125 +350,164 @@ extends TestCase implements TelnetNotificationHandler } } - InputStream is3 = ANSI.server.getInputStream(); + final InputStream is3 = ANSI.server.getInputStream(); Thread.sleep(1000); - if(is3.available() == 0) - { + if (is3.available() == 0) { connect3_ok = true; } - assertTrue(connect1_ok); assertTrue(connect2_ok); assertTrue(connect3_ok); - assertTrue(!STANDARD.client.getLocalOptionState(TelnetOption.ECHO)); - assertTrue(!STANDARD.client.getRemoteOptionState(TelnetOption.ECHO)); - assertTrue(!OPTIONS.client.getLocalOptionState(TelnetOption.ECHO)); - assertTrue(!OPTIONS.client.getRemoteOptionState(TelnetOption.ECHO)); - assertTrue(!ANSI.client.getLocalOptionState(TelnetOption.TERMINAL_TYPE)); - assertTrue(!ANSI.client.getRemoteOptionState(TelnetOption.TERMINAL_TYPE)); + assertFalse(STANDARD.client.getLocalOptionState(TelnetOption.ECHO)); + assertFalse(STANDARD.client.getRemoteOptionState(TelnetOption.ECHO)); + assertFalse(OPTIONS.client.getLocalOptionState(TelnetOption.ECHO)); + assertFalse(OPTIONS.client.getRemoteOptionState(TelnetOption.ECHO)); + assertFalse(ANSI.client.getLocalOptionState(TelnetOption.TERMINAL_TYPE)); + assertFalse(ANSI.client.getRemoteOptionState(TelnetOption.TERMINAL_TYPE)); assertTrue(init2_ok); assertTrue(add_invalid_ok1); assertTrue(add_invalid_ok2); } + /* + * test of max subnegotiation length + */ + public void testMaxSubnegotiationLength() throws Exception { + final byte send[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.SB, (byte) 99, (byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5, (byte) 6, + (byte) 7, (byte) 8, (byte) 9, (byte) 10, (byte) 11, (byte) 12, (byte) 13, (byte) 14, (byte) 15, (byte) TelnetCommand.IAC, + (byte) TelnetCommand.SE, }; + + final OutputStream os1 = SMALL_BUFFER.server.getOutputStream(); + os1.write(send); + os1.flush(); + Thread.sleep(500); + + // we sent 16 bytes, but the buffer size should just be 8 + assertEquals(8, lastSubnegotiationLength); + assertEquals(8, lastSubnegotiation.length); + assertEquals(99, lastSubnegotiation[0]); + assertEquals(1, lastSubnegotiation[1]); + assertEquals(2, lastSubnegotiation[2]); + assertEquals(3, lastSubnegotiation[3]); + assertEquals(4, lastSubnegotiation[4]); + assertEquals(5, lastSubnegotiation[5]); + assertEquals(6, lastSubnegotiation[6]); + assertEquals(7, lastSubnegotiation[7]); + + final OutputStream os2 = STANDARD.server.getOutputStream(); + os2.write(send); + os2.flush(); + Thread.sleep(500); + + // the standard subnegotiation buffer size is 512 + assertEquals(16, lastSubnegotiationLength); + assertEquals(512, lastSubnegotiation.length); + assertEquals(99, lastSubnegotiation[0]); + assertEquals(1, lastSubnegotiation[1]); + assertEquals(2, lastSubnegotiation[2]); + assertEquals(3, lastSubnegotiation[3]); + assertEquals(4, lastSubnegotiation[4]); + assertEquals(5, lastSubnegotiation[5]); + assertEquals(6, lastSubnegotiation[6]); + assertEquals(7, lastSubnegotiation[7]); + } + + /* + * test of option negotiation notification + */ + public void testNotification() throws Exception { + final byte buffread1[] = new byte[6]; + final byte send1[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, (byte) 15, (byte) TelnetCommand.IAC, (byte) TelnetCommand.WILL, (byte) 15, }; + + final byte buffread2[] = new byte[9]; + final byte send2[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, (byte) TelnetOption.TERMINAL_TYPE, (byte) TelnetCommand.IAC, + (byte) TelnetCommand.DONT, (byte) TelnetOption.ECHO, (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, (byte) TelnetOption.SUPPRESS_GO_AHEAD, + (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT, (byte) TelnetOption.SUPPRESS_GO_AHEAD }; + + final byte buffread2b[] = new byte[11]; + + numdo = 0; + numdont = 0; + numwill = 0; + numwont = 0; + OPTIONS.client.registerNotifHandler(this); + + final InputStream is1 = STANDARD.server.getInputStream(); + final OutputStream os1 = STANDARD.server.getOutputStream(); + is1.skip(is1.available()); + os1.write(send1); + os1.flush(); + Thread.sleep(500); + if (is1.available() > 0) { + is1.read(buffread1); + } + + final InputStream is2 = OPTIONS.server.getInputStream(); + final OutputStream os2 = OPTIONS.server.getOutputStream(); + Thread.sleep(500); + is2.skip(is2.available()); + os2.write(send2); + os2.flush(); + Thread.sleep(500); + if (is2.available() > 0) { + is2.read(buffread2); + Thread.sleep(1000); + if (is2.available() > 0) { + is2.read(buffread2b); + } + } + + assertEquals(2, numdo); + assertEquals(1, numdont); + assertEquals(1, numwont); + assertEquals(0, numwill); + } + /* * protocol compliance test for option negotiation */ - public void testOptionNegotiation() throws Exception - { + public void testOptionNegotiation() throws Exception { boolean negotiation1_ok = false; - byte buffread1[] = new byte[6]; - byte send1[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, (byte) 15, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.WILL, (byte) 15, - }; - byte expected1[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT, (byte) 15, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DONT, (byte) 15, - }; + final byte buffread1[] = new byte[6]; + final byte send1[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, (byte) 15, (byte) TelnetCommand.IAC, (byte) TelnetCommand.WILL, (byte) 15, }; + final byte expected1[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT, (byte) 15, (byte) TelnetCommand.IAC, (byte) TelnetCommand.DONT, + (byte) 15, }; boolean negotiation2_ok = false; - byte buffread2[] = new byte[9]; - byte send2[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, - (byte) TelnetOption.TERMINAL_TYPE, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DONT, - (byte) TelnetOption.ECHO, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, - (byte) TelnetOption.SUPPRESS_GO_AHEAD, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT, - (byte) TelnetOption.SUPPRESS_GO_AHEAD - }; - byte expected2[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.WILL, - (byte) TelnetOption.TERMINAL_TYPE, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT, - (byte) TelnetOption.ECHO, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DONT, - (byte) TelnetOption.SUPPRESS_GO_AHEAD - }; - - byte buffread2b[] = new byte[11]; - byte send2b[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.SB, - (byte) TelnetOption.TERMINAL_TYPE, - (byte) 1, (byte) TelnetCommand.IAC, (byte) TelnetCommand.SE, - }; - byte expected2b[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.SB, - (byte) TelnetOption.TERMINAL_TYPE, - (byte) 0, (byte) 'V', (byte) 'T', (byte) '1', (byte) '0', - (byte) '0', - (byte) TelnetCommand.IAC, (byte) TelnetCommand.SE, - }; + final byte buffread2[] = new byte[9]; + final byte send2[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, (byte) TelnetOption.TERMINAL_TYPE, (byte) TelnetCommand.IAC, + (byte) TelnetCommand.DONT, (byte) TelnetOption.ECHO, (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, (byte) TelnetOption.SUPPRESS_GO_AHEAD, + (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT, (byte) TelnetOption.SUPPRESS_GO_AHEAD }; + final byte expected2[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.WILL, (byte) TelnetOption.TERMINAL_TYPE, (byte) TelnetCommand.IAC, + (byte) TelnetCommand.WONT, (byte) TelnetOption.ECHO, (byte) TelnetCommand.IAC, (byte) TelnetCommand.DONT, + (byte) TelnetOption.SUPPRESS_GO_AHEAD }; + + final byte buffread2b[] = new byte[11]; + final byte send2b[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.SB, (byte) TelnetOption.TERMINAL_TYPE, (byte) 1, (byte) TelnetCommand.IAC, + (byte) TelnetCommand.SE, }; + final byte expected2b[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.SB, (byte) TelnetOption.TERMINAL_TYPE, (byte) 0, (byte) 'V', (byte) 'T', + (byte) '1', (byte) '0', (byte) '0', (byte) TelnetCommand.IAC, (byte) TelnetCommand.SE, }; boolean negotiation3_ok = false; - byte buffread3[] = new byte[6]; - byte send3[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, - (byte) TelnetOption.TERMINAL_TYPE, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, - (byte) TelnetOption.SUPPRESS_GO_AHEAD - }; - byte expected3[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.WILL, - (byte) TelnetOption.TERMINAL_TYPE, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT, - (byte) TelnetOption.SUPPRESS_GO_AHEAD - }; - byte buffread3b[] = new byte[10]; - byte send3b[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.SB, - (byte) TelnetOption.TERMINAL_TYPE, - (byte) 1, (byte) TelnetCommand.IAC, (byte) TelnetCommand.SE, - }; - byte expected3b[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.SB, - (byte) TelnetOption.TERMINAL_TYPE, - (byte) 0, (byte) 'A', (byte) 'N', (byte) 'S', (byte) 'I', - (byte) TelnetCommand.IAC, (byte) TelnetCommand.SE, - }; - - - InputStream is1 = STANDARD.server.getInputStream(); - OutputStream os1 = STANDARD.server.getOutputStream(); + final byte buffread3[] = new byte[6]; + final byte send3[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, (byte) TelnetOption.TERMINAL_TYPE, (byte) TelnetCommand.IAC, + (byte) TelnetCommand.DO, (byte) TelnetOption.SUPPRESS_GO_AHEAD }; + final byte expected3[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.WILL, (byte) TelnetOption.TERMINAL_TYPE, (byte) TelnetCommand.IAC, + (byte) TelnetCommand.WONT, (byte) TelnetOption.SUPPRESS_GO_AHEAD }; + final byte buffread3b[] = new byte[10]; + final byte send3b[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.SB, (byte) TelnetOption.TERMINAL_TYPE, (byte) 1, (byte) TelnetCommand.IAC, + (byte) TelnetCommand.SE, }; + final byte expected3b[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.SB, (byte) TelnetOption.TERMINAL_TYPE, (byte) 0, (byte) 'A', (byte) 'N', + (byte) 'S', (byte) 'I', (byte) TelnetCommand.IAC, (byte) TelnetCommand.SE, }; + + final InputStream is1 = STANDARD.server.getInputStream(); + final OutputStream os1 = STANDARD.server.getOutputStream(); is1.skip(is1.available()); os1.write(send1); os1.flush(); Thread.sleep(1000); - if(is1.available() == 6) - { + if (is1.available() == 6) { is1.read(buffread1); if (equalBytes(buffread1, expected1)) { @@ -350,29 +515,26 @@ extends TestCase implements TelnetNotificationHandler } } - InputStream is2 = OPTIONS.server.getInputStream(); - OutputStream os2 = OPTIONS.server.getOutputStream(); + final InputStream is2 = OPTIONS.server.getInputStream(); + final OutputStream os2 = OPTIONS.server.getOutputStream(); Thread.sleep(1000); is2.skip(is2.available()); os2.write(send2); os2.flush(); Thread.sleep(1000); - if(is2.available() == 9) - { + if (is2.available() == 9) { is2.read(buffread2); if (equalBytes(buffread2, expected2)) { negotiation2_ok = true; } - if(negotiation2_ok) - { + if (negotiation2_ok) { negotiation2_ok = false; os2.write(send2b); os2.flush(); Thread.sleep(1000); - if(is2.available() == 11) - { + if (is2.available() == 11) { is2.read(buffread2b); if (equalBytes(buffread2b, expected2b)) { @@ -382,29 +544,26 @@ extends TestCase implements TelnetNotificationHandler } } - InputStream is3 = ANSI.server.getInputStream(); - OutputStream os3 = ANSI.server.getOutputStream(); + final InputStream is3 = ANSI.server.getInputStream(); + final OutputStream os3 = ANSI.server.getOutputStream(); Thread.sleep(1000); is3.skip(is3.available()); os3.write(send3); os3.flush(); Thread.sleep(1000); - if(is3.available() == 6) - { + if (is3.available() == 6) { is3.read(buffread3); if (equalBytes(buffread3, expected3)) { negotiation3_ok = true; } - if(negotiation3_ok) - { + if (negotiation3_ok) { negotiation3_ok = false; os3.write(send3b); os3.flush(); Thread.sleep(1000); - if(is3.available() == 10) - { + if (is3.available() == 10) { is3.read(buffread3b); if (equalBytes(buffread3b, expected3b)) { negotiation3_ok = true; @@ -416,80 +575,54 @@ extends TestCase implements TelnetNotificationHandler assertTrue(negotiation1_ok); assertTrue(negotiation2_ok); assertTrue(negotiation3_ok); - assertTrue(!STANDARD.client.getLocalOptionState(15)); - assertTrue(!STANDARD.client.getRemoteOptionState(15)); - assertTrue(!STANDARD.client.getLocalOptionState(TelnetOption.TERMINAL_TYPE)); - assertTrue(!OPTIONS.client.getLocalOptionState(TelnetOption.ECHO)); - assertTrue(!OPTIONS.client.getRemoteOptionState(TelnetOption.ECHO)); + assertFalse(STANDARD.client.getLocalOptionState(15)); + assertFalse(STANDARD.client.getRemoteOptionState(15)); + assertFalse(STANDARD.client.getLocalOptionState(TelnetOption.TERMINAL_TYPE)); + assertFalse(OPTIONS.client.getLocalOptionState(TelnetOption.ECHO)); + assertFalse(OPTIONS.client.getRemoteOptionState(TelnetOption.ECHO)); assertTrue(OPTIONS.client.getLocalOptionState(TelnetOption.SUPPRESS_GO_AHEAD)); - assertTrue(!OPTIONS.client.getRemoteOptionState(TelnetOption.SUPPRESS_GO_AHEAD)); + assertFalse(OPTIONS.client.getRemoteOptionState(TelnetOption.SUPPRESS_GO_AHEAD)); assertTrue(OPTIONS.client.getLocalOptionState(TelnetOption.TERMINAL_TYPE)); assertTrue(ANSI.client.getLocalOptionState(TelnetOption.TERMINAL_TYPE)); - assertTrue(!OPTIONS.client.getLocalOptionState(TelnetOption.ECHO)); + assertFalse(OPTIONS.client.getLocalOptionState(TelnetOption.ECHO)); } - /* * protocol compliance test for option renegotiation */ - public void testOptionRenegotiation() throws Exception - { + public void testOptionRenegotiation() throws Exception { boolean negotiation1_ok = false; - byte buffread[] = new byte[6]; - byte send[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, - (byte) TelnetOption.ECHO, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DONT, - (byte) TelnetOption.SUPPRESS_GO_AHEAD, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT, - (byte) TelnetOption.SUPPRESS_GO_AHEAD - }; - byte expected[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT, - (byte) TelnetOption.SUPPRESS_GO_AHEAD, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DONT, - (byte) TelnetOption.SUPPRESS_GO_AHEAD - }; - - byte buffread2[] = new byte[3]; - byte send2[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DONT, - (byte) TelnetOption.ECHO, - }; - byte expected2[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT, - (byte) TelnetOption.ECHO, - }; + final byte buffread[] = new byte[6]; + final byte send[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, (byte) TelnetOption.ECHO, (byte) TelnetCommand.IAC, (byte) TelnetCommand.DONT, + (byte) TelnetOption.SUPPRESS_GO_AHEAD, (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT, (byte) TelnetOption.SUPPRESS_GO_AHEAD }; + final byte expected[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT, (byte) TelnetOption.SUPPRESS_GO_AHEAD, (byte) TelnetCommand.IAC, + (byte) TelnetCommand.DONT, (byte) TelnetOption.SUPPRESS_GO_AHEAD }; + final byte buffread2[] = new byte[3]; + final byte send2[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.DONT, (byte) TelnetOption.ECHO, }; + final byte expected2[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT, (byte) TelnetOption.ECHO, }; - InputStream is = OPTIONS.server.getInputStream(); - OutputStream os = OPTIONS.server.getOutputStream(); + final InputStream is = OPTIONS.server.getInputStream(); + final OutputStream os = OPTIONS.server.getOutputStream(); Thread.sleep(1000); is.skip(is.available()); os.write(send); os.flush(); Thread.sleep(1000); - if(is.available() == 6) - { + if (is.available() == 6) { is.read(buffread); if (equalBytes(buffread, expected)) { negotiation1_ok = true; } - if(negotiation1_ok) - { + if (negotiation1_ok) { negotiation1_ok = false; os.write(send2); os.flush(); Thread.sleep(1000); - if(is.available() == 3) - { + if (is.available() == 3) { is.read(buffread2); if (equalBytes(buffread2, expected2)) { negotiation1_ok = true; @@ -499,323 +632,56 @@ extends TestCase implements TelnetNotificationHandler } assertTrue(negotiation1_ok); - assertTrue(!OPTIONS.client.getLocalOptionState(TelnetOption.ECHO)); - } - - /* - * test of option negotiation notification - */ - public void testNotification() throws Exception - { - byte buffread1[] = new byte[6]; - byte send1[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, (byte) 15, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.WILL, (byte) 15, - }; - - byte buffread2[] = new byte[9]; - byte send2[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, - (byte) TelnetOption.TERMINAL_TYPE, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DONT, - (byte) TelnetOption.ECHO, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, - (byte) TelnetOption.SUPPRESS_GO_AHEAD, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT, - (byte) TelnetOption.SUPPRESS_GO_AHEAD - }; - - byte buffread2b[] = new byte[11]; - - - numdo = 0; - numdont = 0; - numwill = 0; - numwont = 0; - OPTIONS.client.registerNotifHandler(this); - - InputStream is1 = STANDARD.server.getInputStream(); - OutputStream os1 = STANDARD.server.getOutputStream(); - is1.skip(is1.available()); - os1.write(send1); - os1.flush(); - Thread.sleep(500); - if(is1.available() > 0) - { - is1.read(buffread1); - } - - InputStream is2 = OPTIONS.server.getInputStream(); - OutputStream os2 = OPTIONS.server.getOutputStream(); - Thread.sleep(500); - is2.skip(is2.available()); - os2.write(send2); - os2.flush(); - Thread.sleep(500); - if(is2.available() > 0) - { - is2.read(buffread2); - Thread.sleep(1000); - if(is2.available() > 0) - { - is2.read(buffread2b); - } - } - - - assertEquals(2, numdo); - assertEquals(1, numdont); - assertEquals(1, numwont); - assertEquals(0, numwill); - } - - - /* - * protocol compliance test in case of option handler removal - */ - public void testDeleteOptionHandler() throws Exception - { - boolean remove_ok = false; - boolean remove_invalid_ok1 = false; - boolean remove_invalid_ok2 = false; - - byte buffread[] = new byte[6]; - byte send[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, - (byte) TelnetOption.ECHO, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, - (byte) TelnetOption.SUPPRESS_GO_AHEAD, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.WILL, - (byte) TelnetOption.SUPPRESS_GO_AHEAD - }; - - byte expected[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT, - (byte) TelnetOption.SUPPRESS_GO_AHEAD, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DONT, - (byte) TelnetOption.SUPPRESS_GO_AHEAD - }; - - InputStream is = OPTIONS.server.getInputStream(); - OutputStream os = OPTIONS.server.getOutputStream(); - Thread.sleep(1000); - is.skip(is.available()); - os.write(send); - os.flush(); - Thread.sleep(1000); - if(is.available() == 0) - { - OPTIONS.client.deleteOptionHandler(TelnetOption.SUPPRESS_GO_AHEAD); - Thread.sleep(1000); - if(is.available() == 6) - { - is.read(buffread); - if (equalBytes(buffread, expected)) { - remove_ok = true; - } - } - } - - try - { - OPTIONS.client.deleteOptionHandler(TelnetOption.SUPPRESS_GO_AHEAD); - } - catch (Exception e) - { - remove_invalid_ok1 = true; - } - - try - { - OPTIONS.client.deleteOptionHandler(550); - } - catch (Exception e) - { - remove_invalid_ok2 = true; - } - - assertTrue(remove_ok); - assertTrue(remove_invalid_ok1); - assertTrue(remove_invalid_ok2); - assertTrue(OPTIONS.client.getLocalOptionState(TelnetOption.ECHO)); - assertTrue(!OPTIONS.client.getLocalOptionState(TelnetOption.SUPPRESS_GO_AHEAD)); - assertTrue(!OPTIONS.client.getLocalOptionState(TelnetOption.SUPPRESS_GO_AHEAD)); - } - - - /* - * test of AYT functionality - */ - public void testAYT() throws Exception - { - boolean ayt_true_ok = false; - boolean ayt_false_ok = false; - - - byte AYT[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.AYT }; - byte response[] = - { (byte) '[', (byte) 'Y', (byte) 'e', (byte) 's', (byte) ']' }; - String inputs[] = new String[1]; - String outputs[] = new String[1]; - inputs[0] = new String (AYT); - outputs[0] = new String (response); - - - OutputStream os = ANSI.server.getOutputStream(); - InputStream is = ANSI.server.getInputStream(); - TelnetTestResponder tr = - new TelnetTestResponder(is, os, inputs, outputs, 30000); - assertNotNull(tr); - boolean res1 = ANSI.client.sendAYT(2000); - - if (res1 == true) { - ayt_true_ok=true; - } - - Thread.sleep(1000); - is.skip(is.available()); - - boolean res2 = ANSI.client.sendAYT(2000); - - if (res2 == false) { - ayt_false_ok=true; - } - - - assertTrue(ayt_true_ok); - assertTrue(ayt_false_ok); - } - - /* - * test of Spy functionality - */ - public void testSpy() throws Exception - { - boolean test1spy_ok = false; - boolean test2spy_ok = false; - boolean stopspy_ok = false; - byte expected1[] = - { (byte) 't', (byte) 'e', (byte) 's', (byte) 't', (byte) '1' }; - byte expected2[] = - { (byte) 't', (byte) 'e', (byte) 's', (byte) 't', (byte) '2' }; - - - PipedOutputStream po = new PipedOutputStream(); - PipedInputStream pi = new PipedInputStream(po); - - OutputStream os = STANDARD.server.getOutputStream(); - OutputStream ostc = STANDARD.client.getOutputStream(); - - STANDARD.client.registerSpyStream(po); - - os.write("test1".getBytes()); - os.flush(); - - Thread.sleep(1000); - byte buffer[] = new byte[5]; - - if(pi.available() == 5) - { - pi.read(buffer); - if (equalBytes(buffer, expected1)) { - test1spy_ok = true; - } - } - - ostc.write("test2".getBytes()); - ostc.flush(); - - Thread.sleep(1000); - - if(pi.available() == 5) - { - pi.read(buffer); - if (equalBytes(buffer, expected2)) { - test2spy_ok = true; - } - } - - STANDARD.client.stopSpyStream(); - os.write("test1".getBytes()); - os.flush(); - ostc.write("test2".getBytes()); - ostc.flush(); - Thread.sleep(1000); - if(pi.available() == 0) - { - stopspy_ok = true; - } - - - assertTrue(test1spy_ok); - assertTrue(test2spy_ok); - assertTrue(stopspy_ok); - pi.close(); + assertFalse(OPTIONS.client.getLocalOptionState(TelnetOption.ECHO)); } /* * test of setReaderThread */ - public void testSetReaderThread() throws Exception - { + public void testSetReaderThread() throws Exception { boolean negotiation1_ok = false; boolean negotiation2_ok = false; boolean read_ok = false; - byte buffread1[] = new byte[6]; - byte send1[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, (byte) 15, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.WILL, (byte) 15, - }; - byte expected1[] = - { - (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT, (byte) 15, - (byte) TelnetCommand.IAC, (byte) TelnetCommand.DONT, (byte) 15, - }; + final byte buffread1[] = new byte[6]; + final byte send1[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.DO, (byte) 15, (byte) TelnetCommand.IAC, (byte) TelnetCommand.WILL, (byte) 15, }; + final byte expected1[] = { (byte) TelnetCommand.IAC, (byte) TelnetCommand.WONT, (byte) 15, (byte) TelnetCommand.IAC, (byte) TelnetCommand.DONT, + (byte) 15, }; - - InputStream is1 = NOREAD.server.getInputStream(); - OutputStream os1 = NOREAD.server.getOutputStream(); + final InputStream is1 = NOREAD.server.getInputStream(); + final OutputStream os1 = NOREAD.server.getOutputStream(); is1.skip(is1.available()); os1.write(send1); os1.flush(); os1.write("A".getBytes()); os1.flush(); Thread.sleep(1000); - InputStream instr = NOREAD.client.getInputStream(); - byte[] buff = new byte[4]; - int ret_read = 0; + final InputStream instr = NOREAD.client.getInputStream(); + final byte[] buff = new byte[4]; - ret_read = instr.read(buff); - if((ret_read == 1) && (buff[0] == 'A')) - { + final int ret_read = instr.read(buff); + if (ret_read == 1 && buff[0] == 'A') { read_ok = true; } - // if(is1.available() == 6) - //{ - int read = 0; - int pos = 0; + // if(is1.available() == 6) + // { + int read = 0; + int pos = 0; - byte[] tmp = new byte[16]; - while ( pos < 5 ) { - read = is1.read(tmp); - System.arraycopy(tmp, 0, buffread1, pos, read); - pos+=read; - } - - if (equalBytes(buffread1, expected1)) { - negotiation1_ok = true; - //} - } + byte[] tmp = new byte[16]; + while (pos < 5) { + read = is1.read(tmp); + System.arraycopy(tmp, 0, buffread1, pos, read); + pos += read; + } + if (equalBytes(buffread1, expected1)) { + negotiation1_ok = true; + // } + } - InputStream is2 = STANDARD.server.getInputStream(); - OutputStream os2 = STANDARD.server.getOutputStream(); + final InputStream is2 = STANDARD.server.getInputStream(); + final OutputStream os2 = STANDARD.server.getOutputStream(); Thread.sleep(1000); is2.skip(is2.available()); os2.write(send1); @@ -823,79 +689,82 @@ extends TestCase implements TelnetNotificationHandler Thread.sleep(1000); tmp = new byte[16]; - while ( pos < 5 ) { + while (pos < 5) { read = is2.read(tmp); System.arraycopy(tmp, 0, buffread1, pos, read); - pos+=read; + pos += read; } - //if(is2.available() == 6) - //{ - is2.read(buffread1); + // if(is2.available() == 6) + // { + is2.read(buffread1); - if (equalBytes(buffread1, expected1)) { - negotiation2_ok = true; - //} - } + if (equalBytes(buffread1, expected1)) { + negotiation2_ok = true; + // } + } - assertTrue(!NOREAD.client.getReaderThread()); + assertFalse(NOREAD.client.getReaderThread()); assertTrue(STANDARD.client.getReaderThread()); assertTrue("Expected read_ok to be true, got " + read_ok, read_ok); assertTrue("Expected negotiation1_ok to be true, got " + negotiation1_ok, negotiation1_ok); assertTrue("Expected negotiation2_ok to be true, got " + negotiation2_ok, negotiation2_ok); } - /* - * Helper method. compares two arrays of int + * test of Spy functionality */ - protected boolean equalBytes(byte a1[], byte a2[]) - { - if(a1.length != a2.length) - { - return(false); - } - else - { - boolean result = true; - for(int ii=0; ii<a1.length; ii++) - { - - if (a1[ii]!= a2[ii]) { - result = false; + public void testSpy() throws Exception { + boolean test1spy_ok = false; + boolean test2spy_ok = false; + boolean stopspy_ok = false; + final byte expected1[] = { (byte) 't', (byte) 'e', (byte) 's', (byte) 't', (byte) '1' }; + final byte expected2[] = { (byte) 't', (byte) 'e', (byte) 's', (byte) 't', (byte) '2' }; + + try (final PipedOutputStream po = new PipedOutputStream(); final PipedInputStream pi = new PipedInputStream(po)) { + + final OutputStream os = STANDARD.server.getOutputStream(); + final OutputStream ostc = STANDARD.client.getOutputStream(); + + STANDARD.client.registerSpyStream(po); + + os.write("test1".getBytes()); + os.flush(); + + Thread.sleep(1000); + final byte buffer[] = new byte[5]; + + if (pi.available() == 5) { + pi.read(buffer); + if (equalBytes(buffer, expected1)) { + test1spy_ok = true; } } - return(result); - } - } - /* - * Callback method called when TelnetClient receives an option - * negotiation command. - * <p> - * @param negotiation_code - type of negotiation command received - * (RECEIVED_DO, RECEIVED_DONT, RECEIVED_WILL, RECEIVED_WONT) - * <p> - * @param option_code - code of the option negotiated - * <p> - */ - @Override - public void receivedNegotiation(int negotiation_code, int option_code) - { - if(negotiation_code == TelnetNotificationHandler.RECEIVED_DO) - { - numdo++; - } - else if(negotiation_code == TelnetNotificationHandler.RECEIVED_DONT) - { - numdont++; - } - else if(negotiation_code == TelnetNotificationHandler.RECEIVED_WILL) - { - numwill++; - } - else if(negotiation_code == TelnetNotificationHandler.RECEIVED_WONT) - { - numwont++; + ostc.write("test2".getBytes()); + ostc.flush(); + + Thread.sleep(1000); + + if (pi.available() == 5) { + pi.read(buffer); + if (equalBytes(buffer, expected2)) { + test2spy_ok = true; + } + } + + STANDARD.client.stopSpyStream(); + os.write("test1".getBytes()); + os.flush(); + ostc.write("test2".getBytes()); + ostc.flush(); + Thread.sleep(1000); + if (pi.available() == 0) { + stopspy_ok = true; + } + + assertTrue(test1spy_ok); + assertTrue(test2spy_ok); + assertTrue(stopspy_ok); } } diff --git a/src/test/java/org/apache/commons/net/telnet/TelnetOptionHandlerTestAbstract.java b/src/test/java/org/apache/commons/net/telnet/TelnetOptionHandlerTestAbstract.java index 664a7c0..272e269 100644 --- a/src/test/java/org/apache/commons/net/telnet/TelnetOptionHandlerTestAbstract.java +++ b/src/test/java/org/apache/commons/net/telnet/TelnetOptionHandlerTestAbstract.java @@ -18,89 +18,75 @@ package org.apache.commons.net.telnet; import junit.framework.TestCase; -/*** - * The TelnetOptionHandlerTest is the abstract class for - * testing TelnetOptionHandler. It can be used to derive - * the actual test classes for TelnetOptionHadler derived - * classes, by adding creation of three new option handlers - * and testing of the specific subnegotiation behaviour. - ***/ -public abstract class TelnetOptionHandlerTestAbstract extends TestCase -{ +/** + * The TelnetOptionHandlerTest is the abstract class for testing TelnetOptionHandler. It can be used to derive the actual test classes for TelnetOptionHadler + * derived classes, by adding creation of three new option handlers and testing of the specific subnegotiation behavior. + */ +public abstract class TelnetOptionHandlerTestAbstract extends TestCase { TelnetOptionHandler opthand1; TelnetOptionHandler opthand2; TelnetOptionHandler opthand3; - /*** - * setUp for the test. The derived test class must implement - * this method by creating opthand1, opthand2, opthand3 - * like in the following: - * opthand1 = new EchoOptionHandler(); - * opthand2 = new EchoOptionHandler(true, true, true, true); - * opthand3 = new EchoOptionHandler(false, false, false, false); - ***/ + /** + * setUp for the test. The derived test class must implement this method by creating opthand1, opthand2, opthand3 like in the following: opthand1 = new + * EchoOptionHandler(); opthand2 = new EchoOptionHandler(true, true, true, true); opthand3 = new EchoOptionHandler(false, false, false, false); + */ @Override protected abstract void setUp(); - /*** - * test of the constructors. The derived class may add - * test of the option code. - ***/ - public void testConstructors() - { + /** + * test of server-driven subnegotiation. Abstract test: the derived class should implement it. + */ + public abstract void testAnswerSubnegotiation(); + // test subnegotiation + + /** + * test of the constructors. The derived class may add test of the option code. + */ + public void testConstructors() { // add test of the option code - assertTrue(!opthand1.getInitLocal()); - assertTrue(!opthand1.getInitRemote()); - assertTrue(!opthand1.getAcceptLocal()); - assertTrue(!opthand1.getAcceptRemote()); + assertFalse(opthand1.getInitLocal()); + assertFalse(opthand1.getInitRemote()); + assertFalse(opthand1.getAcceptLocal()); + assertFalse(opthand1.getAcceptRemote()); assertTrue(opthand2.getInitLocal()); assertTrue(opthand2.getInitRemote()); assertTrue(opthand2.getAcceptLocal()); assertTrue(opthand2.getAcceptRemote()); - assertTrue(!opthand3.getInitLocal()); - assertTrue(!opthand3.getInitRemote()); - assertTrue(!opthand3.getAcceptLocal()); - assertTrue(!opthand3.getAcceptRemote()); + assertFalse(opthand3.getInitLocal()); + assertFalse(opthand3.getInitRemote()); + assertFalse(opthand3.getAcceptLocal()); + assertFalse(opthand3.getAcceptRemote()); } - /*** - * test of setWill/getWill - ***/ - public void testWill() - { - opthand2.setWill(true); - opthand3.setWill(false); - - assertTrue(!opthand1.getWill()); - assertTrue(opthand2.getWill()); - assertTrue(!opthand3.getWill()); - } - - /*** + /** * test of setDo/getDo - ***/ - public void testDo() - { + */ + public void testDo() { opthand2.setDo(true); opthand3.setDo(false); - assertTrue(!opthand1.getDo()); + assertFalse(opthand1.getDo()); assertTrue(opthand2.getDo()); - assertTrue(!opthand3.getDo()); + assertFalse(opthand3.getDo()); } - /*** - * test of client-driven subnegotiation. Abstract test: - * the derived class should implement it. - ***/ + /** + * test of client-driven subnegotiation. Abstract test: the derived class should implement it. + */ public abstract void testStartSubnegotiation(); - /*** - * test of server-driven subnegotiation. Abstract test: - * the derived class should implement it. - ***/ - public abstract void testAnswerSubnegotiation(); - // test subnegotiation + /** + * test of setWill/getWill + */ + public void testWill() { + opthand2.setWill(true); + opthand3.setWill(false); + + assertFalse(opthand1.getWill()); + assertTrue(opthand2.getWill()); + assertFalse(opthand3.getWill()); + } } \ No newline at end of file diff --git a/src/test/java/org/apache/commons/net/telnet/TelnetOptionTest.java b/src/test/java/org/apache/commons/net/telnet/TelnetOptionTest.java index 0538589..e120ee2 100644 --- a/src/test/java/org/apache/commons/net/telnet/TelnetOptionTest.java +++ b/src/test/java/org/apache/commons/net/telnet/TelnetOptionTest.java @@ -18,29 +18,26 @@ package org.apache.commons.net.telnet; import junit.framework.TestCase; -/*** +/** * JUnit test class for TelnetOption - ***/ -public class TelnetOptionTest extends TestCase -{ - /*** - * test of the isValidOption method. - ***/ - public void testisValidOption() - { - assertTrue(TelnetOption.isValidOption(0)); - assertTrue(TelnetOption.isValidOption(91)); - assertTrue(TelnetOption.isValidOption(255)); - assertTrue(!TelnetOption.isValidOption(256)); - } - - /*** + */ +public class TelnetOptionTest extends TestCase { + /** * test of the getOption method. - ***/ - public void testGetOption() - { + */ + public void testGetOption() { assertEquals(TelnetOption.getOption(0), "BINARY"); assertEquals(TelnetOption.getOption(91), "UNASSIGNED"); assertEquals(TelnetOption.getOption(255), "Extended-Options-List"); } + + /** + * test of the isValidOption method. + */ + public void testisValidOption() { + assertTrue(TelnetOption.isValidOption(0)); + assertTrue(TelnetOption.isValidOption(91)); + assertTrue(TelnetOption.isValidOption(255)); + assertFalse(TelnetOption.isValidOption(256)); + } } \ No newline at end of file diff --git a/src/test/java/org/apache/commons/net/telnet/TelnetTestResponder.java b/src/test/java/org/apache/commons/net/telnet/TelnetTestResponder.java index 09dbfb0..5910fb1 100644 --- a/src/test/java/org/apache/commons/net/telnet/TelnetTestResponder.java +++ b/src/test/java/org/apache/commons/net/telnet/TelnetTestResponder.java @@ -19,82 +19,66 @@ package org.apache.commons.net.telnet; import java.io.InputStream; import java.io.OutputStream; - -/*** - * Simple stream responder. - * Waits for strings on an input stream and answers - * sending corresponfing strings on an output stream. - * The reader runs in a separate thread. - ***/ -public class TelnetTestResponder implements Runnable -{ +/** + * Simple stream responder. Waits for strings on an input stream and answers sending corresponfing strings on an output stream. The reader runs in a separate + * thread. + */ +public class TelnetTestResponder implements Runnable { InputStream _is; OutputStream _os; String _inputs[], _outputs[]; long _timeout; - /*** - * Constructor. - * Starts a new thread for the reader. + /** + * Constructor. Starts a new thread for the reader. * <p> - * @param is - InputStream on which to read. - * @param os - OutputStream on which to answer. - * @param inputs - Array of waited for Strings. + * + * @param is - InputStream on which to read. + * @param os - OutputStream on which to answer. + * @param inputs - Array of waited for Strings. * @param outputs - Array of answers. * @param timeout - milliseconds - ***/ - public TelnetTestResponder(InputStream is, OutputStream os, String inputs[], String outputs[], long timeout) - { + */ + public TelnetTestResponder(final InputStream is, final OutputStream os, final String inputs[], final String outputs[], final long timeout) { _is = is; _os = os; _timeout = timeout; _inputs = inputs; _outputs = outputs; - Thread reader = new Thread (this); + final Thread reader = new Thread(this); reader.start(); } - /*** + /** * Runs the responder - ***/ + */ @Override - public void run() - { + public void run() { boolean result = false; - byte buffer[] = new byte[32]; - long starttime = System.currentTimeMillis(); + final byte buffer[] = new byte[32]; + final long starttime = System.currentTimeMillis(); - try - { - String readbytes = ""; - while(!result && - ((System.currentTimeMillis() - starttime) < _timeout)) - { - if(_is.available() > 0) - { - int ret_read = _is.read(buffer); - readbytes = readbytes + new String(buffer, 0, ret_read); + try { + final StringBuilder readbytes = new StringBuilder(); + while (!result && System.currentTimeMillis() - starttime < _timeout) { + if (_is.available() > 0) { + final int ret_read = _is.read(buffer); + readbytes.append(new String(buffer, 0, ret_read)); - for(int ii=0; ii<_inputs.length; ii++) - { - if(readbytes.indexOf(_inputs[ii]) >= 0) - { + for (int ii = 0; ii < _inputs.length; ii++) { + if (readbytes.indexOf(_inputs[ii]) >= 0) { Thread.sleep(1000 * ii); _os.write(_outputs[ii].getBytes()); result = true; } } - } - else - { + } else { Thread.sleep(500); } } - } - catch (Exception e) - { + } catch (final Exception e) { System.err.println("Error while waiting endstring. " + e.getMessage()); } } diff --git a/src/test/java/org/apache/commons/net/telnet/TelnetTestSimpleServer.java b/src/test/java/org/apache/commons/net/telnet/TelnetTestSimpleServer.java index aaa50b2..1503add 100644 --- a/src/test/java/org/apache/commons/net/telnet/TelnetTestSimpleServer.java +++ b/src/test/java/org/apache/commons/net/telnet/TelnetTestSimpleServer.java @@ -16,135 +16,98 @@ */ package org.apache.commons.net.telnet; -import java.net.ServerSocket; -import java.net.Socket; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; -/*** - * Simple TCP server. - * Waits for connections on a TCP port in a separate thread. - ***/ -public class TelnetTestSimpleServer implements Runnable -{ - ServerSocket serverSocket = null; - Socket clientSocket = null; - Thread listener = null; +/** + * Simple TCP server. Waits for connections on a TCP port in a separate thread. + */ +public class TelnetTestSimpleServer implements Runnable { + ServerSocket serverSocket; + Socket clientSocket; + Thread listener; /* - * test of client-driven subnegotiation. - * <p> + * test of client-driven subnegotiation. <p> + * * @param port - server port on which to listen. + * * @throws IOException on error */ - public TelnetTestSimpleServer(int port) throws IOException - { + public TelnetTestSimpleServer(final int port) throws IOException { serverSocket = new ServerSocket(port); - listener = new Thread (this); + listener = new Thread(this); listener.start(); } + public void disconnect() { + if (clientSocket == null) { + return; + } + synchronized (clientSocket) { + try { + clientSocket.notify(); + } catch (final Exception e) { + System.err.println("Exception in notify, " + e.getMessage()); + } + } + } + + public InputStream getInputStream() throws IOException { + if (clientSocket != null) { + return clientSocket.getInputStream(); + } + return null; + } + + public OutputStream getOutputStream() throws IOException { + if (clientSocket != null) { + return clientSocket.getOutputStream(); + } + return null; + } + @Override - public void run() - { + public void run() { boolean bError = false; - while(!bError) - { - try - { + while (!bError) { + try { clientSocket = serverSocket.accept(); - synchronized (clientSocket) - { - try - { + synchronized (clientSocket) { + try { clientSocket.wait(); + } catch (final Exception e) { + System.err.println("Exception in wait, " + e.getMessage()); } - catch (Exception e) - { - System.err.println("Exception in wait, "+ e.getMessage()); - } - try - { + try { clientSocket.close(); - } - catch (Exception e) - { - System.err.println("Exception in close, "+ e.getMessage()); + } catch (final Exception e) { + System.err.println("Exception in close, " + e.getMessage()); } } - } - catch (IOException e) - { + } catch (final IOException e) { bError = true; } } - try - { + try { serverSocket.close(); - } - catch (Exception e) - { - System.err.println("Exception in close, "+ e.getMessage()); - } - } - - - public void disconnect() - { - if (clientSocket == null) { - return; - } - synchronized (clientSocket) - { - try - { - clientSocket.notify(); - } - catch (Exception e) - { - System.err.println("Exception in notify, "+ e.getMessage()); - } + } catch (final Exception e) { + System.err.println("Exception in close, " + e.getMessage()); } } - public void stop() - { + public void stop() { listener.interrupt(); - try - { + try { serverSocket.close(); - } - catch (Exception e) - { - System.err.println("Exception in close, "+ e.getMessage()); - } - } - - public InputStream getInputStream() throws IOException - { - if(clientSocket != null) - { - return(clientSocket.getInputStream()); - } - else - { - return(null); - } - } - - public OutputStream getOutputStream() throws IOException - { - if(clientSocket != null) - { - return(clientSocket.getOutputStream()); - } - else - { - return(null); + } catch (final Exception e) { + System.err.println("Exception in close, " + e.getMessage()); } } } diff --git a/src/test/java/org/apache/commons/net/telnet/TerminalTypeOptionHandlerTest.java b/src/test/java/org/apache/commons/net/telnet/TerminalTypeOptionHandlerTest.java index 4cbbb62..7859e48 100644 --- a/src/test/java/org/apache/commons/net/telnet/TerminalTypeOptionHandlerTest.java +++ b/src/test/java/org/apache/commons/net/telnet/TerminalTypeOptionHandlerTest.java @@ -16,88 +16,64 @@ */ package org.apache.commons.net.telnet; -public class TerminalTypeOptionHandlerTest extends TelnetOptionHandlerTestAbstract -{ +public class TerminalTypeOptionHandlerTest extends TelnetOptionHandlerTestAbstract { + /* + * compares two arrays of int + */ + protected boolean equalInts(final int a1[], final int a2[]) { + if (a1.length != a2.length) { + return false; + } + boolean result = true; + for (int ii = 0; ii < a1.length; ii++) { + if (a1[ii] != a2[ii]) { + result = false; + } + } + return result; + } + @Override - protected void setUp() - { + protected void setUp() { opthand1 = new TerminalTypeOptionHandler("VT100"); opthand2 = new TerminalTypeOptionHandler("ANSI", true, true, true, true); opthand3 = new TerminalTypeOptionHandler("ANSI", false, false, false, false); } - @Override - public void testConstructors() - { - assertEquals(opthand1.getOptionCode(), TelnetOption.TERMINAL_TYPE); - super.testConstructors(); - } - - /* - * test of client-driven subnegotiation. - * Checks that no subnegotiation is made. - */ - @Override - public void testStartSubnegotiation() - { - - int resp1[] = opthand1.startSubnegotiationLocal(); - int resp2[] = opthand1.startSubnegotiationRemote(); - - assertEquals(resp1, null); - assertEquals(resp2, null); - } - - /* - * test of client-driven subnegotiation. - * Checks that the terminal type is sent + * test of client-driven subnegotiation. Checks that the terminal type is sent */ @Override - public void testAnswerSubnegotiation() - { - int subn[] = - { - TelnetOption.TERMINAL_TYPE, 1 - }; + public void testAnswerSubnegotiation() { + final int subn[] = { TelnetOption.TERMINAL_TYPE, 1 }; - int expected1[] = - { - TelnetOption.TERMINAL_TYPE, 0, 'V', 'T', '1', '0', '0' - }; + final int expected1[] = { TelnetOption.TERMINAL_TYPE, 0, 'V', 'T', '1', '0', '0' }; - int expected2[] = - { - TelnetOption.TERMINAL_TYPE, 0, 'A', 'N', 'S', 'I' - }; + final int expected2[] = { TelnetOption.TERMINAL_TYPE, 0, 'A', 'N', 'S', 'I' }; - int resp1[] = opthand1.answerSubnegotiation(subn, subn.length); - int resp2[] = opthand2.answerSubnegotiation(subn, subn.length); + final int resp1[] = opthand1.answerSubnegotiation(subn, subn.length); + final int resp2[] = opthand2.answerSubnegotiation(subn, subn.length); assertTrue(equalInts(resp1, expected1)); assertTrue(equalInts(resp2, expected2)); } + @Override + public void testConstructors() { + assertEquals(opthand1.getOptionCode(), TelnetOption.TERMINAL_TYPE); + super.testConstructors(); + } /* - * compares two arrays of int + * test of client-driven subnegotiation. Checks that no subnegotiation is made. */ - protected boolean equalInts(int a1[], int a2[]) - { - if(a1.length != a2.length) - { - return(false); - } - else - { - boolean result = true; - for(int ii=0; ii<a1.length; ii++) - { - if(a1[ii]!= a2[ii]) { - result = false; - } - } - return(result); - } + @Override + public void testStartSubnegotiation() { + + final int resp1[] = opthand1.startSubnegotiationLocal(); + final int resp2[] = opthand1.startSubnegotiationRemote(); + + assertNull(resp1); + assertNull(resp2); } } diff --git a/src/test/java/org/apache/commons/net/telnet/WindowSizeOptionHandlerTest.java b/src/test/java/org/apache/commons/net/telnet/WindowSizeOptionHandlerTest.java index 9a4e5d4..8fe0c71 100644 --- a/src/test/java/org/apache/commons/net/telnet/WindowSizeOptionHandlerTest.java +++ b/src/test/java/org/apache/commons/net/telnet/WindowSizeOptionHandlerTest.java @@ -16,96 +16,81 @@ */ package org.apache.commons.net.telnet; -/*** +/** * JUnit test class for TerminalTypeOptionHandler - ***/ -public class WindowSizeOptionHandlerTest extends TelnetOptionHandlerTestAbstract -{ - /*** + */ +public class WindowSizeOptionHandlerTest extends TelnetOptionHandlerTestAbstract { + /** + * compares two arrays of int + */ + private void equalInts(final int a1[], final int a2[]) { + assertEquals("Arrays should be the same length", a1.length, a2.length); + for (int ii = 0; ii < a1.length; ii++) { + assertEquals("Array entry " + ii + " should match", a1[ii], a2[ii]); + } + } + + /** * setUp for the test. - ***/ + */ @Override - protected void setUp() - { + protected void setUp() { opthand1 = new WindowSizeOptionHandler(80, 24); opthand2 = new WindowSizeOptionHandler(255, 255, true, true, true, true); opthand3 = new WindowSizeOptionHandler(0xFFFF, 0x00FF, false, false, false, false); } - /*** + /** + * test of client-driven subnegotiation. Checks that nothing is sent + */ + @Override + public void testAnswerSubnegotiation() { + final int subn[] = { TelnetOption.WINDOW_SIZE, 24, 80 }; + + final int resp1[] = opthand1.answerSubnegotiation(subn, subn.length); + final int resp2[] = opthand2.answerSubnegotiation(subn, subn.length); + final int resp3[] = opthand3.answerSubnegotiation(subn, subn.length); + + assertNull(resp1); + assertNull(resp2); + assertNull(resp3); + } + + /** * test of the constructors. - ***/ + */ @Override - public void testConstructors() - { + public void testConstructors() { assertEquals(TelnetOption.WINDOW_SIZE, opthand1.getOptionCode()); super.testConstructors(); } - /*** - * test of client-driven subnegotiation. - * Checks that no subnegotiation is made. - ***/ + /** + * test of client-driven subnegotiation. Checks that no subnegotiation is made. + */ @Override - public void testStartSubnegotiation() - { + public void testStartSubnegotiation() { assertNull(opthand1.startSubnegotiationRemote()); assertNull(opthand2.startSubnegotiationRemote()); assertNull(opthand3.startSubnegotiationRemote()); } - /*** + /** * test of client-driven subnegotiation. * - ***/ - public void testStartSubnegotiationLocal() - { - int[] exp1 = {31, 0, 80, 0, 24}; - int[] start1 = opthand1.startSubnegotiationLocal(); + */ + public void testStartSubnegotiationLocal() { + final int[] exp1 = { 31, 0, 80, 0, 24 }; + final int[] start1 = opthand1.startSubnegotiationLocal(); assertEquals(5, start1.length); equalInts(exp1, start1); - int[] exp2 = {31, 0, 255, 255, 0, 255, 255}; - int[] start2 = opthand2.startSubnegotiationLocal(); + final int[] exp2 = { 31, 0, 255, 255, 0, 255, 255 }; + final int[] start2 = opthand2.startSubnegotiationLocal(); equalInts(exp2, start2); - int[] exp3 = {31, 255, 255, 255, 255, 0, 255, 255}; - int[] start3 = opthand3.startSubnegotiationLocal(); + final int[] exp3 = { 31, 255, 255, 255, 255, 0, 255, 255 }; + final int[] start3 = opthand3.startSubnegotiationLocal(); equalInts(exp3, start3); } - - - - /*** - * test of client-driven subnegotiation. - * Checks that nothing is sent - ***/ - @Override - public void testAnswerSubnegotiation() - { - int subn[] = - { - TelnetOption.WINDOW_SIZE, 24, 80 - }; - - int resp1[] = opthand1.answerSubnegotiation(subn, subn.length); - int resp2[] = opthand2.answerSubnegotiation(subn, subn.length); - int resp3[] = opthand3.answerSubnegotiation(subn, subn.length); - - assertNull(resp1); - assertNull(resp2); - assertNull(resp3); - } - - /*** - * compares two arrays of int - ***/ - private void equalInts(int a1[], int a2[]) - { - assertEquals("Arrays should be the same length", a1.length, a2.length); - for(int ii=0; ii<a1.length; ii++) - { - assertEquals("Array entry "+ii+" should match",a1[ii], a2[ii]); - } - } } diff --git a/src/test/java/org/apache/commons/net/tftp/TFTPServer.java b/src/test/java/org/apache/commons/net/tftp/TFTPServer.java index 2ad8091..ea8a86d 100644 --- a/src/test/java/org/apache/commons/net/tftp/TFTPServer.java +++ b/src/test/java/org/apache/commons/net/tftp/TFTPServer.java @@ -30,27 +30,23 @@ import java.io.PrintStream; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketTimeoutException; -import java.util.HashSet; import java.util.Enumeration; -import java.util.Iterator; +import java.util.HashSet; import org.apache.commons.net.io.FromNetASCIIOutputStream; import org.apache.commons.net.io.ToNetASCIIInputStream; /** - * A fully multi-threaded tftp server. Can handle multiple clients at the same time. Implements RFC - * 1350 and wrapping block numbers for large file support. + * A fully multi-threaded tftp server. Can handle multiple clients at the same time. Implements RFC 1350 and wrapping block numbers for large file support. * - * To launch, just create an instance of the class. An IOException will be thrown if the server - * fails to start for reasons such as port in use, port denied, etc. + * To launch, just create an instance of the class. An IOException will be thrown if the server fails to start for reasons such as port in use, port denied, + * etc. * * To stop, use the shutdown method. * - * To check to see if the server is still running (or if it stopped because of an error), call the - * isRunning() method. + * To check to see if the server is still running (or if it stopped because of an error), call the isRunning() method. * - * By default, events are not logged to stdout/stderr. This can be changed with the - * setLog and setLogError methods. + * By default, events are not logged to stdout/stderr. This can be changed with the setLog and setLogError methods. * * <p> * Example usage is below: @@ -81,476 +77,96 @@ import org.apache.commons.net.io.ToNetASCIIInputStream; * @since 2.0 */ -public class TFTPServer implements Runnable -{ - private static final int DEFAULT_TFTP_PORT = 69; - public static enum ServerMode { GET_ONLY, PUT_ONLY, GET_AND_PUT; } - - private final HashSet<TFTPTransfer> transfers_ = new HashSet<TFTPTransfer>(); - private volatile boolean shutdownServer = false; - private TFTP serverTftp_; - private File serverReadDirectory_; - private File serverWriteDirectory_; - private final int port_; - private final InetAddress laddr_; - private Exception serverException = null; - private final ServerMode mode_; - - /* /dev/null output stream (default) */ - private static final PrintStream nullStream = new PrintStream( - new OutputStream() { - @Override - public void write(int b){} - @Override - public void write(byte[] b) throws IOException {} - } - ); - - // don't have access to a logger api, so we will log to these streams, which - // by default are set to a no-op logger - private PrintStream log_; - private PrintStream logError_; - - private int maxTimeoutRetries_ = 3; - private int socketTimeout_; - private Thread serverThread; - - - /** - * Start a TFTP Server on the default port (69). Gets and Puts occur in the specified - * directories. - * - * The server will start in another thread, allowing this constructor to return immediately. - * - * If a get or a put comes in with a relative path that tries to get outside of the - * serverDirectory, then the get or put will be denied. - * - * GET_ONLY mode only allows gets, PUT_ONLY mode only allows puts, and GET_AND_PUT allows both. - * Modes are defined as int constants in this class. - * - * @param serverReadDirectory directory for GET requests - * @param serverWriteDirectory directory for PUT requests - * @param mode A value as specified above. - * @throws IOException if the server directory is invalid or does not exist. - */ - public TFTPServer(File serverReadDirectory, File serverWriteDirectory, ServerMode mode) - throws IOException - { - this(serverReadDirectory, serverWriteDirectory, DEFAULT_TFTP_PORT, mode, null, null); - } - - /** - * Start a TFTP Server on the specified port. Gets and Puts occur in the specified directory. - * - * The server will start in another thread, allowing this constructor to return immediately. - * - * If a get or a put comes in with a relative path that tries to get outside of the - * serverDirectory, then the get or put will be denied. - * - * GET_ONLY mode only allows gets, PUT_ONLY mode only allows puts, and GET_AND_PUT allows both. - * Modes are defined as int constants in this class. - * - * @param serverReadDirectory directory for GET requests - * @param serverWriteDirectory directory for PUT requests - * @param port the port to use - * @param mode A value as specified above. - * @param log Stream to write log message to. If not provided, uses System.out - * @param errorLog Stream to write error messages to. If not provided, uses System.err. - * @throws IOException if the server directory is invalid or does not exist. - */ - public TFTPServer(File serverReadDirectory, File serverWriteDirectory, int port, ServerMode mode, - PrintStream log, PrintStream errorLog) throws IOException - { - port_ = port; - mode_ = mode; - log_ = (log == null ? nullStream: log); - logError_ = (errorLog == null ? nullStream : errorLog); - laddr_ = null; - launch(serverReadDirectory, serverWriteDirectory); - } - - /** - * Start a TFTP Server on the specified port. Gets and Puts occur in the specified directory. - * - * The server will start in another thread, allowing this constructor to return immediately. - * - * If a get or a put comes in with a relative path that tries to get outside of the - * serverDirectory, then the get or put will be denied. - * - * GET_ONLY mode only allows gets, PUT_ONLY mode only allows puts, and GET_AND_PUT allows both. - * Modes are defined as int constants in this class. - * - * @param serverReadDirectory directory for GET requests - * @param serverWriteDirectory directory for PUT requests - * @param port The local port to bind to. - * @param localaddr The local address to bind to. - * @param mode A value as specified above. - * @param log Stream to write log message to. If not provided, uses System.out - * @param errorLog Stream to write error messages to. If not provided, uses System.err. - * @throws IOException if the server directory is invalid or does not exist. - */ - public TFTPServer(File serverReadDirectory, File serverWriteDirectory, int port, - InetAddress localaddr, ServerMode mode, PrintStream log, PrintStream errorLog) - throws IOException - { - port_ = port; - mode_ = mode; - laddr_ = localaddr; - log_ = (log == null ? nullStream: log); - logError_ = (errorLog == null ? nullStream : errorLog); - launch(serverReadDirectory, serverWriteDirectory); - } - - /** - * Start a TFTP Server on the specified port. Gets and Puts occur in the specified directory. - * - * The server will start in another thread, allowing this constructor to return immediately. - * - * If a get or a put comes in with a relative path that tries to get outside of the - * serverDirectory, then the get or put will be denied. - * - * GET_ONLY mode only allows gets, PUT_ONLY mode only allows puts, and GET_AND_PUT allows both. - * Modes are defined as int constants in this class. - * - * @param serverReadDirectory directory for GET requests - * @param serverWriteDirectory directory for PUT requests - * @param port the port to use - * @param localiface The local network interface to bind to. - * The interface's first address wil be used. - * @param mode A value as specified above. - * @param log Stream to write log message to. If not provided, uses System.out - * @param errorLog Stream to write error messages to. If not provided, uses System.err. - * @throws IOException if the server directory is invalid or does not exist. - */ - public TFTPServer(File serverReadDirectory, File serverWriteDirectory, int port, - NetworkInterface localiface, ServerMode mode, PrintStream log, PrintStream errorLog) - throws IOException - { - mode_ = mode; - port_= port; - InetAddress iaddr = null; - if (localiface != null) - { - Enumeration<InetAddress> ifaddrs = localiface.getInetAddresses(); - if (ifaddrs != null) - { - if (ifaddrs.hasMoreElements()) iaddr = ifaddrs.nextElement(); - } - } - log_ = (log == null ? nullStream: log); - logError_ = (errorLog == null ? nullStream : errorLog); - laddr_ = iaddr; - launch(serverReadDirectory, serverWriteDirectory); - } - - /** - * Set the max number of retries in response to a timeout. Default 3. Min 0. - * - * @param retries number of retries, must be > 0 - */ - public void setMaxTimeoutRetries(int retries) - { - if (retries < 0) - { - throw new RuntimeException("Invalid Value"); - } - maxTimeoutRetries_ = retries; - } - - /** - * Get the current value for maxTimeoutRetries - * @return the max allowed number of retries - */ - public int getMaxTimeoutRetries() - { - return maxTimeoutRetries_; - } - - /** - * Set the socket timeout in milliseconds used in transfers. Defaults to the value here: - * http://commons.apache.org/net/apidocs/org/apache/commons/net/tftp/TFTP.html#DEFAULT_TIMEOUT - * (5000 at the time I write this) Min value of 10. - * @param timeout the timeout; must be larger than 10 - */ - public void setSocketTimeout(int timeout) - { - if (timeout < 10) - { - throw new RuntimeException("Invalid Value"); - } - socketTimeout_ = timeout; - } - - /** - * The current socket timeout used during transfers in milliseconds. - * @return the timeout value - */ - public int getSocketTimeout() - { - return socketTimeout_; +public class TFTPServer implements Runnable { + public enum ServerMode { + GET_ONLY, PUT_ONLY, GET_AND_PUT } /* - * start the server, throw an error if it can't start. + * An instance of an ongoing transfer. */ - private void launch(File serverReadDirectory, File serverWriteDirectory) throws IOException - { - log_.println("Starting TFTP Server on port " + port_ + ". Read directory: " - + serverReadDirectory + " Write directory: " + serverWriteDirectory - + " Server Mode is " + mode_); - - serverReadDirectory_ = serverReadDirectory.getCanonicalFile(); - if (!serverReadDirectory_.exists() || !serverReadDirectory.isDirectory()) - { - throw new IOException("The server read directory " + serverReadDirectory_ - + " does not exist"); - } - - serverWriteDirectory_ = serverWriteDirectory.getCanonicalFile(); - if (!serverWriteDirectory_.exists() || !serverWriteDirectory.isDirectory()) - { - throw new IOException("The server write directory " + serverWriteDirectory_ - + " does not exist"); - } - - serverTftp_ = new TFTP(); - - // This is the value used in response to each client. - socketTimeout_ = serverTftp_.getDefaultTimeout(); - - // we want the server thread to listen forever. - serverTftp_.setDefaultTimeout(0); - - if (laddr_ != null) { - serverTftp_.open(port_, laddr_); - } else { - serverTftp_.open(port_); - } + private class TFTPTransfer implements Runnable { + private final TFTPPacket tftpPacket_; - serverThread = new Thread(this); - serverThread.setDaemon(true); - serverThread.start(); - } + private boolean shutdownTransfer; - @Override - protected void finalize() throws Throwable - { - shutdown(); - } + TFTP transferTftp_; - /** - * check if the server thread is still running. - * - * @return true if running, false if stopped. - * @throws Exception throws the exception that stopped the server if the server is stopped from - * an exception. - */ - public boolean isRunning() throws Exception - { - if (shutdownServer && serverException != null) - { - throw serverException; + public TFTPTransfer(final TFTPPacket tftpPacket) { + tftpPacket_ = tftpPacket; } - return !shutdownServer; - } - - @Override - public void run() - { - try - { - while (!shutdownServer) - { - TFTPPacket tftpPacket; - - tftpPacket = serverTftp_.receive(); - TFTPTransfer tt = new TFTPTransfer(tftpPacket); - synchronized(transfers_) - { - transfers_.add(tt); - } + /* + * Utility method to make sure that paths provided by tftp clients do not get outside of the serverRoot directory. + */ + private File buildSafeFile(final File serverDirectory, final String fileName, final boolean createSubDirs) throws IOException { + File temp = new File(serverDirectory, fileName); + temp = temp.getCanonicalFile(); - Thread thread = new Thread(tt); - thread.setDaemon(true); - thread.start(); - } - } - catch (Exception e) - { - if (!shutdownServer) - { - serverException = e; - logError_.println("Unexpected Error in TFTP Server - Server shut down! + " + e); - } - } - finally - { - shutdownServer = true; // set this to true, so the launching thread can check to see if it started. - if (serverTftp_ != null && serverTftp_.isOpen()) - { - serverTftp_.close(); + if (!isSubdirectoryOf(serverDirectory, temp)) { + throw new IOException("Cannot access files outside of tftp server root."); } - } - } - - /** - * Stop the tftp server (and any currently running transfers) and release all opened network - * resources. - */ - public void shutdown() - { - shutdownServer = true; - synchronized(transfers_) - { - Iterator<TFTPTransfer> it = transfers_.iterator(); - while (it.hasNext()) - { - it.next().shutdown(); + // ensure directory exists (if requested) + if (createSubDirs) { + createDirectory(temp.getParentFile()); } - } - - try - { - serverTftp_.close(); - } - catch (RuntimeException e) - { - // noop - } - try { - serverThread.join(); - } catch (InterruptedException e) { - // we've done the best we could, return - } - } - - /* - * An instance of an ongoing transfer. - */ - private class TFTPTransfer implements Runnable - { - private final TFTPPacket tftpPacket_; - - private boolean shutdownTransfer = false; - - TFTP transferTftp_ = null; - - public TFTPTransfer(TFTPPacket tftpPacket) - { - tftpPacket_ = tftpPacket; + return temp; } - public void shutdown() - { - shutdownTransfer = true; - try - { - transferTftp_.close(); + /* + * recursively create subdirectories + */ + private void createDirectory(final File file) throws IOException { + final File parent = file.getParentFile(); + if (parent == null) { + throw new IOException("Unexpected error creating requested directory"); } - catch (RuntimeException e) - { - // noop + if (!parent.exists()) { + // recurse... + createDirectory(parent); } - } - - @Override - public void run() - { - try - { - transferTftp_ = newTFTP(); - - transferTftp_.beginBufferedOps(); - transferTftp_.setDefaultTimeout(socketTimeout_); - - transferTftp_.open(); - if (tftpPacket_ instanceof TFTPReadRequestPacket) - { - handleRead(((TFTPReadRequestPacket) tftpPacket_)); - } - else if (tftpPacket_ instanceof TFTPWriteRequestPacket) - { - handleWrite((TFTPWriteRequestPacket) tftpPacket_); - } - else - { - log_.println("Unsupported TFTP request (" + tftpPacket_ + ") - ignored."); - } + if (!parent.isDirectory()) { + throw new IOException("Invalid directory path - file in the way of requested folder"); } - catch (Exception e) - { - if (!shutdownTransfer) - { - logError_ - .println("Unexpected Error in during TFTP file transfer. Transfer aborted. " - + e); - } + if (file.isDirectory()) { + return; } - finally - { - try - { - if (transferTftp_ != null && transferTftp_.isOpen()) - { - transferTftp_.endBufferedOps(); - transferTftp_.close(); - } - } - catch (Exception e) - { - // noop - } - synchronized(transfers_) - { - transfers_.remove(this); - } + final boolean result = file.mkdir(); + if (!result) { + throw new IOException("Couldn't create requested directory"); } } /* * Handle a tftp read request. */ - private void handleRead(TFTPReadRequestPacket trrp) throws IOException, TFTPPacketException - { + private void handleRead(final TFTPReadRequestPacket trrp) throws IOException, TFTPPacketException { InputStream is = null; - try - { - if (mode_ == ServerMode.PUT_ONLY) - { - transferTftp_.bufferedSend(new TFTPErrorPacket(trrp.getAddress(), trrp - .getPort(), TFTPErrorPacket.ILLEGAL_OPERATION, - "Read not allowed by server.")); + try { + if (mode_ == ServerMode.PUT_ONLY) { + transferTftp_.bufferedSend( + new TFTPErrorPacket(trrp.getAddress(), trrp.getPort(), TFTPErrorPacket.ILLEGAL_OPERATION, "Read not allowed by server.")); return; } - try - { - is = new BufferedInputStream(new FileInputStream(buildSafeFile( - serverReadDirectory_, trrp.getFilename(), false))); - } - catch (FileNotFoundException e) - { - transferTftp_.bufferedSend(new TFTPErrorPacket(trrp.getAddress(), trrp - .getPort(), TFTPErrorPacket.FILE_NOT_FOUND, e.getMessage())); + try { + is = new BufferedInputStream(new FileInputStream(buildSafeFile(serverReadDirectory_, trrp.getFilename(), false))); + } catch (final FileNotFoundException e) { + transferTftp_.bufferedSend(new TFTPErrorPacket(trrp.getAddress(), trrp.getPort(), TFTPErrorPacket.FILE_NOT_FOUND, e.getMessage())); return; - } - catch (Exception e) - { - transferTftp_.bufferedSend(new TFTPErrorPacket(trrp.getAddress(), trrp - .getPort(), TFTPErrorPacket.UNDEFINED, e.getMessage())); + } catch (final Exception e) { + transferTftp_.bufferedSend(new TFTPErrorPacket(trrp.getAddress(), trrp.getPort(), TFTPErrorPacket.UNDEFINED, e.getMessage())); return; } - if (trrp.getMode() == TFTP.NETASCII_MODE) - { + if (trrp.getMode() == TFTP.NETASCII_MODE) { is = new ToNetASCIIInputStream(is); } - byte[] temp = new byte[TFTPDataPacket.MAX_DATA_LENGTH]; + final byte[] temp = new byte[TFTPDataPacket.MAX_DATA_LENGTH]; TFTPPacket answer; @@ -563,18 +179,14 @@ public class TFTPServer implements Runnable // We are reading a file, so when we read less than the // requested bytes, we know that we are at the end of the file. - while (readLength == TFTPDataPacket.MAX_DATA_LENGTH && !shutdownTransfer) - { - if (sendNext) - { + while (readLength == TFTPDataPacket.MAX_DATA_LENGTH && !shutdownTransfer) { + if (sendNext) { readLength = is.read(temp); - if (readLength == -1) - { + if (readLength == -1) { readLength = 0; } - lastSentData = new TFTPDataPacket(trrp.getAddress(), trrp.getPort(), block, - temp, 0, readLength); + lastSentData = new TFTPDataPacket(trrp.getAddress(), trrp.getPort(), block, temp, 0, readLength); sendData(transferTftp_, lastSentData); // send the data } @@ -582,29 +194,20 @@ public class TFTPServer implements Runnable int timeoutCount = 0; - while (!shutdownTransfer - && (answer == null || !answer.getAddress().equals(trrp.getAddress()) || answer - .getPort() != trrp.getPort())) - { + while (!shutdownTransfer && (answer == null || !answer.getAddress().equals(trrp.getAddress()) || answer.getPort() != trrp.getPort())) { // listen for an answer. - if (answer != null) - { + if (answer != null) { // The answer that we got didn't come from the // expected source, fire back an error, and continue // listening. log_.println("TFTP Server ignoring message from unexpected source."); - transferTftp_.bufferedSend(new TFTPErrorPacket(answer.getAddress(), - answer.getPort(), TFTPErrorPacket.UNKNOWN_TID, - "Unexpected Host or Port")); + transferTftp_.bufferedSend( + new TFTPErrorPacket(answer.getAddress(), answer.getPort(), TFTPErrorPacket.UNKNOWN_TID, "Unexpected Host or Port")); } - try - { + try { answer = transferTftp_.bufferedReceive(); - } - catch (SocketTimeoutException e) - { - if (timeoutCount >= maxTimeoutRetries_) - { + } catch (final SocketTimeoutException e) { + if (timeoutCount >= maxTimeoutRetries_) { throw e; } // didn't get an ack for this data. need to resend @@ -615,58 +218,38 @@ public class TFTPServer implements Runnable } } - if (answer == null || !(answer instanceof TFTPAckPacket)) - { - if (!shutdownTransfer) - { - logError_ - .println("Unexpected response from tftp client during transfer (" - + answer + "). Transfer aborted."); + if (answer == null || !(answer instanceof TFTPAckPacket)) { + if (!shutdownTransfer) { + logError_.println("Unexpected response from tftp client during transfer (" + answer + "). Transfer aborted."); } break; } - else - { - // once we get here, we know we have an answer packet - // from the correct host. - TFTPAckPacket ack = (TFTPAckPacket) answer; - if (ack.getBlockNumber() != block) - { - /* - * The origional tftp spec would have called on us to resend the - * previous data here, however, that causes the SAS Syndrome. - * http://www.faqs.org/rfcs/rfc1123.html section 4.2.3.1 The modified - * spec says that we ignore a duplicate ack. If the packet was really - * lost, we will time out on receive, and resend the previous data at - * that point. - */ - sendNext = false; - } - else - { - // send the next block - block++; - if (block > 65535) - { - // wrap the block number - block = 0; - } - sendNext = true; + // once we get here, we know we have an answer packet + // from the correct host. + final TFTPAckPacket ack = (TFTPAckPacket) answer; + if (ack.getBlockNumber() != block) { + /* + * The origional tftp spec would have called on us to resend the previous data here, however, that causes the SAS Syndrome. + * http://www.faqs.org/rfcs/rfc1123.html section 4.2.3.1 The modified spec says that we ignore a duplicate ack. If the packet was really + * lost, we will time out on receive, and resend the previous data at that point. + */ + sendNext = false; + } else { + // send the next block + block++; + if (block > 65535) { + // wrap the block number + block = 0; } + sendNext = true; } } - } - finally - { - try - { - if (is != null) - { + } finally { + try { + if (is != null) { is.close(); } - } - catch (IOException e) - { + } catch (final IOException e) { // noop } } @@ -675,81 +258,59 @@ public class TFTPServer implements Runnable /* * handle a tftp write request. */ - private void handleWrite(TFTPWriteRequestPacket twrp) throws IOException, - TFTPPacketException - { + private void handleWrite(final TFTPWriteRequestPacket twrp) throws IOException, TFTPPacketException { OutputStream bos = null; - try - { - if (mode_ == ServerMode.GET_ONLY) - { - transferTftp_.bufferedSend(new TFTPErrorPacket(twrp.getAddress(), twrp - .getPort(), TFTPErrorPacket.ILLEGAL_OPERATION, - "Write not allowed by server.")); + try { + if (mode_ == ServerMode.GET_ONLY) { + transferTftp_.bufferedSend( + new TFTPErrorPacket(twrp.getAddress(), twrp.getPort(), TFTPErrorPacket.ILLEGAL_OPERATION, "Write not allowed by server.")); return; } int lastBlock = 0; - String fileName = twrp.getFilename(); - - try - { - File temp = buildSafeFile(serverWriteDirectory_, fileName, true); - if (temp.exists()) - { - transferTftp_.bufferedSend(new TFTPErrorPacket(twrp.getAddress(), twrp - .getPort(), TFTPErrorPacket.FILE_EXISTS, "File already exists")); + final String fileName = twrp.getFilename(); + + try { + final File temp = buildSafeFile(serverWriteDirectory_, fileName, true); + if (temp.exists()) { + transferTftp_.bufferedSend(new TFTPErrorPacket(twrp.getAddress(), twrp.getPort(), TFTPErrorPacket.FILE_EXISTS, "File already exists")); return; } bos = new BufferedOutputStream(new FileOutputStream(temp)); - if (twrp.getMode() == TFTP.NETASCII_MODE) - { + if (twrp.getMode() == TFTP.NETASCII_MODE) { bos = new FromNetASCIIOutputStream(bos); } - } - catch (Exception e) - { - transferTftp_.bufferedSend(new TFTPErrorPacket(twrp.getAddress(), twrp - .getPort(), TFTPErrorPacket.UNDEFINED, e.getMessage())); + } catch (final Exception e) { + transferTftp_.bufferedSend(new TFTPErrorPacket(twrp.getAddress(), twrp.getPort(), TFTPErrorPacket.UNDEFINED, e.getMessage())); return; } TFTPAckPacket lastSentAck = new TFTPAckPacket(twrp.getAddress(), twrp.getPort(), 0); sendData(transferTftp_, lastSentAck); // send the data - while (true) - { + while (true) { // get the response - ensure it is from the right place. TFTPPacket dataPacket = null; int timeoutCount = 0; while (!shutdownTransfer - && (dataPacket == null - || !dataPacket.getAddress().equals(twrp.getAddress()) || dataPacket - .getPort() != twrp.getPort())) - { + && (dataPacket == null || !dataPacket.getAddress().equals(twrp.getAddress()) || dataPacket.getPort() != twrp.getPort())) { // listen for an answer. - if (dataPacket != null) - { + if (dataPacket != null) { // The data that we got didn't come from the // expected source, fire back an error, and continue // listening. log_.println("TFTP Server ignoring message from unexpected source."); - transferTftp_.bufferedSend(new TFTPErrorPacket(dataPacket.getAddress(), - dataPacket.getPort(), TFTPErrorPacket.UNKNOWN_TID, - "Unexpected Host or Port")); + transferTftp_.bufferedSend( + new TFTPErrorPacket(dataPacket.getAddress(), dataPacket.getPort(), TFTPErrorPacket.UNKNOWN_TID, "Unexpected Host or Port")); } - try - { + try { dataPacket = transferTftp_.bufferedReceive(); - } - catch (SocketTimeoutException e) - { - if (timeoutCount >= maxTimeoutRetries_) - { + } catch (final SocketTimeoutException e) { + if (timeoutCount >= maxTimeoutRetries_) { throw e; } // It didn't get our ack. Resend it. @@ -759,31 +320,22 @@ public class TFTPServer implements Runnable } } - if (dataPacket != null && dataPacket instanceof TFTPWriteRequestPacket) - { + if (dataPacket instanceof TFTPWriteRequestPacket) { // it must have missed our initial ack. Send another. lastSentAck = new TFTPAckPacket(twrp.getAddress(), twrp.getPort(), 0); transferTftp_.bufferedSend(lastSentAck); - } - else if (dataPacket == null || !(dataPacket instanceof TFTPDataPacket)) - { - if (!shutdownTransfer) - { - logError_ - .println("Unexpected response from tftp client during transfer (" - + dataPacket + "). Transfer aborted."); + } else if (dataPacket == null || !(dataPacket instanceof TFTPDataPacket)) { + if (!shutdownTransfer) { + logError_.println("Unexpected response from tftp client during transfer (" + dataPacket + "). Transfer aborted."); } break; - } - else - { - int block = ((TFTPDataPacket) dataPacket).getBlockNumber(); - byte[] data = ((TFTPDataPacket) dataPacket).getData(); - int dataLength = ((TFTPDataPacket) dataPacket).getDataLength(); - int dataOffset = ((TFTPDataPacket) dataPacket).getDataOffset(); - - if (block > lastBlock || (lastBlock == 65535 && block == 0)) - { + } else { + final int block = ((TFTPDataPacket) dataPacket).getBlockNumber(); + final byte[] data = ((TFTPDataPacket) dataPacket).getData(); + final int dataLength = ((TFTPDataPacket) dataPacket).getDataLength(); + final int dataOffset = ((TFTPDataPacket) dataPacket).getDataOffset(); + + if (block > lastBlock || lastBlock == 65535 && block == 0) { // it might resend a data block if it missed our ack // - don't rewrite the block. bos.write(data, dataOffset, dataLength); @@ -792,39 +344,26 @@ public class TFTPServer implements Runnable lastSentAck = new TFTPAckPacket(twrp.getAddress(), twrp.getPort(), block); sendData(transferTftp_, lastSentAck); // send the data - if (dataLength < TFTPDataPacket.MAX_DATA_LENGTH) - { + if (dataLength < TFTPDataPacket.MAX_DATA_LENGTH) { // end of stream signal - The tranfer is complete. bos.close(); // But my ack may be lost - so listen to see if I // need to resend the ack. - for (int i = 0; i < maxTimeoutRetries_; i++) - { - try - { + for (int i = 0; i < maxTimeoutRetries_; i++) { + try { dataPacket = transferTftp_.bufferedReceive(); - } - catch (SocketTimeoutException e) - { + } catch (final SocketTimeoutException e) { // this is the expected route - the client // shouldn't be sending any more packets. break; } - if (dataPacket != null - && (!dataPacket.getAddress().equals(twrp.getAddress()) || dataPacket - .getPort() != twrp.getPort())) - { + if (dataPacket != null && (!dataPacket.getAddress().equals(twrp.getAddress()) || dataPacket.getPort() != twrp.getPort())) { // make sure it was from the right client... - transferTftp_ - .bufferedSend(new TFTPErrorPacket(dataPacket - .getAddress(), dataPacket.getPort(), - TFTPErrorPacket.UNKNOWN_TID, - "Unexpected Host or Port")); - } - else - { + transferTftp_.bufferedSend(new TFTPErrorPacket(dataPacket.getAddress(), dataPacket.getPort(), TFTPErrorPacket.UNKNOWN_TID, + "Unexpected Host or Port")); + } else { // This means they sent us the last // datapacket again, must have missed our // ack. resend it. @@ -837,103 +376,335 @@ public class TFTPServer implements Runnable } } } - } - finally - { - if (bos != null) - { + } finally { + if (bos != null) { bos.close(); } } } /* - * Utility method to make sure that paths provided by tftp clients do not get outside of the - * serverRoot directory. + * recursively check to see if one directory is a parent of another. */ - private File buildSafeFile(File serverDirectory, String fileName, boolean createSubDirs) - throws IOException - { - File temp = new File(serverDirectory, fileName); - temp = temp.getCanonicalFile(); - - if (!isSubdirectoryOf(serverDirectory, temp)) - { - throw new IOException("Cannot access files outside of tftp server root."); + private boolean isSubdirectoryOf(final File parent, final File child) { + final File childsParent = child.getParentFile(); + if (childsParent == null) { + return false; } - - // ensure directory exists (if requested) - if (createSubDirs) - { - createDirectory(temp.getParentFile()); + if (childsParent.equals(parent)) { + return true; } - - return temp; + return isSubdirectoryOf(parent, childsParent); } - /* - * recursively create subdirectories - */ - private void createDirectory(File file) throws IOException - { - File parent = file.getParentFile(); - if (parent == null) - { - throw new IOException("Unexpected error creating requested directory"); - } - if (!parent.exists()) - { - // recurse... - createDirectory(parent); - } + @Override + public void run() { + try { + transferTftp_ = newTFTP(); - if (parent.isDirectory()) - { - if (file.isDirectory()) - { - return; + transferTftp_.beginBufferedOps(); + transferTftp_.setDefaultTimeout(socketTimeout_); + + transferTftp_.open(); + + if (tftpPacket_ instanceof TFTPReadRequestPacket) { + handleRead((TFTPReadRequestPacket) tftpPacket_); + } else if (tftpPacket_ instanceof TFTPWriteRequestPacket) { + handleWrite((TFTPWriteRequestPacket) tftpPacket_); + } else { + log_.println("Unsupported TFTP request (" + tftpPacket_ + ") - ignored."); + } + } catch (final Exception e) { + if (!shutdownTransfer) { + logError_.println("Unexpected Error in during TFTP file transfer. Transfer aborted. " + e); + } + } finally { + try { + if (transferTftp_ != null && transferTftp_.isOpen()) { + transferTftp_.endBufferedOps(); + transferTftp_.close(); + } + } catch (final Exception e) { + // noop } - boolean result = file.mkdir(); - if (!result) - { - throw new IOException("Couldn't create requested directory"); + synchronized (transfers_) { + transfers_.remove(this); } } - else - { - throw new IOException( - "Invalid directory path - file in the way of requested folder"); + } + + public void shutdown() { + shutdownTransfer = true; + try { + transferTftp_.close(); + } catch (final RuntimeException e) { + // noop } } + } - /* - * recursively check to see if one directory is a parent of another. - */ - private boolean isSubdirectoryOf(File parent, File child) - { - File childsParent = child.getParentFile(); - if (childsParent == null) - { - return false; + private static final int DEFAULT_TFTP_PORT = 69; + /* /dev/null output stream (default) */ + private static final PrintStream nullStream = new PrintStream(new OutputStream() { + @Override + public void write(final byte[] b) throws IOException { + } + + @Override + public void write(final int b) { + } + }); + private final HashSet<TFTPTransfer> transfers_ = new HashSet<>(); + private volatile boolean shutdownServer; + private TFTP serverTftp_; + private File serverReadDirectory_; + private File serverWriteDirectory_; + private final int port_; + private final InetAddress laddr_; + + private Exception serverException; + + private final ServerMode mode_; + // don't have access to a logger api, so we will log to these streams, which + // by default are set to a no-op logger + private PrintStream log_; + + private PrintStream logError_; + private int maxTimeoutRetries_ = 3; + private int socketTimeout_; + + private Thread serverThread; + + /** + * Start a TFTP Server on the specified port. Gets and Puts occur in the specified directory. + * + * The server will start in another thread, allowing this constructor to return immediately. + * + * If a get or a put comes in with a relative path that tries to get outside of the serverDirectory, then the get or put will be denied. + * + * GET_ONLY mode only allows gets, PUT_ONLY mode only allows puts, and GET_AND_PUT allows both. Modes are defined as int constants in this class. + * + * @param serverReadDirectory directory for GET requests + * @param serverWriteDirectory directory for PUT requests + * @param port The local port to bind to. + * @param localaddr The local address to bind to. + * @param mode A value as specified above. + * @param log Stream to write log message to. If not provided, uses System.out + * @param errorLog Stream to write error messages to. If not provided, uses System.err. + * @throws IOException if the server directory is invalid or does not exist. + */ + public TFTPServer(final File serverReadDirectory, final File serverWriteDirectory, final int port, final InetAddress localaddr, final ServerMode mode, + final PrintStream log, final PrintStream errorLog) throws IOException { + port_ = port; + mode_ = mode; + laddr_ = localaddr; + log_ = log == null ? nullStream : log; + logError_ = errorLog == null ? nullStream : errorLog; + launch(serverReadDirectory, serverWriteDirectory); + } + + /** + * Start a TFTP Server on the specified port. Gets and Puts occur in the specified directory. + * + * The server will start in another thread, allowing this constructor to return immediately. + * + * If a get or a put comes in with a relative path that tries to get outside of the serverDirectory, then the get or put will be denied. + * + * GET_ONLY mode only allows gets, PUT_ONLY mode only allows puts, and GET_AND_PUT allows both. Modes are defined as int constants in this class. + * + * @param serverReadDirectory directory for GET requests + * @param serverWriteDirectory directory for PUT requests + * @param port the port to use + * @param localiface The local network interface to bind to. The interface's first address wil be used. + * @param mode A value as specified above. + * @param log Stream to write log message to. If not provided, uses System.out + * @param errorLog Stream to write error messages to. If not provided, uses System.err. + * @throws IOException if the server directory is invalid or does not exist. + */ + public TFTPServer(final File serverReadDirectory, final File serverWriteDirectory, final int port, final NetworkInterface localiface, final ServerMode mode, + final PrintStream log, final PrintStream errorLog) throws IOException { + mode_ = mode; + port_ = port; + InetAddress iaddr = null; + if (localiface != null) { + final Enumeration<InetAddress> ifaddrs = localiface.getInetAddresses(); + if ((ifaddrs != null) && ifaddrs.hasMoreElements()) { + iaddr = ifaddrs.nextElement(); } - if (childsParent.equals(parent)) - { - return true; + } + log_ = log == null ? nullStream : log; + logError_ = errorLog == null ? nullStream : errorLog; + laddr_ = iaddr; + launch(serverReadDirectory, serverWriteDirectory); + } + + /** + * Start a TFTP Server on the specified port. Gets and Puts occur in the specified directory. + * + * The server will start in another thread, allowing this constructor to return immediately. + * + * If a get or a put comes in with a relative path that tries to get outside of the serverDirectory, then the get or put will be denied. + * + * GET_ONLY mode only allows gets, PUT_ONLY mode only allows puts, and GET_AND_PUT allows both. Modes are defined as int constants in this class. + * + * @param serverReadDirectory directory for GET requests + * @param serverWriteDirectory directory for PUT requests + * @param port the port to use + * @param mode A value as specified above. + * @param log Stream to write log message to. If not provided, uses System.out + * @param errorLog Stream to write error messages to. If not provided, uses System.err. + * @throws IOException if the server directory is invalid or does not exist. + */ + public TFTPServer(final File serverReadDirectory, final File serverWriteDirectory, final int port, final ServerMode mode, final PrintStream log, + final PrintStream errorLog) throws IOException { + port_ = port; + mode_ = mode; + log_ = log == null ? nullStream : log; + logError_ = errorLog == null ? nullStream : errorLog; + laddr_ = null; + launch(serverReadDirectory, serverWriteDirectory); + } + + /** + * Start a TFTP Server on the default port (69). Gets and Puts occur in the specified directories. + * + * The server will start in another thread, allowing this constructor to return immediately. + * + * If a get or a put comes in with a relative path that tries to get outside of the serverDirectory, then the get or put will be denied. + * + * GET_ONLY mode only allows gets, PUT_ONLY mode only allows puts, and GET_AND_PUT allows both. Modes are defined as int constants in this class. + * + * @param serverReadDirectory directory for GET requests + * @param serverWriteDirectory directory for PUT requests + * @param mode A value as specified above. + * @throws IOException if the server directory is invalid or does not exist. + */ + public TFTPServer(final File serverReadDirectory, final File serverWriteDirectory, final ServerMode mode) throws IOException { + this(serverReadDirectory, serverWriteDirectory, DEFAULT_TFTP_PORT, mode, null, null); + } + + @Override + protected void finalize() throws Throwable { + shutdown(); + } + + /** + * Get the current value for maxTimeoutRetries + * + * @return the max allowed number of retries + */ + public int getMaxTimeoutRetries() { + return maxTimeoutRetries_; + } + + /** + * The current socket timeout used during transfers in milliseconds. + * + * @return the timeout value + */ + public int getSocketTimeout() { + return socketTimeout_; + } + + /** + * check if the server thread is still running. + * + * @return true if running, false if stopped. + * @throws Exception throws the exception that stopped the server if the server is stopped from an exception. + */ + public boolean isRunning() throws Exception { + if (shutdownServer && serverException != null) { + throw serverException; + } + return !shutdownServer; + } + + /* + * start the server, throw an error if it can't start. + */ + private void launch(final File serverReadDirectory, final File serverWriteDirectory) throws IOException { + log_.println("Starting TFTP Server on port " + port_ + ". Read directory: " + serverReadDirectory + " Write directory: " + serverWriteDirectory + + " Server Mode is " + mode_); + + serverReadDirectory_ = serverReadDirectory.getCanonicalFile(); + if (!serverReadDirectory_.exists() || !serverReadDirectory.isDirectory()) { + throw new IOException("The server read directory " + serverReadDirectory_ + " does not exist"); + } + + serverWriteDirectory_ = serverWriteDirectory.getCanonicalFile(); + if (!serverWriteDirectory_.exists() || !serverWriteDirectory.isDirectory()) { + throw new IOException("The server write directory " + serverWriteDirectory_ + " does not exist"); + } + + serverTftp_ = new TFTP(); + + // This is the value used in response to each client. + socketTimeout_ = serverTftp_.getDefaultTimeout(); + + // we want the server thread to listen forever. + serverTftp_.setDefaultTimeout(0); + + if (laddr_ != null) { + serverTftp_.open(port_, laddr_); + } else { + serverTftp_.open(port_); + } + + serverThread = new Thread(this); + serverThread.setDaemon(true); + serverThread.start(); + } + + /* + * Allow test code to customise the TFTP instance + */ + TFTP newTFTP() { + return new TFTP(); + } + + @Override + public void run() { + try { + while (!shutdownServer) { + final TFTPPacket tftpPacket; + + tftpPacket = serverTftp_.receive(); + + final TFTPTransfer tt = new TFTPTransfer(tftpPacket); + synchronized (transfers_) { + transfers_.add(tt); + } + + final Thread thread = new Thread(tt); + thread.setDaemon(true); + thread.start(); } - else - { - return isSubdirectoryOf(parent, childsParent); + } catch (final Exception e) { + if (!shutdownServer) { + serverException = e; + logError_.println("Unexpected Error in TFTP Server - Server shut down! + " + e); + } + } finally { + shutdownServer = true; // set this to true, so the launching thread can check to see if it started. + if (serverTftp_ != null && serverTftp_.isOpen()) { + serverTftp_.close(); } } } + /* + * Also allow customisation of sending data/ack so can generate errors if needed + */ + void sendData(final TFTP tftp, final TFTPPacket data) throws IOException { + tftp.bufferedSend(data); + } + /** * Set the stream object to log debug / informational messages. By default, this is a no-op * * @param log the stream to use for logging */ - public void setLog(PrintStream log) - { + public void setLog(final PrintStream log) { this.log_ = log; } @@ -942,22 +713,55 @@ public class TFTPServer implements Runnable * * @param logError the stream to use for logging errors */ - public void setLogError(PrintStream logError) - { + public void setLogError(final PrintStream logError) { this.logError_ = logError; } - /* - * Allow test code to customise the TFTP instance + /** + * Set the max number of retries in response to a timeout. Default 3. Min 0. + * + * @param retries number of retries, must be > 0 */ - TFTP newTFTP() { - return new TFTP(); + public void setMaxTimeoutRetries(final int retries) { + if (retries < 0) { + throw new RuntimeException("Invalid Value"); + } + maxTimeoutRetries_ = retries; } - /* - * Also allow customisation of sending data/ack so can generate errors if needed + /** + * Set the socket timeout in milliseconds used in transfers. Defaults to the value here: + * https://commons.apache.org/net/apidocs/org/apache/commons/net/tftp/TFTP.html#DEFAULT_TIMEOUT (5000 at the time I write this) Min value of 10. + * + * @param timeout the timeout; must be larger than 10 */ - void sendData(TFTP tftp, TFTPPacket data) throws IOException { - tftp.bufferedSend(data); + public void setSocketTimeout(final int timeout) { + if (timeout < 10) { + throw new RuntimeException("Invalid Value"); + } + socketTimeout_ = timeout; + } + + /** + * Stop the tftp server (and any currently running transfers) and release all opened network resources. + */ + public void shutdown() { + shutdownServer = true; + + synchronized (transfers_) { + transfers_.forEach(TFTPTransfer::shutdown); + } + + try { + serverTftp_.close(); + } catch (final RuntimeException e) { + // noop + } + + try { + serverThread.join(); + } catch (final InterruptedException e) { + // we've done the best we could, return + } } } diff --git a/src/test/java/org/apache/commons/net/tftp/TFTPServerMain.java b/src/test/java/org/apache/commons/net/tftp/TFTPServerMain.java index ecb5809..a433470 100644 --- a/src/test/java/org/apache/commons/net/tftp/TFTPServerMain.java +++ b/src/test/java/org/apache/commons/net/tftp/TFTPServerMain.java @@ -24,45 +24,35 @@ import java.util.Map; import java.util.Random; /** - * Main class for TFTPServer. - * This allows CLI use of the server. + * Main class for TFTPServer. This allows CLI use of the server. + * * @since 3.6 */ public class TFTPServerMain { - private static final String USAGE = - "Usage: TFTPServerMain [options] [port]\n\n" + - "port - the port to use (default 6901)\n" + - "\t-p path to server directory (default java.io.tempdir)\n" + - "\t-r randomly introduce errors\n" + - "\t-v verbose (trace packets)\n" - ; + private static final String USAGE = "Usage: TFTPServerMain [options] [port]\n\n" + "port - the port to use (default 6901)\n" + + "\t-p path to server directory (default java.io.tempdir)\n" + "\t-r randomly introduce errors\n" + "\t-v verbose (trace packets)\n"; - public static void main(String [] args) throws Exception { + public static void main(final String[] args) throws Exception { int port = 6901; int argc; - Map<String,String> opts = new HashMap<String,String>(); + final Map<String, String> opts = new HashMap<>(); opts.put("-p", System.getProperty("java.io.tmpdir")); // Parse options - for (argc = 0; argc < args.length; argc++) - { - String arg = args[argc]; - if (arg.startsWith("-")) - { - if (arg.equals("-v")) { - opts.put(arg, arg); - } else if (arg.equals("-r")) { - opts.put(arg, arg); - } else if (arg.equals("-p")) { - opts.put(arg, args[++argc]); - } else { - System.err.println("Error: unrecognized option."); - System.err.print(USAGE); - System.exit(1); - } - } else { + for (argc = 0; argc < args.length; argc++) { + final String arg = args[argc]; + if (!arg.startsWith("-")) { break; } + if (arg.equals("-v") || arg.equals("-r")) { + opts.put(arg, arg); + } else if (arg.equals("-p")) { + opts.put(arg, args[++argc]); + } else { + System.err.println("Error: unrecognized option."); + System.err.print(USAGE); + System.exit(1); + } } if (argc < args.length) { @@ -75,75 +65,74 @@ public class TFTPServerMain { final File serverDirectory = new File(opts.get("-p")); System.out.println("Server directory: " + serverDirectory); - final TFTPServer tftpS = new TFTPServer(serverDirectory, serverDirectory, port, - TFTPServer.ServerMode.GET_AND_PUT, null, null){ + final TFTPServer tftpS = new TFTPServer(serverDirectory, serverDirectory, port, TFTPServer.ServerMode.GET_AND_PUT, null, null) { @Override - void sendData(TFTP tftp, TFTPPacket packet) throws IOException { + TFTP newTFTP() { + if (verbose) { + return new TFTP() { + @Override + protected void trace(final String direction, final TFTPPacket packet) { + System.out.println(direction + " " + packet.toString()); + } + }; + } + return new TFTP(); + } + + @Override + void sendData(final TFTP tftp, final TFTPPacket packet) throws IOException { if (rand == null) { super.sendData(tftp, packet); return; } - int rint = rand.nextInt(10); - switch(rint) { - case 0: - System.out.println("Bump port " + packet); - int port = packet.getPort(); - packet.setPort(port+5); - super.sendData(tftp, packet); - packet.setPort(port); - break; - case 1: - if (packet instanceof TFTPDataPacket) { - TFTPDataPacket data = (TFTPDataPacket) packet; - System.out.println("Change data block num"); - data._blockNumber--; - super.sendData(tftp, packet); - data._blockNumber++; - } - if (packet instanceof TFTPAckPacket) { - TFTPAckPacket ack = (TFTPAckPacket) packet; - System.out.println("Change ack block num"); - ack._blockNumber--; - super.sendData(tftp, packet); - ack._blockNumber++; - } - break; - case 2: - System.out.println("Drop packet: " + packet); - break; - case 3: - System.out.println("Dupe packet: " + packet); - super.sendData(tftp, packet); + final int rint = rand.nextInt(10); + switch (rint) { + case 0: + System.out.println("Bump port " + packet); + final int port = packet.getPort(); + packet.setPort(port + 5); + super.sendData(tftp, packet); + packet.setPort(port); + break; + case 1: + if (packet instanceof TFTPDataPacket) { + final TFTPDataPacket data = (TFTPDataPacket) packet; + System.out.println("Change data block num"); + data.blockNumber--; super.sendData(tftp, packet); - break; - default: + data.blockNumber++; + } + if (packet instanceof TFTPAckPacket) { + final TFTPAckPacket ack = (TFTPAckPacket) packet; + System.out.println("Change ack block num"); + ack.blockNumber--; super.sendData(tftp, packet); - break; - } - } - - TFTP newTFTP(){ - if (verbose) { - return new TFTP() { - @Override - protected void trace(String direction, TFTPPacket packet) { - System.out.println(direction + " " + packet.toString()); - } - }; - } else { - return new TFTP(); + ack.blockNumber++; + } + break; + case 2: + System.out.println("Drop packet: " + packet); + break; + case 3: + System.out.println("Dupe packet: " + packet); + super.sendData(tftp, packet); + super.sendData(tftp, packet); + break; + default: + super.sendData(tftp, packet); + break; } } }; Runtime.getRuntime().addShutdownHook(new Thread() { + @Override public void run() { System.out.println("Server shutting down"); tftpS.shutdown(); System.out.println("Server exit"); - } } - ); + }); System.out.println("Started the server on " + port); Thread.sleep(99999999L); } diff --git a/src/test/java/org/apache/commons/net/tftp/TFTPServerPathTest.java b/src/test/java/org/apache/commons/net/tftp/TFTPServerPathTest.java index 2b90a52..d9d89ae 100644 --- a/src/test/java/org/apache/commons/net/tftp/TFTPServerPathTest.java +++ b/src/test/java/org/apache/commons/net/tftp/TFTPServerPathTest.java @@ -26,100 +26,82 @@ import org.apache.commons.net.tftp.TFTPServer.ServerMode; import junit.framework.TestCase; /** - * Some basic tests to ensure that the TFTP Server is honoring its read/write mode, and preventing - * files from being read or written from outside of the assigned roots. + * Some basic tests to ensure that the TFTP Server is honoring its read/write mode, and preventing files from being read or written from outside of the assigned + * roots. */ -public class TFTPServerPathTest extends TestCase -{ +public class TFTPServerPathTest extends TestCase { private static final int SERVER_PORT = 6901; String filePrefix = "tftp-"; File serverDirectory = new File(System.getProperty("java.io.tmpdir")); - public void testReadOnly() throws IOException - { + public void testReadOnly() throws IOException { // Start a read-only server - TFTPServer tftpS = new TFTPServer(serverDirectory, serverDirectory, SERVER_PORT, - ServerMode.GET_ONLY, null, null); + final TFTPServer tftpS = new TFTPServer(serverDirectory, serverDirectory, SERVER_PORT, ServerMode.GET_ONLY, null, null); // Create our TFTP instance to handle the file transfer. - TFTPClient tftp = new TFTPClient(); + final TFTPClient tftp = new TFTPClient(); tftp.open(); tftp.setSoTimeout(2000); // make a file to work with. - File file = new File(serverDirectory, filePrefix + "source.txt"); + final File file = new File(serverDirectory, filePrefix + "source.txt"); file.createNewFile(); // Read the file from the tftp server. - File out = new File(serverDirectory, filePrefix + "out"); + final File out = new File(serverDirectory, filePrefix + "out"); // cleanup old failed runs out.delete(); - assertTrue("Couldn't clear output location", !out.exists()); + assertFalse("Couldn't clear output location", out.exists()); - FileOutputStream output = new FileOutputStream(out); - - tftp.receiveFile(file.getName(), TFTP.BINARY_MODE, output, "localhost", SERVER_PORT); - output.close(); + try (final FileOutputStream output = new FileOutputStream(out)) { + tftp.receiveFile(file.getName(), TFTP.BINARY_MODE, output, "localhost", SERVER_PORT); + } assertTrue("file not created", out.exists()); out.delete(); - FileInputStream fis = new FileInputStream(file); - try - { + try (final FileInputStream fis = new FileInputStream(file)) { tftp.sendFile(out.getName(), TFTP.BINARY_MODE, fis, "localhost", SERVER_PORT); fail("Server allowed write"); - } - catch (IOException e) - { + } catch (final IOException e) { // expected path } - fis.close(); file.delete(); tftpS.shutdown(); } - public void testWriteOnly() throws IOException - { + public void testWriteOnly() throws IOException { // Start a write-only server - TFTPServer tftpS = new TFTPServer(serverDirectory, serverDirectory, SERVER_PORT, - ServerMode.PUT_ONLY, null, null); + final TFTPServer tftpS = new TFTPServer(serverDirectory, serverDirectory, SERVER_PORT, ServerMode.PUT_ONLY, null, null); // Create our TFTP instance to handle the file transfer. - TFTPClient tftp = new TFTPClient(); + final TFTPClient tftp = new TFTPClient(); tftp.open(); tftp.setSoTimeout(2000); // make a file to work with. - File file = new File(serverDirectory, filePrefix + "source.txt"); + final File file = new File(serverDirectory, filePrefix + "source.txt"); file.createNewFile(); - File out = new File(serverDirectory, filePrefix + "out"); + final File out = new File(serverDirectory, filePrefix + "out"); // cleanup old failed runs out.delete(); - assertTrue("Couldn't clear output location", !out.exists()); - - FileOutputStream output = new FileOutputStream(out); + assertFalse("Couldn't clear output location", out.exists()); - try - { + try (final FileOutputStream output = new FileOutputStream(out)) { tftp.receiveFile(file.getName(), TFTP.BINARY_MODE, output, "localhost", SERVER_PORT); fail("Server allowed read"); - } - catch (IOException e) - { + } catch (final IOException e) { // expected path } - output.close(); out.delete(); - FileInputStream fis = new FileInputStream(file); - tftp.sendFile(out.getName(), TFTP.BINARY_MODE, fis, "localhost", SERVER_PORT); - - fis.close(); + try (final FileInputStream fis = new FileInputStream(file)) { + tftp.sendFile(out.getName(), TFTP.BINARY_MODE, fis, "localhost", SERVER_PORT); + } assertTrue("file not created", out.exists()); @@ -129,36 +111,27 @@ public class TFTPServerPathTest extends TestCase tftpS.shutdown(); } - public void testWriteOutsideHome() throws IOException - { + public void testWriteOutsideHome() throws IOException { // Start a server - TFTPServer tftpS = new TFTPServer(serverDirectory, serverDirectory, SERVER_PORT, - ServerMode.GET_AND_PUT, null, null); + final TFTPServer tftpS = new TFTPServer(serverDirectory, serverDirectory, SERVER_PORT, ServerMode.GET_AND_PUT, null, null); // Create our TFTP instance to handle the file transfer. - TFTPClient tftp = new TFTPClient(); + final TFTPClient tftp = new TFTPClient(); tftp.open(); - File file = new File(serverDirectory, filePrefix + "source.txt"); + final File file = new File(serverDirectory, filePrefix + "source.txt"); file.createNewFile(); assertFalse("test construction error", new File(serverDirectory, "../foo").exists()); - FileInputStream fis = new FileInputStream(file); - try - { + try (final FileInputStream fis = new FileInputStream(file)) { tftp.sendFile("../foo", TFTP.BINARY_MODE, fis, "localhost", SERVER_PORT); fail("Server allowed write!"); - } - catch (IOException e) - { + } catch (final IOException e) { // expected path } - fis.close(); - - assertFalse("file created when it should not have been", - new File(serverDirectory, "../foo").exists()); + assertFalse("file created when it should not have been", new File(serverDirectory, "../foo").exists()); // cleanup file.delete(); @@ -166,5 +139,4 @@ public class TFTPServerPathTest extends TestCase tftpS.shutdown(); } - } diff --git a/src/test/java/org/apache/commons/net/tftp/TFTPTest.java b/src/test/java/org/apache/commons/net/tftp/TFTPTest.java index bf294d5..42d92f4 100644 --- a/src/test/java/org/apache/commons/net/tftp/TFTPTest.java +++ b/src/test/java/org/apache/commons/net/tftp/TFTPTest.java @@ -30,11 +30,9 @@ import org.apache.commons.net.tftp.TFTPServer.ServerMode; import junit.framework.TestCase; /** - * Test the TFTP Server and TFTP Client by creating some files in the system temp folder and then - * uploading and downloading them. + * Test the TFTP Server and TFTP Client by creating some files in the system temp folder and then uploading and downloading them. */ -public class TFTPTest extends TestCase -{ +public class TFTPTest extends TestCase { private static final int SERVER_PORT = 6902; private static TFTPServer tftpS; private static final File serverDirectory = new File(System.getProperty("java.io.tmpdir")); @@ -44,10 +42,8 @@ public class TFTPTest extends TestCase static int testsLeftToRun = 6; // only want to do this once... - static - { - try - { + static { + try { files[0] = createFile(new File(serverDirectory, filePrefix + "empty.txt"), 0); files[1] = createFile(new File(serverDirectory, filePrefix + "small.txt"), 1); files[2] = createFile(new File(serverDirectory, filePrefix + "511.txt"), 511); @@ -58,125 +54,104 @@ public class TFTPTest extends TestCase files[7] = createFile(new File(serverDirectory, filePrefix + "huge.txt"), 37000 * 1024); // Start the server - tftpS = new TFTPServer(serverDirectory, serverDirectory, SERVER_PORT, ServerMode.GET_AND_PUT, - null, null); + tftpS = new TFTPServer(serverDirectory, serverDirectory, SERVER_PORT, ServerMode.GET_AND_PUT, null, null); tftpS.setSocketTimeout(2000); - } - catch (IOException e) - { + } catch (final IOException e) { e.printStackTrace(); } } - @Override - protected void tearDown() throws Exception - { - testsLeftToRun--; - if (testsLeftToRun <= 0) - { - if (tftpS != null) - { - tftpS.shutdown(); - } - for (File file : files) - { - file.delete(); - } - } - super.tearDown(); - } - /* * Create a file, size specified in bytes */ - private static File createFile(File file, int size) throws IOException - { - OutputStream os = new BufferedOutputStream(new FileOutputStream(file)); - byte[] temp = "0".getBytes(); - for (int i = 0; i < size; i++) - { - os.write(temp); + private static File createFile(final File file, final int size) throws IOException { + try (final OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) { + final byte[] temp = "0".getBytes(); + for (int i = 0; i < size; i++) { + os.write(temp); + } } - os.close(); return file; } - public void testTFTPBinaryDownloads() throws Exception - { - // test with the smaller files - for (int i = 0; i < 6; i++) - { - testDownload(TFTP.BINARY_MODE, files[i]); + private boolean filesIdentical(final File a, final File b) throws IOException { + if (!a.exists() || !b.exists()) { + return false; } - } - public void testASCIIDownloads() throws Exception - { - // test with the smaller files - for (int i = 0; i < 6; i++) - { - try { - testDownload(TFTP.ASCII_MODE, files[i]); - } catch (IOException e) { - fail("Entry "+i+" Error "+e.toString()); + if (a.length() != b.length()) { + return false; + } + + try (final InputStream fisA = new BufferedInputStream(new FileInputStream(a)); + final InputStream fisB = new BufferedInputStream(new FileInputStream(b))) { + + int aBit = fisA.read(); + int bBit = fisB.read(); + + while (aBit != -1) { + if (aBit != bBit) { + fisA.close(); + fisB.close(); + return false; + } + aBit = fisA.read(); + bBit = fisB.read(); } } + return true; } - public void testTFTPBinaryUploads() throws Exception - { - // test with the smaller files - for (int i = 0; i < 6; i++) - { - testUpload(TFTP.BINARY_MODE, files[i]); + @Override + protected void tearDown() throws Exception { + testsLeftToRun--; + if (testsLeftToRun <= 0) { + if (tftpS != null) { + tftpS.shutdown(); + } + for (final File file : files) { + file.delete(); + } } + super.tearDown(); } - public void testASCIIUploads() throws Exception - { + public void testASCIIDownloads() { // test with the smaller files - for (int i = 0; i < 6; i++) - { - testUpload(TFTP.ASCII_MODE, files[i]); - } - } + for (int i = 0; i < 6; i++) { + try { + testDownload(TFTP.ASCII_MODE, files[i]); + } catch (final IOException e) { + fail("Entry " + i + " Error " + e.toString()); + } - public void testHugeUploads() throws Exception - { - for (int i = 5; i < files.length; i++) - { - testUpload(TFTP.BINARY_MODE, files[i]); } } - public void testHugeDownloads() throws Exception - { + public void testASCIIUploads() throws Exception { // test with the smaller files - for (int i = 5; i < files.length; i++) - { - testDownload(TFTP.BINARY_MODE, files[i]); + for (int i = 0; i < 6; i++) { + testUpload(TFTP.ASCII_MODE, files[i]); } } - private void testDownload(int mode, File file) throws IOException - { + private void testDownload(final int mode, final File file) throws IOException { // Create our TFTP instance to handle the file transfer. - TFTPClient tftp = new TFTPClient(); + final TFTPClient tftp = new TFTPClient(); tftp.open(); tftp.setSoTimeout(2000); - File out = new File(serverDirectory, filePrefix + "download"); + final File out = new File(serverDirectory, filePrefix + "download"); // cleanup old failed runs out.delete(); - assertTrue("Couldn't clear output location", !out.exists()); + assertFalse("Couldn't clear output location", out.exists()); - FileOutputStream output = new FileOutputStream(out); - - tftp.receiveFile(file.getName(), mode, output, "localhost", SERVER_PORT); - output.close(); + try (final FileOutputStream output = new FileOutputStream(out)) { + tftp.receiveFile(file.getName(), mode, output, "localhost", SERVER_PORT); + } assertTrue("file not created", out.exists()); assertTrue("files not identical on file " + file, filesIdentical(out, file)); @@ -185,21 +160,47 @@ public class TFTPTest extends TestCase out.delete(); } - private void testUpload(int mode, File file) throws Exception - { + public void testHugeDownloads() throws Exception { + // test with the smaller files + for (int i = 5; i < files.length; i++) { + testDownload(TFTP.BINARY_MODE, files[i]); + } + } + + public void testHugeUploads() throws Exception { + for (int i = 5; i < files.length; i++) { + testUpload(TFTP.BINARY_MODE, files[i]); + } + } + + public void testTFTPBinaryDownloads() throws Exception { + // test with the smaller files + for (int i = 0; i < 6; i++) { + testDownload(TFTP.BINARY_MODE, files[i]); + } + } + + public void testTFTPBinaryUploads() throws Exception { + // test with the smaller files + for (int i = 0; i < 6; i++) { + testUpload(TFTP.BINARY_MODE, files[i]); + } + } + + private void testUpload(final int mode, final File file) throws Exception { // Create our TFTP instance to handle the file transfer. - TFTPClient tftp = new TFTPClient(); + final TFTPClient tftp = new TFTPClient(); tftp.open(); tftp.setSoTimeout(2000); - File in = new File(serverDirectory, filePrefix + "upload"); + final File in = new File(serverDirectory, filePrefix + "upload"); // cleanup old failed runs in.delete(); - assertTrue("Couldn't clear output location", !in.exists()); + assertFalse("Couldn't clear output location", in.exists()); - FileInputStream fis = new FileInputStream(file); - tftp.sendFile(in.getName(), mode, fis, "localhost", SERVER_PORT); - fis.close(); + try (final FileInputStream fis = new FileInputStream(file)) { + tftp.sendFile(in.getName(), mode, fis, "localhost", SERVER_PORT); + } // need to give the server a bit of time to receive our last packet, and // close out its file buffers, etc. @@ -209,39 +210,4 @@ public class TFTPTest extends TestCase in.delete(); } - - private boolean filesIdentical(File a, File b) throws IOException - { - if (!a.exists() || !b.exists()) - { - return false; - } - - if (a.length() != b.length()) - { - return false; - } - - InputStream fisA = new BufferedInputStream(new FileInputStream(a)); - InputStream fisB = new BufferedInputStream(new FileInputStream(b)); - - int aBit = fisA.read(); - int bBit = fisB.read(); - - while (aBit != -1) - { - if (aBit != bBit) - { - fisA.close(); - fisB.close(); - return false; - } - aBit = fisA.read(); - bBit = fisB.read(); - } - - fisA.close(); - fisB.close(); - return true; - } } diff --git a/src/test/java/org/apache/commons/net/time/TimeTCPClientTest.java b/src/test/java/org/apache/commons/net/time/TimeTCPClientTest.java index 5224ae0..a9815fb 100644 --- a/src/test/java/org/apache/commons/net/time/TimeTCPClientTest.java +++ b/src/test/java/org/apache/commons/net/time/TimeTCPClientTest.java @@ -16,26 +16,32 @@ */ package org.apache.commons.net.time; +import java.io.IOException; import java.net.InetAddress; import java.util.Calendar; -import java.io.IOException; import java.util.TimeZone; import junit.framework.TestCase; -public class TimeTCPClientTest extends TestCase -{ +public class TimeTCPClientTest extends TestCase { private TimeTestSimpleServer server1; private int _port = 3333; // default test port - protected void openConnections() throws Exception - { + protected void closeConnections() { + try { + server1.stop(); + Thread.sleep(1000); + } catch (final Exception e) { + // ignored + } + } + + protected void openConnections() throws Exception { try { server1 = new TimeTestSimpleServer(_port); server1.connect(); - } catch (IOException ioe) - { + } catch (final IOException ioe) { // try again on another port _port = 4000; server1 = new TimeTestSimpleServer(_port); @@ -44,66 +50,45 @@ public class TimeTCPClientTest extends TestCase server1.start(); } - /* - * tests the constant basetime used by TimeClient against tha - * computed from Calendar class. - */ - public void testInitial() { - TimeZone utcZone = TimeZone.getTimeZone("UTC"); - Calendar calendar = Calendar.getInstance(utcZone); - calendar.set(1900, Calendar.JANUARY, 1, 0, 0, 0); - calendar.set(Calendar.MILLISECOND, 0); - long baseTime = calendar.getTime().getTime() / 1000L; - - assertEquals(baseTime, -TimeTCPClient.SECONDS_1900_TO_1970); - } - /* * tests the times retrieved via the Time protocol implementation. */ - public void testCompareTimes() throws Exception - { + public void testCompareTimes() throws Exception { openConnections(); long time, time2; long clientTime, clientTime2; - TimeTCPClient client = new TimeTCPClient(); - try - { + final TimeTCPClient client = new TimeTCPClient(); + try { // Not sure why code used to use getLocalHost. final InetAddress localHost = InetAddress.getByName("localhost"); // WAS InetAddress.getLocalHost(); - try - { + try { // We want to timeout if a response takes longer than 60 seconds client.setDefaultTimeout(60000); client.connect(localHost, _port); clientTime = client.getDate().getTime(); time = System.currentTimeMillis(); - } catch (IOException e) { // catch the first connect error; assume second will work if this does - fail("IOError <"+e+"> trying to connect to " + localHost + " " + _port ); + } catch (final IOException e) { // catch the first connect error; assume second will work if this does + fail("IOError <" + e + "> trying to connect to " + localHost + " " + _port); throw e; - } finally - { - if(client.isConnected()) { - client.disconnect(); - } + } finally { + if (client.isConnected()) { + client.disconnect(); + } } - try - { + try { // We want to timeout if a response takes longer than 60 seconds client.setDefaultTimeout(60000); client.connect(localHost, _port); - clientTime2 = (client.getTime() - TimeTCPClient.SECONDS_1900_TO_1970)*1000L; + clientTime2 = (client.getTime() - TimeTCPClient.SECONDS_1900_TO_1970) * 1000L; time2 = System.currentTimeMillis(); - } finally - { - if(client.isConnected()) { - client.disconnect(); - } + } finally { + if (client.isConnected()) { + client.disconnect(); + } } - } finally - { + } finally { closeConnections(); } @@ -112,16 +97,16 @@ public class TimeTCPClientTest extends TestCase assertTrue(Math.abs(time2 - clientTime2) < 5000); } - protected void closeConnections() - { - try - { - server1.stop(); - Thread.sleep(1000); - } catch (Exception e) - { - // ignored - } + /* + * tests the constant basetime used by TimeClient against tha computed from Calendar class. + */ + public void testInitial() { + final TimeZone utcZone = TimeZone.getTimeZone("UTC"); + final Calendar calendar = Calendar.getInstance(utcZone); + calendar.set(1900, Calendar.JANUARY, 1, 0, 0, 0); + calendar.set(Calendar.MILLISECOND, 0); + final long baseTime = calendar.getTime().getTime() / 1000L; + + assertEquals(baseTime, -TimeTCPClient.SECONDS_1900_TO_1970); } } - diff --git a/src/test/java/org/apache/commons/net/time/TimeTestSimpleServer.java b/src/test/java/org/apache/commons/net/time/TimeTestSimpleServer.java index b50d717..21d6fd8 100644 --- a/src/test/java/org/apache/commons/net/time/TimeTestSimpleServer.java +++ b/src/test/java/org/apache/commons/net/time/TimeTestSimpleServer.java @@ -1,5 +1,3 @@ -package org.apache.commons.net.time; - /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -17,109 +15,86 @@ package org.apache.commons.net.time; * limitations under the License. */ +package org.apache.commons.net.time; + import java.io.DataOutputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; /** - * The TimetSimpleServer class is a simple TCP implementation of a server - * for the Time Protocol described in RFC 868. + * The TimetSimpleServer class is a simple TCP implementation of a server for the Time Protocol described in RFC 868. * <p> - * Listens for TCP socket connections on the time protocol port and writes - * the local time to socket outputStream as 32-bit integer of seconds - * since midnight on 1 January 1900 GMT. - * See <A HREF="ftp://ftp.rfc-editor.org/in-notes/rfc868.txt"> the spec </A> for - * details. + * Listens for TCP socket connections on the time protocol port and writes the local time to socket outputStream as 32-bit integer of seconds since midnight on + * 1 January 1900 GMT. See <A HREF="ftp://ftp.rfc-editor.org/in-notes/rfc868.txt"> the spec </A> for details. * <p> * Note this is for <B>debugging purposes only</B> and not meant to be run as a realiable time service. * - * @version $Revision: 1741829 $ */ -public class TimeTestSimpleServer implements Runnable -{ +public class TimeTestSimpleServer implements Runnable { /** * baseline time 1900-01-01T00:00:00 UTC */ public static final long SECONDS_1900_TO_1970 = 2208988800L; - /*** The default time port. It is set to 37 according to RFC 868. ***/ + /** The default time port. It is set to 37 according to RFC 868. */ public static final int DEFAULT_PORT = 37; + public static void main(final String[] args) { + final TimeTestSimpleServer server = new TimeTestSimpleServer(); + try { + server.start(); + } catch (final IOException e) { + // ignored + } + } + private ServerSocket server; private final int port; - private boolean running = false; - public TimeTestSimpleServer() - { + private boolean running; + + public TimeTestSimpleServer() { port = DEFAULT_PORT; } - public TimeTestSimpleServer(int port) - { + public TimeTestSimpleServer(final int port) { this.port = port; } - public void connect() throws IOException - { - if (server == null) - { + public void connect() throws IOException { + if (server == null) { server = new ServerSocket(port); } } - public int getPort() - { + public int getPort() { return server == null ? port : server.getLocalPort(); } - public boolean isRunning() - { + public boolean isRunning() { return running; } - /* - * Start time service and provide time to client connections. - */ - public void start() throws IOException - { - if (server == null) - { - connect(); - } - if (!running) - { - running = true; - new Thread(this).start(); - } - } - @Override - public void run() - { + public void run() { Socket socket = null; - while (running) - { - try - { + while (running) { + try { socket = server.accept(); - DataOutputStream os = new DataOutputStream(socket.getOutputStream()); + final DataOutputStream os = new DataOutputStream(socket.getOutputStream()); // add 500 ms to round off to nearest second - int time = (int) ((System.currentTimeMillis() + 500) / 1000 + SECONDS_1900_TO_1970); + final int time = (int) ((System.currentTimeMillis() + 500) / 1000 + SECONDS_1900_TO_1970); os.writeInt(time); os.flush(); - } catch (IOException e) - { + } catch (final IOException e) { // ignored - } finally - { + } finally { if (socket != null) { - try - { - socket.close(); // force closing of the socket - } catch (IOException e) - { + try { + socket.close(); // force closing of the socket + } catch (final IOException e) { System.err.println("close socket error: " + e); } } @@ -127,35 +102,32 @@ public class TimeTestSimpleServer implements Runnable } } + /* + * Start time service and provide time to client connections. + */ + public void start() throws IOException { + if (server == null) { + connect(); + } + if (!running) { + running = true; + new Thread(this).start(); + } + } + /* * Close server socket. */ - public void stop() - { + public void stop() { running = false; - if (server != null) - { - try - { - server.close(); // force closing of the socket - } catch (IOException e) - { + if (server != null) { + try { + server.close(); // force closing of the socket + } catch (final IOException e) { System.err.println("close socket error: " + e); } server = null; } } - public static void main(String[] args) - { - TimeTestSimpleServer server = new TimeTestSimpleServer(); - try - { - server.start(); - } catch (IOException e) - { - // ignored - } - } - } diff --git a/src/test/java/org/apache/commons/net/util/Base64Test.java b/src/test/java/org/apache/commons/net/util/Base64Test.java index afe6030..1bc1f79 100644 --- a/src/test/java/org/apache/commons/net/util/Base64Test.java +++ b/src/test/java/org/apache/commons/net/util/Base64Test.java @@ -18,9 +18,12 @@ package org.apache.commons.net.util; -import static org.junit.Assert.*; - -import java.util.Arrays; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import org.junit.Ignore; import org.junit.Test; @@ -29,15 +32,15 @@ public class Base64Test { @Test public void testBase64() { - Base64 b64 = new Base64(); + final Base64 b64 = new Base64(); assertFalse(b64.isUrlSafe()); } @Test public void testBase64Boolean() { - Base64 b64 = new Base64(true); + final Base64 b64 = new Base64(true); assertTrue(b64.isUrlSafe()); - assertTrue(Arrays.equals(new byte[]{'\r','\n'}, b64.getLineSeparator())); + assertArrayEquals(new byte[] { '\r', '\n' }, b64.getLineSeparator()); } @Test @@ -52,97 +55,78 @@ public class Base64Test { @Test public void testBase64IntByteArray() { - Base64 b64; - b64 = new Base64(8, new byte[]{}); + final Base64 b64; + b64 = new Base64(8, new byte[] {}); assertFalse(b64.isUrlSafe()); - assertTrue(Arrays.equals(new byte[]{}, b64.getLineSeparator())); + assertArrayEquals(new byte[] {}, b64.getLineSeparator()); } @Test public void testBase64IntByteArrayBoolean() { Base64 b64; - b64 = new Base64(8, new byte[]{}, false); + b64 = new Base64(8, new byte[] {}, false); assertFalse(b64.isUrlSafe()); - b64 = new Base64(8, new byte[]{}, true); + b64 = new Base64(8, new byte[] {}, true); assertTrue(b64.isUrlSafe()); } @Test - public void testIsBase64() { - assertTrue(Base64.isBase64((byte)'b')); - assertFalse(Base64.isBase64((byte)' ')); - } - - @Test - public void testIsArrayByteBase64() { - assertTrue(Base64.isArrayByteBase64(new byte[]{'b',' '})); - assertFalse(Base64.isArrayByteBase64(new byte[]{'?'})); - } - - @Test - public void testEncodeBase64ByteArray() { - byte[] binaryData=null; - assertTrue(Arrays.equals(binaryData, Base64.encodeBase64(binaryData))); - } - - @Test @Ignore - public void testEncodeBase64StringByteArray() { - fail("Not yet implemented"); - } - - @Test @Ignore - public void testEncodeBase64StringUnChunked() { - fail("Not yet implemented"); - } - - @Test @Ignore - public void testEncodeBase64StringByteArrayBoolean() { + @Ignore + public void testDecodeBase64ByteArray() { fail("Not yet implemented"); } - @Test @Ignore - public void testEncodeBase64URLSafe() { + @Test + @Ignore + public void testDecodeBase64String() { fail("Not yet implemented"); } - @Test @Ignore - public void testEncodeBase64URLSafeString() { + @Test + @Ignore + public void testDecodeByteArray() { fail("Not yet implemented"); } - @Test @Ignore - public void testEncodeBase64Chunked() { + @Test + @Ignore + public void testDecodeInteger() { fail("Not yet implemented"); } - @Test @Ignore + @Test + @Ignore public void testDecodeObject() { fail("Not yet implemented"); } - @Test @Ignore + @Test + @Ignore public void testDecodeString() { fail("Not yet implemented"); } - @Test @Ignore - public void testDecodeByteArray() { - fail("Not yet implemented"); + @Test + public void testEncodeBase64ByteArray() { + final byte[] binaryData = null; + assertArrayEquals(binaryData, Base64.encodeBase64(binaryData)); } - @Test @Ignore + @Test + @Ignore public void testEncodeBase64ByteArrayBoolean() { fail("Not yet implemented"); } - @Test @Ignore + @Test + @Ignore public void testEncodeBase64ByteArrayBooleanBoolean() { fail("Not yet implemented"); } @Test public void testEncodeBase64ByteArrayBooleanBooleanInt() { - byte[] binaryData = new byte[]{'1','2','3'}; + final byte[] binaryData = { '1', '2', '3' }; byte[] encoded; encoded = Base64.encodeBase64(binaryData, false, false); assertNotNull(encoded); @@ -150,7 +134,7 @@ public class Base64Test { try { Base64.encodeBase64(binaryData, false, false, 3); fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { + } catch (final IllegalArgumentException expected) { // expected } encoded = Base64.encodeBase64(binaryData, false, false, 4); // NET-483 @@ -162,7 +146,7 @@ public class Base64Test { try { Base64.encodeBase64(binaryData, true, false, 5); fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException expected) { + } catch (final IllegalArgumentException expected) { // expected } encoded = Base64.encodeBase64(binaryData, true, false, 6); @@ -170,42 +154,80 @@ public class Base64Test { assertEquals(6, encoded.length); } - @Test @Ignore - public void testDecodeBase64String() { + @Test + @Ignore + public void testEncodeBase64Chunked() { fail("Not yet implemented"); } - @Test @Ignore - public void testDecodeBase64ByteArray() { + @Test + @Ignore + public void testEncodeBase64StringByteArray() { fail("Not yet implemented"); } - @Test @Ignore - public void testEncodeObject() { + @Test + @Ignore + public void testEncodeBase64StringByteArrayBoolean() { fail("Not yet implemented"); } - @Test @Ignore - public void testEncodeToString() { + @Test + @Ignore + public void testEncodeBase64StringUnChunked() { fail("Not yet implemented"); } - @Test @Ignore - public void testEncodeByteArray() { + @Test + @Ignore + public void testEncodeBase64URLSafe() { fail("Not yet implemented"); } - @Test @Ignore - public void testDecodeInteger() { + @Test + @Ignore + public void testEncodeBase64URLSafeString() { + fail("Not yet implemented"); + } + + @Test + @Ignore + public void testEncodeByteArray() { fail("Not yet implemented"); } - @Test @Ignore + @Test + @Ignore public void testEncodeInteger() { fail("Not yet implemented"); } - @Test @Ignore + @Test + @Ignore + public void testEncodeObject() { + fail("Not yet implemented"); + } + + @Test + @Ignore + public void testEncodeToString() { + fail("Not yet implemented"); + } + + @Test + public void testIsArrayByteBase64() { + assertTrue(Base64.isArrayByteBase64(new byte[] { 'b', ' ' })); + assertFalse(Base64.isArrayByteBase64(new byte[] { '?' })); + } + + @Test + public void testIsBase64() { + assertTrue(Base64.isBase64((byte) 'b')); + assertFalse(Base64.isBase64((byte) ' ')); + } + + @Test + @Ignore public void testToIntegerBytes() { fail("Not yet implemented"); } diff --git a/src/test/java/org/apache/commons/net/util/UtilTest.java b/src/test/java/org/apache/commons/net/util/UtilTest.java index 2156131..a4b0de8 100644 --- a/src/test/java/org/apache/commons/net/util/UtilTest.java +++ b/src/test/java/org/apache/commons/net/util/UtilTest.java @@ -37,89 +37,105 @@ import org.junit.Test; public class UtilTest { - private final Writer dest = new CharArrayWriter(); - private final Reader source = new CharArrayReader(new char[]{'a'}); - private final InputStream src = new ByteArrayInputStream(new byte[]{'z'}); - private final OutputStream dst = new ByteArrayOutputStream(); + static class CSL implements CopyStreamListener { - @Test - public void testcloseQuietly() { - Util.closeQuietly((Closeable) null); - Util.closeQuietly((Socket) null); - } + final long expectedTotal; + final int expectedBytes; + final long expectedSize; - @Test - public void testReader0() throws Exception { - long streamSize=0; - int bufferSize=0; - Util.copyReader(source, dest, bufferSize, streamSize, new CSL(1,1,streamSize)); - } + CSL(final long totalBytesTransferred, final int bytesTransferred, final long streamSize) { + this.expectedTotal = totalBytesTransferred; + this.expectedBytes = bytesTransferred; + this.expectedSize = streamSize; + } - @Test - public void testReader1() throws Exception { - long streamSize=0; - int bufferSize=1; - Util.copyReader(source, dest, bufferSize, streamSize, new CSL(1,1,streamSize)); - } + @Override + public void bytesTransferred(final CopyStreamEvent event) { + } - @Test - public void testReader_1() throws Exception { - long streamSize=0; - int bufferSize=-1; - Util.copyReader(source, dest, bufferSize, streamSize, new CSL(1,1,streamSize)); - } + @Override + public void bytesTransferred(final long totalBytesTransferred, final int bytesTransferred, final long streamSize) { + Assert.assertEquals("Wrong total", expectedTotal, totalBytesTransferred); + Assert.assertEquals("Wrong streamSize", expectedSize, streamSize); + Assert.assertEquals("Wrong bytes", expectedBytes, bytesTransferred); + } - @Test - public void testStream0() throws Exception { - long streamSize=0; - int bufferSize=0; - Util.copyStream(src, dst, bufferSize, streamSize, new CSL(1,1,streamSize)); } - @Test - public void testStream1() throws Exception { - long streamSize=0; - int bufferSize=1; - Util.copyStream(src, dst, bufferSize, streamSize, new CSL(1,1,streamSize)); + // Class to check overall counts as well as batch size + static class CSLtotal implements CopyStreamListener { + + final long expectedTotal; + final long expectedBytes; + volatile long totalBytesTransferredTotal; + volatile long bytesTransferredTotal; + + CSLtotal(final long totalBytesTransferred, final long bytesTransferred) { + this.expectedTotal = totalBytesTransferred; + this.expectedBytes = bytesTransferred; + } + + @Override + public void bytesTransferred(final CopyStreamEvent event) { + } + + @Override + public void bytesTransferred(final long totalBytesTransferred, final int bytesTransferred, final long streamSize) { + Assert.assertEquals("Wrong bytes", expectedBytes, bytesTransferred); + this.totalBytesTransferredTotal = totalBytesTransferred; + this.bytesTransferredTotal += bytesTransferred; + } + + void checkExpected() { + Assert.assertEquals("Wrong totalBytesTransferred total", expectedTotal, totalBytesTransferredTotal); + Assert.assertEquals("Total should equal sum of parts", totalBytesTransferredTotal, bytesTransferredTotal); + } + } + private final Writer dest = new CharArrayWriter(); + private final Reader source = new CharArrayReader(new char[] { 'a' }); + + private final InputStream src = new ByteArrayInputStream(new byte[] { 'z' }); + + private final OutputStream dst = new ByteArrayOutputStream(); + @Test - public void testStream_1() throws Exception { - long streamSize=0; - int bufferSize=-1; - Util.copyStream(src, dst, bufferSize, streamSize, new CSL(1,1,streamSize)); + public void testcloseQuietly() { + Util.closeQuietly((Closeable) null); + Util.closeQuietly((Socket) null); } @Test public void testNET550_Reader() throws Exception { - final char[] buff = new char[]{'a', 'b', 'c', 'd'}; // must be multiple of 2 + final char[] buff = { 'a', 'b', 'c', 'd' }; // must be multiple of 2 final int bufflen = buff.length; - { // Check buffer size 1 processes in chunks of 1 - Reader rdr = new CharArrayReader(buff); + { // Check buffer size 1 processes in chunks of 1 + final Reader rdr = new CharArrayReader(buff); final CSLtotal listener = new CSLtotal(bufflen, 1); Util.copyReader(rdr, dest, 1, 0, listener); // buffer size 1 listener.checkExpected(); } - { // Check bufsize 2 uses chunks of 2 - Reader rdr = new CharArrayReader(buff); + { // Check bufsize 2 uses chunks of 2 + final Reader rdr = new CharArrayReader(buff); final CSLtotal listener = new CSLtotal(bufflen, 2); Util.copyReader(rdr, dest, 2, 0, listener); // buffer size 2 listener.checkExpected(); } - { // Check bigger size reads the lot - Reader rdr = new CharArrayReader(buff); + { // Check bigger size reads the lot + final Reader rdr = new CharArrayReader(buff); final CSLtotal listener = new CSLtotal(bufflen, bufflen); Util.copyReader(rdr, dest, 20, 0, listener); // buffer size 20 listener.checkExpected(); } - { // Check negative size reads reads full amount - Reader rdr = new CharArrayReader(buff); + { // Check negative size reads reads full amount + final Reader rdr = new CharArrayReader(buff); final CSLtotal listener = new CSLtotal(bufflen, bufflen); Util.copyReader(rdr, dest, -1, 0, listener); // buffer size -1 listener.checkExpected(); } - { // Check zero size reads reads full amount - Reader rdr = new CharArrayReader(buff); + { // Check zero size reads reads full amount + final Reader rdr = new CharArrayReader(buff); final CSLtotal listener = new CSLtotal(bufflen, bufflen); Util.copyReader(rdr, dest, 0, 0, listener); // buffer size -1 listener.checkExpected(); @@ -128,90 +144,79 @@ public class UtilTest { @Test public void testNET550_Stream() throws Exception { - final byte[] buff = new byte[]{'a', 'b', 'c', 'd'}; // must be multiple of 2 + final byte[] buff = { 'a', 'b', 'c', 'd' }; // must be multiple of 2 final int bufflen = buff.length; - { // Check buffer size 1 processes in chunks of 1 - InputStream is = new ByteArrayInputStream(buff); + { // Check buffer size 1 processes in chunks of 1 + final InputStream is = new ByteArrayInputStream(buff); final CSLtotal listener = new CSLtotal(bufflen, 1); Util.copyStream(is, dst, 1, 0, listener); // buffer size 1 listener.checkExpected(); } - { // Check bufsize 2 uses chunks of 2 - InputStream is = new ByteArrayInputStream(buff); + { // Check bufsize 2 uses chunks of 2 + final InputStream is = new ByteArrayInputStream(buff); final CSLtotal listener = new CSLtotal(bufflen, 2); Util.copyStream(is, dst, 2, 0, listener); // buffer size 2 listener.checkExpected(); } - { // Check bigger size reads the lot - InputStream is = new ByteArrayInputStream(buff); + { // Check bigger size reads the lot + final InputStream is = new ByteArrayInputStream(buff); final CSLtotal listener = new CSLtotal(bufflen, bufflen); Util.copyStream(is, dst, 20, 0, listener); // buffer size 20 listener.checkExpected(); } - { // Check negative size reads reads full amount - InputStream is = new ByteArrayInputStream(buff); + { // Check negative size reads reads full amount + final InputStream is = new ByteArrayInputStream(buff); final CSLtotal listener = new CSLtotal(bufflen, bufflen); Util.copyStream(is, dst, -1, 0, listener); // buffer size -1 listener.checkExpected(); } - { // Check zero size reads reads full amount - InputStream is = new ByteArrayInputStream(buff); + { // Check zero size reads reads full amount + final InputStream is = new ByteArrayInputStream(buff); final CSLtotal listener = new CSLtotal(bufflen, bufflen); Util.copyStream(is, dst, 0, 0, listener); // buffer size -1 listener.checkExpected(); } } - static class CSL implements CopyStreamListener { - - final long expectedTotal; - final int expectedBytes; - final long expectedSize; - CSL(long totalBytesTransferred, int bytesTransferred, long streamSize) { - this.expectedTotal = totalBytesTransferred; - this.expectedBytes = bytesTransferred; - this.expectedSize = streamSize; - } - @Override - public void bytesTransferred(CopyStreamEvent event) { - } - - @Override - public void bytesTransferred(long totalBytesTransferred, int bytesTransferred, long streamSize) { - Assert.assertEquals("Wrong total", expectedTotal, totalBytesTransferred); - Assert.assertEquals("Wrong streamSize", expectedSize, streamSize); - Assert.assertEquals("Wrong bytes", expectedBytes, bytesTransferred); - } - + @Test + public void testReader_1() throws Exception { + final long streamSize = 0; + final int bufferSize = -1; + Util.copyReader(source, dest, bufferSize, streamSize, new CSL(1, 1, streamSize)); } - // Class to check overall counts as well as batch size - static class CSLtotal implements CopyStreamListener { - - final long expectedTotal; - final long expectedBytes; - volatile long totalBytesTransferredTotal; - volatile long bytesTransferredTotal; + @Test + public void testReader0() throws Exception { + final long streamSize = 0; + final int bufferSize = 0; + Util.copyReader(source, dest, bufferSize, streamSize, new CSL(1, 1, streamSize)); + } - CSLtotal(long totalBytesTransferred, long bytesTransferred) { - this.expectedTotal = totalBytesTransferred; - this.expectedBytes = bytesTransferred; - } - @Override - public void bytesTransferred(CopyStreamEvent event) { - } + @Test + public void testReader1() throws Exception { + final long streamSize = 0; + final int bufferSize = 1; + Util.copyReader(source, dest, bufferSize, streamSize, new CSL(1, 1, streamSize)); + } - @Override - public void bytesTransferred(long totalBytesTransferred, int bytesTransferred, long streamSize) { - Assert.assertEquals("Wrong bytes", expectedBytes, bytesTransferred); - this.totalBytesTransferredTotal = totalBytesTransferred; - this.bytesTransferredTotal += bytesTransferred; - } + @Test + public void testStream_1() throws Exception { + final long streamSize = 0; + final int bufferSize = -1; + Util.copyStream(src, dst, bufferSize, streamSize, new CSL(1, 1, streamSize)); + } - void checkExpected() { - Assert.assertEquals("Wrong totalBytesTransferred total", expectedTotal, totalBytesTransferredTotal); - Assert.assertEquals("Total should equal sum of parts", totalBytesTransferredTotal, bytesTransferredTotal); - } + @Test + public void testStream0() throws Exception { + final long streamSize = 0; + final int bufferSize = 0; + Util.copyStream(src, dst, bufferSize, streamSize, new CSL(1, 1, streamSize)); + } + @Test + public void testStream1() throws Exception { + final long streamSize = 0; + final int bufferSize = 1; + Util.copyStream(src, dst, bufferSize, streamSize, new CSL(1, 1, streamSize)); } } diff --git a/src/test/resources/org/apache/commons/net/ftpsserver/ftpserver.jks b/src/test/resources/org/apache/commons/net/ftpsserver/ftpserver.jks new file mode 100644 index 0000000000000000000000000000000000000000..53e9ee648b24436a6071b201bdded4a6b0e31d84 GIT binary patch literal 3051 zcmchZXHb*r7RTQ-B18x^7J3yzdqa~*6$l1JM5Gf-P^uJ>x`+@$6O@jCvJwQ0p(9d6 zX-W}T>LMbF(p^9igr$fi+_<BocjnIhxF4Pm=gfbeIrD$c?>vXK$XWyd0Q&tv08ZC{ zOa3mGgIq2H03={>Z&M8bARr(W&IEZlxD+`ca0nL42Z0~}I2Atsd{2`7?q^T%CHA)t zyQZ<O;%{ow4m5|Jo>zCPADnGie&s_uHX6efjIN|ttTf?XkrrxYFA0W~Aqa$+pz0MG zt}(XAJ0Wav0;^bGTU?oYe_b*`s?qUs%NwuPiXvp`@z`3zjcXZe7(PNwo`1!c4$Emp zBeZ^fJaPSAQyaPphVf1kyEB6jSu82NHUC85#tUd5N@t+6B_xPzF0@&hC*v8GpSH&+ zXJ6%Ynz+i*P3Xuc?G%PmR;5Qn36fsAsc_WW)MCk4N_9I%eQ#^>6&}x5f(vm0*rX#} zk5t+xt9xG@T{WO|Tke`--e(qD%#$_y7Sy-mUkDCC8!;rs5A`zW<Si#kbzJz44mCa` zd-z6X`<d%xwXVC<XAiaKat-)NSXL+@O@;vr+xdvE(g<!qCFgJ)^+8<X`0x|mWC{Fs zUV+?pgjqZ_|McIR(;g4x7^WGzh9Yr%3vDvk-!lnoN8>M<<nGg-n!ewl3xB|+XN@yR z9(k%7?rN4rnd`PU@JZRRzjlqU3zLwDm@O$#cHE&aD~cB6kGdx!i5^(ZxUNjIgp0b1 z$9ep2*WHA7^~XkESU6iQ*&yrSr)QEh(6ceh)9gUrzHa8<d3x6ahWLKkO%?C`#~$|+ zhr^OH@+&Kefd}ymga%$eyW0a~ksfWUiMKk>Ntnj?7rGWBDs768fcxHk#7qDxAhXAS zV!Xe?s7|FzwVqoz#L+pm<)fANW(a@GlG*~fKx>v!C7d%RKE=T_54$amFx_2Z7<7#$ z*lzl#LHG{JGO6f|b{aO0;wrN|hSfKP<73+kmvS396fVP!6S+cfbuBw~AJ@O{z2?SB zR#z!4aN_@x`_;zyjlkgN%!xWHme<}l$3leC>kXkfQ^fo|(Cxwfj11+=MSXoQC(|2n z8X9qW9fKjSwH193o^^-j$J7@`V`FW(9>BWdRJWe?lynvMl6SqAgWgc8oKMtLE?Bwh zS(9BQ9dY3y&A|73ucx{~^!<cJ^Um-SM(Z}BJ=fzsg}dj|=b(Y6Pl>AD{A329(U|9` zbCk~ha+TGysbH`ybyu0P0lIAR@v|9TzY3f}gcGxmAd$4U63N5r-`Pqe1sK@}_K@lf zJJ19~b_~5awvLolIHMeGGQ8pY=9%#BYK1lU75OA$^TSo|?aOVsa*mGE;mTwepU%6- zvZoOF!8OS}>0GZ2PW89=Ma-D6Dj(1f*W8fn8dRU3C`U`EQ-XOD60}3~TmK&59v5X= z-&6^3MQiF=Yp5J9wCqeS-p1=)byjHRF6S$<6G{tIj}J25xggVT_4=LeLvCNUZe$`& zqe|H2kM6#30k!O@5$4B;4T0*7LYp-$(UgpO*~RpCF4>P{9`%#79&&$y=U0nk2d4(l zG#4EePH95)kD1;aeSb{UluTQQQd*pLbJTzOiPh?_{*f;~<)E42U90-|m<@hSt6S3D z5+fcdNe52~?@rR=8qXwl$P_I2*V0=6F1aS%xV@t3n!80xKa{}ay;<W5c@+6gp%V8y zV`3`3L(g#`#ks3j^9mXCMR88y%G=4>wIAh-HqqDmYdl&$%ooDA${v)-H9DCmzfI(L znOjBnsj+Mk@mX^e8wfMU9XU6o8g45lAkSO;Rf(wT`a(uWvot9hF2OVOJ3Mhtno==W z%$|=B0Aa793E}|&<_}U~z91E9mj{DFpinqQ)n@?Y=HSG}5@R>GArKS-0NrsYkdp&u z1LqKc+ML4ivd1So*d>6p@ww>Z8|?EVgZ!C6v+rPQxCA&3Um~A(cljYf{gm*7yx$}+ z0ZuR9^JFh~U;hA5Y!9~@hyy`&Pz$Gl({S9wtqy|PIJNKLzgP(sBK4~c>`wwlg`feD z3gLoMArK(zWiB>3_;`<RbAbFwjq0$3s6P4!B|t*at=nA)r;RjXh-d`GiFo5rSmgI! zy=sfsy%%vf4Urm|$9NfHv0;wxJMS#zb1sb-O0s{Q8?>q2bU`Yrf`01Q=fbd0XQhK> zeNEmexopOujQzF_Mu$e5?bF$K$J}r-o*9aH)azv2m2n_GYKowA#U#4NBV#DT<GE## zuAxpxnjyt(IuYM+&v^}NW&7KTh=qQiVS)2pR$_Hbh>)9T{%XGQQ{UaAPQ8b~jitvI zTUS@iuOq{@5BwI}P&Z?g?_@N3cz|&yo^~h7nBU<g=ZD^`oXzB=BCV|6`Bq7#&A_rc z{=$cSLr@q5fD}lA;-Dz|6-9aA7`Wip`o+Rt!KFV<QCa-07ZrAHcXAB>Y9hOr*d{^% z5Dg>9$I1IHuVSD1irwZTxznp-%I8J5Q<nyy@@nyP!&j_jlIk{J(`D;b9fen^A!QR& z3ot&O7cizh>p-XT8_c>IEj>ZuE_3{vGiDaBJaT-Ux5S!!BZW0Rn`^uLjM`3)Z5mt4 zSZ2D8cpjT_yIWP}zcJ?Jq{)yUpU~c8rRdOuX`E>cDX`@~t=PtcI`Wk?2f4J%?sH`| zGF2kENTJf4Nb>axl&e~Ea_%-zC?~t2I9W^C{7vPOZj4yldZq_8v_o1?I5^hyUeJ*g z!9G4@dhOJyVF%If7;~n7uH=m483_?Fa_QQ~KX+QAg)~DsI@S9DZ)|jMxBWjG%D3(0 zyqAZIPXL=oo=p6Sgr88N*ia(bP_{wg1Ond$5XFIH^Vp{ihp}n&_=Uz(gdbr2UmCv? z%k>k!@0tIJqiMh;<9`bKh9jFiPz|pJs^dYBjiZ_tco3)cJ^VLIp+f4|>?!>)g1yE; z#G#Rq05jrLq-Y?&g4OuZ-&H=&{I$+kXQjJj0<?-5P-;)gc@Rz;N}c@bpqFUB)!e@J z7+n7&$Hr?UQimd{;k)8`uI}P3%X@`1+8q1}uhg>>Pl`rZbsfT+I(C0`Fz7;P_}8fQ zp?0xWsHU}mBPLaCJ#e*LkvxK3T9Jb7yE90HJckMaS?4U1g``nERcZCq{14HnWqXme zZ>4Qa=Ec0s(R=qtLkUUikzM_785K{q4lPi>*qNjBkW{ym4t7b8UpSn2?}y}#q*R+> z{G{j1<uL+v{hVqyI_Z<Tn9%FiVTtk+1?6Ij59xuaJ_Qk|ElEx1&tIKGAu>8Wg`b-k zCsX1XQR9=2vowyh>#k=gS8QZ2K+JVm{bpELWHbPcq?~=*Cnt#xXldGLiwhUx@Eaa2 zHhCm-^WGzZ>q>Kt?5WE19cb6xp@;gj=lDO>yp`F@S&hMurR}rM+0IucUXd=LA8K1p zCl%#)ym>#XjOS_am^TtHt+NmsFeyb$Qfxv1B5ex1_+g@aCa*NZd7VGKD50`(LR!%Y z@9Lc@;__eJ3j+aAne7=RW$qQ}mh7ng6}*%(2Fgutv7Ltsx|6t{RP!3F^=$dLCr<Iy d9x%H@JmA7g@MM}Pq>hs&v7Yyg8#P~s{0Hn|`a=K! literal 0 HcmV?d00001 diff --git a/src/test/resources/org/apache/commons/net/ftpsserver/users.properties b/src/test/resources/org/apache/commons/net/ftpsserver/users.properties new file mode 100644 index 0000000..521c443 --- /dev/null +++ b/src/test/resources/org/apache/commons/net/ftpsserver/users.properties @@ -0,0 +1,43 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Password is "admin" +ftpserver.user.admin.userpassword=21232F297A57A5A743894A0E4A801FC3 +ftpserver.user.admin.homedirectory=target/test-classes/org/apache/commons/net/test-data +ftpserver.user.admin.enableflag=true +ftpserver.user.admin.writepermission=true +ftpserver.user.admin.maxloginnumber=0 +ftpserver.user.admin.maxloginperip=0 +ftpserver.user.admin.idletime=0 +ftpserver.user.admin.uploadrate=0 +ftpserver.user.admin.downloadrate=0 + +ftpserver.user.anonymous.userpassword= +ftpserver.user.anonymous.homedirectory=target/test-classes/org/apache/commons/net/test-data +ftpserver.user.anonymous.enableflag=true +ftpserver.user.anonymous.writepermission=false +ftpserver.user.anonymous.maxloginnumber=20 +ftpserver.user.anonymous.maxloginperip=2 +ftpserver.user.anonymous.idletime=300 +ftpserver.user.anonymous.uploadrate=4800 +ftpserver.user.anonymous.downloadrate=4800 + +# password is "test" +ftpserver.user.test.userpassword=098f6bcd4621d373cade4e832627b4f6 +ftpserver.user.test.homedirectory=target/test-classes/org/apache/commons/net/test-data +ftpserver.user.test.enableflag=true +ftpserver.user.test.writepermission=true diff --git a/src/test/resources/org/apache/commons/net/test-data/file.txt b/src/test/resources/org/apache/commons/net/test-data/file.txt new file mode 100644 index 0000000..eba8c13 --- /dev/null +++ b/src/test/resources/org/apache/commons/net/test-data/file.txt @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +“We are all in the gutter, but some of us are looking at the stars.” + +― Oscar Wilde, Lady Windermere's Fan -- GitLab