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