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