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