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