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