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