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