NNTPClient.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.net.nntp;

  18. import java.io.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.Reader;
  21. import java.io.StringWriter;
  22. import java.io.Writer;
  23. import java.util.ArrayList;
  24. import java.util.Vector;

  25. import org.apache.commons.net.MalformedServerReplyException;
  26. import org.apache.commons.net.io.DotTerminatedMessageReader;
  27. import org.apache.commons.net.io.DotTerminatedMessageWriter;
  28. import org.apache.commons.net.io.Util;
  29. import org.apache.commons.net.util.NetConstants;

  30. /**
  31.  * NNTPClient encapsulates all the functionality necessary to post and retrieve articles from an NNTP server. As with all classes derived from
  32.  * {@link org.apache.commons.net.SocketClient}, you must first connect to the server with {@link org.apache.commons.net.SocketClient#connect connect } before
  33.  * doing anything, and finally {@link org.apache.commons.net.nntp.NNTP#disconnect disconnect() } after you're completely finished interacting with the server.
  34.  * Remember that the {@link org.apache.commons.net.nntp.NNTP#isAllowedToPost isAllowedToPost()} method is defined in {@link org.apache.commons.net.nntp.NNTP}.
  35.  * <p>
  36.  * 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
  37.  * 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
  38.  * a {@link org.apache.commons.net.nntp.NNTPReply#SERVICE_DISCONTINUED NNTPReply.SERVICE_DISCONTINUED } response to a command. When that occurs, the NNTP class
  39.  * method encountering that reply will throw an {@link org.apache.commons.net.nntp.NNTPConnectionClosedException} . <code>NNTPConectionClosedException</code> is
  40.  * 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
  41.  * appear before the more general <code>IOException</code> catch block. When you encounter an
  42.  * {@link org.apache.commons.net.nntp.NNTPConnectionClosedException} , you must disconnect the connection with
  43.  * {@link org.apache.commons.net.nntp.NNTP#disconnect disconnect() } to properly clean up the system resources used by NNTP. Before disconnecting, you may check
  44.  * the last reply code and text with {@link org.apache.commons.net.nntp.NNTP#getReplyCode getReplyCode } and
  45.  * {@link org.apache.commons.net.nntp.NNTP#getReplyString getReplyString }.
  46.  * </p>
  47.  * <p>
  48.  * 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
  49.  * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the
  50.  * 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
  51.  * lenient as possible.
  52.  * </p>
  53.  *
  54.  * @see NNTP
  55.  * @see NNTPConnectionClosedException
  56.  * @see org.apache.commons.net.MalformedServerReplyException
  57.  */

  58. public class NNTPClient extends NNTP {

  59.     private static final NewsgroupInfo[] EMPTY_NEWSGROUP_INFO_ARRAY = {};

  60.     /**
  61.      * Parse a response line from {@link #retrieveArticleInfo(long, long)}.
  62.      *
  63.      * @param line a response line
  64.      * @return the parsed {@link Article}, if unparseable then isDummy() will be true, and the subject will contain the raw info.
  65.      * @since 3.0
  66.      */
  67.     static Article parseArticleEntry(final String line) {
  68.         // Extract the article information
  69.         // Mandatory format (from NNTP RFC 2980) is :
  70.         // articleNumber\tSubject\tAuthor\tDate\tID\tReference(s)\tByte Count\tLine Count

  71.         final Article article = new Article();
  72.         article.setSubject(line); // in case parsing fails
  73.         final String[] parts = line.split("\t");
  74.         if (parts.length > 6) {
  75.             int i = 0;
  76.             try {
  77.                 article.setArticleNumber(Long.parseLong(parts[i++]));
  78.                 article.setSubject(parts[i++]);
  79.                 article.setFrom(parts[i++]);
  80.                 article.setDate(parts[i++]);
  81.                 article.setArticleId(parts[i++]);
  82.                 article.addReference(parts[i++]);
  83.             } catch (final NumberFormatException e) {
  84.                 // ignored, already handled
  85.             }
  86.         }
  87.         return article;
  88.     }

  89.     /*
  90.      * 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 =
  91.      * name of the group.)
  92.      */

  93.     private static void parseGroupReply(final String reply, final NewsgroupInfo info) throws MalformedServerReplyException {
  94.         final String[] tokens = reply.split(" ");
  95.         if (tokens.length >= 5) {
  96.             int i = 1; // Skip numeric response value
  97.             try {
  98.                 // Get estimated article count
  99.                 info.setArticleCount(Long.parseLong(tokens[i++]));
  100.                 // Get first article number
  101.                 info.setFirstArticle(Long.parseLong(tokens[i++]));
  102.                 // Get last article number
  103.                 info.setLastArticle(Long.parseLong(tokens[i++]));
  104.                 // Get newsgroup name
  105.                 info.setNewsgroup(tokens[i++]);

  106.                 info.setPostingPermission(NewsgroupInfo.UNKNOWN_POSTING_PERMISSION);
  107.                 return;
  108.             } catch (final NumberFormatException e) {
  109.                 // drop through to report error
  110.             }
  111.         }

  112.         throw new MalformedServerReplyException("Could not parse newsgroup info.\nServer reply: " + reply);
  113.     }

  114.     // Format: group last first p
  115.     static NewsgroupInfo parseNewsgroupListEntry(final String entry) {
  116.         final String[] tokens = entry.split(" ");
  117.         if (tokens.length < 4) {
  118.             return null;
  119.         }
  120.         final NewsgroupInfo result = new NewsgroupInfo();

  121.         int i = 0;

  122.         result.setNewsgroup(tokens[i++]);

  123.         try {
  124.             final long lastNum = Long.parseLong(tokens[i++]);
  125.             final long firstNum = Long.parseLong(tokens[i++]);
  126.             result.setFirstArticle(firstNum);
  127.             result.setLastArticle(lastNum);
  128.             if (firstNum == 0 && lastNum == 0) {
  129.                 result.setArticleCount(0);
  130.             } else {
  131.                 result.setArticleCount(lastNum - firstNum + 1);
  132.             }
  133.         } catch (final NumberFormatException e) {
  134.             return null;
  135.         }

  136.         switch (tokens[i++].charAt(0)) {
  137.         case 'y':
  138.         case 'Y':
  139.             result.setPostingPermission(NewsgroupInfo.PERMITTED_POSTING_PERMISSION);
  140.             break;
  141.         case 'n':
  142.         case 'N':
  143.             result.setPostingPermission(NewsgroupInfo.PROHIBITED_POSTING_PERMISSION);
  144.             break;
  145.         case 'm':
  146.         case 'M':
  147.             result.setPostingPermission(NewsgroupInfo.MODERATED_POSTING_PERMISSION);
  148.             break;
  149.         default:
  150.             result.setPostingPermission(NewsgroupInfo.UNKNOWN_POSTING_PERMISSION);
  151.             break;
  152.         }

  153.         return result;
  154.     }

  155.     @SuppressWarnings("deprecation")
  156.     private void ai2ap(final ArticleInfo ai, final ArticlePointer ap) {
  157.         if (ap != null) { // ai cannot be null
  158.             ap.articleId = ai.articleId;
  159.             ap.articleNumber = (int) ai.articleNumber;
  160.         }
  161.     }

  162.     private ArticleInfo ap2ai(@SuppressWarnings("deprecation") final ArticlePointer ap) {
  163.         if (ap == null) {
  164.             return null;
  165.         }
  166.         return new ArticleInfo();
  167.     }

  168.     /**
  169.      * 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
  170.      * server.
  171.      *
  172.      * @param user a valid user name
  173.      * @param password the corresponding password
  174.      * @return True for successful login, false for a failure
  175.      * @throws IOException on error
  176.      */
  177.     public boolean authenticate(final String user, final String password) throws IOException {
  178.         int replyCode = authinfoUser(user);

  179.         if (replyCode == NNTPReply.MORE_AUTH_INFO_REQUIRED) {
  180.             replyCode = authinfoPass(password);

  181.             if (replyCode == NNTPReply.AUTHENTICATION_ACCEPTED) {
  182.                 this._isAllowedToPost = true;
  183.                 return true;
  184.             }
  185.         }
  186.         return false;
  187.     }

  188.     /**
  189.      * There are a few NNTPClient methods that do not complete the entire sequence of NNTP commands to complete a transaction. These commands require some
  190.      * action by the programmer after the reception of a positive preliminary command. After the programmer's code completes its actions, it must call this
  191.      * method to receive the completion reply from the server and verify the success of the entire transaction.
  192.      * <p>
  193.      * For example
  194.      * </p>
  195.      * <pre>
  196.      * writer = client.postArticle();
  197.      * if (writer == null) // failure
  198.      *     return false;
  199.      * header = new SimpleNNTPHeader("foobar@foo.com", "Just testing");
  200.      * header.addNewsgroup("alt.test");
  201.      * writer.write(header.toString());
  202.      * writer.write("This is just a test");
  203.      * writer.close();
  204.      * if (!client.completePendingCommand()) // failure
  205.      *     return false;
  206.      * </pre>
  207.      *
  208.      * @return True if successfully completed, false if not.
  209.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  210.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  211.      *                                       independently as itself.
  212.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  213.      */
  214.     public boolean completePendingCommand() throws IOException {
  215.         return NNTPReply.isPositiveCompletion(getReply());
  216.     }

  217.     public Writer forwardArticle(final String articleId) throws IOException {
  218.         if (!NNTPReply.isPositiveIntermediate(ihave(articleId))) {
  219.             return null;
  220.         }

  221.         return new DotTerminatedMessageWriter(_writer_);
  222.     }

  223.     /**
  224.      * Return article headers for all articles between lowArticleNumber and highArticleNumber, inclusively, using the XOVER command.
  225.      *
  226.      * @param lowArticleNumber  low
  227.      * @param highArticleNumber high
  228.      * @return an Iterable of Articles
  229.      * @throws IOException if the command failed
  230.      * @since 3.0
  231.      */
  232.     public Iterable<Article> iterateArticleInfo(final long lowArticleNumber, final long highArticleNumber) throws IOException {
  233.         final BufferedReader info = retrieveArticleInfo(lowArticleNumber, highArticleNumber);
  234.         if (info == null) {
  235.             throw new IOException("XOVER command failed: " + getReplyString());
  236.         }
  237.         // N.B. info is already DotTerminated, so don't rewrap
  238.         return new ArticleIterator(new ReplyIterator(info, false));
  239.     }

  240.     /**
  241.      * 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,
  242.      * 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.
  243.      * Each String which is returned is a unique message identifier including the enclosing &lt; and &gt;.
  244.      *
  245.      * @param query The query restricting how to search for new news. You must add at least one newsgroup to the query.
  246.      * @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,
  247.      *         no strings will be returned.
  248.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  249.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  250.      *                                       independently as itself.
  251.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  252.      * @since 3.0
  253.      */
  254.     public Iterable<String> iterateNewNews(final NewGroupsOrNewsQuery query) throws IOException {
  255.         if (NNTPReply.isPositiveCompletion(newnews(query.getNewsgroups(), query.getDate(), query.getTime(), query.isGMT(), query.getDistributions()))) {
  256.             return new ReplyIterator(_reader_);
  257.         }
  258.         throw new IOException("NEWNEWS command failed: " + getReplyString());
  259.     }

  260.     /**
  261.      * 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
  262.      * added, no entries will be returned. This uses the "NEWGROUPS" command.
  263.      *
  264.      * @param query The query restricting how to search for new newsgroups.
  265.      * @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
  266.      *         will be returned.
  267.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  268.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  269.      *                                       independently as itself.
  270.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  271.      * @since 3.0
  272.      */
  273.     public Iterable<String> iterateNewNewsgroupListing(final NewGroupsOrNewsQuery query) throws IOException {
  274.         if (NNTPReply.isPositiveCompletion(newgroups(query.getDate(), query.getTime(), query.isGMT(), query.getDistributions()))) {
  275.             return new ReplyIterator(_reader_);
  276.         }
  277.         throw new IOException("NEWGROUPS command failed: " + getReplyString());
  278.     }

  279.     /**
  280.      * 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
  281.      * added, no entries will be returned. This uses the "NEWGROUPS" command.
  282.      *
  283.      * @param query The query restricting how to search for new newsgroups.
  284.      * @return An iterable of NewsgroupInfo instances containing the information for each new newsgroup added to the NNTP server. If no newsgroups were added,
  285.      *         no entries will be returned.
  286.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  287.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  288.      *                                       independently as itself.
  289.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  290.      * @since 3.0
  291.      */
  292.     public Iterable<NewsgroupInfo> iterateNewNewsgroups(final NewGroupsOrNewsQuery query) throws IOException {
  293.         return new NewsgroupIterator(iterateNewNewsgroupListing(query));
  294.     }

  295.     /**
  296.      * List all newsgroups served by the NNTP server. If no newsgroups are served, no entries will be returned. The method uses the "LIST" command.
  297.      *
  298.      * @return An iterable of NewsgroupInfo instances containing the information for each newsgroup served by the NNTP server. If no newsgroups are served, no
  299.      *         entries 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> iterateNewsgroupListing() throws IOException {
  307.         if (NNTPReply.isPositiveCompletion(list())) {
  308.             return new ReplyIterator(_reader_);
  309.         }
  310.         throw new IOException("LIST command failed: " + getReplyString());
  311.     }

  312.     /**
  313.      * List the newsgroups that match a given pattern. Uses the "LIST ACTIVE" command.
  314.      *
  315.      * @param wildmat a pseudo-regex pattern (cf. RFC 2980)
  316.      * @return An iterable of Strings containing the raw information for each newsgroup served by the NNTP server corresponding to the supplied pattern. If no
  317.      *         such newsgroups are served, no entries will be returned.
  318.      * @throws IOException on error
  319.      * @since 3.0
  320.      */
  321.     public Iterable<String> iterateNewsgroupListing(final String wildmat) throws IOException {
  322.         if (NNTPReply.isPositiveCompletion(listActive(wildmat))) {
  323.             return new ReplyIterator(_reader_);
  324.         }
  325.         throw new IOException("LIST ACTIVE " + wildmat + " command failed: " + getReplyString());
  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 Strings containing the raw information for each newsgroup served by the NNTP server. If no newsgroups are served, no entries will
  331.      *         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<NewsgroupInfo> iterateNewsgroups() throws IOException {
  339.         return new NewsgroupIterator(iterateNewsgroupListing());
  340.     }

  341.     /**
  342.      * List the newsgroups that match a given pattern. Uses the "LIST ACTIVE" command.
  343.      *
  344.      * @param wildmat a pseudo-regex pattern (cf. RFC 2980)
  345.      * @return An iterable NewsgroupInfo instances containing the information for each newsgroup served by the NNTP server corresponding to the supplied
  346.      *         pattern. If no such newsgroups are served, no entries will be returned.
  347.      * @throws IOException on error
  348.      * @since 3.0
  349.      */
  350.     public Iterable<NewsgroupInfo> iterateNewsgroups(final String wildmat) throws IOException {
  351.         return new NewsgroupIterator(iterateNewsgroupListing(wildmat));
  352.     }

  353.     /**
  354.      * List the command help from the server.
  355.      *
  356.      * @return The sever help information.
  357.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  358.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  359.      *                                       independently as itself.
  360.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  361.      */
  362.     public String listHelp() throws IOException {
  363.         if (!NNTPReply.isInformational(help())) {
  364.             return null;
  365.         }

  366.         try (final StringWriter help = new StringWriter(); final BufferedReader reader = new DotTerminatedMessageReader(_reader_)) {
  367.             Util.copyReader(reader, help);
  368.             return help.toString();
  369.         }
  370.     }

  371.     /**
  372.      * 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
  373.      * 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
  374.      * fail. Each String in the returned array is a unique message identifier including the enclosing &lt; and &gt;. This uses the "NEWNEWS" command.
  375.      *
  376.      * @param query The query restricting how to search for new news. You must add at least one newsgroup to the query.
  377.      * @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
  378.      *         zero length array will be returned. If the command fails, null will be returned.
  379.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  380.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  381.      *                                       independently as itself.
  382.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  383.      *
  384.      * @see #iterateNewNews(NewGroupsOrNewsQuery)
  385.      */
  386.     public String[] listNewNews(final NewGroupsOrNewsQuery query) throws IOException {
  387.         if (!NNTPReply.isPositiveCompletion(newnews(query.getNewsgroups(), query.getDate(), query.getTime(), query.isGMT(), query.getDistributions()))) {
  388.             return null;
  389.         }

  390.         final Vector<String> list = new Vector<>();
  391.         try (final BufferedReader reader = new DotTerminatedMessageReader(_reader_)) {

  392.             String line;
  393.             while ((line = reader.readLine()) != null) {
  394.                 list.addElement(line);
  395.             }
  396.         }

  397.         final int size = list.size();
  398.         if (size < 1) {
  399.             return NetConstants.EMPTY_STRING_ARRAY;
  400.         }

  401.         final String[] result = new String[size];
  402.         list.copyInto(result);

  403.         return result;
  404.     }

  405.     /**
  406.      * 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
  407.      * added, a zero length array will be returned. If the command fails, null will be returned. This uses the "NEWGROUPS" command.
  408.      *
  409.      * @param query The query restricting how to search for new newsgroups.
  410.      * @return An array of NewsgroupInfo instances containing the information for each new newsgroup added to the NNTP server. If no newsgroups were added, a
  411.      *         zero length array will be returned. If the command fails, null will be returned.
  412.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  413.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  414.      *                                       independently as itself.
  415.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  416.      * @see #iterateNewNewsgroups(NewGroupsOrNewsQuery)
  417.      * @see #iterateNewNewsgroupListing(NewGroupsOrNewsQuery)
  418.      */
  419.     public NewsgroupInfo[] listNewNewsgroups(final NewGroupsOrNewsQuery query) throws IOException {
  420.         if (!NNTPReply.isPositiveCompletion(newgroups(query.getDate(), query.getTime(), query.isGMT(), query.getDistributions()))) {
  421.             return null;
  422.         }

  423.         return readNewsgroupListing();
  424.     }

  425.     /**
  426.      * 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
  427.      * returned. The method uses the "LIST" command.
  428.      *
  429.      * @return An array of NewsgroupInfo instances containing the information for each newsgroup served by the NNTP server. If no newsgroups are served, a zero
  430.      *         length array will be returned. If the command fails, null will be returned.
  431.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  432.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  433.      *                                       independently as itself.
  434.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  435.      * @see #iterateNewsgroupListing()
  436.      * @see #iterateNewsgroups()
  437.      */
  438.     public NewsgroupInfo[] listNewsgroups() throws IOException {
  439.         if (!NNTPReply.isPositiveCompletion(list())) {
  440.             return null;
  441.         }

  442.         return readNewsgroupListing();
  443.     }

  444.     /**
  445.      * List the newsgroups that match a given pattern. Uses the "LIST ACTIVE" command.
  446.      *
  447.      * @param wildmat a pseudo-regex pattern (cf. RFC 2980)
  448.      * @return An array of NewsgroupInfo instances containing the information for each newsgroup served by the NNTP server corresponding to the supplied
  449.      *         pattern. If no such newsgroups are served, a zero length array will be returned. If the command fails, null will be returned.
  450.      * @throws IOException on error
  451.      * @see #iterateNewsgroupListing(String)
  452.      * @see #iterateNewsgroups(String)
  453.      */
  454.     public NewsgroupInfo[] listNewsgroups(final String wildmat) throws IOException {
  455.         if (!NNTPReply.isPositiveCompletion(listActive(wildmat))) {
  456.             return null;
  457.         }
  458.         return readNewsgroupListing();
  459.     }

  460.     /**
  461.      * Send a "LIST OVERVIEW.FMT" command to the server.
  462.      *
  463.      * @return the contents of the Overview format, of {@code null} if the command failed
  464.      * @throws IOException on error
  465.      */
  466.     public String[] listOverviewFmt() throws IOException {
  467.         if (!NNTPReply.isPositiveCompletion(sendCommand("LIST", "OVERVIEW.FMT"))) {
  468.             return null;
  469.         }

  470.         try (final BufferedReader reader = new DotTerminatedMessageReader(_reader_)) {
  471.             String line;
  472.             final ArrayList<String> list = new ArrayList<>();
  473.             while ((line = reader.readLine()) != null) {
  474.                 list.add(line);
  475.             }
  476.             return list.toArray(NetConstants.EMPTY_STRING_ARRAY);
  477.         }
  478.     }

  479.     /**
  480.      * 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
  481.      * connection.
  482.      *
  483.      * @return True if successfully completed, false if not.
  484.      * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  485.      */
  486.     public boolean logout() throws IOException {
  487.         return NNTPReply.isPositiveCompletion(quit());
  488.     }

  489.     /**
  490.      * Parse the reply and store the id and number in the pointer.
  491.      *
  492.      * @param reply   the reply to parse "22n nnn <aaa>"
  493.      * @param pointer the pointer to update
  494.      *
  495.      * @throws MalformedServerReplyException if response could not be parsed
  496.      */
  497.     private void parseArticlePointer(final String reply, final ArticleInfo pointer) throws MalformedServerReplyException {
  498.         final String[] tokens = reply.split(" ");
  499.         if (tokens.length >= 3) { // OK, we can parset the line
  500.             int i = 1; // skip reply code
  501.             try {
  502.                 // Get article number
  503.                 pointer.articleNumber = Long.parseLong(tokens[i++]);
  504.                 // Get article id
  505.                 pointer.articleId = tokens[i++];
  506.                 return; // done
  507.             } catch (final NumberFormatException e) {
  508.                 // drop through and raise exception
  509.             }
  510.         }
  511.         throw new MalformedServerReplyException("Could not parse article pointer.\nServer reply: " + reply);
  512.     }

  513.     /**
  514.      * Post an article to the NNTP server. This method returns a DotTerminatedMessageWriter instance to which the article can be written. Null is returned if
  515.      * the posting attempt fails. You should check {@link NNTP#isAllowedToPost isAllowedToPost() } before trying to post. However, a posting attempt can fail
  516.      * due to malformed headers.
  517.      * <p>
  518.      * 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.
  519.      * The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned Writer actually writes directly to
  520.      * 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
  521.      * properly.
  522.      * </p>
  523.      * <p>
  524.      * Different NNTP servers will require different header formats, but you can use the provided {@link org.apache.commons.net.nntp.SimpleNNTPHeader} class to
  525.      * construct the bare minimum acceptable header for most newsreaders. To construct more complicated headers you should refer to RFC 822. When the Java Mail
  526.      * API is finalized, you will be able to use it to compose fully compliant Internet text messages. The DotTerminatedMessageWriter takes care of doubling
  527.      * 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.
  528.      * </p>
  529.      * <p>
  530.      * Upon closing the returned Writer, you need to call {@link #completePendingCommand completePendingCommand() } to finalize the posting and verify its
  531.      * success or failure from the server reply.
  532.      * </p>
  533.      *
  534.      * @return A DotTerminatedMessageWriter to which the article (including header) can be written. Returns null if the command fails.
  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 Writer postArticle() throws IOException {
  538.         if (!NNTPReply.isPositiveIntermediate(post())) {
  539.             return null;
  540.         }

  541.         return new DotTerminatedMessageWriter(_writer_);
  542.     }

  543.     private NewsgroupInfo[] readNewsgroupListing() throws IOException {

  544.         // Start of with a big vector because we may be reading a very large
  545.         // amount of groups.
  546.         final Vector<NewsgroupInfo> list = new Vector<>(2048);

  547.         String line;
  548.         try (final BufferedReader reader = new DotTerminatedMessageReader(_reader_)) {
  549.             while ((line = reader.readLine()) != null) {
  550.                 final NewsgroupInfo tmp = parseNewsgroupListEntry(line);
  551.                 if (tmp == null) {
  552.                     throw new MalformedServerReplyException(line);
  553.                 }
  554.                 list.addElement(tmp);
  555.             }
  556.         }
  557.         final int size;
  558.         if ((size = list.size()) < 1) {
  559.             return EMPTY_NEWSGROUP_INFO_ARRAY;
  560.         }

  561.         final NewsgroupInfo[] info = new NewsgroupInfo[size];
  562.         list.copyInto(info);

  563.         return info;
  564.     }

  565.     private BufferedReader retrieve(final int command, final long articleNumber, final ArticleInfo pointer) throws IOException {
  566.         if (!NNTPReply.isPositiveCompletion(sendCommand(command, Long.toString(articleNumber)))) {
  567.             return null;
  568.         }

  569.         if (pointer != null) {
  570.             parseArticlePointer(getReplyString(), pointer);
  571.         }

  572.         return new DotTerminatedMessageReader(_reader_);
  573.     }

  574.     private BufferedReader retrieve(final int command, final String articleId, final ArticleInfo pointer) throws IOException {
  575.         if (articleId != null) {
  576.             if (!NNTPReply.isPositiveCompletion(sendCommand(command, articleId))) {
  577.                 return null;
  578.             }
  579.         } else if (!NNTPReply.isPositiveCompletion(sendCommand(command))) {
  580.             return null;
  581.         }

  582.         if (pointer != null) {
  583.             parseArticlePointer(getReplyString(), pointer);
  584.         }

  585.         return new DotTerminatedMessageReader(_reader_);
  586.     }

  587.     /**
  588.      * Same as <code>retrieveArticle((String) null)</code> Note: the return can be cast to a {@link BufferedReader}
  589.      *
  590.      * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist.
  591.      * @throws IOException if an IO error occurs
  592.      */
  593.     public Reader retrieveArticle() throws IOException {
  594.         return retrieveArticle((String) null);
  595.     }

  596.     /**
  597.      * @param articleNumber The number of the article to retrieve
  598.      * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist.
  599.      * @throws IOException on error
  600.      * @deprecated 3.0 use {@link #retrieveArticle(long)} instead
  601.      */
  602.     @Deprecated
  603.     public Reader retrieveArticle(final int articleNumber) throws IOException {
  604.         return retrieveArticle((long) articleNumber);
  605.     }

  606.     /**
  607.      * @param articleNumber The number of the article to retrieve.
  608.      * @param pointer       A parameter through which to return the article's number and unique id
  609.      * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist.
  610.      * @throws IOException on error
  611.      * @deprecated 3.0 use {@link #retrieveArticle(long, ArticleInfo)} instead
  612.      */
  613.     @Deprecated
  614.     public Reader retrieveArticle(final int articleNumber, final ArticlePointer pointer) throws IOException {
  615.         final ArticleInfo ai = ap2ai(pointer);
  616.         final Reader rdr = retrieveArticle(articleNumber, ai);
  617.         ai2ap(ai, pointer);
  618.         return rdr;
  619.     }

  620.     /**
  621.      * Same as <code>retrieveArticle(articleNumber, null)</code>
  622.      *
  623.      * @param articleNumber the article number to fetch
  624.      * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist.
  625.      * @throws IOException if an IO error occurs
  626.      */
  627.     public BufferedReader retrieveArticle(final long articleNumber) throws IOException {
  628.         return retrieveArticle(articleNumber, null);
  629.     }

  630.     /**
  631.      * Retrieves an article from the currently selected newsgroup. The article is referenced by its article number. The article number and identifier contained
  632.      * in the server reply are returned through an ArticleInfo. The <code>articleId</code> field of the ArticleInfo cannot always be trusted because some NNTP
  633.      * servers do not correctly follow the RFC 977 reply format.
  634.      * <p>
  635.      * A DotTerminatedMessageReader is returned from which the article can be read. If the article does not exist, null is returned.
  636.      * </p>
  637.      * <p>
  638.      * 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
  639.      * instance. The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned BufferedReader actually
  640.      * 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
  641.      * follow these requirements, your program will not work properly.
  642.      * </p>
  643.      *
  644.      * @param articleNumber The number of the article to retrieve.
  645.      * @param pointer       A parameter through which to return the article's number and unique id. The articleId field cannot always be trusted because of
  646.      *                      server deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned
  647.      *                      article information.
  648.      * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist.
  649.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  650.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  651.      *                                       independently as itself.
  652.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  653.      */
  654.     public BufferedReader retrieveArticle(final long articleNumber, final ArticleInfo pointer) throws IOException {
  655.         return retrieve(NNTPCommand.ARTICLE, articleNumber, pointer);
  656.     }

  657.     /**
  658.      * Same as <code>retrieveArticle(articleId, (ArticleInfo) null)</code> Note: the return can be cast to a {@link BufferedReader}
  659.      *
  660.      * @param articleId the article id to retrieve
  661.      * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist.
  662.      * @throws IOException if an IO error occurs
  663.      */
  664.     public Reader retrieveArticle(final String articleId) throws IOException {
  665.         return retrieveArticle(articleId, (ArticleInfo) null);
  666.     }

  667.     /**
  668.      * Retrieves an article from the NNTP server. The article is referenced by its unique article identifier (including the enclosing &lt; and &gt;). The
  669.      * article number and identifier contained in the server reply are returned through an ArticleInfo. The <code>articleId</code> field of the ArticleInfo
  670.      * cannot always be trusted because some NNTP servers do not correctly follow the RFC 977 reply format.
  671.      * <p>
  672.      * A DotTerminatedMessageReader is returned from which the article can be read. If the article does not exist, null is returned.
  673.      * </p>
  674.      * <p>
  675.      * 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
  676.      * instance. The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned BufferedReader actually
  677.      * 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
  678.      * follow these requirements, your program will not work properly.
  679.      * </p>
  680.      *
  681.      * @param articleId The unique article identifier of the article to retrieve. If this parameter is null, the currently selected article is retrieved.
  682.      * @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
  683.      *                  deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned article
  684.      *                  information.
  685.      * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist.
  686.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  687.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  688.      *                                       independently as itself.
  689.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  690.      */
  691.     public BufferedReader retrieveArticle(final String articleId, final ArticleInfo pointer) throws IOException {
  692.         return retrieve(NNTPCommand.ARTICLE, articleId, pointer);

  693.     }

  694.     /**
  695.      * @param articleId The unique article identifier of the article to retrieve
  696.      * @param pointer   A parameter through which to return the article's number and unique id
  697.      * @deprecated 3.0 use {@link #retrieveArticle(String, ArticleInfo)} instead
  698.      * @return A DotTerminatedMessageReader instance from which the article can be read. null if the article does not exist.
  699.      * @throws IOException on error
  700.      */
  701.     @Deprecated
  702.     public Reader retrieveArticle(final String articleId, final ArticlePointer pointer) throws IOException {
  703.         final ArticleInfo ai = ap2ai(pointer);
  704.         final Reader rdr = retrieveArticle(articleId, ai);
  705.         ai2ap(ai, pointer);
  706.         return rdr;
  707.     }

  708.     /**
  709.      * Same as <code>retrieveArticleBody(null)</code> Note: the return can be cast to a {@link BufferedReader}
  710.      *
  711.      * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist.
  712.      * @throws IOException if an error occurs
  713.      */
  714.     public Reader retrieveArticleBody() throws IOException {
  715.         return retrieveArticleBody(null);
  716.     }

  717.     /**
  718.      * @param a tba
  719.      * @return tba
  720.      * @throws IOException tba
  721.      * @deprecated 3.0 use {@link #retrieveArticleBody(long)} instead
  722.      */
  723.     @Deprecated
  724.     public Reader retrieveArticleBody(final int a) throws IOException {
  725.         return retrieveArticleBody((long) a);
  726.     }

  727.     /**
  728.      * @param a  tba
  729.      * @param ap tba
  730.      * @return tba
  731.      * @throws IOException tba
  732.      * @deprecated 3.0 use {@link #retrieveArticleBody(long, ArticleInfo)} instead
  733.      */
  734.     @Deprecated
  735.     public Reader retrieveArticleBody(final int a, final ArticlePointer ap) throws IOException {
  736.         final ArticleInfo ai = ap2ai(ap);
  737.         final Reader rdr = retrieveArticleBody(a, ai);
  738.         ai2ap(ai, ap);
  739.         return rdr;
  740.     }

  741.     /**
  742.      * Same as <code>retrieveArticleBody(articleNumber, null)</code>
  743.      *
  744.      * @param articleNumber the article number
  745.      * @return the reader
  746.      * @throws IOException if an error occurs
  747.      */
  748.     public BufferedReader retrieveArticleBody(final long articleNumber) throws IOException {
  749.         return retrieveArticleBody(articleNumber, null);
  750.     }

  751.     /**
  752.      * Retrieves an article body from the currently selected newsgroup. The article is referenced by its article number. The article number and identifier
  753.      * contained in the server reply are returned through an ArticleInfo. The <code>articleId</code> field of the ArticleInfo cannot always be trusted because
  754.      * 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.      * <p>
  759.      * 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
  760.      * instance. The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned BufferedReader actually
  761.      * 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
  762.      * follow these requirements, your program will not work properly.
  763.      * </p>
  764.      *
  765.      * @param articleNumber The number of the article whose body is being retrieved.
  766.      * @param pointer       A parameter through which to return the article's number and unique id. The articleId field cannot always be trusted because of
  767.      *                      server deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned
  768.      *                      article information.
  769.      * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist.
  770.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  771.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  772.      *                                       independently as itself.
  773.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  774.      */
  775.     public BufferedReader retrieveArticleBody(final long articleNumber, final ArticleInfo pointer) throws IOException {
  776.         return retrieve(NNTPCommand.BODY, articleNumber, pointer);
  777.     }

  778.     /**
  779.      * Same as <code>retrieveArticleBody(articleId, (ArticleInfo) null)</code> Note: the return can be cast to a {@link BufferedReader}
  780.      *
  781.      * @param articleId the article id
  782.      * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist.
  783.      * @throws IOException if an error occurs
  784.      */
  785.     public Reader retrieveArticleBody(final String articleId) throws IOException {
  786.         return retrieveArticleBody(articleId, (ArticleInfo) null);
  787.     }

  788.     /**
  789.      * Retrieves an article body from the NNTP server. The article is referenced by its unique article identifier (including the enclosing &lt; and &gt;). The
  790.      * article number and identifier contained in the server reply are returned through an ArticleInfo. The <code>articleId</code> field of the ArticleInfo
  791.      * cannot always be trusted because some NNTP servers do not correctly follow the RFC 977 reply format.
  792.      * <p>
  793.      * A DotTerminatedMessageReader is returned from which the article can be read. If the article does not exist, null is returned.
  794.      * </p>
  795.      * <p>
  796.      * 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
  797.      * instance. The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned BufferedReader actually
  798.      * 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
  799.      * follow these requirements, your program will not work properly.
  800.      * </p>
  801.      *
  802.      * @param articleId The unique article identifier of the article whose body is being retrieved. If this parameter is null, the body of the currently
  803.      *                  selected article is retrieved.
  804.      * @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
  805.      *                  deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned article
  806.      *                  information.
  807.      * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist.
  808.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  809.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  810.      *                                       independently as itself.
  811.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  812.      */
  813.     public BufferedReader retrieveArticleBody(final String articleId, final ArticleInfo pointer) throws IOException {
  814.         return retrieve(NNTPCommand.BODY, articleId, pointer);

  815.     }

  816.     /**
  817.      * @param articleId The unique article identifier of the article to retrieve
  818.      * @param pointer   A parameter through which to return the article's number and unique id
  819.      * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist.
  820.      * @throws IOException on error
  821.      * @deprecated 3.0 use {@link #retrieveArticleBody(String, ArticleInfo)} instead
  822.      */
  823.     @Deprecated
  824.     public Reader retrieveArticleBody(final String articleId, final ArticlePointer pointer) throws IOException {
  825.         final ArticleInfo ai = ap2ai(pointer);
  826.         final Reader rdr = retrieveArticleBody(articleId, ai);
  827.         ai2ap(ai, pointer);
  828.         return rdr;
  829.     }

  830.     /**
  831.      * Same as <code>retrieveArticleHeader((String) null)</code> Note: the return can be cast to a {@link BufferedReader}
  832.      *
  833.      * @return the reader
  834.      * @throws IOException if an error occurs
  835.      */
  836.     public Reader retrieveArticleHeader() throws IOException {
  837.         return retrieveArticleHeader((String) null);
  838.     }

  839.     /**
  840.      * @param a tba
  841.      * @return tba
  842.      * @throws IOException tba
  843.      * @deprecated 3.0 use {@link #retrieveArticleHeader(long)} instead
  844.      */
  845.     @Deprecated
  846.     public Reader retrieveArticleHeader(final int a) throws IOException {
  847.         return retrieveArticleHeader((long) a);
  848.     }

  849.     /**
  850.      * @param a  tba
  851.      * @param ap tba
  852.      * @return tba
  853.      * @throws IOException tba
  854.      * @deprecated 3.0 use {@link #retrieveArticleHeader(long, ArticleInfo)} instead
  855.      */
  856.     @Deprecated
  857.     public Reader retrieveArticleHeader(final int a, final ArticlePointer ap) throws IOException {
  858.         final ArticleInfo ai = ap2ai(ap);
  859.         final Reader rdr = retrieveArticleHeader(a, ai);
  860.         ai2ap(ai, ap);
  861.         return rdr;
  862.     }

  863.     /**
  864.      * Same as <code>retrieveArticleHeader(articleNumber, null)</code>
  865.      *
  866.      * @param articleNumber the article number
  867.      * @return the reader
  868.      * @throws IOException if an error occurs
  869.      */
  870.     public BufferedReader retrieveArticleHeader(final long articleNumber) throws IOException {
  871.         return retrieveArticleHeader(articleNumber, null);
  872.     }

  873.     /**
  874.      * Retrieves an article header from the currently selected newsgroup. The article is referenced by its article number. The article number and identifier
  875.      * contained in the server reply are returned through an ArticleInfo. The <code>articleId</code> field of the ArticleInfo cannot always be trusted because
  876.      * some NNTP servers do not correctly follow the RFC 977 reply format.
  877.      * <p>
  878.      * A DotTerminatedMessageReader is returned from which the article can be read. If the article does not exist, null is returned.
  879.      * </p>
  880.      * <p>
  881.      * 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
  882.      * instance. The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned BufferedReader actually
  883.      * 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
  884.      * follow these requirements, your program will not work properly.
  885.      * </p>
  886.      *
  887.      * @param articleNumber The number of the article whose header is being retrieved.
  888.      * @param pointer       A parameter through which to return the article's number and unique id. The articleId field cannot always be trusted because of
  889.      *                      server deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned
  890.      *                      article information.
  891.      * @return A DotTerminatedMessageReader instance from which the article header can be read. null if the article does not exist.
  892.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  893.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  894.      *                                       independently as itself.
  895.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  896.      */
  897.     public BufferedReader retrieveArticleHeader(final long articleNumber, final ArticleInfo pointer) throws IOException {
  898.         return retrieve(NNTPCommand.HEAD, articleNumber, pointer);
  899.     }

  900.     /**
  901.      * Same as <code>retrieveArticleHeader(articleId, (ArticleInfo) null)</code> Note: the return can be cast to a {@link BufferedReader}
  902.      *
  903.      * @param articleId the article id to fetch
  904.      * @return the reader
  905.      * @throws IOException if an error occurs
  906.      */
  907.     public Reader retrieveArticleHeader(final String articleId) throws IOException {
  908.         return retrieveArticleHeader(articleId, (ArticleInfo) null);
  909.     }

  910.     /**
  911.      * Retrieves an article header from the NNTP server. The article is referenced by its unique article identifier (including the enclosing &lt; and &gt;). The
  912.      * article number and identifier contained in the server reply are returned through an ArticleInfo. The <code>articleId</code> field of the ArticleInfo
  913.      * cannot always be trusted because some NNTP servers do not correctly follow the RFC 977 reply format.
  914.      * <p>
  915.      * A DotTerminatedMessageReader is returned from which the article can be read. If the article does not exist, null is returned.
  916.      * </p>
  917.      * <p>
  918.      * 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
  919.      * instance. The NNTP protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned BufferedReader actually
  920.      * 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
  921.      * follow these requirements, your program will not work properly.
  922.      * </p>
  923.      *
  924.      * @param articleId The unique article identifier of the article whose header is being retrieved. If this parameter is null, the header of the currently
  925.      *                  selected article is retrieved.
  926.      * @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
  927.      *                  deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned article
  928.      *                  information.
  929.      * @return A DotTerminatedMessageReader instance from which the article header can be read. null if the article does not exist.
  930.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  931.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  932.      *                                       independently as itself.
  933.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  934.      */
  935.     public BufferedReader retrieveArticleHeader(final String articleId, final ArticleInfo pointer) throws IOException {
  936.         return retrieve(NNTPCommand.HEAD, articleId, pointer);

  937.     }

  938.     /**
  939.      * @param articleId The unique article identifier of the article to retrieve
  940.      * @param pointer   A parameter through which to return the article's number and unique id
  941.      * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist.
  942.      * @throws IOException on error
  943.      * @deprecated 3.0 use {@link #retrieveArticleHeader(String, ArticleInfo)} instead
  944.      */
  945.     @Deprecated
  946.     public Reader retrieveArticleHeader(final String articleId, final ArticlePointer pointer) throws IOException {
  947.         final ArticleInfo ai = ap2ai(pointer);
  948.         final Reader rdr = retrieveArticleHeader(articleId, ai);
  949.         ai2ap(ai, pointer);
  950.         return rdr;
  951.     }

  952.     /**
  953.      * @param lowArticleNumber to fetch
  954.      * @return a DotTerminatedReader if successful, null otherwise
  955.      * @throws IOException tba
  956.      * @deprecated 3.0 use {@link #retrieveArticleInfo(long)} instead
  957.      */
  958.     @Deprecated
  959.     public Reader retrieveArticleInfo(final int lowArticleNumber) throws IOException {
  960.         return retrieveArticleInfo((long) lowArticleNumber);
  961.     }

  962.     /**
  963.      * @param lowArticleNumber  to fetch
  964.      * @param highArticleNumber to fetch
  965.      * @return a DotTerminatedReader if successful, null otherwise
  966.      * @throws IOException on error
  967.      * @deprecated 3.0 use {@link #retrieveArticleInfo(long, long)} instead
  968.      */
  969.     @Deprecated
  970.     public Reader retrieveArticleInfo(final int lowArticleNumber, final int highArticleNumber) throws IOException {
  971.         return retrieveArticleInfo((long) lowArticleNumber, (long) highArticleNumber);
  972.     }

  973.     /**
  974.      * Return article headers for a specified post.
  975.      *
  976.      * @param articleNumber the article to retrieve headers for
  977.      * @return a DotTerminatedReader if successful, null otherwise
  978.      * @throws IOException on error
  979.      */
  980.     public BufferedReader retrieveArticleInfo(final long articleNumber) throws IOException {
  981.         return retrieveArticleInfo(Long.toString(articleNumber));
  982.     }

  983.     /**
  984.      * Return article headers for all articles between lowArticleNumber and highArticleNumber, inclusively. Uses the XOVER command.
  985.      *
  986.      * @param lowArticleNumber  low number
  987.      * @param highArticleNumber high number
  988.      * @return a DotTerminatedReader if successful, null otherwise
  989.      * @throws IOException on error
  990.      */
  991.     public BufferedReader retrieveArticleInfo(final long lowArticleNumber, final long highArticleNumber) throws IOException {
  992.         return retrieveArticleInfo(lowArticleNumber + "-" + highArticleNumber);
  993.     }

  994.     /**
  995.      * Private implementation of XOVER functionality.
  996.      *
  997.      * See {@link NNTP#xover} for legal agument formats. Alternatively, read RFC 2980 :-)
  998.      *
  999.      * @param articleRange
  1000.      * @return Returns a DotTerminatedMessageReader if successful, null otherwise
  1001.      * @throws IOException
  1002.      */
  1003.     private BufferedReader retrieveArticleInfo(final String articleRange) throws IOException {
  1004.         if (!NNTPReply.isPositiveCompletion(xover(articleRange))) {
  1005.             return null;
  1006.         }

  1007.         return new DotTerminatedMessageReader(_reader_);
  1008.     }

  1009.     /**
  1010.      * @param a tba
  1011.      * @param b tba
  1012.      * @return tba
  1013.      * @throws IOException tba
  1014.      * @deprecated 3.0 use {@link #retrieveHeader(String, long)} instead
  1015.      */
  1016.     @Deprecated
  1017.     public Reader retrieveHeader(final String a, final int b) throws IOException {
  1018.         return retrieveHeader(a, (long) b);
  1019.     }

  1020.     /**
  1021.      * @param header            the header
  1022.      * @param lowArticleNumber  to fetch
  1023.      * @param highArticleNumber to fetch
  1024.      * @return a DotTerminatedReader if successful, null otherwise
  1025.      * @throws IOException on error
  1026.      * @deprecated 3.0 use {@link #retrieveHeader(String, long, long)} instead
  1027.      */
  1028.     @Deprecated
  1029.     public Reader retrieveHeader(final String header, final int lowArticleNumber, final int highArticleNumber) throws IOException {
  1030.         return retrieveHeader(header, (long) lowArticleNumber, (long) highArticleNumber);
  1031.     }

  1032.     /**
  1033.      * Return an article header for a specified post.
  1034.      *
  1035.      * @param header        the header to retrieve
  1036.      * @param articleNumber the article to retrieve the header for
  1037.      * @return a DotTerminatedReader if successful, null otherwise
  1038.      * @throws IOException on error
  1039.      */
  1040.     public BufferedReader retrieveHeader(final String header, final long articleNumber) throws IOException {
  1041.         return retrieveHeader(header, Long.toString(articleNumber));
  1042.     }

  1043.     /**
  1044.      * Return an article header for all articles between lowArticleNumber and highArticleNumber, inclusively.
  1045.      *
  1046.      * @param header            the header
  1047.      * @param lowArticleNumber  to fetch
  1048.      * @param highArticleNumber to fetch
  1049.      * @return a DotTerminatedReader if successful, null otherwise
  1050.      * @throws IOException on error
  1051.      */
  1052.     public BufferedReader retrieveHeader(final String header, final long lowArticleNumber, final long highArticleNumber) throws IOException {
  1053.         return retrieveHeader(header, lowArticleNumber + "-" + highArticleNumber);
  1054.     }

  1055.     /**
  1056.      * Private implementation of XHDR functionality.
  1057.      * <p>
  1058.      * See {@link NNTP#xhdr} for legal argument formats. Alternatively, read RFC 1036.
  1059.      * </p>
  1060.      *
  1061.      * @param header
  1062.      * @param articleRange
  1063.      * @return Returns a {@link DotTerminatedMessageReader} if successful, {@code null} otherwise
  1064.      * @throws IOException
  1065.      */
  1066.     private BufferedReader retrieveHeader(final String header, final String articleRange) throws IOException {
  1067.         if (!NNTPReply.isPositiveCompletion(xhdr(header, articleRange))) {
  1068.             return null;
  1069.         }

  1070.         return new DotTerminatedMessageReader(_reader_);
  1071.     }

  1072.     /**
  1073.      * Same as <code>selectArticle((String) null, articleId)</code>. Useful for retrieving the current article number.
  1074.      *
  1075.      * @param pointer to the article
  1076.      * @return true if OK
  1077.      * @throws IOException on error
  1078.      */
  1079.     public boolean selectArticle(final ArticleInfo pointer) throws IOException {
  1080.         return selectArticle(null, pointer);
  1081.     }

  1082.     /**
  1083.      * @param pointer A parameter through which to return the article's number and unique id
  1084.      * @return True if successful, false if not.
  1085.      * @throws IOException on error
  1086.      * @deprecated 3.0 use {@link #selectArticle(ArticleInfo)} instead
  1087.      */
  1088.     @Deprecated
  1089.     public boolean selectArticle(final ArticlePointer pointer) throws IOException {
  1090.         final ArticleInfo ai = ap2ai(pointer);
  1091.         final boolean b = selectArticle(ai);
  1092.         ai2ap(ai, pointer);
  1093.         return b;

  1094.     }

  1095.     /**
  1096.      * @param a tba
  1097.      * @return tba
  1098.      * @throws IOException tba
  1099.      * @deprecated 3.0 use {@link #selectArticle(long)} instead
  1100.      */
  1101.     @Deprecated
  1102.     public boolean selectArticle(final int a) throws IOException {
  1103.         return selectArticle((long) a);
  1104.     }

  1105.     /**
  1106.      * @param a  tba
  1107.      * @param ap tba
  1108.      * @return tba
  1109.      * @throws IOException tba
  1110.      * @deprecated 3.0 use {@link #selectArticle(long, ArticleInfo)} instead
  1111.      */
  1112.     @Deprecated
  1113.     public boolean selectArticle(final int a, final ArticlePointer ap) throws IOException {
  1114.         final ArticleInfo ai = ap2ai(ap);
  1115.         final boolean b = selectArticle(a, ai);
  1116.         ai2ap(ai, ap);
  1117.         return b;
  1118.     }

  1119.     /**
  1120.      * Same as <code>selectArticle(articleNumber, null)</code>
  1121.      *
  1122.      * @param articleNumber the numger
  1123.      * @return true if successful
  1124.      * @throws IOException on error
  1125.      */
  1126.     public boolean selectArticle(final long articleNumber) throws IOException {
  1127.         return selectArticle(articleNumber, null);
  1128.     }

  1129.     /**
  1130.      * 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
  1131.      * 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
  1132.      * retrieving it, or to obtain an article's unique identifier given its number.
  1133.      *
  1134.      * @param articleNumber The number of the article to select from the currently selected newsgroup.
  1135.      * @param pointer       A parameter through which to return the article's number and unique id. Although the articleId field cannot always be trusted
  1136.      *                      because of server deviations from RFC 977 reply formats, we haven't found a server that misformats this information in response to
  1137.      *                      this particular command. You may set this parameter to null if you do not desire to retrieve the returned article information.
  1138.      * @return True if successful, false if not.
  1139.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  1140.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  1141.      *                                       independently as itself.
  1142.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  1143.      */
  1144.     public boolean selectArticle(final long articleNumber, final ArticleInfo pointer) throws IOException {
  1145.         if (!NNTPReply.isPositiveCompletion(stat(articleNumber))) {
  1146.             return false;
  1147.         }

  1148.         if (pointer != null) {
  1149.             parseArticlePointer(getReplyString(), pointer);
  1150.         }

  1151.         return true;
  1152.     }

  1153.     /**
  1154.      * Same as <code>selectArticle(articleId, (ArticleInfo) null)</code>
  1155.      *
  1156.      * @param articleId the article's Id
  1157.      * @return true if successful
  1158.      * @throws IOException on error
  1159.      */
  1160.     public boolean selectArticle(final String articleId) throws IOException {
  1161.         return selectArticle(articleId, (ArticleInfo) null);
  1162.     }

  1163.     /**
  1164.      * Select an article by its unique identifier (including enclosing &lt; and &gt;) and return its article number and id through the pointer parameter. This
  1165.      * 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
  1166.      * the article by its number.
  1167.      *
  1168.      * @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
  1169.      *                  selected
  1170.      * @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
  1171.      *                  deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned article
  1172.      *                  information.
  1173.      * @return True if successful, false if not.
  1174.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  1175.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  1176.      *                                       independently as itself.
  1177.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  1178.      */
  1179.     public boolean selectArticle(final String articleId, final ArticleInfo pointer) throws IOException {
  1180.         if (articleId != null) {
  1181.             if (!NNTPReply.isPositiveCompletion(stat(articleId))) {
  1182.                 return false;
  1183.             }
  1184.         } else if (!NNTPReply.isPositiveCompletion(stat())) {
  1185.             return false;
  1186.         }

  1187.         if (pointer != null) {
  1188.             parseArticlePointer(getReplyString(), pointer);
  1189.         }

  1190.         return true;
  1191.     }

  1192.     /**
  1193.      * @param articleId The unique article identifier of the article to retrieve
  1194.      * @param pointer   A parameter through which to return the article's number and unique id
  1195.      * @return A DotTerminatedMessageReader instance from which the article body can be read. null if the article does not exist.
  1196.      * @throws IOException on error
  1197.      * @deprecated 3.0 use {@link #selectArticle(String, ArticleInfo)} instead
  1198.      */
  1199.     @Deprecated
  1200.     public boolean selectArticle(final String articleId, final ArticlePointer pointer) throws IOException {
  1201.         final ArticleInfo ai = ap2ai(pointer);
  1202.         final boolean b = selectArticle(articleId, ai);
  1203.         ai2ap(ai, pointer);
  1204.         return b;

  1205.     }

  1206.     /**
  1207.      * Same as <code>selectNewsgroup(newsgroup, null)</code>
  1208.      *
  1209.      * @param newsgroup the newsgroup name
  1210.      * @return true if newsgroup exist and was selected
  1211.      * @throws IOException if an error occurs
  1212.      */
  1213.     public boolean selectNewsgroup(final String newsgroup) throws IOException {
  1214.         return selectNewsgroup(newsgroup, null);
  1215.     }

  1216.     /**
  1217.      * Select the specified newsgroup to be the target of for future article retrieval and posting operations. Also return the newsgroup information contained
  1218.      * in the server reply through the info parameter.
  1219.      *
  1220.      * @param newsgroup The newsgroup to select.
  1221.      * @param info      A parameter through which the newsgroup information of the selected newsgroup contained in the server reply is returned. Set this to
  1222.      *                  null if you do not desire this information.
  1223.      * @return True if the newsgroup exists and was selected, false otherwise.
  1224.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  1225.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  1226.      *                                       independently as itself.
  1227.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  1228.      */
  1229.     public boolean selectNewsgroup(final String newsgroup, final NewsgroupInfo info) throws IOException {
  1230.         if (!NNTPReply.isPositiveCompletion(group(newsgroup))) {
  1231.             return false;
  1232.         }

  1233.         if (info != null) {
  1234.             parseGroupReply(getReplyString(), info);
  1235.         }

  1236.         return true;
  1237.     }

  1238.     /**
  1239.      * Same as <code>selectNextArticle((ArticleInfo) null)</code>
  1240.      *
  1241.      * @return true if successful
  1242.      * @throws IOException on error
  1243.      */
  1244.     public boolean selectNextArticle() throws IOException {
  1245.         return selectNextArticle((ArticleInfo) null);
  1246.     }

  1247.     /**
  1248.      * Select the article following the currently selected article in the currently selected newsgroup and return its number and unique id through the pointer
  1249.      * parameter. Because of deviating server implementations, the articleId information cannot be trusted. To obtain the article identifier, issue a
  1250.      * <code>selectArticle(pointer.articleNumber, pointer)</code> immediately afterward.
  1251.      *
  1252.      * @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
  1253.      *                deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned article
  1254.      *                information.
  1255.      * @return True if successful, false if not (e.g., there is no following article).
  1256.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  1257.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  1258.      *                                       independently as itself.
  1259.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  1260.      */
  1261.     public boolean selectNextArticle(final ArticleInfo pointer) throws IOException {
  1262.         if (!NNTPReply.isPositiveCompletion(next())) {
  1263.             return false;
  1264.         }

  1265.         if (pointer != null) {
  1266.             parseArticlePointer(getReplyString(), pointer);
  1267.         }

  1268.         return true;
  1269.     }

  1270.     /**
  1271.      * @param pointer A parameter through which to return the article's number and unique id
  1272.      * @return True if successful, false if not.
  1273.      * @throws IOException on error
  1274.      * @deprecated 3.0 use {@link #selectNextArticle(ArticleInfo)} instead
  1275.      */
  1276.     @Deprecated
  1277.     public boolean selectNextArticle(final ArticlePointer pointer) throws IOException {
  1278.         final ArticleInfo ai = ap2ai(pointer);
  1279.         final boolean b = selectNextArticle(ai);
  1280.         ai2ap(ai, pointer);
  1281.         return b;

  1282.     }

  1283.     /**
  1284.      * Same as <code>selectPreviousArticle((ArticleInfo) null)</code>
  1285.      *
  1286.      * @return true if successful
  1287.      * @throws IOException on error
  1288.      */
  1289.     public boolean selectPreviousArticle() throws IOException {
  1290.         return selectPreviousArticle((ArticleInfo) null);
  1291.     }

  1292.     // Helper methods

  1293.     /**
  1294.      * Select the article preceding the currently selected article in the currently selected newsgroup and return its number and unique id through the pointer
  1295.      * parameter. Because of deviating server implementations, the articleId information cannot be trusted. To obtain the article identifier, issue a
  1296.      * <code>selectArticle(pointer.articleNumber, pointer)</code> immediately afterward.
  1297.      *
  1298.      * @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
  1299.      *                deviations from RFC 977 reply formats. You may set this parameter to null if you do not desire to retrieve the returned article
  1300.      *                information.
  1301.      * @return True if successful, false if not (e.g., there is no previous article).
  1302.      * @throws NNTPConnectionClosedException If the NNTP server prematurely closes the connection as a result of the client being idle or some other reason
  1303.      *                                       causing the server to send NNTP reply code 400. This exception may be caught either as an IOException or
  1304.      *                                       independently as itself.
  1305.      * @throws IOException                   If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
  1306.      */
  1307.     public boolean selectPreviousArticle(final ArticleInfo pointer) throws IOException {
  1308.         if (!NNTPReply.isPositiveCompletion(last())) {
  1309.             return false;
  1310.         }

  1311.         if (pointer != null) {
  1312.             parseArticlePointer(getReplyString(), pointer);
  1313.         }

  1314.         return true;
  1315.     }

  1316.     /**
  1317.      * @param pointer A parameter through which to return the article's number and unique id
  1318.      * @return True if successful, false if not.
  1319.      * @throws IOException on error
  1320.      * @deprecated 3.0 use {@link #selectPreviousArticle(ArticleInfo)} instead
  1321.      */
  1322.     @Deprecated
  1323.     public boolean selectPreviousArticle(final ArticlePointer pointer) throws IOException {
  1324.         final ArticleInfo ai = ap2ai(pointer);
  1325.         final boolean b = selectPreviousArticle(ai);
  1326.         ai2ap(ai, pointer);
  1327.         return b;
  1328.     }
  1329. }