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