summaryrefslogtreecommitdiff
path: root/org/apache/commons/net/nntp
diff options
context:
space:
mode:
Diffstat (limited to 'org/apache/commons/net/nntp')
-rw-r--r--org/apache/commons/net/nntp/Article.java253
-rw-r--r--org/apache/commons/net/nntp/ArticlePointer.java39
-rw-r--r--org/apache/commons/net/nntp/NNTP.java1022
-rw-r--r--org/apache/commons/net/nntp/NNTPClient.java1285
-rw-r--r--org/apache/commons/net/nntp/NNTPCommand.java83
-rw-r--r--org/apache/commons/net/nntp/NNTPConnectionClosedException.java56
-rw-r--r--org/apache/commons/net/nntp/NNTPReply.java209
-rw-r--r--org/apache/commons/net/nntp/NewGroupsOrNewsQuery.java283
-rw-r--r--org/apache/commons/net/nntp/NewsgroupInfo.java155
-rw-r--r--org/apache/commons/net/nntp/SimpleNNTPHeader.java164
-rw-r--r--org/apache/commons/net/nntp/Threadable.java34
-rw-r--r--org/apache/commons/net/nntp/Threader.java481
12 files changed, 4064 insertions, 0 deletions
diff --git a/org/apache/commons/net/nntp/Article.java b/org/apache/commons/net/nntp/Article.java
new file mode 100644
index 0000000..cccba3b
--- /dev/null
+++ b/org/apache/commons/net/nntp/Article.java
@@ -0,0 +1,253 @@
+/*
+ * 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.nntp;
+
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+/**
+ * This is a class that contains the basic state needed for message retrieval and threading.
+ * With thanks to Jamie Zawinski <jwz@jwz.org>
+ * @author rwinston <rwinston@apache.org>
+ *
+ */
+public class Article implements Threadable {
+ private int articleNumber;
+ private String subject;
+ private String date;
+ private String articleId;
+ private String simplifiedSubject;
+ private String from;
+ private StringBuffer header;
+ private StringBuffer references;
+ private boolean isReply = false;
+
+ public Article kid, next;
+
+ public Article() {
+ header = new StringBuffer();
+ }
+
+ /**
+ * Adds an arbitrary header key and value to this message's header.
+ * @param name the header name
+ * @param val the header value
+ */
+ public void addHeaderField(String name, String val) {
+ header.append(name);
+ header.append(": ");
+ header.append(val);
+ header.append('\n');
+ }
+
+ /**
+ * Adds a message-id to the list of messages that this message references (i.e. replies to)
+ * @param msgId
+ */
+ public void addReference(String msgId) {
+ if (references == null) {
+ references = new StringBuffer();
+ references.append("References: ");
+ }
+ references.append(msgId);
+ references.append("\t");
+ }
+
+ /**
+ * Returns the MessageId references as an array of Strings
+ * @return an array of message-ids
+ */
+ public String[] getReferences() {
+ if (references == null)
+ return new String[0];
+ ArrayList<String> list = new ArrayList<String>();
+ int terminator = references.toString().indexOf(':');
+ StringTokenizer st =
+ new StringTokenizer(references.substring(terminator), "\t");
+ while (st.hasMoreTokens()) {
+ list.add(st.nextToken());
+ }
+ return list.toArray(new String[list.size()]);
+ }
+
+ /**
+ * Attempts to parse the subject line for some typical reply signatures, and strip them out
+ *
+ */
+ 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')) {
+
+ if (subject.charAt(start + 2) == ':') {
+ start += 3; // Skip "Re:"
+ isReply = true;
+ 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;
+ isReply = true;
+ done = false;
+ }
+ }
+ }
+
+ if (simplifiedSubject == "(no subject)")
+ 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);
+ }
+ }
+
+ /**
+ * 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 static void printThread(Article article, int depth) {
+ for (int i = 0; i < depth; ++i)
+ System.out.print("==>");
+ System.out.println(article.getSubject() + "\t" + article.getFrom());
+ if (article.kid != null)
+ printThread(article.kid, depth + 1);
+ if (article.next != null)
+ printThread(article.next, depth);
+ }
+
+ public String getArticleId() {
+ return articleId;
+ }
+
+ public int getArticleNumber() {
+ return articleNumber;
+ }
+
+ public String getDate() {
+ return date;
+ }
+
+ public String getFrom() {
+ return from;
+ }
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public void setArticleId(String string) {
+ articleId = string;
+ }
+
+ public void setArticleNumber(int i) {
+ articleNumber = i;
+ }
+
+ public void setDate(String string) {
+ date = string;
+ }
+
+ public void setFrom(String string) {
+ from = string;
+ }
+
+ public void setSubject(String string) {
+ subject = string;
+ }
+
+
+ public boolean isDummy() {
+ return (getSubject() == null);
+ }
+
+ public String messageThreadId() {
+ return articleId;
+ }
+
+ public String[] messageThreadReferences() {
+ return getReferences();
+ }
+
+ public String simplifiedSubject() {
+ if(simplifiedSubject == null)
+ simplifySubject();
+ return simplifiedSubject;
+ }
+
+
+ public boolean subjectIsReply() {
+ if(simplifiedSubject == null)
+ simplifySubject();
+ return isReply;
+ }
+
+
+ public void setChild(Threadable child) {
+ this.kid = (Article) child;
+ flushSubjectCache();
+ }
+
+ private void flushSubjectCache() {
+ simplifiedSubject = null;
+ }
+
+
+ public void setNext(Threadable next) {
+ this.next = (Article)next;
+ flushSubjectCache();
+ }
+
+
+ public Threadable makeDummy() {
+ return new Article();
+ }
+}
diff --git a/org/apache/commons/net/nntp/ArticlePointer.java b/org/apache/commons/net/nntp/ArticlePointer.java
new file mode 100644
index 0000000..a810cad
--- /dev/null
+++ b/org/apache/commons/net/nntp/ArticlePointer.java
@@ -0,0 +1,39 @@
+/*
+ * 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.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}.
+ * @author Daniel F. Savarese
+ * @see NNTPClient
+ */
+public final class ArticlePointer
+{
+ /** The number of the referenced article. */
+ public int articleNumber;
+ /**
+ * The unique id of the referenced article, including the enclosing
+ * &lt and &gt 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/org/apache/commons/net/nntp/NNTP.java b/org/apache/commons/net/nntp/NNTP.java
new file mode 100644
index 0000000..225e064
--- /dev/null
+++ b/org/apache/commons/net/nntp/NNTP.java
@@ -0,0 +1,1022 @@
+/*
+ * 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.nntp;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+
+import org.apache.commons.net.MalformedServerReplyException;
+import org.apache.commons.net.ProtocolCommandListener;
+import org.apache.commons.net.ProtocolCommandSupport;
+import org.apache.commons.net.SocketClient;
+
+/***
+ * 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 }.
+ * <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.
+ * <p>
+ * <p>
+ * @author Daniel F. Savarese
+ * @author Rory Winston
+ * @author Ted Wise
+ * @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 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 StringBuffer __commandBuffer;
+
+ boolean _isAllowedToPost;
+ int _replyCode;
+ String _replyString;
+
+ /**
+ * 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.
+ */
+ protected BufferedWriter _writer_;
+
+ /***
+ * 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()
+ {
+ setDefaultPort(DEFAULT_PORT);
+ __commandBuffer = new StringBuffer();
+ _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);
+ }
+
+ if (_commandSupport_.getListenerCount() > 0)
+ _commandSupport_.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_}.
+ ***/
+ @Override
+ protected void _connectAction_() throws IOException
+ {
+ super._connectAction_();
+ _reader_ =
+ new BufferedReader(new InputStreamReader(_input_,
+ __DEFAULT_ENCODING));
+ _writer_ =
+ new BufferedWriter(new OutputStreamWriter(_output_,
+ __DEFAULT_ENCODING));
+ __getReply();
+
+ _isAllowedToPost = (_replyCode == NNTPReply.SERVER_READY_POSTING_ALLOWED);
+ }
+
+ /***
+ * Adds a ProtocolCommandListener. Delegates this task to
+ * {@link #_commandSupport_ _commandSupport_ }.
+ * <p>
+ * @param listener The ProtocolCommandListener to add.
+ ***/
+ public void addProtocolCommandListener(ProtocolCommandListener listener)
+ {
+ _commandSupport_.addProtocolCommandListener(listener);
+ }
+
+ /***
+ * Removes a ProtocolCommandListener. Delegates this task to
+ * {@link #_commandSupport_ _commandSupport_ }.
+ * <p>
+ * @param listener The ProtocolCommandListener to remove.
+ ***/
+ public void removeProtocolCommandListener(ProtocolCommandListener listener)
+ {
+ _commandSupport_.removeProtocolCommandListener(listener);
+ }
+
+ /***
+ * 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>
+ * @exception IOException If an error occurs while disconnecting.
+ ***/
+ @Override
+ public void disconnect() throws IOException
+ {
+ super.disconnect();
+ _reader_ = null;
+ _writer_ = null;
+ _replyString = null;
+ _isAllowedToPost = false;
+ }
+
+
+ /***
+ * 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;
+ }
+
+
+ /***
+ * 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.
+ * @exception 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.
+ * @exception 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
+ {
+ String message;
+
+ __commandBuffer.setLength(0);
+ __commandBuffer.append(command);
+
+ if (args != null)
+ {
+ __commandBuffer.append(' ');
+ __commandBuffer.append(args);
+ }
+ __commandBuffer.append(SocketClient.NETASCII_EOL);
+
+ _writer_.write(message = __commandBuffer.toString());
+ _writer_.flush();
+
+ if (_commandSupport_.getListenerCount() > 0)
+ _commandSupport_.fireCommandSent(command, message);
+
+ __getReply();
+ return _replyCode;
+ }
+
+
+ /***
+ * 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.
+ * @exception 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.
+ * @exception 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._commands[command], args);
+ }
+
+
+ /***
+ * 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.
+ * @exception 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.
+ * @exception 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 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.
+ * @exception 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.
+ * @exception 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 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>
+ * @return The integer value of the reply code of the last NNTP reply.
+ ***/
+ public int getReplyCode()
+ {
+ return _replyCode;
+ }
+
+ /***
+ * 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.
+ * @exception 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.
+ * @exception IOException If an I/O error occurs while
+ * receiving the server reply.
+ ***/
+ public int getReply() throws IOException
+ {
+ __getReply();
+ return _replyCode;
+ }
+
+
+ /***
+ * 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;
+ }
+
+
+ /***
+ * A convenience method to send the NNTP ARTICLE 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 &lt and &gt characters.
+ * @return The reply code received from the server.
+ * @exception 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.
+ * @exception 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);
+ }
+
+ /***
+ * A convenience method to send the NNTP ARTICLE 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.
+ * @exception 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.
+ * @exception IOException If an I/O error occurs while either sending the
+ * command or receiving the server reply.
+ ***/
+ public int article(int articleNumber) throws IOException
+ {
+ return sendCommand(NNTPCommand.ARTICLE, Integer.toString(articleNumber));
+ }
+
+ /***
+ * A convenience method to send the NNTP ARTICLE command to the server,
+ * receive the initial reply, and return the reply code.
+ * <p>
+ * @return The reply code received from the server.
+ * @exception 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.
+ * @exception 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);
+ }
+
+
+
+ /***
+ * 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 &lt and &gt characters.
+ * @return The reply code received from the server.
+ * @exception 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.
+ * @exception 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);
+ }
+
+ /***
+ * 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.
+ * @exception 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.
+ * @exception IOException If an I/O error occurs while either sending the
+ * command or receiving the server reply.
+ ***/
+ public int body(int articleNumber) throws IOException
+ {
+ return sendCommand(NNTPCommand.BODY, Integer.toString(articleNumber));
+ }
+
+ /***
+ * 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.
+ * @exception 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.
+ * @exception 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);
+ }
+
+
+
+ /***
+ * 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 &lt and &gt characters.
+ * @return The reply code received from the server.
+ * @exception 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.
+ * @exception 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);
+ }
+
+ /***
+ * 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.
+ * @exception 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.
+ * @exception IOException If an I/O error occurs while either sending the
+ * command or receiving the server reply.
+ ***/
+ public int head(int articleNumber) throws IOException
+ {
+ return sendCommand(NNTPCommand.HEAD, Integer.toString(articleNumber));
+ }
+
+ /***
+ * A convenience method to send the NNTP HEAD command to the server,
+ * receive the initial reply, and return the reply code.
+ * <p>
+ * @return The reply code received from the server.
+ * @exception 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.
+ * @exception 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 messageId The message identifier of the requested article,
+ * including the encapsulating &lt and &gt characters.
+ * @return The reply code received from the server.
+ * @exception 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.
+ * @exception 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);
+ }
+
+ /***
+ * 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.
+ * @exception 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.
+ * @exception IOException If an I/O error occurs while either sending the
+ * command or receiving the server reply.
+ ***/
+ public int stat(int articleNumber) throws IOException
+ {
+ return sendCommand(NNTPCommand.STAT, Integer.toString(articleNumber));
+ }
+
+ /***
+ * A convenience method to send the NNTP STAT command to the server,
+ * receive the initial reply, and return the reply code.
+ * <p>
+ * @return The reply code received from the server.
+ * @exception 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.
+ * @exception 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);
+ }
+
+
+ /***
+ * 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.
+ * @exception 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.
+ * @exception 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);
+ }
+
+
+ /***
+ * 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.
+ * @exception 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.
+ * @exception 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.
+ * <p>
+ * @param messageId The article identifier,
+ * including the encapsulating &lt and &gt characters.
+ * @return The reply code received from the server.
+ * @exception 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.
+ * @exception IOException If an I/O error occurs while either sending the
+ * command or receiving the server reply.
+ ***/
+ public int ihave(String messageId) throws IOException
+ {
+ return sendCommand(NNTPCommand.IHAVE, messageId);
+ }
+
+
+ /***
+ * 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.
+ * @exception 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.
+ * @exception 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.
+ * <p>
+ * @return The reply code received from the server.
+ * @exception 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.
+ * @exception 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.
+ * <p>
+ * @return The reply code received from the server.
+ * @exception 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.
+ * @exception 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 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.
+ * @return The reply code received from the server.
+ * @exception 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.
+ * @exception 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
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append(date);
+ buffer.append(' ');
+ buffer.append(time);
+
+ if (GMT)
+ {
+ buffer.append(' ');
+ buffer.append("GMT");
+ }
+
+ if (distributions != null)
+ {
+ buffer.append(" <");
+ buffer.append(distributions);
+ buffer.append('>');
+ }
+
+ return sendCommand(NNTPCommand.NEWGROUPS, buffer.toString());
+ }
+
+
+ /***
+ * A convenience method to send the NNTP NEWGROUPS 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.
+ * @exception 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.
+ * @exception 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
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append(newsgroups);
+ buffer.append(' ');
+ buffer.append(date);
+ buffer.append(' ');
+ buffer.append(time);
+
+ if (GMT)
+ {
+ buffer.append(' ');
+ buffer.append("GMT");
+ }
+
+ if (distributions != null)
+ {
+ buffer.append(" <");
+ buffer.append(distributions);
+ buffer.append('>');
+ }
+
+ return sendCommand(NNTPCommand.NEWNEWS, buffer.toString());
+ }
+
+
+
+ /***
+ * 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.
+ * @exception 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.
+ * @exception 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.
+ * <p>
+ * @return The reply code received from the server.
+ * @exception 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.
+ * @exception 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.
+ * @exception 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.
+ * @exception 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);
+ }
+
+ /***
+ * 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.
+ * @exception 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.
+ * @exception 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);
+ }
+
+ /***
+ * 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.
+ * @exception 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.
+ * @exception 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);
+ }
+
+ /***
+ * 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.
+ * @exception 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.
+ * @exception 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 {
+ StringBuffer command = new StringBuffer(header);
+ command.append(" ");
+ command.append(selectedArticles);
+ return sendCommand(NNTPCommand.XHDR, command.toString());
+ }
+
+ /**
+ * A convenience wrapper for the extended LIST command that takes
+ * an argument, allowing us to selectively list multiple groups.
+ * <p>
+ * @param wildmat A wildmat (pseudo-regex) pattern. See RFC 2980 for
+ * details.
+ * @return the reply code received from the server.
+ * @throws IOException
+ */
+ public int listActive(String wildmat) throws IOException {
+ StringBuffer command = new StringBuffer("ACTIVE ");
+ command.append(wildmat);
+ return sendCommand(NNTPCommand.LIST, command.toString());
+ }
+}
+
+/* Emacs configuration
+ * Local variables: **
+ * mode: java **
+ * c-basic-offset: 4 **
+ * indent-tabs-mode: nil **
+ * End: **
+ */
diff --git a/org/apache/commons/net/nntp/NNTPClient.java b/org/apache/commons/net/nntp/NNTPClient.java
new file mode 100644
index 0000000..e10ce90
--- /dev/null
+++ b/org/apache/commons/net/nntp/NNTPClient.java
@@ -0,0 +1,1285 @@
+/*
+ * 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.nntp;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+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;
+
+/***
+ * 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 }.
+ * <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.
+ * <p>
+ * <p>
+ * @author Daniel F. Savarese
+ * @author Rory Winston
+ * @author Ted Wise
+ * @see NNTP
+ * @see NNTPConnectionClosedException
+ * @see org.apache.commons.net.MalformedServerReplyException
+ ***/
+
+public class NNTPClient extends NNTP
+{
+
+ private void __parseArticlePointer(String reply, ArticlePointer pointer)
+ throws MalformedServerReplyException
+ {
+ StringTokenizer tokenizer;
+
+ // Do loop is a kluge to simulate goto
+ do
+ {
+ tokenizer = new StringTokenizer(reply);
+
+ if (tokenizer.countTokens() < 3)
+ break;
+
+ // Skip numeric response value
+ tokenizer.nextToken();
+ // Get article number
+ try
+ {
+ pointer.articleNumber = Integer.parseInt(tokenizer.nextToken());
+ }
+ catch (NumberFormatException e)
+ {
+ break;
+ }
+
+ // Get article id
+ pointer.articleId = tokenizer.nextToken();
+ return ;
+ }
+ while (false);
+
+ throw new MalformedServerReplyException(
+ "Could not parse article pointer.\nServer reply: " + reply);
+ }
+
+
+ private void __parseGroupReply(String reply, NewsgroupInfo info)
+ throws MalformedServerReplyException
+ {
+ String count, first, last;
+ StringTokenizer tokenizer;
+
+ // Do loop is a kluge to simulate goto
+ do
+ {
+ tokenizer = new StringTokenizer(reply);
+
+ if (tokenizer.countTokens() < 5)
+ break;
+
+ // Skip numeric response value
+ tokenizer.nextToken();
+ // Get estimated article count
+ count = tokenizer.nextToken();
+ // Get first article number
+ first = tokenizer.nextToken();
+ // Get last article number
+ last = tokenizer.nextToken();
+ // Get newsgroup name
+ info._setNewsgroup(tokenizer.nextToken());
+
+ try
+ {
+ info._setArticleCount(Integer.parseInt(count));
+ info._setFirstArticle(Integer.parseInt(first));
+ info._setLastArticle(Integer.parseInt(last));
+ }
+ catch (NumberFormatException e)
+ {
+ break;
+ }
+
+ info._setPostingPermission(NewsgroupInfo.UNKNOWN_POSTING_PERMISSION);
+ return ;
+ }
+ while (false);
+
+ throw new MalformedServerReplyException(
+ "Could not parse newsgroup info.\nServer reply: " + reply);
+ }
+
+
+ private NewsgroupInfo __parseNewsgroupListEntry(String entry)
+ {
+ NewsgroupInfo result;
+ StringTokenizer tokenizer;
+ int lastNum, firstNum;
+ String last, first, permission;
+
+ result = new NewsgroupInfo();
+ tokenizer = new StringTokenizer(entry);
+
+ if (tokenizer.countTokens() < 4)
+ return null;
+
+ result._setNewsgroup(tokenizer.nextToken());
+ last = tokenizer.nextToken();
+ first = tokenizer.nextToken();
+ permission = tokenizer.nextToken();
+
+ try
+ {
+ lastNum = Integer.parseInt(last);
+ firstNum = Integer.parseInt(first);
+ result._setFirstArticle(firstNum);
+ result._setLastArticle(lastNum);
+
+ if((firstNum == 0) && (lastNum == 0))
+ result._setArticleCount(0);
+ else
+ result._setArticleCount(lastNum - firstNum + 1);
+ }
+ catch (NumberFormatException e)
+ {
+ return null;
+ }
+
+ switch (permission.charAt(0))
+ {
+ case 'y':
+ case 'Y':
+ result._setPostingPermission(
+ NewsgroupInfo.PERMITTED_POSTING_PERMISSION);
+ break;
+ case 'n':
+ case 'N':
+ result._setPostingPermission(
+ NewsgroupInfo.PROHIBITED_POSTING_PERMISSION);
+ break;
+ case 'm':
+ case 'M':
+ result._setPostingPermission(
+ NewsgroupInfo.MODERATED_POSTING_PERMISSION);
+ break;
+ default:
+ result._setPostingPermission(
+ NewsgroupInfo.UNKNOWN_POSTING_PERMISSION);
+ break;
+ }
+
+ return result;
+ }
+
+ private NewsgroupInfo[] __readNewsgroupListing() throws IOException
+ {
+ int size;
+ String line;
+ Vector<NewsgroupInfo> list;
+ BufferedReader reader;
+ NewsgroupInfo tmp, info[];
+
+ reader = new BufferedReader(new DotTerminatedMessageReader(_reader_));
+ // Start of with a big vector because we may be reading a very large
+ // amount of groups.
+ list = new Vector<NewsgroupInfo>(2048);
+
+ while ((line = reader.readLine()) != null)
+ {
+ tmp = __parseNewsgroupListEntry(line);
+ if (tmp != null)
+ list.addElement(tmp);
+ else
+ throw new MalformedServerReplyException(line);
+ }
+
+ if ((size = list.size()) < 1)
+ return new NewsgroupInfo[0];
+
+ info = new NewsgroupInfo[size];
+ list.copyInto(info);
+
+ return info;
+ }
+
+
+ private Reader __retrieve(int command,
+ String articleId, ArticlePointer pointer)
+ throws IOException
+ {
+ Reader reader;
+
+ 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);
+
+ reader = new DotTerminatedMessageReader(_reader_);
+ return reader;
+ }
+
+
+ private Reader __retrieve(int command,
+ int articleNumber, ArticlePointer pointer)
+ throws IOException
+ {
+ Reader reader;
+
+ if (!NNTPReply.isPositiveCompletion(sendCommand(command,
+ Integer.toString(articleNumber))))
+ return null;
+
+ if (pointer != null)
+ __parseArticlePointer(getReplyString(), pointer);
+
+ reader = new DotTerminatedMessageReader(_reader_);
+ return reader;
+ }
+
+
+
+ /***
+ * Retrieves an article from the NNTP server. The article is referenced
+ * by its unique article identifier (including the enclosing &lt and &gt).
+ * The article number and identifier contained in the server reply
+ * are returned through an ArticlePointer. The <code> articleId </code>
+ * field of the ArticlePointer 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
+ * Reader instance.
+ * The NNTP protocol uses the same stream for issuing commands as it does
+ * for returning results. Therefore the returned Reader 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
+ * be read. null if the article does not exist.
+ * @exception 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.
+ * @exception IOException If an I/O error occurs while either sending a
+ * command to the server or receiving a reply from the server.
+ ***/
+ public Reader retrieveArticle(String articleId, ArticlePointer pointer)
+ throws IOException
+ {
+ return __retrieve(NNTPCommand.ARTICLE, articleId, pointer);
+
+ }
+
+ /*** Same as <code> retrieveArticle(articleId, null) </code> ***/
+ public Reader retrieveArticle(String articleId) throws IOException
+ {
+ return retrieveArticle(articleId, null);
+ }
+
+ /*** Same as <code> retrieveArticle(null) </code> ***/
+ public Reader retrieveArticle() throws IOException
+ {
+ return retrieveArticle(null);
+ }
+
+
+ /***
+ * 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 ArticlePointer. The <code> articleId </code>
+ * field of the ArticlePointer 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
+ * Reader instance.
+ * The NNTP protocol uses the same stream for issuing commands as it does
+ * for returning results. Therefore the returned Reader 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
+ * be read. null if the article does not exist.
+ * @exception 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.
+ * @exception IOException If an I/O error occurs while either sending a
+ * command to the server or receiving a reply from the server.
+ ***/
+ public Reader retrieveArticle(int articleNumber, ArticlePointer pointer)
+ throws IOException
+ {
+ return __retrieve(NNTPCommand.ARTICLE, articleNumber, pointer);
+ }
+
+ /*** Same as <code> retrieveArticle(articleNumber, null) </code> ***/
+ public Reader retrieveArticle(int articleNumber) throws IOException
+ {
+ return retrieveArticle(articleNumber, null);
+ }
+
+
+
+ /***
+ * Retrieves an article header from the NNTP server. The article is
+ * referenced
+ * by its unique article identifier (including the enclosing &lt and &gt).
+ * The article number and identifier contained in the server reply
+ * are returned through an ArticlePointer. The <code> articleId </code>
+ * field of the ArticlePointer 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
+ * Reader instance.
+ * The NNTP protocol uses the same stream for issuing commands as it does
+ * for returning results. Therefore the returned Reader 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 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.
+ * @exception 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.
+ * @exception IOException If an I/O error occurs while either sending a
+ * command to the server or receiving a reply from the server.
+ ***/
+ public Reader retrieveArticleHeader(String articleId, ArticlePointer pointer)
+ throws IOException
+ {
+ return __retrieve(NNTPCommand.HEAD, articleId, pointer);
+
+ }
+
+ /*** Same as <code> retrieveArticleHeader(articleId, null) </code> ***/
+ public Reader retrieveArticleHeader(String articleId) throws IOException
+ {
+ return retrieveArticleHeader(articleId, null);
+ }
+
+ /*** Same as <code> retrieveArticleHeader(null) </code> ***/
+ public Reader retrieveArticleHeader() throws IOException
+ {
+ return retrieveArticleHeader(null);
+ }
+
+
+ /***
+ * 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 ArticlePointer. The <code> articleId </code>
+ * field of the ArticlePointer 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
+ * Reader instance.
+ * The NNTP protocol uses the same stream for issuing commands as it does
+ * for returning results. Therefore the returned Reader 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.
+ * @exception 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.
+ * @exception IOException If an I/O error occurs while either sending a
+ * command to the server or receiving a reply from the server.
+ ***/
+ public Reader retrieveArticleHeader(int articleNumber,
+ ArticlePointer pointer)
+ throws IOException
+ {
+ return __retrieve(NNTPCommand.HEAD, articleNumber, pointer);
+ }
+
+
+ /*** Same as <code> retrieveArticleHeader(articleNumber, null) </code> ***/
+ public Reader retrieveArticleHeader(int articleNumber) throws IOException
+ {
+ return retrieveArticleHeader(articleNumber, null);
+ }
+
+
+
+ /***
+ * Retrieves an article body from the NNTP server. The article is
+ * referenced
+ * by its unique article identifier (including the enclosing &lt and &gt).
+ * The article number and identifier contained in the server reply
+ * are returned through an ArticlePointer. The <code> articleId </code>
+ * field of the ArticlePointer 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
+ * Reader instance.
+ * The NNTP protocol uses the same stream for issuing commands as it does
+ * for returning results. Therefore the returned Reader 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 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.
+ * @exception 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.
+ * @exception 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, ArticlePointer pointer)
+ throws IOException
+ {
+ return __retrieve(NNTPCommand.BODY, articleId, pointer);
+
+ }
+
+ /*** Same as <code> retrieveArticleBody(articleId, null) </code> ***/
+ public Reader retrieveArticleBody(String articleId) throws IOException
+ {
+ return retrieveArticleBody(articleId, null);
+ }
+
+ /*** Same as <code> retrieveArticleBody(null) </code> ***/
+ public Reader retrieveArticleBody() throws IOException
+ {
+ return retrieveArticleBody(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 ArticlePointer. The <code> articleId </code>
+ * field of the ArticlePointer 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
+ * Reader instance.
+ * The NNTP protocol uses the same stream for issuing commands as it does
+ * for returning results. Therefore the returned Reader 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.
+ * @exception 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.
+ * @exception 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(int articleNumber,
+ ArticlePointer pointer)
+ throws IOException
+ {
+ return __retrieve(NNTPCommand.BODY, articleNumber, pointer);
+ }
+
+
+ /*** Same as <code> retrieveArticleBody(articleNumber, null) </code> ***/
+ public Reader retrieveArticleBody(int articleNumber) throws IOException
+ {
+ return retrieveArticleBody(articleNumber, null);
+ }
+
+
+ /***
+ * 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.
+ * @exception 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.
+ * @exception 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;
+
+ if (info != null)
+ __parseGroupReply(getReplyString(), info);
+
+ return true;
+ }
+
+ /*** Same as <code> selectNewsgroup(newsgroup, null) </code> ***/
+ public boolean selectNewsgroup(String newsgroup) throws IOException
+ {
+ return selectNewsgroup(newsgroup, null);
+ }
+
+ /***
+ * List the command help from the server.
+ * <p>
+ * @return The sever help information.
+ * @exception 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.
+ * @exception 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
+ {
+ StringWriter help;
+ Reader reader;
+
+ if (!NNTPReply.isInformational(help()))
+ return null;
+
+ help = new StringWriter();
+ reader = new DotTerminatedMessageReader(_reader_);
+ Util.copyReader(reader, help);
+ reader.close();
+ help.close();
+ return help.toString();
+ }
+
+
+ /***
+ * Select an article by its unique identifier (including enclosing
+ * &lt and &gt) 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.
+ * @exception 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.
+ * @exception 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, ArticlePointer 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;
+ }
+
+ /**** Same as <code> selectArticle(articleId, null) </code> ***/
+ public boolean selectArticle(String articleId) throws IOException
+ {
+ return selectArticle(articleId, null);
+ }
+
+ /****
+ * Same as <code> selectArticle(null, articleId) </code>. Useful
+ * for retrieving the current article number.
+ ***/
+ public boolean selectArticle(ArticlePointer pointer) throws IOException
+ {
+ return selectArticle(null, pointer);
+ }
+
+
+ /***
+ * 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.
+ * @exception 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.
+ * @exception 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(int articleNumber, ArticlePointer pointer)
+ throws IOException
+ {
+ if (!NNTPReply.isPositiveCompletion(stat(articleNumber)))
+ return false;
+
+ if (pointer != null)
+ __parseArticlePointer(getReplyString(), pointer);
+
+ return true;
+ }
+
+
+ /*** Same as <code> selectArticle(articleNumber, null) </code> ***/
+ public boolean selectArticle(int articleNumber) throws IOException
+ {
+ return selectArticle(articleNumber, null);
+ }
+
+
+ /***
+ * 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).
+ * @exception 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.
+ * @exception 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(ArticlePointer pointer)
+ throws IOException
+ {
+ if (!NNTPReply.isPositiveCompletion(last()))
+ return false;
+
+ if (pointer != null)
+ __parseArticlePointer(getReplyString(), pointer);
+
+ return true;
+ }
+
+ /*** Same as <code> selectPreviousArticle(null) </code> ***/
+ public boolean selectPreviousArticle() throws IOException
+ {
+ return selectPreviousArticle(null);
+ }
+
+
+ /***
+ * 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).
+ * @exception 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.
+ * @exception 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(ArticlePointer pointer) throws IOException
+ {
+ if (!NNTPReply.isPositiveCompletion(next()))
+ return false;
+
+ if (pointer != null)
+ __parseArticlePointer(getReplyString(), pointer);
+
+ return true;
+ }
+
+
+ /*** Same as <code> selectNextArticle(null) </code> ***/
+ public boolean selectNextArticle() throws IOException
+ {
+ return selectNextArticle(null);
+ }
+
+
+ /***
+ * 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.
+ * <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.
+ * @exception 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.
+ * @exception 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() throws IOException
+ {
+ if (!NNTPReply.isPositiveCompletion(list()))
+ return null;
+
+ return __readNewsgroupListing();
+ }
+
+ /**
+ * An overloaded listNewsgroups() command that allows us to
+ * specify with a pattern what groups we want to list. Wraps the
+ * LIST ACTIVE command.
+ * <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
+ */
+ public NewsgroupInfo[] listNewsgroups(String wildmat) throws IOException
+ {
+ if(!NNTPReply.isPositiveCompletion(listActive(wildmat)))
+ return null;
+ return __readNewsgroupListing();
+ }
+
+
+ /***
+ * 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.
+ * <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.
+ * @exception 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.
+ * @exception IOException If an I/O error occurs while either sending a
+ * command to the server or receiving a reply from the server.
+ ***/
+ public NewsgroupInfo[] listNewNewsgroups(NewGroupsOrNewsQuery query)
+ throws IOException
+ {
+ if (!NNTPReply.isPositiveCompletion(newgroups(
+ query.getDate(), query.getTime(),
+ query.isGMT(), query.getDistributions())))
+ return null;
+
+ return __readNewsgroupListing();
+ }
+
+
+ /***
+ * 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 &lt and &gt.
+ * <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.
+ * @exception 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.
+ * @exception IOException If an I/O error occurs while either sending a
+ * command to the server or receiving a reply from the server.
+ ***/
+ public String[] listNewNews(NewGroupsOrNewsQuery query)
+ throws IOException
+ {
+ int size;
+ String line;
+ Vector<String> list;
+ String[] result;
+ BufferedReader reader;
+
+ if (!NNTPReply.isPositiveCompletion(newnews(
+ query.getNewsgroups(), query.getDate(), query.getTime(),
+ query.isGMT(), query.getDistributions())))
+ return null;
+
+ list = new Vector<String>();
+ reader = new BufferedReader(new DotTerminatedMessageReader(_reader_));
+
+ while ((line = reader.readLine()) != null)
+ list.addElement(line);
+
+ size = list.size();
+
+ if (size < 1)
+ return new String[0];
+
+ result = new String[size];
+ list.copyInto(result);
+
+ return result;
+ }
+
+ /***
+ * 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.
+ * @exception 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.
+ * @exception 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());
+ }
+
+ /***
+ * 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.
+ * @exception 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;
+
+ return new DotTerminatedMessageWriter(_writer_);
+ }
+
+
+ public Writer forwardArticle(String articleId) throws IOException
+ {
+ if (!NNTPReply.isPositiveIntermediate(ihave(articleId)))
+ return null;
+
+ return new DotTerminatedMessageWriter(_writer_);
+ }
+
+
+ /***
+ * 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>
+ * @return True if successfully completed, false if not.
+ * @exception 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());
+ }
+
+
+ /**
+ * 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
+ */
+ 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;
+ }
+
+ /***
+ * Private implementation of XOVER functionality.
+ *
+ * See {@link NNTP#xover}
+ * for legal agument formats. Alternatively, read RFC 2980 :-)
+ * <p>
+ * @param articleRange
+ * @return Returns a DotTerminatedMessageReader if successful, null
+ * otherwise
+ * @exception IOException
+ */
+ private Reader __retrieveArticleInfo(String articleRange)
+ throws IOException
+ {
+ if (!NNTPReply.isPositiveCompletion(xover(articleRange)))
+ return null;
+
+ return new DotTerminatedMessageReader(_reader_);
+ }
+
+ /**
+ * Return article headers for a specified post.
+ * <p>
+ * @param articleNumber the article to retrieve headers for
+ * @return a DotTerminatedReader if successful, null otherwise
+ * @throws IOException
+ */
+ public Reader retrieveArticleInfo(int articleNumber) throws IOException
+ {
+ return __retrieveArticleInfo(Integer.toString(articleNumber));
+ }
+
+ /**
+ * Return article headers for all articles between lowArticleNumber
+ * and highArticleNumber, inclusively.
+ * <p>
+ * @param lowArticleNumber
+ * @param highArticleNumber
+ * @return a DotTerminatedReader if successful, null otherwise
+ * @throws IOException
+ */
+ public Reader retrieveArticleInfo(int lowArticleNumber,
+ int highArticleNumber)
+ throws IOException
+ {
+ return
+ __retrieveArticleInfo(lowArticleNumber + "-" +
+ highArticleNumber);
+ }
+
+ /***
+ * Private implementation of XHDR functionality.
+ *
+ * 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
+ * @exception IOException
+ */
+ private Reader __retrieveHeader(String header, String articleRange)
+ throws IOException
+ {
+ if (!NNTPReply.isPositiveCompletion(xhdr(header, articleRange)))
+ return null;
+
+ 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
+ */
+ public Reader retrieveHeader(String header, int articleNumber)
+ throws IOException
+ {
+ return __retrieveHeader(header, Integer.toString(articleNumber));
+ }
+
+ /**
+ * Return an article header for all articles between lowArticleNumber
+ * and highArticleNumber, inclusively.
+ * <p>
+ * @param header
+ * @param lowArticleNumber
+ * @param highArticleNumber
+ * @return a DotTerminatedReader if successful, null otherwise
+ * @throws IOException
+ */
+ public Reader retrieveHeader(String header, int lowArticleNumber,
+ int highArticleNumber)
+ throws IOException
+ {
+ return
+ __retrieveHeader(header,lowArticleNumber + "-" + highArticleNumber);
+ }
+}
+
+
+/* Emacs configuration
+ * Local variables: **
+ * mode: java **
+ * c-basic-offset: 4 **
+ * indent-tabs-mode: nil **
+ * End: **
+ */
diff --git a/org/apache/commons/net/nntp/NNTPCommand.java b/org/apache/commons/net/nntp/NNTPCommand.java
new file mode 100644
index 0000000..09f4015
--- /dev/null
+++ b/org/apache/commons/net/nntp/NNTPCommand.java
@@ -0,0 +1,83 @@
+/*
+ * 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.nntp;
+
+/***
+ * NNTPCommand stores a set of constants for NNTP command codes. To interpret
+ * the meaning of the codes, familiarity with RFC 977 is assumed.
+ * <p>
+ * @author Daniel F. Savarese
+ * @author Rory Winston
+ * @author Ted Wise
+ ***/
+
+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 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()
+ {}
+
+ 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];
+ }
+
+}
+
+/* Emacs configuration
+ * Local variables: **
+ * mode: java **
+ * c-basic-offset: 4 **
+ * indent-tabs-mode: nil **
+ * End: **
+ */
diff --git a/org/apache/commons/net/nntp/NNTPConnectionClosedException.java b/org/apache/commons/net/nntp/NNTPConnectionClosedException.java
new file mode 100644
index 0000000..948ed12
--- /dev/null
+++ b/org/apache/commons/net/nntp/NNTPConnectionClosedException.java
@@ -0,0 +1,56 @@
+/*
+ * 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.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.
+ * <p>
+ * <p>
+ * @author Daniel F. Savarese
+ * @see NNTP
+ * @see NNTPClient
+ ***/
+
+public final class NNTPConnectionClosedException extends IOException
+{
+
+ /*** Constructs a NNTPConnectionClosedException with no message ***/
+ public NNTPConnectionClosedException()
+ {
+ super();
+ }
+
+ /***
+ * Constructs a NNTPConnectionClosedException with a specified message.
+ * <p>
+ * @param message The message explaining the reason for the exception.
+ ***/
+ public NNTPConnectionClosedException(String message)
+ {
+ super(message);
+ }
+
+}
diff --git a/org/apache/commons/net/nntp/NNTPReply.java b/org/apache/commons/net/nntp/NNTPReply.java
new file mode 100644
index 0000000..f7d0fbd
--- /dev/null
+++ b/org/apache/commons/net/nntp/NNTPReply.java
@@ -0,0 +1,209 @@
+/*
+ * 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.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. For those who think in terms of the actual reply code values,
+ * a set of CODE_NUM constants are provided where NUM is the numerical value
+ * of the code.
+ * <p>
+ * <p>
+ * @author Daniel F. Savarese
+ ***/
+
+public final class NNTPReply
+{
+
+ public static final int CODE_100 = 100;
+ public static final int CODE_199 = 199;
+ public static final int CODE_200 = 200;
+ public static final int CODE_201 = 201;
+ public static final int CODE_202 = 202;
+ public static final int CODE_205 = 205;
+ public static final int CODE_211 = 211;
+ public static final int CODE_215 = 215;
+ public static final int CODE_220 = 220;
+ public static final int CODE_221 = 221;
+ public static final int CODE_222 = 222;
+ public static final int CODE_223 = 223;
+ public static final int CODE_230 = 230;
+ public static final int CODE_231 = 231;
+ public static final int CODE_235 = 235;
+ public static final int CODE_240 = 240;
+ public static final int CODE_281 = 281;
+ public static final int CODE_335 = 335;
+ public static final int CODE_340 = 340;
+ public static final int CODE_381 = 381;
+ public static final int CODE_400 = 400;
+ public static final int CODE_408 = 408;
+ public static final int CODE_411 = 411;
+ public static final int CODE_412 = 412;
+ public static final int CODE_420 = 420;
+ public static final int CODE_421 = 421;
+ public static final int CODE_422 = 422;
+ public static final int CODE_423 = 423;
+ public static final int CODE_430 = 430;
+ public static final int CODE_435 = 435;
+ public static final int CODE_436 = 436;
+ public static final int CODE_437 = 437;
+ public static final int CODE_440 = 440;
+ public static final int CODE_441 = 441;
+ public static final int CODE_482 = 482;
+ public static final int CODE_500 = 500;
+ public static final int CODE_501 = 501;
+ public static final int CODE_502 = 502;
+ public static final int CODE_503 = 503;
+
+ public static final int HELP_TEXT_FOLLOWS = CODE_100;
+ public static final int DEBUG_OUTPUT = CODE_199;
+ public static final int SERVER_READY_POSTING_ALLOWED = CODE_200;
+ public static final int SERVER_READY_POSTING_NOT_ALLOWED = CODE_201;
+ public static final int SLAVE_STATUS_NOTED = CODE_202;
+ public static final int CLOSING_CONNECTION = CODE_205;
+ public static final int GROUP_SELECTED = CODE_211;
+ public static final int ARTICLE_RETRIEVED_HEAD_AND_BODY_FOLLOW = CODE_220;
+ public static final int ARTICLE_RETRIEVED_HEAD_FOLLOWS = CODE_221;
+ public static final int ARTICLE_RETRIEVED_BODY_FOLLOWS = CODE_222;
+ public static final int
+ ARTICLE_RETRIEVED_REQUEST_TEXT_SEPARATELY = CODE_223;
+ public static final int ARTICLE_LIST_BY_MESSAGE_ID_FOLLOWS = CODE_230;
+ public static final int NEW_NEWSGROUP_LIST_FOLLOWS = CODE_231;
+ public static final int ARTICLE_TRANSFERRED_OK = CODE_235;
+ public static final int ARTICLE_POSTED_OK = CODE_240;
+ public static final int AUTHENTICATION_ACCEPTED = CODE_281;
+ public static final int SEND_ARTICLE_TO_TRANSFER = CODE_335;
+ public static final int SEND_ARTICLE_TO_POST = CODE_340;
+ public static final int MORE_AUTH_INFO_REQUIRED = CODE_381;
+ public static final int SERVICE_DISCONTINUED = CODE_400;
+ public static final int NO_SUCH_NEWSGROUP = CODE_411;
+ public static final int AUTHENTICATION_REQUIRED = CODE_408;
+ public static final int NO_NEWSGROUP_SELECTED = CODE_412;
+ public static final int NO_CURRENT_ARTICLE_SELECTED = CODE_420;
+ public static final int NO_NEXT_ARTICLE = CODE_421;
+ public static final int NO_PREVIOUS_ARTICLE = CODE_422;
+ public static final int NO_SUCH_ARTICLE_NUMBER = CODE_423;
+ public static final int NO_SUCH_ARTICLE_FOUND = CODE_430;
+ public static final int ARTICLE_NOT_WANTED = CODE_435;
+ public static final int TRANSFER_FAILED = CODE_436;
+ public static final int ARTICLE_REJECTED = CODE_437;
+ public static final int POSTING_NOT_ALLOWED = CODE_440;
+ public static final int POSTING_FAILED = CODE_441;
+ public static final int AUTHENTICATION_REJECTED = CODE_482;
+ public static final int COMMAND_NOT_RECOGNIZED = CODE_500;
+ public static final int COMMAND_SYNTAX_ERROR = CODE_501;
+ public static final int PERMISSION_DENIED = CODE_502;
+ public static final int PROGRAM_FAULT = CODE_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.
+ * <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);
+ }
+
+ /***
+ * 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 postive completion response, false
+ * if not.
+ ***/
+ public static boolean isPositiveCompletion(int reply)
+ {
+ return (reply >= 200 && reply < 300);
+ }
+
+ /***
+ * 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 postive intermediate response, false
+ * if not.
+ ***/
+ public static boolean isPositiveIntermediate(int reply)
+ {
+ return (reply >= 300 && reply < 400);
+ }
+
+ /***
+ * 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 negative transient response, false
+ * if not.
+ ***/
+ public static boolean isNegativeTransient(int reply)
+ {
+ return (reply >= 400 && reply < 500);
+ }
+
+ /***
+ * 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 negative permanent response, false
+ * if not.
+ ***/
+ public static boolean isNegativePermanent(int reply)
+ {
+ return (reply >= 500 && reply < 600);
+ }
+
+}
+
+/* Emacs configuration
+ * Local variables: **
+ * mode: java **
+ * c-basic-offset: 4 **
+ * indent-tabs-mode: nil **
+ * End: **
+ */
diff --git a/org/apache/commons/net/nntp/NewGroupsOrNewsQuery.java b/org/apache/commons/net/nntp/NewGroupsOrNewsQuery.java
new file mode 100644
index 0000000..a0d9aeb
--- /dev/null
+++ b/org/apache/commons/net/nntp/NewGroupsOrNewsQuery.java
@@ -0,0 +1,283 @@
+/*
+ * 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.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.
+ * <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.
+ * <p>
+ * <p>
+ * @author Daniel F. Savarese
+ * @see NNTPClient
+ ***/
+
+public final class NewGroupsOrNewsQuery
+{
+ private String __date, __time;
+ private StringBuffer __distributions;
+ private StringBuffer __newsgroups;
+ private 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)
+ {
+ int num;
+ String str;
+ StringBuffer buffer;
+
+ __distributions = null;
+ __newsgroups = null;
+ __isGMT = gmt;
+
+ buffer = new StringBuffer();
+
+ // Get year
+ num = date.get(Calendar.YEAR);
+ str = Integer.toString(num);
+ num = str.length();
+
+ if (num >= 2)
+ buffer.append(str.substring(num - 2));
+ else
+ buffer.append("00");
+
+ // Get month
+ num = date.get(Calendar.MONTH) + 1;
+ str = Integer.toString(num);
+ num = str.length();
+
+ if (num == 1)
+ {
+ buffer.append('0');
+ buffer.append(str);
+ }
+ else if (num == 2)
+ buffer.append(str);
+ else
+ buffer.append("01");
+
+ // Get day
+ num = date.get(Calendar.DAY_OF_MONTH);
+ str = Integer.toString(num);
+ num = str.length();
+
+ if (num == 1)
+ {
+ buffer.append('0');
+ buffer.append(str);
+ }
+ else if (num == 2)
+ buffer.append(str);
+ else
+ buffer.append("01");
+
+ __date = buffer.toString();
+
+ buffer.setLength(0);
+
+ // Get hour
+ num = date.get(Calendar.HOUR_OF_DAY);
+ str = Integer.toString(num);
+ num = str.length();
+
+ if (num == 1)
+ {
+ buffer.append('0');
+ buffer.append(str);
+ }
+ else if (num == 2)
+ buffer.append(str);
+ else
+ buffer.append("00");
+
+ // Get minutes
+ num = date.get(Calendar.MINUTE);
+ str = Integer.toString(num);
+ num = str.length();
+
+ if (num == 1)
+ {
+ buffer.append('0');
+ buffer.append(str);
+ }
+ else if (num == 2)
+ buffer.append(str);
+ else
+ buffer.append("00");
+
+
+ // Get seconds
+ num = date.get(Calendar.SECOND);
+ str = Integer.toString(num);
+ num = str.length();
+
+ if (num == 1)
+ {
+ buffer.append('0');
+ buffer.append(str);
+ }
+ else if (num == 2)
+ buffer.append(str);
+ else
+ buffer.append("00");
+
+ __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.
+ * <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(',');
+ else
+ __newsgroups = new StringBuffer();
+ __newsgroups.append(newsgroup);
+ }
+
+
+ /***
+ * 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>
+ * 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);
+ }
+
+
+ /***
+ * 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 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 NNTP query formatted date (year, month, day in the form
+ * YYMMDD.
+ * <p>
+ * @return The NNTP query formatted date.
+ ***/
+ public String getDate()
+ {
+ return __date;
+ }
+
+ /***
+ * 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;
+ }
+
+ /***
+ * 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;
+ }
+
+ /***
+ * Return the comma separated list of distributions. This may be null
+ * if there are no distributions.
+ * <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
+ * <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());
+ }
+}
diff --git a/org/apache/commons/net/nntp/NewsgroupInfo.java b/org/apache/commons/net/nntp/NewsgroupInfo.java
new file mode 100644
index 0000000..aa48d16
--- /dev/null
+++ b/org/apache/commons/net/nntp/NewsgroupInfo.java
@@ -0,0 +1,155 @@
+/*
+ * 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.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.
+ * <p>
+ * <p>
+ * @author Daniel F. Savarese
+ * @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 static final int UNKNOWN_POSTING_PERMISSION = 0;
+
+ /*** 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. ***/
+ 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 int __estimatedArticleCount;
+ private int __firstArticle, __lastArticle;
+ private int __postingPermission;
+
+ void _setNewsgroup(String newsgroup)
+ {
+ __newsgroup = newsgroup;
+ }
+
+ void _setArticleCount(int count)
+ {
+ __estimatedArticleCount = count;
+ }
+
+ void _setFirstArticle(int first)
+ {
+ __firstArticle = first;
+ }
+
+ void _setLastArticle(int last)
+ {
+ __lastArticle = last;
+ }
+
+ void _setPostingPermission(int permission)
+ {
+ __postingPermission = permission;
+ }
+
+ /***
+ * Get the newsgroup name.
+ * <p>
+ * @return The name of the newsgroup.
+ ***/
+ public String getNewsgroup()
+ {
+ return __newsgroup;
+ }
+
+ /***
+ * 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 int getArticleCount()
+ {
+ return __estimatedArticleCount;
+ }
+
+ /***
+ * Get the number of the first article in the newsgroup.
+ * <p>
+ * @return The number of the first article in the newsgroup.
+ ***/
+ public int getFirstArticle()
+ {
+ return __firstArticle;
+ }
+
+ /***
+ * Get the number of the last article in the newsgroup.
+ * <p>
+ * @return The number of the last article in the newsgroup.
+ ***/
+ public int getLastArticle()
+ {
+ return __lastArticle;
+ }
+
+ /***
+ * 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;
+ }
+
+ /*
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ 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();
+}
+ */
+}
diff --git a/org/apache/commons/net/nntp/SimpleNNTPHeader.java b/org/apache/commons/net/nntp/SimpleNNTPHeader.java
new file mode 100644
index 0000000..d18a8cf
--- /dev/null
+++ b/org/apache/commons/net/nntp/SimpleNNTPHeader.java
@@ -0,0 +1,164 @@
+/*
+ * 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.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.
+ * <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:
+ * <pre>
+ * writer = client.postArticle();
+ * 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;
+ * </pre>
+ * <p>
+ * <p>
+ * @author Daniel F. Savarese
+ * @see NNTPClient
+ ***/
+
+public class SimpleNNTPHeader
+{
+ private String __subject, __from;
+ private StringBuilder __newsgroups;
+ private StringBuilder __headerFields;
+ private int __newsgroupCount;
+
+ /***
+ * 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;
+ }
+
+ /***
+ * 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:
+ * <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');
+ }
+
+
+ /***
+ * Returns the address used in the <code> From: </code> header field.
+ * <p>
+ * @return The from address.
+ ***/
+ public String getFromAddress()
+ {
+ return __from;
+ }
+
+ /***
+ * Returns the subject used in the <code> Subject: </code> header field.
+ * <p>
+ * @return The subject.
+ ***/
+ public String getSubject()
+ {
+ return __subject;
+ }
+
+ /***
+ * Returns the contents of the <code> Newsgroups: </code> header field.
+ * <p>
+ * @return The comma-separated list of newsgroups to which the article
+ * is being posted.
+ ***/
+ public String getNewsgroups()
+ {
+ return __newsgroups.toString();
+ }
+
+ /***
+ * 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()
+ {
+ StringBuffer header = new StringBuffer();
+
+ header.append("From: ");
+ header.append(__from);
+ header.append("\nNewsgroups: ");
+ header.append(__newsgroups.toString());
+ header.append("\nSubject: ");
+ header.append(__subject);
+ header.append('\n');
+ if (__headerFields.length() > 0)
+ header.append(__headerFields.toString());
+ header.append('\n');
+
+ return header.toString();
+ }
+}
diff --git a/org/apache/commons/net/nntp/Threadable.java b/org/apache/commons/net/nntp/Threadable.java
new file mode 100644
index 0000000..518a9a4
--- /dev/null
+++ b/org/apache/commons/net/nntp/Threadable.java
@@ -0,0 +1,34 @@
+/*
+ * 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.nntp;
+
+/**
+ * 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();
+}
diff --git a/org/apache/commons/net/nntp/Threader.java b/org/apache/commons/net/nntp/Threader.java
new file mode 100644
index 0000000..d702c67
--- /dev/null
+++ b/org/apache/commons/net/nntp/Threader.java
@@ -0,0 +1,481 @@
+/*
+ * 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.nntp;
+
+/**
+ * This is an implementation of a message threading algorithm, as originally devised by Zamie Zawinski.
+ * See <a href="http://www.jwz.org/doc/threading.html">http://www.jwz.org/doc/threading.html</a> for details.
+ * For his Java implementation, see <a href="http://lxr.mozilla.org/mozilla/source/grendel/sources/grendel/view/Threader.java">http://lxr.mozilla.org/mozilla/source/grendel/sources/grendel/view/Threader.java</a>
+ *
+ * @author rwinston <rwinston@checkfree.com>
+ *
+ */
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+public class Threader {
+ private ThreadContainer root;
+ private HashMap<String,ThreadContainer> idTable;
+ private int bogusIdCount = 0;
+
+ /**
+ * The main threader entry point - The client passes in an array of Threadable objects, and
+ * the Threader constructs a connected 'graph' of messages
+ * @param messages
+ * @return
+ */
+ public Threadable thread(Threadable[] messages) {
+ if (messages == null)
+ return null;
+
+ idTable = new HashMap<String,ThreadContainer>();
+
+ // walk through each Threadable element
+ for (int i = 0; i < messages.length; ++i) {
+ if (!messages[i].isDummy())
+ buildContainer(messages[i]);
+ }
+
+ root = findRootSet();
+ idTable.clear();
+ idTable = null;
+
+ pruneEmptyContainers(root);
+
+ root.reverseChildren();
+ gatherSubjects();
+
+ 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();
+ root = null;
+
+ return result;
+ }
+
+ /**
+ *
+ * @param threadable
+ */
+ private void buildContainer(Threadable threadable) {
+ String id = threadable.messageThreadId();
+ ThreadContainer container = idTable.get(id);
+
+ // A ThreadContainer exists for this id already. This should be a forward reference, but may
+ // 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...
+ id = "<Bogus-id:" + (bogusIdCount++) + ">";
+ container = null;
+ } else {
+ // The container just contained a forward reference to this message, so let's
+ // fill in the threadable field of the container with this message
+ container.threadable = threadable;
+ }
+ }
+
+ // No container exists for that message Id. Create one and insert it into the hash table.
+ if (container == null) {
+ container = new ThreadContainer();
+ container.threadable = threadable;
+ idTable.put(id, container);
+ }
+
+ // Iterate through all of the references and create ThreadContainers for any references that
+ // don't have them.
+ ThreadContainer parentRef = null;
+ {
+ String[] references = threadable.messageThreadReferences();
+ for (int i = 0; i < references.length; ++i) {
+ String refString = references[i];
+ ThreadContainer ref = idTable.get(refString);
+
+ // if this id doesnt have a container, create one
+ if (ref == null) {
+ ref = new ThreadContainer();
+ idTable.put(refString, ref);
+ }
+
+ // 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)
+ && !(parentRef.findChild(ref))) {
+ // Link ref into the parent's child list
+ ref.parent = parentRef;
+ ref.next = parentRef.child;
+ parentRef.child = ref;
+ }
+ parentRef = ref;
+ }
+ }
+
+ // 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)))
+ parentRef = null;
+
+ // if it has a parent already, its because we saw this message in a References: field, and presumed
+ // a parent based on the other entries in that field. Now that we have the actual message, we can
+ // throw away the old parent and use this new one
+ if (container.parent != null) {
+ ThreadContainer rest, prev;
+
+ 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);
+ }
+
+ // Unlink this container from the parent's child list
+ if (prev == null)
+ container.parent.child = container.next;
+ else
+ prev.next = container.next;
+
+ container.next = null;
+ container.parent = null;
+ }
+
+ // If we have a parent, link container into the parents child list
+ if (parentRef != null) {
+ container.parent = parentRef;
+ container.next = parentRef.child;
+ parentRef.child = container;
+ }
+ }
+
+ /**
+ * Find the root set of all existing ThreadContainers
+ * @return root the ThreadContainer representing the root node
+ */
+ private ThreadContainer findRootSet() {
+ ThreadContainer root = new ThreadContainer();
+ Iterator<String> iter = idTable.keySet().iterator();
+
+ while (iter.hasNext()) {
+ Object key = iter.next();
+ ThreadContainer c = idTable.get(key);
+ if (c.parent == null) {
+ if (c.next != null)
+ throw new RuntimeException(
+ "c.next is " + c.next.toString());
+ c.next = root.child;
+ root.child = c;
+ }
+ }
+ return root;
+ }
+
+ /**
+ * 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.
+ */
+ private void gatherSubjects() {
+
+ int count = 0;
+
+ for (ThreadContainer c = root.child; c != null; c = c.next)
+ count++;
+
+ // TODO verify this will avoid rehashing
+ HashMap<String, ThreadContainer> subjectTable = new HashMap<String, ThreadContainer>((int) (count * 1.2), (float) 0.9);
+ count = 0;
+
+ for (ThreadContainer c = root.child; c != null; c = c.next) {
+ Threadable threadable = c.threadable;
+
+ // No threadable? If so, it is a dummy node in the root set.
+ // Only root set members may be dummies, and they alway have at least 2 kids
+ // Take the first kid as representative of the subject
+ if (threadable == null)
+ threadable = c.child.threadable;
+
+ String subj = threadable.simplifiedSubject();
+
+ if (subj == null || subj == "")
+ continue;
+
+ ThreadContainer old = subjectTable.get(subj);
+
+ // Add this container to the table iff:
+ // - There exists no container with this subject
+ // - or this is a dummy container and the old one is not - the dummy one is
+ // more interesting as a root, so put it in the table instead
+ // - 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())) {
+ subjectTable.put(subj, c);
+ count++;
+ }
+ }
+
+ // If the table is empty, we're done
+ if (count == 0)
+ return;
+
+ // 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)) {
+ Threadable threadable = c.threadable;
+
+ // is it a dummy node?
+ if (threadable == null)
+ threadable = c.child.threadable;
+
+ String subj = threadable.simplifiedSubject();
+
+ // Dont thread together all subjectless messages
+ if (subj == null || subj == "")
+ continue;
+
+ ThreadContainer old = subjectTable.get(subj);
+
+ if (old == c) // That's us
+ continue;
+
+ // We have now found another container in the root set with the same subject
+ // Remove the "second" message from the root set
+ if (prev == null)
+ root.child = c.next;
+ else
+ prev.next = c.next;
+ c.next = null;
+
+ 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);
+
+ tail.next = c.child;
+
+ for (tail = c.child; tail != null; tail = tail.next)
+ tail.parent = old;
+
+ 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
+ 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();
+ newc.threadable = old.threadable;
+ newc.child = old.child;
+
+ for (ThreadContainer tail = newc.child;
+ tail != null;
+ tail = tail.next)
+ tail.parent = newc;
+
+ old.threadable = null;
+ old.child = null;
+
+ c.parent = old;
+ newc.parent = old;
+
+ // Old is now a dummy- give it 2 kids , c and newc
+ old.child = c;
+ c.next = newc;
+ }
+ // We've done a merge, so keep the same prev
+ c = prev;
+ }
+
+ subjectTable.clear();
+ subjectTable = null;
+
+ }
+}
+
+/**
+ * A placeholder utility class, used for constructing a tree of Threadables
+ * Originall 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
+ * @author Rory Winston <rwinston@checkfree.com>
+ */
+class ThreadContainer {
+ Threadable threadable;
+ ThreadContainer parent;
+ ThreadContainer prev;
+ ThreadContainer next;
+ ThreadContainer child;
+
+ /**
+ *
+ * @param container
+ * @return true if child is under self's tree. Detects circular references
+ */
+ boolean findChild(ThreadContainer target) {
+ if (child == null)
+ return false;
+
+ else if (child == target)
+ return true;
+ else
+ return child.findChild(target);
+ }
+
+ // Copy the ThreadContainer tree structure down into the underlying Threadable objects
+ // (Make the Threadable tree look like the ThreadContainer tree)
+ void flush() {
+ if (parent != null && threadable == null)
+ throw new RuntimeException("no threadable in " + this.toString());
+
+ parent = null;
+
+ if (threadable != null)
+ threadable.setChild(child == null ? null : child.threadable);
+
+ if (child != null) {
+ child.flush();
+ child = null;
+ }
+
+ if (threadable != null)
+ threadable.setNext(next == null ? null : next.threadable);
+
+ if (next != null) {
+ next.flush();
+ next = null;
+ }
+
+ threadable = null;
+ }
+
+ /**
+ * Reverse the entire set of children
+ *
+ */
+ 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))
+ kid.next = prev;
+
+ child = prev;
+
+ // Do it for the kids
+ for (kid = child; kid != null; kid = kid.next)
+ kid.reverseChildren();
+ }
+ }
+}