From 063284837c8c366e5502b1b0264b8eb807b61732 Mon Sep 17 00:00:00 2001 From: Joe Robinson Date: Wed, 27 Oct 2010 14:21:09 +0100 Subject: Basic upload functionality to predifined location, with basic file browser --- org/apache/commons/net/nntp/Article.java | 253 ++++ org/apache/commons/net/nntp/ArticlePointer.java | 39 + org/apache/commons/net/nntp/NNTP.java | 1022 ++++++++++++++++ org/apache/commons/net/nntp/NNTPClient.java | 1285 ++++++++++++++++++++ org/apache/commons/net/nntp/NNTPCommand.java | 83 ++ .../net/nntp/NNTPConnectionClosedException.java | 56 + org/apache/commons/net/nntp/NNTPReply.java | 209 ++++ .../commons/net/nntp/NewGroupsOrNewsQuery.java | 283 +++++ org/apache/commons/net/nntp/NewsgroupInfo.java | 155 +++ org/apache/commons/net/nntp/SimpleNNTPHeader.java | 164 +++ org/apache/commons/net/nntp/Threadable.java | 34 + org/apache/commons/net/nntp/Threader.java | 481 ++++++++ 12 files changed, 4064 insertions(+) create mode 100644 org/apache/commons/net/nntp/Article.java create mode 100644 org/apache/commons/net/nntp/ArticlePointer.java create mode 100644 org/apache/commons/net/nntp/NNTP.java create mode 100644 org/apache/commons/net/nntp/NNTPClient.java create mode 100644 org/apache/commons/net/nntp/NNTPCommand.java create mode 100644 org/apache/commons/net/nntp/NNTPConnectionClosedException.java create mode 100644 org/apache/commons/net/nntp/NNTPReply.java create mode 100644 org/apache/commons/net/nntp/NewGroupsOrNewsQuery.java create mode 100644 org/apache/commons/net/nntp/NewsgroupInfo.java create mode 100644 org/apache/commons/net/nntp/SimpleNNTPHeader.java create mode 100644 org/apache/commons/net/nntp/Threadable.java create mode 100644 org/apache/commons/net/nntp/Threader.java (limited to 'org/apache/commons/net/nntp') 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 + * @author rwinston + * + */ +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 list = new ArrayList(); + 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 + * < and > symbols which are technically not part of the + * identifier, but are required by all NNTP commands taking an + * article id as an argument. + */ + public String articleId; +} diff --git a/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. + *

+ * 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} + * . + * NNTPConectionClosedException + * is a subclass of IOException 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 IOException + * 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 }. + *

+ * 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. + *

+ *

+ * @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 + * DEFAULT_PORT 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_ }. + *

+ * @param listener The ProtocolCommandListener to add. + ***/ + public void addProtocolCommandListener(ProtocolCommandListener listener) + { + _commandSupport_.addProtocolCommandListener(listener); + } + + /*** + * Removes a ProtocolCommandListener. Delegates this task to + * {@link #_commandSupport_ _commandSupport_ }. + *

+ * @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. + *

+ * @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. + *

+ * @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 }. + *

+ * @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 }. + *

+ * @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 }. + *

+ * @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 }. + *

+ * @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 + * connect is of type void. + *

+ * @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. + *

+ * @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. + *

+ * @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. + *

+ * @param messageId The message identifier of the requested article, + * including the encapsulating < and > 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. + *

+ * @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. + *

+ * @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. + *

+ * @param messageId The message identifier of the requested article, + * including the encapsulating < and > 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. + *

+ * @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. + *

+ * @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. + *

+ * @param messageId The message identifier of the requested article, + * including the encapsulating < and > 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. + *

+ * @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. + *

+ * @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. + *

+ * @param messageId The message identifier of the requested article, + * including the encapsulating < and > 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. + *

+ * @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. + *

+ * @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. + *

+ * @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. + *

+ * @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. + *

+ * @param messageId The article identifier, + * including the encapsulating < and > 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. + *

+ * @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. + *

+ * @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. + *

+ * @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. + *

+ * @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. + *

+ * @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. + *

+ * @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. + *

+ * @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) + *

+ * @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) + *

+ * @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. + *

+ * @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. + *

+ * @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. + *

+ * @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}. + *

+ * 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} + * . + * NNTPConectionClosedException + * is a subclass of IOException 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 IOException + * 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 }. + *

+ * 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. + *

+ *

+ * @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 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(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 < and >). + * The article number and identifier contained in the server reply + * are returned through an ArticlePointer. The articleId + * field of the ArticlePointer cannot always be trusted because some + * NNTP servers do not correctly follow the RFC 977 reply format. + *

+ * A DotTerminatedMessageReader is returned from which the article can + * be read. If the article does not exist, null is returned. + *

+ * 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. + *

+ * @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 retrieveArticle(articleId, null) ***/ + public Reader retrieveArticle(String articleId) throws IOException + { + return retrieveArticle(articleId, null); + } + + /*** Same as retrieveArticle(null) ***/ + 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 articleId + * field of the ArticlePointer cannot always be trusted because some + * NNTP servers do not correctly follow the RFC 977 reply format. + *

