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