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