+ * A DotTerminatedMessageReader is returned from which the article can + * be read. If the article does not exist, null is returned. + *

+ * 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. + *

+ * @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 retrieveArticle(articleNumber, null) ***/ + 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 < and >). + * The article number and identifier contained in the server reply + * are returned through an ArticlePointer. The articleId + * field of the ArticlePointer cannot always be trusted because some + * NNTP servers do not correctly follow the RFC 977 reply format. + *

+ * A DotTerminatedMessageReader is returned from which the article can + * be read. If the article does not exist, null is returned. + *

+ * 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. + *

+ * @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 retrieveArticleHeader(articleId, null) ***/ + public Reader retrieveArticleHeader(String articleId) throws IOException + { + return retrieveArticleHeader(articleId, null); + } + + /*** Same as retrieveArticleHeader(null) ***/ + 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 articleId + * field of the ArticlePointer cannot always be trusted because some + * NNTP servers do not correctly follow the RFC 977 reply format. + *

+ * A DotTerminatedMessageReader is returned from which the article can + * be read. If the article does not exist, null is returned. + *

+ * 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. + *

+ * @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 retrieveArticleHeader(articleNumber, null) ***/ + 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 < and >). + * The article number and identifier contained in the server reply + * are returned through an ArticlePointer. The articleId + * field of the ArticlePointer cannot always be trusted because some + * NNTP servers do not correctly follow the RFC 977 reply format. + *

+ * A DotTerminatedMessageReader is returned from which the article can + * be read. If the article does not exist, null is returned. + *

+ * 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. + *

+ * @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 retrieveArticleBody(articleId, null) ***/ + public Reader retrieveArticleBody(String articleId) throws IOException + { + return retrieveArticleBody(articleId, null); + } + + /*** Same as retrieveArticleBody(null) ***/ + 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 articleId + * field of the ArticlePointer cannot always be trusted because some + * NNTP servers do not correctly follow the RFC 977 reply format. + *

+ * A DotTerminatedMessageReader is returned from which the article can + * be read. If the article does not exist, null is returned. + *

+ * 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. + *

+ * @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 retrieveArticleBody(articleNumber, null) ***/ + 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. + *

+ * @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 selectNewsgroup(newsgroup, null) ***/ + public boolean selectNewsgroup(String newsgroup) throws IOException + { + return selectNewsgroup(newsgroup, null); + } + + /*** + * List the command help from the server. + *

+ * @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 + * < and >) and return its article number and id through the + * pointer parameter. This is achieved through the STAT command. + * According to RFC 977, this will NOT set the current article pointer + * on the server. To do that, you must reference the article by its + * number. + *

+ * @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 selectArticle(articleId, null) ***/ + public boolean selectArticle(String articleId) throws IOException + { + return selectArticle(articleId, null); + } + + /**** + * Same as selectArticle(null, articleId) . 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. + *

+ * @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 selectArticle(articleNumber, null) ***/ + 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 + * selectArticle(pointer.articleNumber, pointer) immediately + * afterward. + *

