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