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