+ * @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 selectPreviousArticle(null) ***/ + 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 + * selectArticle(pointer.articleNumber, pointer) immediately + * afterward. + *

+ * @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 selectNextArticle(null) ***/ + 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. + *

+ * @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. + *

+ * @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. + *

+ * @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 < and >. + *

+ * @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 list; + String[] result; + BufferedReader reader; + + if (!NNTPReply.isPositiveCompletion(newnews( + query.getNewsgroups(), query.getDate(), query.getTime(), + query.isGMT(), query.getDistributions()))) + return null; + + list = new Vector(); + 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. + *

+ * For example + *

+     * 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;
+     * 
+ *

+ * @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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * @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. + *

+ * @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. + *

+ * @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 :-) + *

+ * @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. + *

+ * @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. + *

+ * @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. + *

+ * @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. + *

+ * @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. + *

+ * @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. + *

+ * @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. + *

+ * @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. + *

+ *

+ * @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. + *

+ * @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. + *

+ *

+ * @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. + *

+ * @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. + *

+ * @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. + *

+ * @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. + *

+ * @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. + *

+ * @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. + *

+ * You might use the class as follows: + *

+ * query = new NewsGroupsOrNewsQuery(new GregorianCalendar(97, 11, 15), false);
+ * query.addDistribution("comp");
+ * NewsgroupInfo[] newsgroups = client.listNewgroups(query);
+ * 
+ * This will retrieve the list of newsgroups starting with the comp. + * distribution prefix created since midnight 11/15/97. + *

+ *

+ * @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. + *

+ * @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 * wildcard, as in + * comp.lang.* or comp.lang.java.* . Adding + * at least one newsgroup is mandatory for the NEWNEWS command. + *

+ * @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 * wildcard, as in + * comp.lang.* or comp.lang.java.* . + *

+ * The following would create a query that searched for new news in + * all comp.lang.java newsgroups except for comp.lang.java.advocacy. + *

+     * query.addNewsgroup("comp.lang.java.*");
+     * query.omitNewsgroup("comp.lang.java.advocacy");
+     * 
+ *

+ * @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. + *

+ * @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. + *

+ * @return The NNTP query formatted date. + ***/ + public String getDate() + { + return __date; + } + + /*** + * Return the NNTP query formatted time (hour, minutes, seconds in the form + * HHMMSS. + *

+ * @return The NNTP query formatted time. + ***/ + public String getTime() + { + return __time; + } + + /*** + * Return whether or not the query date should be treated as GMT. + *

+ * @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. + *

+ * @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 + *

+ * @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. + *

+ *

+ * @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. + *

+ * @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. + *

+ * @return The estimated number of articles in the newsgroup. + ***/ + public int getArticleCount() + { + return __estimatedArticleCount; + } + + /*** + * Get the number of the first article in the newsgroup. + *

+ * @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. + *

+ * @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 POSTING_PERMISSION constants. + *

+ * @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. + *

+ * 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: + *

+ * 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;
+ * 
+ *

+ *

+ * @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. + *

+ * @param from The value of the From: header field. This + * should be the article poster's email address. + * @param subject The value of the Subject: 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 Newsgroups: field. + *

+ * @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: + *

+     * header.addHeaderField("Organization", "Foobar, Inc.");
+     * 
+ *

+ * @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 From: header field. + *

+ * @return The from address. + ***/ + public String getFromAddress() + { + return __from; + } + + /*** + * Returns the subject used in the Subject: header field. + *

+ * @return The subject. + ***/ + public String getSubject() + { + return __subject; + } + + /*** + * Returns the contents of the Newsgroups: header field. + *

+ * @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. + *

+ * @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 + * + */ +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 http://www.jwz.org/doc/threading.html for details. + * For his Java implementation, see http://lxr.mozilla.org/mozilla/source/grendel/sources/grendel/view/Threader.java + * + * @author rwinston + * + */ + +import java.util.HashMap; +import java.util.Iterator; + +public class Threader { + private ThreadContainer root; + private HashMap 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(); + + // 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 = ""; + 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 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 subjectTable = new HashMap((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 here + * Threadable objects + * @author Rory Winston + */ +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(); + } + } +} -- cgit v1.2.3