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