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