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