View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net.nntp;
19  
20  import java.io.BufferedReader;
21  import java.io.IOException;
22  import java.io.Reader;
23  import java.io.StringWriter;
24  import java.io.Writer;
25  import java.util.ArrayList;
26  import java.util.Vector;
27  
28  import org.apache.commons.net.MalformedServerReplyException;
29  import org.apache.commons.net.io.DotTerminatedMessageReader;
30  import org.apache.commons.net.io.DotTerminatedMessageWriter;
31  import org.apache.commons.net.io.Util;
32  import org.apache.commons.net.util.NetConstants;
33  
34  /**
35   * NNTPClient encapsulates all the functionality necessary to post and retrieve articles from an NNTP server. As with all classes derived from
36   * {@link org.apache.commons.net.SocketClient}, you must first connect to the server with {@link org.apache.commons.net.SocketClient#connect connect } before
37   * doing anything, and finally {@link org.apache.commons.net.nntp.NNTP#disconnect disconnect() } after you're completely finished interacting with the server.
38   * Remember that the {@link org.apache.commons.net.nntp.NNTP#isAllowedToPost isAllowedToPost()} method is defined in {@link org.apache.commons.net.nntp.NNTP}.
39   * <p>
40   * 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
41   * 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
42   * a {@link org.apache.commons.net.nntp.NNTPReply#SERVICE_DISCONTINUED NNTPReply.SERVICE_DISCONTINUED } response to a command. When that occurs, the NNTP class
43   * method encountering that reply will throw an {@link org.apache.commons.net.nntp.NNTPConnectionClosedException} . <code>NNTPConectionClosedException</code> is
44   * a subclass of <code> IOException </code> and therefore need not be caught separately, but if you are going to catch it separately, its catch block must
45   * appear before the more general <code> IOException </code> catch block. When you encounter an
46   * {@link org.apache.commons.net.nntp.NNTPConnectionClosedException} , you must disconnect the connection with
47   * {@link org.apache.commons.net.nntp.NNTP#disconnect disconnect() } to properly clean up the system resources used by NNTP. Before disconnecting, you may check
48   * the last reply code and text with {@link org.apache.commons.net.nntp.NNTP#getReplyCode getReplyCode } and
49   * {@link org.apache.commons.net.nntp.NNTP#getReplyString getReplyString }.
50   * </p>
51   * <p>
52   * 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
53   * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the
54   * 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
55   * lenient as possible.
56   * </p>
57   *
58   * @see NNTP
59   * @see NNTPConnectionClosedException
60   * @see org.apache.commons.net.MalformedServerReplyException
61   */
62  
63  public class NNTPClient extends NNTP {
64  
65      private static final NewsgroupInfo[] EMPTY_NEWSGROUP_INFO_ARRAY = {};
66  
67      /**
68       * Parse a response line from {@link #retrieveArticleInfo(long, long)}.
69       *
70       * @param line a response line
71       * @return the parsed {@link Article}, if unparseable then isDummy() will be true, and the subject will contain the raw info.
72       * @since 3.0
73       */
74      static Article parseArticleEntry(final String line) {
75          // Extract the article information
76          // Mandatory format (from NNTP RFC 2980) is :
77          // articleNumber\tSubject\tAuthor\tDate\tID\tReference(s)\tByte Count\tLine Count
78  
79          final Article article = new Article();
80          article.setSubject(line); // in case parsing fails
81          final String[] parts = line.split("\t");
82          if (parts.length > 6) {
83              int i = 0;
84              try {
85                  article.setArticleNumber(Long.parseLong(parts[i++]));
86                  article.setSubject(parts[i++]);
87                  article.setFrom(parts[i++]);
88                  article.setDate(parts[i++]);
89                  article.setArticleId(parts[i++]);
90                  article.addReference(parts[i++]);
91              } catch (final NumberFormatException e) {
92                  // ignored, already handled
93              }
94          }
95          return article;
96      }
97  
98      /*
99       * 211 n f l s group selected (n = estimated number of articles in group, f = first article number in the group, l = last article number in the group, s =
100      * name of the group.)
101      */
102 
103     private static void parseGroupReply(final String reply, final NewsgroupInfo info) throws MalformedServerReplyException {
104         final String[] tokens = reply.split(" ");
105         if (tokens.length >= 5) {
106             int i = 1; // Skip numeric response value
107             try {
108                 // Get estimated article count
109                 info.setArticleCount(Long.parseLong(tokens[i++]));
110                 // Get first article number
111                 info.setFirstArticle(Long.parseLong(tokens[i++]));
112                 // Get last article number
113                 info.setLastArticle(Long.parseLong(tokens[i++]));
114                 // Get newsgroup name
115                 info.setNewsgroup(tokens[i++]);
116 
117                 info.setPostingPermission(NewsgroupInfo.UNKNOWN_POSTING_PERMISSION);
118                 return;
119             } catch (final NumberFormatException e) {
120                 // drop through to report error
121             }
122         }
123 
124         throw new MalformedServerReplyException("Could not parse newsgroup info.\nServer reply: " + reply);
125     }
126 
127     // Format: group last first p
128     static NewsgroupInfo parseNewsgroupListEntry(final String entry) {
129         final String[] tokens = entry.split(" ");
130         if (tokens.length < 4) {
131             return null;
132         }
133         final NewsgroupInfo result = new NewsgroupInfo();
134 
135         int i = 0;
136 
137         result.setNewsgroup(tokens[i++]);
138 
139         try {
140             final long lastNum = Long.parseLong(tokens[i++]);
141             final long firstNum = Long.parseLong(tokens[i++]);
142             result.setFirstArticle(firstNum);
143             result.setLastArticle(lastNum);
144             if (firstNum == 0 && lastNum == 0) {
145                 result.setArticleCount(0);
146             } else {
147                 result.setArticleCount(lastNum - firstNum + 1);
148             }
149         } catch (final NumberFormatException e) {
150             return null;
151         }
152 
153         switch (tokens[i++].charAt(0)) {
154         case 'y':
155         case 'Y':
156             result.setPostingPermission(NewsgroupInfo.PERMITTED_POSTING_PERMISSION);
157             break;
158         case 'n':
159         case 'N':
160             result.setPostingPermission(NewsgroupInfo.PROHIBITED_POSTING_PERMISSION);
161             break;
162         case 'm':
163         case 'M':
164             result.setPostingPermission(NewsgroupInfo.MODERATED_POSTING_PERMISSION);
165             break;
166         default:
167             result.setPostingPermission(NewsgroupInfo.UNKNOWN_POSTING_PERMISSION);
168             break;
169         }
170 
171         return result;
172     }
173 
174     @SuppressWarnings("deprecation")
175     private void ai2ap(final ArticleInfo ai, final ArticlePointer ap) {
176         if (ap != null) { // ai cannot be null
177             ap.articleId = ai.articleId;
178             ap.articleNumber = (int) ai.articleNumber;
179         }
180     }
181 
182     private ArticleInfo ap2ai(@SuppressWarnings("deprecation") final ArticlePointer ap) {
183         if (ap == null) {
184             return null;
185         }
186         final ArticleInfo ai = new ArticleInfo();
187         return ai;
188     }
189 
190     /**
191      * 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
192      * server.
193      *
194      * @param user a valid user name
195      * @param password the corresponding password
196      * @return True for successful login, false for a failure
197      * @throws IOException on error
198      */
199     public boolean authenticate(final String user, final String password) throws IOException {
200         int replyCode = authinfoUser(user);
201 
202         if (replyCode == NNTPReply.MORE_AUTH_INFO_REQUIRED) {
203             replyCode = authinfoPass(password);
204 
205             if (replyCode == NNTPReply.AUTHENTICATION_ACCEPTED) {
206                 this._isAllowedToPost = true;
207                 return true;
208             }
209         }
210         return false;
211     }
212 
213     /**
214      * There are a few NNTPClient methods that do not complete the entire sequence of NNTP commands to complete a transaction. These commands require some
215      * action by the programmer after the reception of a positive preliminary command. After the programmer's code completes its actions, it must call this
216      * method to receive the completion reply from the server and verify the success of the entire transaction.
217      * <p>
218      * For example
219      * </p>
220      * <pre>
221      * writer = client.postArticle();
222      * if (writer == null) // failure
223      *     return false;
224      * header = new SimpleNNTPHeader("foobar@foo.com", "Just testing");
225      * header.addNewsgroup("alt.test");
226      * writer.write(header.toString());
227      * writer.write("This is just a test");
228      * writer.close();
229      * if (!client.completePendingCommand()) // failure
230      *     return false;
231      * </pre>
232      *
233      * @return True if successfully completed, false if not.
234      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
235      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
236      *                                       independently as itself.
237      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
238      */
239     public boolean completePendingCommand() throws IOException {
240         return NNTPReply.isPositiveCompletion(getReply());
241     }
242 
243     public Writer forwardArticle(final String articleId) throws IOException {
244         if (!NNTPReply.isPositiveIntermediate(ihave(articleId))) {
245             return null;
246         }
247 
248         return new DotTerminatedMessageWriter(_writer_);
249     }
250 
251     /**
252      * Return article headers for all articles between lowArticleNumber and highArticleNumber, inclusively, using the XOVER command.
253      *
254      * @param lowArticleNumber  low
255      * @param highArticleNumber high
256      * @return an Iterable of Articles
257      * @throws IOException if the command failed
258      * @since 3.0
259      */
260     public Iterable<Article> iterateArticleInfo(final long lowArticleNumber, final long highArticleNumber) throws IOException {
261         final BufferedReader info = retrieveArticleInfo(lowArticleNumber, highArticleNumber);
262         if (info == null) {
263             throw new IOException("XOVER command failed: " + getReplyString());
264         }
265         // N.B. info is already DotTerminated, so don't rewrap
266         return new ArticleIterator(new ReplyIterator(info, false));
267     }
268 
269     /**
270      * List all new articles added to the NNTP server since a particular date subject to the conditions of the specified query. If no new news is found,
271      * no entries will be returned. This uses the "NEWNEWS" command. You must add at least one newsgroup to the query, else the command will fail.
272      * Each String which is returned is a unique message identifier including the enclosing &lt; and &gt;.
273      *
274      * @param query The query restricting how to search for new news. You must add at least one newsgroup to the query.
275      * @return An iterator of String instances containing the unique message identifiers for each new article added to the NNTP server. If no new news is found,
276      *         no strings will be returned.
277      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
278      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
279      *                                       independently as itself.
280      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
281      * @since 3.0
282      */
283     public Iterable<String> iterateNewNews(final NewGroupsOrNewsQuery query) throws IOException {
284         if (NNTPReply.isPositiveCompletion(newnews(query.getNewsgroups(), query.getDate(), query.getTime(), query.isGMT(), query.getDistributions()))) {
285             return new ReplyIterator(_reader_);
286         }
287         throw new IOException("NEWNEWS command failed: " + getReplyString());
288     }
289 
290     /**
291      * 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
292      * added, no entries will be returned. This uses the "NEWGROUPS" command.
293      *
294      * @param query The query restricting how to search for new newsgroups.
295      * @return An iterable of Strings containing the raw information for each new newsgroup added to the NNTP server. If no newsgroups were added, no entries
296      *         will be returned.
297      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
298      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
299      *                                       independently as itself.
300      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
301      * @since 3.0
302      */
303     public Iterable<String> iterateNewNewsgroupListing(final NewGroupsOrNewsQuery query) throws IOException {
304         if (NNTPReply.isPositiveCompletion(newgroups(query.getDate(), query.getTime(), query.isGMT(), query.getDistributions()))) {
305             return new ReplyIterator(_reader_);
306         }
307         throw new IOException("NEWGROUPS command failed: " + getReplyString());
308     }
309 
310     /**
311      * 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
312      * added, no entries will be returned. This uses the "NEWGROUPS" command.
313      *
314      * @param query The query restricting how to search for new newsgroups.
315      * @return An iterable of NewsgroupInfo instances containing the information for each new newsgroup added to the NNTP server. If no newsgroups were added,
316      *         no entries will be returned.
317      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
318      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
319      *                                       independently as itself.
320      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
321      * @since 3.0
322      */
323     public Iterable<NewsgroupInfo> iterateNewNewsgroups(final NewGroupsOrNewsQuery query) throws IOException {
324         return new NewsgroupIterator(iterateNewNewsgroupListing(query));
325     }
326 
327     /**
328      * List all newsgroups served by the NNTP server. If no newsgroups are served, no entries will be returned. The method uses the "LIST" command.
329      *
330      * @return An iterable of NewsgroupInfo instances containing the information for each newsgroup served by the NNTP server. If no newsgroups are served, no
331      *         entries will be returned.
332      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
333      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
334      *                                       independently as itself.
335      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
336      * @since 3.0
337      */
338     public Iterable<String> iterateNewsgroupListing() throws IOException {
339         if (NNTPReply.isPositiveCompletion(list())) {
340             return new ReplyIterator(_reader_);
341         }
342         throw new IOException("LIST command failed: " + getReplyString());
343     }
344 
345     /**
346      * List the newsgroups that match a given pattern. Uses the "LIST ACTIVE" command.
347      *
348      * @param wildmat a pseudo-regex pattern (cf. RFC 2980)
349      * @return An iterable of Strings containing the raw information for each newsgroup served by the NNTP server corresponding to the supplied pattern. If no
350      *         such newsgroups are served, no entries will be returned.
351      * @throws IOException on error
352      * @since 3.0
353      */
354     public Iterable<String> iterateNewsgroupListing(final String wildmat) throws IOException {
355         if (NNTPReply.isPositiveCompletion(listActive(wildmat))) {
356             return new ReplyIterator(_reader_);
357         }
358         throw new IOException("LIST ACTIVE " + wildmat + " command failed: " + getReplyString());
359     }
360 
361     /**
362      * List all newsgroups served by the NNTP server. If no newsgroups are served, no entries will be returned. The method uses the "LIST" command.
363      *
364      * @return An iterable of Strings containing the raw information for each newsgroup served by the NNTP server. If no newsgroups are served, no entries will
365      *         be returned.
366      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
367      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
368      *                                       independently as itself.
369      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
370      * @since 3.0
371      */
372     public Iterable<NewsgroupInfo> iterateNewsgroups() throws IOException {
373         return new NewsgroupIterator(iterateNewsgroupListing());
374     }
375 
376     /**
377      * List the newsgroups that match a given pattern. Uses the "LIST ACTIVE" command.
378      *
379      * @param wildmat a pseudo-regex pattern (cf. RFC 2980)
380      * @return An iterable NewsgroupInfo instances containing the information for each newsgroup served by the NNTP server corresponding to the supplied
381      *         pattern. If no such newsgroups are served, no entries will be returned.
382      * @throws IOException on error
383      * @since 3.0
384      */
385     public Iterable<NewsgroupInfo> iterateNewsgroups(final String wildmat) throws IOException {
386         return new NewsgroupIterator(iterateNewsgroupListing(wildmat));
387     }
388 
389     /**
390      * List the command help from the server.
391      *
392      * @return The sever help information.
393      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
394      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
395      *                                       independently as itself.
396      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
397      */
398     public String listHelp() throws IOException {
399         if (!NNTPReply.isInformational(help())) {
400             return null;
401         }
402 
403         try (final StringWriter help = new StringWriter(); final BufferedReader reader = new DotTerminatedMessageReader(_reader_)) {
404             Util.copyReader(reader, help);
405             return help.toString();
406         }
407     }
408 
409     /**
410      * List all new articles added to the NNTP server since a particular date subject to the conditions of the specified query. If no new news is found, a
411      * 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
412      * fail. Each String in the returned array is a unique message identifier including the enclosing &lt; and &gt;. This uses the "NEWNEWS" command.
413      *
414      * @param query The query restricting how to search for new news. You must add at least one newsgroup to the query.
415      * @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
416      *         zero length array will be returned. If the command fails, null will be returned.
417      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
418      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
419      *                                       independently as itself.
420      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
421      *
422      * @see #iterateNewNews(NewGroupsOrNewsQuery)
423      */
424     public String[] listNewNews(final NewGroupsOrNewsQuery query) throws IOException {
425         if (!NNTPReply.isPositiveCompletion(newnews(query.getNewsgroups(), query.getDate(), query.getTime(), query.isGMT(), query.getDistributions()))) {
426             return null;
427         }
428 
429         final Vector<String> list = new Vector<>();
430         try (final BufferedReader reader = new DotTerminatedMessageReader(_reader_)) {
431 
432             String line;
433             while ((line = reader.readLine()) != null) {
434                 list.addElement(line);
435             }
436         }
437 
438         final int size = list.size();
439         if (size < 1) {
440             return NetConstants.EMPTY_STRING_ARRAY;
441         }
442 
443         final String[] result = new String[size];
444         list.copyInto(result);
445 
446         return result;
447     }
448 
449     /**
450      * 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
451      * added, a zero length array will be returned. If the command fails, null will be returned. This uses the "NEWGROUPS" command.
452      *
453      * @param query The query restricting how to search for new newsgroups.
454      * @return An array of NewsgroupInfo instances containing the information for each new newsgroup added to the NNTP server. If no newsgroups were added, a
455      *         zero length array will be returned. If the command fails, null will be returned.
456      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
457      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
458      *                                       independently as itself.
459      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
460      * @see #iterateNewNewsgroups(NewGroupsOrNewsQuery)
461      * @see #iterateNewNewsgroupListing(NewGroupsOrNewsQuery)
462      */
463     public NewsgroupInfo[] listNewNewsgroups(final NewGroupsOrNewsQuery query) throws IOException {
464         if (!NNTPReply.isPositiveCompletion(newgroups(query.getDate(), query.getTime(), query.isGMT(), query.getDistributions()))) {
465             return null;
466         }
467 
468         return readNewsgroupListing();
469     }
470 
471     /**
472      * 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
473      * returned. The method uses the "LIST" command.
474      *
475      * @return An array of NewsgroupInfo instances containing the information for each newsgroup served by the NNTP server. If no newsgroups are served, a zero
476      *         length array will be returned. If the command fails, null will be returned.
477      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
478      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
479      *                                       independently as itself.
480      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
481      * @see #iterateNewsgroupListing()
482      * @see #iterateNewsgroups()
483      */
484     public NewsgroupInfo[] listNewsgroups() throws IOException {
485         if (!NNTPReply.isPositiveCompletion(list())) {
486             return null;
487         }
488 
489         return readNewsgroupListing();
490     }
491 
492     /**
493      * List the newsgroups that match a given pattern. Uses the "LIST ACTIVE" command.
494      *
495      * @param wildmat a pseudo-regex pattern (cf. RFC 2980)
496      * @return An array of NewsgroupInfo instances containing the information for each newsgroup served by the NNTP server corresponding to the supplied
497      *         pattern. If no such newsgroups are served, a zero length array will be returned. If the command fails, null will be returned.
498      * @throws IOException on error
499      * @see #iterateNewsgroupListing(String)
500      * @see #iterateNewsgroups(String)
501      */
502     public NewsgroupInfo[] listNewsgroups(final String wildmat) throws IOException {
503         if (!NNTPReply.isPositiveCompletion(listActive(wildmat))) {
504             return null;
505         }
506         return readNewsgroupListing();
507     }
508 
509     /**
510      * Send a "LIST OVERVIEW.FMT" command to the server.
511      *
512      * @return the contents of the Overview format, of {@code null} if the command failed
513      * @throws IOException on error
514      */
515     public String[] listOverviewFmt() throws IOException {
516         if (!NNTPReply.isPositiveCompletion(sendCommand("LIST", "OVERVIEW.FMT"))) {
517             return null;
518         }
519 
520         try (final BufferedReader reader = new DotTerminatedMessageReader(_reader_)) {
521             String line;
522             final ArrayList<String> list = new ArrayList<>();
523             while ((line = reader.readLine()) != null) {
524                 list.add(line);
525             }
526             return list.toArray(NetConstants.EMPTY_STRING_ARRAY);
527         }
528     }
529 
530     /**
531      * 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
532      * connection.
533      *
534      * @return True if successfully completed, false if not.
535      * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
536      */
537     public boolean logout() throws IOException {
538         return NNTPReply.isPositiveCompletion(quit());
539     }
540 
541     /**
542      * Parse the reply and store the id and number in the pointer.
543      *
544      * @param reply   the reply to parse "22n nnn <aaa>"
545      * @param pointer the pointer to update
546      *
547      * @throws MalformedServerReplyException if response could not be parsed
548      */
549     private void parseArticlePointer(final String reply, final ArticleInfo pointer) throws MalformedServerReplyException {
550         final String[] tokens = reply.split(" ");
551         if (tokens.length >= 3) { // OK, we can parset the line
552             int i = 1; // skip reply code
553             try {
554                 // Get article number
555                 pointer.articleNumber = Long.parseLong(tokens[i++]);
556                 // Get article id
557                 pointer.articleId = tokens[i++];
558                 return; // done
559             } catch (final NumberFormatException e) {
560                 // drop through and raise exception
561             }
562         }
563         throw new MalformedServerReplyException("Could not parse article pointer.\nServer reply: " + reply);
564     }
565 
566     /**
567      * Post an article to the NNTP server. This method returns a DotTerminatedMessageWriter instance to which the article can be written. Null is returned if
568      * the posting attempt fails. You should check {@link NNTP#isAllowedToPost isAllowedToPost() } before trying to post. However, a posting attempt can fail
569      * due to malformed headers.
570      * <p>
571      * 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.
572      * The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned Writer actually writes directly to
573      * 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
574      * properly.
575      * </p>
576      * <p>
577      * Different NNTP servers will require different header formats, but you can use the provided {@link org.apache.commons.net.nntp.SimpleNNTPHeader} class to
578      * construct the bare minimum acceptable header for most newsreaders. To construct more complicated headers you should refer to RFC 822. When the Java Mail
579      * API is finalized, you will be able to use it to compose fully compliant Internet text messages. The DotTerminatedMessageWriter takes care of doubling
580      * 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.
581      * </p>
582      * <p>
583      * Upon closing the returned Writer, you need to call {@link #completePendingCommand completePendingCommand() } to finalize the posting and verify its
584      * success or failure from the server reply.
585      * </p>
586      *
587      * @return A DotTerminatedMessageWriter to which the article (including header) can be written. Returns null if the command fails.
588      * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
589      */
590 
591     public Writer postArticle() throws IOException {
592         if (!NNTPReply.isPositiveIntermediate(post())) {
593             return null;
594         }
595 
596         return new DotTerminatedMessageWriter(_writer_);
597     }
598 
599     private NewsgroupInfo[] readNewsgroupListing() throws IOException {
600 
601         // Start of with a big vector because we may be reading a very large
602         // amount of groups.
603         final Vector<NewsgroupInfo> list = new Vector<>(2048);
604 
605         String line;
606         try (final BufferedReader reader = new DotTerminatedMessageReader(_reader_)) {
607             while ((line = reader.readLine()) != null) {
608                 final NewsgroupInfo tmp = parseNewsgroupListEntry(line);
609                 if (tmp == null) {
610                     throw new MalformedServerReplyException(line);
611                 }
612                 list.addElement(tmp);
613             }
614         }
615         final int size;
616         if ((size = list.size()) < 1) {
617             return EMPTY_NEWSGROUP_INFO_ARRAY;
618         }
619 
620         final NewsgroupInfo[] info = new NewsgroupInfo[size];
621         list.copyInto(info);
622 
623         return info;
624     }
625 
626     private BufferedReader retrieve(final int command, final long articleNumber, final ArticleInfo pointer) throws IOException {
627         if (!NNTPReply.isPositiveCompletion(sendCommand(command, Long.toString(articleNumber)))) {
628             return null;
629         }
630 
631         if (pointer != null) {
632             parseArticlePointer(getReplyString(), pointer);
633         }
634 
635         return new DotTerminatedMessageReader(_reader_);
636     }
637 
638     private BufferedReader retrieve(final int command, final String articleId, final ArticleInfo pointer) throws IOException {
639         if (articleId != null) {
640             if (!NNTPReply.isPositiveCompletion(sendCommand(command, articleId))) {
641                 return null;
642             }
643         } else if (!NNTPReply.isPositiveCompletion(sendCommand(command))) {
644             return null;
645         }
646 
647         if (pointer != null) {
648             parseArticlePointer(getReplyString(), pointer);
649         }
650 
651         return new DotTerminatedMessageReader(_reader_);
652     }
653 
654     /**
655      * Same as <code> retrieveArticle((String) null) </code> Note: the return can be cast to a {@link BufferedReader}
656      *
657      * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist.
658      * @throws IOException if an IO error occurs
659      */
660     public Reader retrieveArticle() throws IOException {
661         return retrieveArticle((String) null);
662     }
663 
664     /**
665      * @param articleNumber The number of the article to retrieve
666      * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist.
667      * @throws IOException on error
668      * @deprecated 3.0 use {@link #retrieveArticle(long)} instead
669      */
670     @Deprecated
671     public Reader retrieveArticle(final int articleNumber) throws IOException {
672         return retrieveArticle((long) articleNumber);
673     }
674 
675     /**
676      * @param articleNumber The number of the article to retrieve.
677      * @param pointer       A parameter through which to return the article's number and unique id
678      * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist.
679      * @throws IOException on error
680      * @deprecated 3.0 use {@link #retrieveArticle(long, ArticleInfo)} instead
681      */
682     @Deprecated
683     public Reader retrieveArticle(final int articleNumber, final ArticlePointer pointer) throws IOException {
684         final ArticleInfo ai = ap2ai(pointer);
685         final Reader rdr = retrieveArticle(articleNumber, ai);
686         ai2ap(ai, pointer);
687         return rdr;
688     }
689 
690     /**
691      * Same as <code> retrieveArticle(articleNumber, null) </code>
692      *
693      * @param articleNumber the article number to fetch
694      * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist.
695      * @throws IOException if an IO error occurs
696      */
697     public BufferedReader retrieveArticle(final long articleNumber) throws IOException {
698         return retrieveArticle(articleNumber, null);
699     }
700 
701     /**
702      * Retrieves an article from the currently selected newsgroup. The article is referenced by its article number. The article number and identifier contained
703      * in the server reply are returned through an ArticleInfo. The <code> articleId </code> field of the ArticleInfo cannot always be trusted because some NNTP
704      * servers do not correctly follow the RFC 977 reply format.
705      * <p>
706      * A DotTerminatedMessageReader is returned from which the article can be read. If the article does not exist, null is returned.
707      * </p>
708      * <p>
709      * You must not issue any commands to the NNTP server (i.e., call any other methods) until you finish reading the message from the returned BufferedReader
710      * instance. The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned BufferedReader actually
711      * 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
712      * follow these requirements, your program will not work properly.
713      * </p>
714      *
715      * @param articleNumber The number of the article to retrieve.
716      * @param pointer       A parameter through which to return the article's number and unique id. The articleId field cannot always be trusted because of
717      *                      server deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned
718      *                      article information.
719      * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist.
720      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
721      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
722      *                                       independently as itself.
723      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
724      */
725     public BufferedReader retrieveArticle(final long articleNumber, final ArticleInfo pointer) throws IOException {
726         return retrieve(NNTPCommand.ARTICLE, articleNumber, pointer);
727     }
728 
729     /**
730      * Same as <code> retrieveArticle(articleId, (ArticleInfo) null) </code> Note: the return can be cast to a {@link BufferedReader}
731      *
732      * @param articleId the article id to retrieve
733      * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist.
734      * @throws IOException if an IO error occurs
735      */
736     public Reader retrieveArticle(final String articleId) throws IOException {
737         return retrieveArticle(articleId, (ArticleInfo) null);
738     }
739 
740     /**
741      * Retrieves an article from the NNTP server. The article is referenced by its unique article identifier (including the enclosing &lt; and &gt;). The
742      * article number and identifier contained in the server reply are returned through an ArticleInfo. The <code> articleId </code> field of the ArticleInfo
743      * cannot always be trusted because some NNTP servers do not correctly follow the RFC 977 reply format.
744      * <p>
745      * A DotTerminatedMessageReader is returned from which the article can be read. If the article does not exist, null is returned.
746      * </p>
747      * <p>
748      * You must not issue any commands to the NNTP server (i.e., call any other methods) until you finish reading the message from the returned BufferedReader
749      * instance. The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned BufferedReader actually
750      * 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
751      * follow these requirements, your program will not work properly.
752      * </p>
753      *
754      * @param articleId The unique article identifier of the article to retrieve. If this parameter is null, the currently selected article is retrieved.
755      * @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
756      *                  deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned article
757      *                  information.
758      * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist.
759      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
760      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
761      *                                       independently as itself.
762      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
763      */
764     public BufferedReader retrieveArticle(final String articleId, final ArticleInfo pointer) throws IOException {
765         return retrieve(NNTPCommand.ARTICLE, articleId, pointer);
766 
767     }
768 
769     /**
770      * @param articleId The unique article identifier of the article to retrieve
771      * @param pointer   A parameter through which to return the article's number and unique id
772      * @deprecated 3.0 use {@link #retrieveArticle(String, ArticleInfo)} instead
773      * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist.
774      * @throws IOException on error
775      */
776     @Deprecated
777     public Reader retrieveArticle(final String articleId, final ArticlePointer pointer) throws IOException {
778         final ArticleInfo ai = ap2ai(pointer);
779         final Reader rdr = retrieveArticle(articleId, ai);
780         ai2ap(ai, pointer);
781         return rdr;
782     }
783 
784     /**
785      * Same as <code> retrieveArticleBody(null) </code> Note: the return can be cast to a {@link BufferedReader}
786      *
787      * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist.
788      * @throws IOException if an error occurs
789      */
790     public Reader retrieveArticleBody() throws IOException {
791         return retrieveArticleBody(null);
792     }
793 
794     /**
795      * @param a tba
796      * @return tba
797      * @throws IOException tba
798      * @deprecated 3.0 use {@link #retrieveArticleBody(long)} instead
799      */
800     @Deprecated
801     public Reader retrieveArticleBody(final int a) throws IOException {
802         return retrieveArticleBody((long) a);
803     }
804 
805     /**
806      * @param a  tba
807      * @param ap tba
808      * @return tba
809      * @throws IOException tba
810      * @deprecated 3.0 use {@link #retrieveArticleBody(long, ArticleInfo)} instead
811      */
812     @Deprecated
813     public Reader retrieveArticleBody(final int a, final ArticlePointer ap) throws IOException {
814         final ArticleInfo ai = ap2ai(ap);
815         final Reader rdr = retrieveArticleBody(a, ai);
816         ai2ap(ai, ap);
817         return rdr;
818     }
819 
820     /**
821      * Same as <code> retrieveArticleBody(articleNumber, null) </code>
822      *
823      * @param articleNumber the article number
824      * @return the reader
825      * @throws IOException if an error occurs
826      */
827     public BufferedReader retrieveArticleBody(final long articleNumber) throws IOException {
828         return retrieveArticleBody(articleNumber, null);
829     }
830 
831     /**
832      * Retrieves an article body from the currently selected newsgroup. The article is referenced by its article number. The article number and identifier
833      * contained in the server reply are returned through an ArticleInfo. The <code> articleId </code> field of the ArticleInfo cannot always be trusted because
834      * some NNTP servers do not correctly follow the RFC 977 reply format.
835      * <p>
836      * A DotTerminatedMessageReader is returned from which the article can be read. If the article does not exist, null is returned.
837      * </p>
838      * <p>
839      * You must not issue any commands to the NNTP server (i.e., call any other methods) until you finish reading the message from the returned BufferedReader
840      * instance. The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned BufferedReader actually
841      * 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
842      * follow these requirements, your program will not work properly.
843      * </p>
844      *
845      * @param articleNumber The number of the article whose body is being retrieved.
846      * @param pointer       A parameter through which to return the article's number and unique id. The articleId field cannot always be trusted because of
847      *                      server deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned
848      *                      article information.
849      * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist.
850      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
851      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
852      *                                       independently as itself.
853      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
854      */
855     public BufferedReader retrieveArticleBody(final long articleNumber, final ArticleInfo pointer) throws IOException {
856         return retrieve(NNTPCommand.BODY, articleNumber, pointer);
857     }
858 
859     /**
860      * Same as <code> retrieveArticleBody(articleId, (ArticleInfo) null) </code> Note: the return can be cast to a {@link BufferedReader}
861      *
862      * @param articleId the article id
863      * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist.
864      * @throws IOException if an error occurs
865      */
866     public Reader retrieveArticleBody(final String articleId) throws IOException {
867         return retrieveArticleBody(articleId, (ArticleInfo) null);
868     }
869 
870     /**
871      * Retrieves an article body from the NNTP server. The article is referenced by its unique article identifier (including the enclosing &lt; and &gt;). The
872      * article number and identifier contained in the server reply are returned through an ArticleInfo. The <code> articleId </code> field of the ArticleInfo
873      * cannot always be trusted because some NNTP servers do not correctly follow the RFC 977 reply format.
874      * <p>
875      * A DotTerminatedMessageReader is returned from which the article can be read. If the article does not exist, null is returned.
876      * </p>
877      * <p>
878      * You must not issue any commands to the NNTP server (i.e., call any other methods) until you finish reading the message from the returned BufferedReader
879      * instance. The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned BufferedReader actually
880      * 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
881      * follow these requirements, your program will not work properly.
882      * </p>
883      *
884      * @param articleId The unique article identifier of the article whose body is being retrieved. If this parameter is null, the body of the currently
885      *                  selected article is retrieved.
886      * @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
887      *                  deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned article
888      *                  information.
889      * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist.
890      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
891      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
892      *                                       independently as itself.
893      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
894      */
895     public BufferedReader retrieveArticleBody(final String articleId, final ArticleInfo pointer) throws IOException {
896         return retrieve(NNTPCommand.BODY, articleId, pointer);
897 
898     }
899 
900     /**
901      * @param articleId The unique article identifier of the article to retrieve
902      * @param pointer   A parameter through which to return the article's number and unique id
903      * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist.
904      * @throws IOException on error
905      * @deprecated 3.0 use {@link #retrieveArticleBody(String, ArticleInfo)} instead
906      */
907     @Deprecated
908     public Reader retrieveArticleBody(final String articleId, final ArticlePointer pointer) throws IOException {
909         final ArticleInfo ai = ap2ai(pointer);
910         final Reader rdr = retrieveArticleBody(articleId, ai);
911         ai2ap(ai, pointer);
912         return rdr;
913     }
914 
915     /**
916      * Same as <code> retrieveArticleHeader((String) null) </code> Note: the return can be cast to a {@link BufferedReader}
917      *
918      * @return the reader
919      * @throws IOException if an error occurs
920      */
921     public Reader retrieveArticleHeader() throws IOException {
922         return retrieveArticleHeader((String) null);
923     }
924 
925     /**
926      * @param a tba
927      * @return tba
928      * @throws IOException tba
929      * @deprecated 3.0 use {@link #retrieveArticleHeader(long)} instead
930      */
931     @Deprecated
932     public Reader retrieveArticleHeader(final int a) throws IOException {
933         return retrieveArticleHeader((long) a);
934     }
935 
936     /**
937      * @param a  tba
938      * @param ap tba
939      * @return tba
940      * @throws IOException tba
941      * @deprecated 3.0 use {@link #retrieveArticleHeader(long, ArticleInfo)} instead
942      */
943     @Deprecated
944     public Reader retrieveArticleHeader(final int a, final ArticlePointer ap) throws IOException {
945         final ArticleInfo ai = ap2ai(ap);
946         final Reader rdr = retrieveArticleHeader(a, ai);
947         ai2ap(ai, ap);
948         return rdr;
949     }
950 
951     /**
952      * Same as <code> retrieveArticleHeader(articleNumber, null) </code>
953      *
954      * @param articleNumber the article number
955      * @return the reader
956      * @throws IOException if an error occurs
957      */
958     public BufferedReader retrieveArticleHeader(final long articleNumber) throws IOException {
959         return retrieveArticleHeader(articleNumber, null);
960     }
961 
962     /**
963      * Retrieves an article header from the currently selected newsgroup. The article is referenced by its article number. The article number and identifier
964      * contained in the server reply are returned through an ArticleInfo. The <code> articleId </code> field of the ArticleInfo cannot always be trusted because
965      * some NNTP servers do not correctly follow the RFC 977 reply format.
966      * <p>
967      * A DotTerminatedMessageReader is returned from which the article can be read. If the article does not exist, null is returned.
968      * </p>
969      * <p>
970      * You must not issue any commands to the NNTP server (i.e., call any other methods) until you finish reading the message from the returned BufferedReader
971      * instance. The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned BufferedReader actually
972      * 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
973      * follow these requirements, your program will not work properly.
974      * </p>
975      *
976      * @param articleNumber The number of the article whose header is being retrieved.
977      * @param pointer       A parameter through which to return the article's number and unique id. The articleId field cannot always be trusted because of
978      *                      server deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned
979      *                      article information.
980      * @return A DotTerminatedMessageReader instance from which the article header can be read. null if the article does not exist.
981      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
982      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
983      *                                       independently as itself.
984      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
985      */
986     public BufferedReader retrieveArticleHeader(final long articleNumber, final ArticleInfo pointer) throws IOException {
987         return retrieve(NNTPCommand.HEAD, articleNumber, pointer);
988     }
989 
990     /**
991      * Same as <code> retrieveArticleHeader(articleId, (ArticleInfo) null) </code> Note: the return can be cast to a {@link BufferedReader}
992      *
993      * @param articleId the article id to fetch
994      * @return the reader
995      * @throws IOException if an error occurs
996      */
997     public Reader retrieveArticleHeader(final String articleId) throws IOException {
998         return retrieveArticleHeader(articleId, (ArticleInfo) null);
999     }
1000 
1001     /**
1002      * Retrieves an article header from the NNTP server. The article is referenced by its unique article identifier (including the enclosing &lt; and &gt;). The
1003      * article number and identifier contained in the server reply are returned through an ArticleInfo. The <code> articleId </code> field of the ArticleInfo
1004      * cannot always be trusted because some NNTP servers do not correctly follow the RFC 977 reply format.
1005      * <p>
1006      * A DotTerminatedMessageReader is returned from which the article can be read. If the article does not exist, null is returned.
1007      * </p>
1008      * <p>
1009      * You must not issue any commands to the NNTP server (i.e., call any other methods) until you finish reading the message from the returned BufferedReader
1010      * instance. The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned BufferedReader actually
1011      * 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
1012      * follow these requirements, your program will not work properly.
1013      * </p>
1014      *
1015      * @param articleId The unique article identifier of the article whose header is being retrieved. If this parameter is null, the header of the currently
1016      *                  selected article is retrieved.
1017      * @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
1018      *                  deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned article
1019      *                  information.
1020      * @return A DotTerminatedMessageReader instance from which the article header can be read. null if the article does not exist.
1021      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
1022      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
1023      *                                       independently as itself.
1024      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
1025      */
1026     public BufferedReader retrieveArticleHeader(final String articleId, final ArticleInfo pointer) throws IOException {
1027         return retrieve(NNTPCommand.HEAD, articleId, pointer);
1028 
1029     }
1030 
1031     /**
1032      * @param articleId The unique article identifier of the article to retrieve
1033      * @param pointer   A parameter through which to return the article's number and unique id
1034      * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist.
1035      * @throws IOException on error
1036      * @deprecated 3.0 use {@link #retrieveArticleHeader(String, ArticleInfo)} instead
1037      */
1038     @Deprecated
1039     public Reader retrieveArticleHeader(final String articleId, final ArticlePointer pointer) throws IOException {
1040         final ArticleInfo ai = ap2ai(pointer);
1041         final Reader rdr = retrieveArticleHeader(articleId, ai);
1042         ai2ap(ai, pointer);
1043         return rdr;
1044     }
1045 
1046     /**
1047      * @param lowArticleNumber to fetch
1048      * @return a DotTerminatedReader if successful, null otherwise
1049      * @throws IOException tba
1050      * @deprecated 3.0 use {@link #retrieveArticleInfo(long)} instead
1051      */
1052     @Deprecated
1053     public Reader retrieveArticleInfo(final int lowArticleNumber) throws IOException {
1054         return retrieveArticleInfo((long) lowArticleNumber);
1055     }
1056 
1057     /**
1058      * @param lowArticleNumber  to fetch
1059      * @param highArticleNumber to fetch
1060      * @return a DotTerminatedReader if successful, null otherwise
1061      * @throws IOException on error
1062      * @deprecated 3.0 use {@link #retrieveArticleInfo(long, long)} instead
1063      */
1064     @Deprecated
1065     public Reader retrieveArticleInfo(final int lowArticleNumber, final int highArticleNumber) throws IOException {
1066         return retrieveArticleInfo((long) lowArticleNumber, (long) highArticleNumber);
1067     }
1068 
1069     /**
1070      * Return article headers for a specified post.
1071      *
1072      * @param articleNumber the article to retrieve headers for
1073      * @return a DotTerminatedReader if successful, null otherwise
1074      * @throws IOException on error
1075      */
1076     public BufferedReader retrieveArticleInfo(final long articleNumber) throws IOException {
1077         return retrieveArticleInfo(Long.toString(articleNumber));
1078     }
1079 
1080     /**
1081      * Return article headers for all articles between lowArticleNumber and highArticleNumber, inclusively. Uses the XOVER command.
1082      *
1083      * @param lowArticleNumber  low number
1084      * @param highArticleNumber high number
1085      * @return a DotTerminatedReader if successful, null otherwise
1086      * @throws IOException on error
1087      */
1088     public BufferedReader retrieveArticleInfo(final long lowArticleNumber, final long highArticleNumber) throws IOException {
1089         return retrieveArticleInfo(lowArticleNumber + "-" + highArticleNumber);
1090     }
1091 
1092     /**
1093      * Private implementation of XOVER functionality.
1094      *
1095      * See {@link NNTP#xover} for legal agument formats. Alternatively, read RFC 2980 :-)
1096      *
1097      * @param articleRange
1098      * @return Returns a DotTerminatedMessageReader if successful, null otherwise
1099      * @throws IOException
1100      */
1101     private BufferedReader retrieveArticleInfo(final String articleRange) throws IOException {
1102         if (!NNTPReply.isPositiveCompletion(xover(articleRange))) {
1103             return null;
1104         }
1105 
1106         return new DotTerminatedMessageReader(_reader_);
1107     }
1108 
1109     /**
1110      * @param a tba
1111      * @param b tba
1112      * @return tba
1113      * @throws IOException tba
1114      * @deprecated 3.0 use {@link #retrieveHeader(String, long)} instead
1115      */
1116     @Deprecated
1117     public Reader retrieveHeader(final String a, final int b) throws IOException {
1118         return retrieveHeader(a, (long) b);
1119     }
1120 
1121     /**
1122      * @param header            the header
1123      * @param lowArticleNumber  to fetch
1124      * @param highArticleNumber to fetch
1125      * @return a DotTerminatedReader if successful, null otherwise
1126      * @throws IOException on error
1127      * @deprecated 3.0 use {@link #retrieveHeader(String, long, long)} instead
1128      */
1129     @Deprecated
1130     public Reader retrieveHeader(final String header, final int lowArticleNumber, final int highArticleNumber) throws IOException {
1131         return retrieveHeader(header, (long) lowArticleNumber, (long) highArticleNumber);
1132     }
1133 
1134     /**
1135      * Return an article header for a specified post.
1136      *
1137      * @param header        the header to retrieve
1138      * @param articleNumber the article to retrieve the header for
1139      * @return a DotTerminatedReader if successful, null otherwise
1140      * @throws IOException on error
1141      */
1142     public BufferedReader retrieveHeader(final String header, final long articleNumber) throws IOException {
1143         return retrieveHeader(header, Long.toString(articleNumber));
1144     }
1145 
1146     /**
1147      * Return an article header for all articles between lowArticleNumber and highArticleNumber, inclusively.
1148      *
1149      * @param header            the header
1150      * @param lowArticleNumber  to fetch
1151      * @param highArticleNumber to fetch
1152      * @return a DotTerminatedReader if successful, null otherwise
1153      * @throws IOException on error
1154      */
1155     public BufferedReader retrieveHeader(final String header, final long lowArticleNumber, final long highArticleNumber) throws IOException {
1156         return retrieveHeader(header, lowArticleNumber + "-" + highArticleNumber);
1157     }
1158 
1159     /**
1160      * Private implementation of XHDR functionality.
1161      * <p>
1162      * See {@link NNTP#xhdr} for legal argument formats. Alternatively, read RFC 1036.
1163      * </p>
1164      *
1165      * @param header
1166      * @param articleRange
1167      * @return Returns a {@link DotTerminatedMessageReader} if successful, {@code null} otherwise
1168      * @throws IOException
1169      */
1170     private BufferedReader retrieveHeader(final String header, final String articleRange) throws IOException {
1171         if (!NNTPReply.isPositiveCompletion(xhdr(header, articleRange))) {
1172             return null;
1173         }
1174 
1175         return new DotTerminatedMessageReader(_reader_);
1176     }
1177 
1178     /***
1179      * Same as <code> selectArticle((String) null, articleId) </code>. Useful for retrieving the current article number.
1180      *
1181      * @param pointer to the article
1182      * @return true if OK
1183      * @throws IOException on error
1184      */
1185     public boolean selectArticle(final ArticleInfo pointer) throws IOException {
1186         return selectArticle(null, pointer);
1187     }
1188 
1189     /**
1190      * @param pointer A parameter through which to return the article's number and unique id
1191      * @return True if successful, false if not.
1192      * @throws IOException on error
1193      * @deprecated 3.0 use {@link #selectArticle(ArticleInfo)} instead
1194      */
1195     @Deprecated
1196     public boolean selectArticle(final ArticlePointer pointer) throws IOException {
1197         final ArticleInfo ai = ap2ai(pointer);
1198         final boolean b = selectArticle(ai);
1199         ai2ap(ai, pointer);
1200         return b;
1201 
1202     }
1203 
1204     /**
1205      * @param a tba
1206      * @return tba
1207      * @throws IOException tba
1208      * @deprecated 3.0 use {@link #selectArticle(long)} instead
1209      */
1210     @Deprecated
1211     public boolean selectArticle(final int a) throws IOException {
1212         return selectArticle((long) a);
1213     }
1214 
1215     /**
1216      * @param a  tba
1217      * @param ap tba
1218      * @return tba
1219      * @throws IOException tba
1220      * @deprecated 3.0 use {@link #selectArticle(long, ArticleInfo)} instead
1221      */
1222     @Deprecated
1223     public boolean selectArticle(final int a, final ArticlePointer ap) throws IOException {
1224         final ArticleInfo ai = ap2ai(ap);
1225         final boolean b = selectArticle(a, ai);
1226         ai2ap(ai, ap);
1227         return b;
1228     }
1229 
1230     /**
1231      * Same as <code> selectArticle(articleNumber, null) </code>
1232      *
1233      * @param articleNumber the numger
1234      * @return true if successful
1235      * @throws IOException on error
1236      */
1237     public boolean selectArticle(final long articleNumber) throws IOException {
1238         return selectArticle(articleNumber, null);
1239     }
1240 
1241     /**
1242      * 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
1243      * 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
1244      * retrieving it, or to obtain an article's unique identifier given its number.
1245      *
1246      * @param articleNumber The number of the article to select from the currently selected newsgroup.
1247      * @param pointer       A parameter through which to return the article's number and unique id. Although the articleId field cannot always be trusted
1248      *                      because of server deviations from RFC 977 reply formats, we haven't found a server that misformats this information in response to
1249      *                      this particular command. You may set this parameter to null if you do not desire to retrieve the returned article information.
1250      * @return True if successful, false if not.
1251      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
1252      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
1253      *                                       independently as itself.
1254      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
1255      */
1256     public boolean selectArticle(final long articleNumber, final ArticleInfo pointer) throws IOException {
1257         if (!NNTPReply.isPositiveCompletion(stat(articleNumber))) {
1258             return false;
1259         }
1260 
1261         if (pointer != null) {
1262             parseArticlePointer(getReplyString(), pointer);
1263         }
1264 
1265         return true;
1266     }
1267 
1268     /**
1269      * Same as <code> selectArticle(articleId, (ArticleInfo) null) </code>
1270      *
1271      * @param articleId the article's Id
1272      * @return true if successful
1273      * @throws IOException on error
1274      */
1275     public boolean selectArticle(final String articleId) throws IOException {
1276         return selectArticle(articleId, (ArticleInfo) null);
1277     }
1278 
1279     /**
1280      * Select an article by its unique identifier (including enclosing &lt; and &gt;) and return its article number and id through the pointer parameter. This
1281      * 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
1282      * the article by its number.
1283      *
1284      * @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
1285      *                  selected
1286      * @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
1287      *                  deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned article
1288      *                  information.
1289      * @return True if successful, false if not.
1290      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
1291      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
1292      *                                       independently as itself.
1293      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
1294      */
1295     public boolean selectArticle(final String articleId, final ArticleInfo pointer) throws IOException {
1296         if (articleId != null) {
1297             if (!NNTPReply.isPositiveCompletion(stat(articleId))) {
1298                 return false;
1299             }
1300         } else if (!NNTPReply.isPositiveCompletion(stat())) {
1301             return false;
1302         }
1303 
1304         if (pointer != null) {
1305             parseArticlePointer(getReplyString(), pointer);
1306         }
1307 
1308         return true;
1309     }
1310 
1311     /**
1312      * @param articleId The unique article identifier of the article to retrieve
1313      * @param pointer   A parameter through which to return the article's number and unique id
1314      * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist.
1315      * @throws IOException on error
1316      * @deprecated 3.0 use {@link #selectArticle(String, ArticleInfo)} instead
1317      */
1318     @Deprecated
1319     public boolean selectArticle(final String articleId, final ArticlePointer pointer) throws IOException {
1320         final ArticleInfo ai = ap2ai(pointer);
1321         final boolean b = selectArticle(articleId, ai);
1322         ai2ap(ai, pointer);
1323         return b;
1324 
1325     }
1326 
1327     /**
1328      * Same as <code> selectNewsgroup(newsgroup, null) </code>
1329      *
1330      * @param newsgroup the newsgroup name
1331      * @return true if newsgroup exist and was selected
1332      * @throws IOException if an error occurs
1333      */
1334     public boolean selectNewsgroup(final String newsgroup) throws IOException {
1335         return selectNewsgroup(newsgroup, null);
1336     }
1337 
1338     /**
1339      * Select the specified newsgroup to be the target of for future article retrieval and posting operations. Also return the newsgroup information contained
1340      * in the server reply through the info parameter.
1341      *
1342      * @param newsgroup The newsgroup to select.
1343      * @param info      A parameter through which the newsgroup information of the selected newsgroup contained in the server reply is returned. Set this to
1344      *                  null if you do not desire this information.
1345      * @return True if the newsgroup exists and was selected, false otherwise.
1346      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
1347      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
1348      *                                       independently as itself.
1349      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
1350      */
1351     public boolean selectNewsgroup(final String newsgroup, final NewsgroupInfo info) throws IOException {
1352         if (!NNTPReply.isPositiveCompletion(group(newsgroup))) {
1353             return false;
1354         }
1355 
1356         if (info != null) {
1357             parseGroupReply(getReplyString(), info);
1358         }
1359 
1360         return true;
1361     }
1362 
1363     /**
1364      * Same as <code> selectNextArticle((ArticleInfo) null) </code>
1365      *
1366      * @return true if successful
1367      * @throws IOException on error
1368      */
1369     public boolean selectNextArticle() throws IOException {
1370         return selectNextArticle((ArticleInfo) null);
1371     }
1372 
1373     /**
1374      * Select the article following the currently selected article in the currently selected newsgroup and return its number and unique id through the pointer
1375      * parameter. Because of deviating server implementations, the articleId information cannot be trusted. To obtain the article identifier, issue a
1376      * <code> selectArticle(pointer.articleNumber, pointer) </code> immediately afterward.
1377      *
1378      * @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
1379      *                deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned article
1380      *                information.
1381      * @return True if successful, false if not (e.g., there is no following article).
1382      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
1383      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
1384      *                                       independently as itself.
1385      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
1386      */
1387     public boolean selectNextArticle(final ArticleInfo pointer) throws IOException {
1388         if (!NNTPReply.isPositiveCompletion(next())) {
1389             return false;
1390         }
1391 
1392         if (pointer != null) {
1393             parseArticlePointer(getReplyString(), pointer);
1394         }
1395 
1396         return true;
1397     }
1398 
1399     /**
1400      * @param pointer A parameter through which to return the article's number and unique id
1401      * @return True if successful, false if not.
1402      * @throws IOException on error
1403      * @deprecated 3.0 use {@link #selectNextArticle(ArticleInfo)} instead
1404      */
1405     @Deprecated
1406     public boolean selectNextArticle(final ArticlePointer pointer) throws IOException {
1407         final ArticleInfo ai = ap2ai(pointer);
1408         final boolean b = selectNextArticle(ai);
1409         ai2ap(ai, pointer);
1410         return b;
1411 
1412     }
1413 
1414     /**
1415      * Same as <code> selectPreviousArticle((ArticleInfo) null) </code>
1416      *
1417      * @return true if successful
1418      * @throws IOException on error
1419      */
1420     public boolean selectPreviousArticle() throws IOException {
1421         return selectPreviousArticle((ArticleInfo) null);
1422     }
1423 
1424     // Helper methods
1425 
1426     /**
1427      * Select the article preceding the currently selected article in the currently selected newsgroup and return its number and unique id through the pointer
1428      * parameter. Because of deviating server implementations, the articleId information cannot be trusted. To obtain the article identifier, issue a
1429      * <code> selectArticle(pointer.articleNumber, pointer) </code> immediately afterward.
1430      *
1431      * @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
1432      *                deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned article
1433      *                information.
1434      * @return True if successful, false if not (e.g., there is no previous article).
1435      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
1436      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
1437      *                                       independently as itself.
1438      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
1439      */
1440     public boolean selectPreviousArticle(final ArticleInfo pointer) throws IOException {
1441         if (!NNTPReply.isPositiveCompletion(last())) {
1442             return false;
1443         }
1444 
1445         if (pointer != null) {
1446             parseArticlePointer(getReplyString(), pointer);
1447         }
1448 
1449         return true;
1450     }
1451 
1452     /**
1453      * @param pointer A parameter through which to return the article's number and unique id
1454      * @return True if successful, false if not.
1455      * @throws IOException on error
1456      * @deprecated 3.0 use {@link #selectPreviousArticle(ArticleInfo)} instead
1457      */
1458     @Deprecated
1459     public boolean selectPreviousArticle(final ArticlePointer pointer) throws IOException {
1460         final ArticleInfo ai = ap2ai(pointer);
1461         final boolean b = selectPreviousArticle(ai);
1462         ai2ap(ai, pointer);
1463         return b;
1464     }
1465 }