    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}