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.StringTokenizer;
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 Daniel F. Savarese
083 * @author Rory Winston
084 * @author Ted Wise
085 * @see NNTP
086 * @see NNTPConnectionClosedException
087 * @see org.apache.commons.net.MalformedServerReplyException
088 ***/
089
090public class NNTPClient extends NNTP
091{
092
093    private void __parseArticlePointer(String reply, ArticlePointer pointer)
094    throws MalformedServerReplyException
095    {
096        StringTokenizer tokenizer;
097
098        // Do loop is a kluge to simulate goto
099        do
100        {
101            tokenizer = new StringTokenizer(reply);
102
103            if (tokenizer.countTokens() < 3)
104                break;
105
106            // Skip numeric response value
107            tokenizer.nextToken();
108            // Get article number
109            try
110            {
111                pointer.articleNumber = Integer.parseInt(tokenizer.nextToken());
112            }
113            catch (NumberFormatException e)
114            {
115                break;
116            }
117
118            // Get article id
119            pointer.articleId = tokenizer.nextToken();
120            return ;
121        }
122        while (false);
123
124        throw new MalformedServerReplyException(
125            "Could not parse article pointer.\nServer reply: " + reply);
126    }
127
128
129    private void __parseGroupReply(String reply, NewsgroupInfo info)
130    throws MalformedServerReplyException
131    {
132        String count, first, last;
133        StringTokenizer tokenizer;
134
135        // Do loop is a kluge to simulate goto
136        do
137        {
138            tokenizer = new StringTokenizer(reply);
139
140            if (tokenizer.countTokens() < 5)
141                break;
142
143            // Skip numeric response value
144            tokenizer.nextToken();
145            // Get estimated article count
146            count = tokenizer.nextToken();
147            // Get first article number
148            first = tokenizer.nextToken();
149            // Get last article number
150            last = tokenizer.nextToken();
151            // Get newsgroup name
152            info._setNewsgroup(tokenizer.nextToken());
153
154            try
155            {
156                info._setArticleCount(Integer.parseInt(count));
157                info._setFirstArticle(Integer.parseInt(first));
158                info._setLastArticle(Integer.parseInt(last));
159            }
160            catch (NumberFormatException e)
161            {
162                break;
163            }
164
165            info._setPostingPermission(NewsgroupInfo.UNKNOWN_POSTING_PERMISSION);
166            return ;
167        }
168        while (false);
169
170        throw new MalformedServerReplyException(
171            "Could not parse newsgroup info.\nServer reply: " + reply);
172    }
173
174
175    private NewsgroupInfo __parseNewsgroupListEntry(String entry)
176    {
177        NewsgroupInfo result;
178        StringTokenizer tokenizer;
179        int lastNum, firstNum;
180        String last, first, permission;
181
182        result = new NewsgroupInfo();
183        tokenizer = new StringTokenizer(entry);
184
185        if (tokenizer.countTokens() < 4)
186            return null;
187
188        result._setNewsgroup(tokenizer.nextToken());
189        last = tokenizer.nextToken();
190        first = tokenizer.nextToken();
191        permission = tokenizer.nextToken();
192
193        try
194        {
195            lastNum = Integer.parseInt(last);
196            firstNum = Integer.parseInt(first);
197            result._setFirstArticle(firstNum);
198            result._setLastArticle(lastNum);
199
200        if((firstNum == 0) && (lastNum == 0))
201            result._setArticleCount(0);
202        else
203            result._setArticleCount(lastNum - firstNum + 1);
204        }
205        catch (NumberFormatException e)
206        {
207            return null;
208        }
209
210        switch (permission.charAt(0))
211        {
212        case 'y':
213        case 'Y':
214            result._setPostingPermission(
215                NewsgroupInfo.PERMITTED_POSTING_PERMISSION);
216            break;
217        case 'n':
218        case 'N':
219            result._setPostingPermission(
220                NewsgroupInfo.PROHIBITED_POSTING_PERMISSION);
221            break;
222        case 'm':
223        case 'M':
224            result._setPostingPermission(
225                NewsgroupInfo.MODERATED_POSTING_PERMISSION);
226            break;
227        default:
228            result._setPostingPermission(
229                NewsgroupInfo.UNKNOWN_POSTING_PERMISSION);
230            break;
231        }
232
233        return result;
234    }
235
236    private NewsgroupInfo[] __readNewsgroupListing() throws IOException
237    {
238        int size;
239        String line;
240        Vector<NewsgroupInfo> list;
241        BufferedReader reader;
242        NewsgroupInfo tmp, info[];
243
244        reader = new BufferedReader(new DotTerminatedMessageReader(_reader_));
245        // Start of with a big vector because we may be reading a very large
246        // amount of groups.
247        list = new Vector<NewsgroupInfo>(2048);
248
249        while ((line = reader.readLine()) != null)
250        {
251            tmp = __parseNewsgroupListEntry(line);
252            if (tmp != null)
253                list.addElement(tmp);
254            else
255                throw new MalformedServerReplyException(line);
256        }
257
258        if ((size = list.size()) < 1)
259            return new NewsgroupInfo[0];
260
261        info = new NewsgroupInfo[size];
262        list.copyInto(info);
263
264        return info;
265    }
266
267
268    private Reader __retrieve(int command,
269                              String articleId, ArticlePointer pointer)
270    throws IOException
271    {
272        Reader reader;
273
274        if (articleId != null)
275        {
276            if (!NNTPReply.isPositiveCompletion(sendCommand(command, articleId)))
277                return null;
278        }
279        else
280        {
281            if (!NNTPReply.isPositiveCompletion(sendCommand(command)))
282                return null;
283        }
284
285
286        if (pointer != null)
287            __parseArticlePointer(getReplyString(), pointer);
288
289        reader = new DotTerminatedMessageReader(_reader_);
290        return reader;
291    }
292
293
294    private Reader __retrieve(int command,
295                              int articleNumber, ArticlePointer pointer)
296    throws IOException
297    {
298        Reader reader;
299
300        if (!NNTPReply.isPositiveCompletion(sendCommand(command,
301                                            Integer.toString(articleNumber))))
302            return null;
303
304        if (pointer != null)
305            __parseArticlePointer(getReplyString(), pointer);
306
307        reader = new DotTerminatedMessageReader(_reader_);
308        return reader;
309    }
310
311
312
313    /***
314     * Retrieves an article from the NNTP server.  The article is referenced
315     * by its unique article identifier (including the enclosing &lt and &gt).
316     * The article number and identifier contained in the server reply
317     * are returned through an ArticlePointer.  The <code> articleId </code>
318     * field of the ArticlePointer cannot always be trusted because some
319     * NNTP servers do not correctly follow the RFC 977 reply format.
320     * <p>
321     * A DotTerminatedMessageReader is returned from which the article can
322     * be read.  If the article does not exist, null is returned.
323     * <p>
324     * You must not issue any commands to the NNTP server (i.e., call any
325     * other methods) until you finish reading the message from the returned
326     * Reader instance.
327     * The NNTP protocol uses the same stream for issuing commands as it does
328     * for returning results.  Therefore the returned Reader actually reads
329     * directly from the NNTP connection.  After the end of message has been
330     * reached, new commands can be executed and their replies read.  If
331     * you do not follow these requirements, your program will not work
332     * properly.
333     * <p>
334     * @param articleId  The unique article identifier of the article to
335     *     retrieve.  If this parameter is null, the currently selected
336     *     article is retrieved.
337     * @param pointer    A parameter through which to return the article's
338     *   number and unique id.  The articleId field cannot always be trusted
339     *   because of server deviations from RFC 977 reply formats.  You may
340     *   set this parameter to null if you do not desire to retrieve the
341     *   returned article information.
342     * @return A DotTerminatedMessageReader instance from which the article
343     *         be read.  null if the article does not exist.
344     * @exception NNTPConnectionClosedException
345     *      If the NNTP server prematurely closes the connection as a result
346     *      of the client being idle or some other reason causing the server
347     *      to send NNTP reply code 400.  This exception may be caught either
348     *      as an IOException or independently as itself.
349     * @exception IOException  If an I/O error occurs while either sending a
350     *      command to the server or receiving a reply from the server.
351     ***/
352    public Reader retrieveArticle(String articleId, ArticlePointer pointer)
353    throws IOException
354    {
355        return __retrieve(NNTPCommand.ARTICLE, articleId, pointer);
356
357    }
358
359    /*** Same as <code> retrieveArticle(articleId, null) </code> ***/
360    public Reader retrieveArticle(String articleId) throws IOException
361    {
362        return retrieveArticle(articleId, null);
363    }
364
365    /*** Same as <code> retrieveArticle(null) </code> ***/
366    public Reader retrieveArticle() throws IOException
367    {
368        return retrieveArticle(null);
369    }
370
371
372    /***
373     * Retrieves an article from the currently selected newsgroup.  The
374     * article is referenced by its article number.
375     * The article number and identifier contained in the server reply
376     * are returned through an ArticlePointer.  The <code> articleId </code>
377     * field of the ArticlePointer cannot always be trusted because some
378     * NNTP servers do not correctly follow the RFC 977 reply format.
379     * <p>
380     * A DotTerminatedMessageReader is returned from which the article can
381     * be read.  If the article does not exist, null is returned.
382     * <p>
383     * You must not issue any commands to the NNTP server (i.e., call any
384     * other methods) until you finish reading the message from the returned
385     * Reader instance.
386     * The NNTP protocol uses the same stream for issuing commands as it does
387     * for returning results.  Therefore the returned Reader actually reads
388     * directly from the NNTP connection.  After the end of message has been
389     * reached, new commands can be executed and their replies read.  If
390     * you do not follow these requirements, your program will not work
391     * properly.
392     * <p>
393     * @param articleNumber  The number of the the article to
394     *     retrieve.
395     * @param pointer    A parameter through which to return the article's
396     *   number and unique id.  The articleId field cannot always be trusted
397     *   because of server deviations from RFC 977 reply formats.  You may
398     *   set this parameter to null if you do not desire to retrieve the
399     *   returned article information.
400     * @return A DotTerminatedMessageReader instance from which the article
401     *         be read.  null if the article does not exist.
402     * @exception NNTPConnectionClosedException
403     *      If the NNTP server prematurely closes the connection as a result
404     *      of the client being idle or some other reason causing the server
405     *      to send NNTP reply code 400.  This exception may be caught either
406     *      as an IOException or independently as itself.
407     * @exception IOException  If an I/O error occurs while either sending a
408     *      command to the server or receiving a reply from the server.
409     ***/
410    public Reader retrieveArticle(int articleNumber, ArticlePointer pointer)
411    throws IOException
412    {
413        return __retrieve(NNTPCommand.ARTICLE, articleNumber, pointer);
414    }
415
416    /*** Same as <code> retrieveArticle(articleNumber, null) </code> ***/
417    public Reader retrieveArticle(int articleNumber) throws IOException
418    {
419        return retrieveArticle(articleNumber, null);
420    }
421
422
423
424    /***
425     * Retrieves an article header from the NNTP server.  The article is
426     * referenced
427     * by its unique article identifier (including the enclosing &lt and &gt).
428     * The article number and identifier contained in the server reply
429     * are returned through an ArticlePointer.  The <code> articleId </code>
430     * field of the ArticlePointer cannot always be trusted because some
431     * NNTP servers do not correctly follow the RFC 977 reply format.
432     * <p>
433     * A DotTerminatedMessageReader is returned from which the article can
434     * be read.  If the article does not exist, null is returned.
435     * <p>
436     * You must not issue any commands to the NNTP server (i.e., call any
437     * other methods) until you finish reading the message from the returned
438     * Reader instance.
439     * The NNTP protocol uses the same stream for issuing commands as it does
440     * for returning results.  Therefore the returned Reader actually reads
441     * directly from the NNTP connection.  After the end of message has been
442     * reached, new commands can be executed and their replies read.  If
443     * you do not follow these requirements, your program will not work
444     * properly.
445     * <p>
446     * @param articleId  The unique article identifier of the article whose
447     *    header is being retrieved.  If this parameter is null, the
448     *    header of the currently selected article is retrieved.
449     * @param pointer    A parameter through which to return the article's
450     *   number and unique id.  The articleId field cannot always be trusted
451     *   because of server deviations from RFC 977 reply formats.  You may
452     *   set this parameter to null if you do not desire to retrieve the
453     *   returned article information.
454     * @return A DotTerminatedMessageReader instance from which the article
455     *         header can be read.  null if the article does not exist.
456     * @exception NNTPConnectionClosedException
457     *      If the NNTP server prematurely closes the connection as a result
458     *      of the client being idle or some other reason causing the server
459     *      to send NNTP reply code 400.  This exception may be caught either
460     *      as an IOException or independently as itself.
461     * @exception IOException  If an I/O error occurs while either sending a
462     *      command to the server or receiving a reply from the server.
463     ***/
464    public Reader retrieveArticleHeader(String articleId, ArticlePointer pointer)
465    throws IOException
466    {
467        return __retrieve(NNTPCommand.HEAD, articleId, pointer);
468
469    }
470
471    /*** Same as <code> retrieveArticleHeader(articleId, null) </code> ***/
472    public Reader retrieveArticleHeader(String articleId) throws IOException
473    {
474        return retrieveArticleHeader(articleId, null);
475    }
476
477    /*** Same as <code> retrieveArticleHeader(null) </code> ***/
478    public Reader retrieveArticleHeader() throws IOException
479    {
480        return retrieveArticleHeader(null);
481    }
482
483
484    /***
485     * Retrieves an article header from the currently selected newsgroup.  The
486     * article is referenced by its article number.
487     * The article number and identifier contained in the server reply
488     * are returned through an ArticlePointer.  The <code> articleId </code>
489     * field of the ArticlePointer cannot always be trusted because some
490     * NNTP servers do not correctly follow the RFC 977 reply format.
491     * <p>
492     * A DotTerminatedMessageReader is returned from which the article can
493     * be read.  If the article does not exist, null is returned.
494     * <p>
495     * You must not issue any commands to the NNTP server (i.e., call any
496     * other methods) until you finish reading the message from the returned
497     * Reader instance.
498     * The NNTP protocol uses the same stream for issuing commands as it does
499     * for returning results.  Therefore the returned Reader actually reads
500     * directly from the NNTP connection.  After the end of message has been
501     * reached, new commands can be executed and their replies read.  If
502     * you do not follow these requirements, your program will not work
503     * properly.
504     * <p>
505     * @param articleNumber  The number of the the article whose header is
506     *     being retrieved.
507     * @param pointer    A parameter through which to return the article's
508     *   number and unique id.  The articleId field cannot always be trusted
509     *   because of server deviations from RFC 977 reply formats.  You may
510     *   set this parameter to null if you do not desire to retrieve the
511     *   returned article information.
512     * @return A DotTerminatedMessageReader instance from which the article
513     *         header can be read.  null if the article does not exist.
514     * @exception NNTPConnectionClosedException
515     *      If the NNTP server prematurely closes the connection as a result
516     *      of the client being idle or some other reason causing the server
517     *      to send NNTP reply code 400.  This exception may be caught either
518     *      as an IOException or independently as itself.
519     * @exception IOException  If an I/O error occurs while either sending a
520     *      command to the server or receiving a reply from the server.
521     ***/
522    public Reader retrieveArticleHeader(int articleNumber,
523                                        ArticlePointer pointer)
524    throws IOException
525    {
526        return __retrieve(NNTPCommand.HEAD, articleNumber, pointer);
527    }
528
529
530    /*** Same as <code> retrieveArticleHeader(articleNumber, null) </code> ***/
531    public Reader retrieveArticleHeader(int articleNumber) throws IOException
532    {
533        return retrieveArticleHeader(articleNumber, null);
534    }
535
536
537
538    /***
539     * Retrieves an article body from the NNTP server.  The article is
540     * referenced
541     * by its unique article identifier (including the enclosing &lt and &gt).
542     * The article number and identifier contained in the server reply
543     * are returned through an ArticlePointer.  The <code> articleId </code>
544     * field of the ArticlePointer cannot always be trusted because some
545     * NNTP servers do not correctly follow the RFC 977 reply format.
546     * <p>
547     * A DotTerminatedMessageReader is returned from which the article can
548     * be read.  If the article does not exist, null is returned.
549     * <p>
550     * You must not issue any commands to the NNTP server (i.e., call any
551     * other methods) until you finish reading the message from the returned
552     * Reader instance.
553     * The NNTP protocol uses the same stream for issuing commands as it does
554     * for returning results.  Therefore the returned Reader actually reads
555     * directly from the NNTP connection.  After the end of message has been
556     * reached, new commands can be executed and their replies read.  If
557     * you do not follow these requirements, your program will not work
558     * properly.
559     * <p>
560     * @param articleId  The unique article identifier of the article whose
561     *    body is being retrieved.  If this parameter is null, the
562     *    body of the currently selected article is retrieved.
563     * @param pointer    A parameter through which to return the article's
564     *   number and unique id.  The articleId field cannot always be trusted
565     *   because of server deviations from RFC 977 reply formats.  You may
566     *   set this parameter to null if you do not desire to retrieve the
567     *   returned article information.
568     * @return A DotTerminatedMessageReader instance from which the article
569     *         body can be read.  null if the article does not exist.
570     * @exception NNTPConnectionClosedException
571     *      If the NNTP server prematurely closes the connection as a result
572     *      of the client being idle or some other reason causing the server
573     *      to send NNTP reply code 400.  This exception may be caught either
574     *      as an IOException or independently as itself.
575     * @exception IOException  If an I/O error occurs while either sending a
576     *      command to the server or receiving a reply from the server.
577     ***/
578    public Reader retrieveArticleBody(String articleId, ArticlePointer pointer)
579    throws IOException
580    {
581        return __retrieve(NNTPCommand.BODY, articleId, pointer);
582
583    }
584
585    /*** Same as <code> retrieveArticleBody(articleId, null) </code> ***/
586    public Reader retrieveArticleBody(String articleId) throws IOException
587    {
588        return retrieveArticleBody(articleId, null);
589    }
590
591    /*** Same as <code> retrieveArticleBody(null) </code> ***/
592    public Reader retrieveArticleBody() throws IOException
593    {
594        return retrieveArticleBody(null);
595    }
596
597
598    /***
599     * Retrieves an article body from the currently selected newsgroup.  The
600     * article is referenced by its article number.
601     * The article number and identifier contained in the server reply
602     * are returned through an ArticlePointer.  The <code> articleId </code>
603     * field of the ArticlePointer cannot always be trusted because some
604     * NNTP servers do not correctly follow the RFC 977 reply format.
605     * <p>
606     * A DotTerminatedMessageReader is returned from which the article can
607     * be read.  If the article does not exist, null is returned.
608     * <p>
609     * You must not issue any commands to the NNTP server (i.e., call any
610     * other methods) until you finish reading the message from the returned
611     * Reader instance.
612     * The NNTP protocol uses the same stream for issuing commands as it does
613     * for returning results.  Therefore the returned Reader actually reads
614     * directly from the NNTP connection.  After the end of message has been
615     * reached, new commands can be executed and their replies read.  If
616     * you do not follow these requirements, your program will not work
617     * properly.
618     * <p>
619     * @param articleNumber  The number of the the article whose body is
620     *     being retrieved.
621     * @param pointer    A parameter through which to return the article's
622     *   number and unique id.  The articleId field cannot always be trusted
623     *   because of server deviations from RFC 977 reply formats.  You may
624     *   set this parameter to null if you do not desire to retrieve the
625     *   returned article information.
626     * @return A DotTerminatedMessageReader instance from which the article
627     *         body can be read.  null if the article does not exist.
628     * @exception NNTPConnectionClosedException
629     *      If the NNTP server prematurely closes the connection as a result
630     *      of the client being idle or some other reason causing the server
631     *      to send NNTP reply code 400.  This exception may be caught either
632     *      as an IOException or independently as itself.
633     * @exception IOException  If an I/O error occurs while either sending a
634     *      command to the server or receiving a reply from the server.
635     ***/
636    public Reader retrieveArticleBody(int articleNumber,
637                                      ArticlePointer pointer)
638    throws IOException
639    {
640        return __retrieve(NNTPCommand.BODY, articleNumber, pointer);
641    }
642
643
644    /*** Same as <code> retrieveArticleBody(articleNumber, null) </code> ***/
645    public Reader retrieveArticleBody(int articleNumber) throws IOException
646    {
647        return retrieveArticleBody(articleNumber, null);
648    }
649
650
651    /***
652     * Select the specified newsgroup to be the target of for future article
653     * retrieval and posting operations.  Also return the newsgroup
654     * information contained in the server reply through the info parameter.
655     * <p>
656     * @param newsgroup  The newsgroup to select.
657     * @param info  A parameter through which the newsgroup information of
658     *      the selected newsgroup contained in the server reply is returned.
659     *      Set this to null if you do not desire this information.
660     * @return True if the newsgroup exists and was selected, false otherwise.
661     * @exception NNTPConnectionClosedException
662     *      If the NNTP server prematurely closes the connection as a result
663     *      of the client being idle or some other reason causing the server
664     *      to send NNTP reply code 400.  This exception may be caught either
665     *      as an IOException or independently as itself.
666     * @exception IOException  If an I/O error occurs while either sending a
667     *      command to the server or receiving a reply from the server.
668     ***/
669    public boolean selectNewsgroup(String newsgroup, NewsgroupInfo info)
670    throws IOException
671    {
672        if (!NNTPReply.isPositiveCompletion(group(newsgroup)))
673            return false;
674
675        if (info != null)
676            __parseGroupReply(getReplyString(), info);
677
678        return true;
679    }
680
681    /*** Same as <code> selectNewsgroup(newsgroup, null) </code> ***/
682    public boolean selectNewsgroup(String newsgroup) throws IOException
683    {
684        return selectNewsgroup(newsgroup, null);
685    }
686
687    /***
688     * List the command help from the server.
689     * <p>
690     * @return The sever help information.
691     * @exception NNTPConnectionClosedException
692     *      If the NNTP server prematurely closes the connection as a result
693     *      of the client being idle or some other reason causing the server
694     *      to send NNTP reply code 400.  This exception may be caught either
695     *      as an IOException or independently as itself.
696     * @exception IOException  If an I/O error occurs while either sending a
697     *      command to the server or receiving a reply from the server.
698     ***/
699    public String listHelp() throws IOException
700    {
701        StringWriter help;
702        Reader reader;
703
704        if (!NNTPReply.isInformational(help()))
705            return null;
706
707        help = new StringWriter();
708        reader = new DotTerminatedMessageReader(_reader_);
709        Util.copyReader(reader, help);
710        reader.close();
711        help.close();
712        return help.toString();
713    }
714
715
716    /***
717     * Select an article by its unique identifier (including enclosing
718     * &lt and &gt) and return its article number and id through the
719     * pointer parameter.  This is achieved through the STAT command.
720     * According to RFC 977, this will NOT set the current article pointer
721     * on the server.  To do that, you must reference the article by its
722     * number.
723     * <p>
724     * @param articleId  The unique article identifier of the article that
725     *    is being selectedd.  If this parameter is null, the
726     *    body of the current article is selected
727     * @param pointer    A parameter through which to return the article's
728     *   number and unique id.  The articleId field cannot always be trusted
729     *   because of server deviations from RFC 977 reply formats.  You may
730     *   set this parameter to null if you do not desire to retrieve the
731     *   returned article information.
732     * @return True if successful, false if not.
733     * @exception NNTPConnectionClosedException
734     *      If the NNTP server prematurely closes the connection as a result
735     *      of the client being idle or some other reason causing the server
736     *      to send NNTP reply code 400.  This exception may be caught either
737     *      as an IOException or independently as itself.
738     * @exception IOException  If an I/O error occurs while either sending a
739     *      command to the server or receiving a reply from the server.
740     ***/
741    public boolean selectArticle(String articleId, ArticlePointer pointer)
742    throws IOException
743    {
744        if (articleId != null)
745        {
746            if (!NNTPReply.isPositiveCompletion(stat(articleId)))
747                return false;
748        }
749        else
750        {
751            if (!NNTPReply.isPositiveCompletion(stat()))
752                return false;
753        }
754
755        if (pointer != null)
756            __parseArticlePointer(getReplyString(), pointer);
757
758        return true;
759    }
760
761    /**** Same as <code> selectArticle(articleId, null) </code> ***/
762    public boolean selectArticle(String articleId) throws IOException
763    {
764        return selectArticle(articleId, null);
765    }
766
767    /****
768     * Same as <code> selectArticle(null, articleId) </code>.  Useful
769     * for retrieving the current article number.
770     ***/
771    public boolean selectArticle(ArticlePointer pointer) throws IOException
772    {
773        return selectArticle(null, pointer);
774    }
775
776
777    /***
778     * Select an article in the currently selected newsgroup by its number.
779     * and return its article number and id through the
780     * pointer parameter.  This is achieved through the STAT command.
781     * According to RFC 977, this WILL set the current article pointer
782     * on the server.  Use this command to select an article before retrieving
783     * it, or to obtain an article's unique identifier given its number.
784     * <p>
785     * @param articleNumber The number of the article to select from the
786     *       currently selected newsgroup.
787     * @param pointer    A parameter through which to return the article's
788     *   number and unique id.  Although the articleId field cannot always
789     *   be trusted because of server deviations from RFC 977 reply formats,
790     *   we haven't found a server that misformats this information in response
791     *   to this particular command.  You may set this parameter to null if
792     *   you do not desire to retrieve the returned article information.
793     * @return True if successful, false if not.
794     * @exception NNTPConnectionClosedException
795     *      If the NNTP server prematurely closes the connection as a result
796     *      of the client being idle or some other reason causing the server
797     *      to send NNTP reply code 400.  This exception may be caught either
798     *      as an IOException or independently as itself.
799     * @exception IOException  If an I/O error occurs while either sending a
800     *      command to the server or receiving a reply from the server.
801     ***/
802    public boolean selectArticle(int articleNumber, ArticlePointer pointer)
803    throws IOException
804    {
805        if (!NNTPReply.isPositiveCompletion(stat(articleNumber)))
806            return false;
807
808        if (pointer != null)
809            __parseArticlePointer(getReplyString(), pointer);
810
811        return true;
812    }
813
814
815    /*** Same as <code> selectArticle(articleNumber, null) </code> ***/
816    public boolean selectArticle(int articleNumber) throws IOException
817    {
818        return selectArticle(articleNumber, null);
819    }
820
821
822    /***
823     * Select the article preceeding the currently selected article in the
824     * currently selected newsgroup and return its number and unique id
825     * through the pointer parameter.  Because of deviating server
826     * implementations, the articleId information cannot be trusted.  To
827     * obtain the article identifier, issue a
828     * <code> selectArticle(pointer.articleNumber, pointer) </code> immediately
829     * afterward.
830     * <p>
831     * @param pointer    A parameter through which to return the article's
832     *   number and unique id.  The articleId field cannot always be trusted
833     *   because of server deviations from RFC 977 reply formats.  You may
834     *   set this parameter to null if you do not desire to retrieve the
835     *   returned article information.
836     * @return True if successful, false if not (e.g., there is no previous
837     *     article).
838     * @exception NNTPConnectionClosedException
839     *      If the NNTP server prematurely closes the connection as a result
840     *      of the client being idle or some other reason causing the server
841     *      to send NNTP reply code 400.  This exception may be caught either
842     *      as an IOException or independently as itself.
843     * @exception IOException  If an I/O error occurs while either sending a
844     *      command to the server or receiving a reply from the server.
845     ***/
846    public boolean selectPreviousArticle(ArticlePointer pointer)
847    throws IOException
848    {
849        if (!NNTPReply.isPositiveCompletion(last()))
850            return false;
851
852        if (pointer != null)
853            __parseArticlePointer(getReplyString(), pointer);
854
855        return true;
856    }
857
858    /*** Same as <code> selectPreviousArticle(null) </code> ***/
859    public boolean selectPreviousArticle() throws IOException
860    {
861        return selectPreviousArticle(null);
862    }
863
864
865    /***
866     * Select the article following the currently selected article in the
867     * currently selected newsgroup and return its number and unique id
868     * through the pointer parameter.  Because of deviating server
869     * implementations, the articleId information cannot be trusted.  To
870     * obtain the article identifier, issue a
871     * <code> selectArticle(pointer.articleNumber, pointer) </code> immediately
872     * afterward.
873     * <p>
874     * @param pointer    A parameter through which to return the article's
875     *   number and unique id.  The articleId field cannot always be trusted
876     *   because of server deviations from RFC 977 reply formats.  You may
877     *   set this parameter to null if you do not desire to retrieve the
878     *   returned article information.
879     * @return True if successful, false if not (e.g., there is no following
880     *         article).
881     * @exception NNTPConnectionClosedException
882     *      If the NNTP server prematurely closes the connection as a result
883     *      of the client being idle or some other reason causing the server
884     *      to send NNTP reply code 400.  This exception may be caught either
885     *      as an IOException or independently as itself.
886     * @exception IOException  If an I/O error occurs while either sending a
887     *      command to the server or receiving a reply from the server.
888     ***/
889    public boolean selectNextArticle(ArticlePointer pointer) throws IOException
890    {
891        if (!NNTPReply.isPositiveCompletion(next()))
892            return false;
893
894        if (pointer != null)
895            __parseArticlePointer(getReplyString(), pointer);
896
897        return true;
898    }
899
900
901    /*** Same as <code> selectNextArticle(null) </code> ***/
902    public boolean selectNextArticle() throws IOException
903    {
904        return selectNextArticle(null);
905    }
906
907
908    /***
909     * List all newsgroups served by the NNTP server.  If no newsgroups
910     * are served, a zero length array will be returned.  If the command
911     * fails, null will be returned.
912     * <p>
913     * @return An array of NewsgroupInfo instances containing the information
914     *    for each newsgroup served by the NNTP server.   If no newsgroups
915     *    are served, a zero length array will be returned.  If the command
916     *    fails, null will be returned.
917     * @exception NNTPConnectionClosedException
918     *      If the NNTP server prematurely closes the connection as a result
919     *      of the client being idle or some other reason causing the server
920     *      to send NNTP reply code 400.  This exception may be caught either
921     *      as an IOException or independently as itself.
922     * @exception IOException  If an I/O error occurs while either sending a
923     *      command to the server or receiving a reply from the server.
924     ***/
925    public NewsgroupInfo[] listNewsgroups() throws IOException
926    {
927        if (!NNTPReply.isPositiveCompletion(list()))
928            return null;
929
930        return __readNewsgroupListing();
931    }
932
933    /**
934     * An overloaded listNewsgroups() command that allows us to
935     * specify with a pattern what groups we want to list. Wraps the
936     * LIST ACTIVE command.
937     * <p>
938     * @param wildmat a pseudo-regex pattern (cf. RFC 2980)
939     * @return An array of NewsgroupInfo instances containing the information
940     *    for each newsgroup served by the NNTP server corresponding to the
941     *    supplied pattern.   If no such newsgroups are served, a zero length
942     *    array will be returned.  If the command fails, null will be returned.
943     * @throws IOException
944     */
945    public NewsgroupInfo[] listNewsgroups(String wildmat) throws IOException
946    {
947        if(!NNTPReply.isPositiveCompletion(listActive(wildmat)))
948            return null;
949        return __readNewsgroupListing();
950    }
951
952
953    /***
954     * List all new newsgroups added to the NNTP server since a particular
955     * date subject to the conditions of the specified query.  If no new
956     * newsgroups were added, a zero length array will be returned.  If the
957     * command fails, null will be returned.
958     * <p>
959     * @param query  The query restricting how to search for new newsgroups.
960     * @return An array of NewsgroupInfo instances containing the information
961     *    for each new newsgroup added to the NNTP server.   If no newsgroups
962     *    were added, a zero length array will be returned.  If the command
963     *    fails, null will be returned.
964     * @exception NNTPConnectionClosedException
965     *      If the NNTP server prematurely closes the connection as a result
966     *      of the client being idle or some other reason causing the server
967     *      to send NNTP reply code 400.  This exception may be caught either
968     *      as an IOException or independently as itself.
969     * @exception IOException  If an I/O error occurs while either sending a
970     *      command to the server or receiving a reply from the server.
971     ***/
972    public NewsgroupInfo[] listNewNewsgroups(NewGroupsOrNewsQuery query)
973    throws IOException
974    {
975        if (!NNTPReply.isPositiveCompletion(newgroups(
976                                                query.getDate(), query.getTime(),
977                                                query.isGMT(), query.getDistributions())))
978            return null;
979
980        return __readNewsgroupListing();
981    }
982
983
984    /***
985     * List all new articles added to the NNTP server since a particular
986     * date subject to the conditions of the specified query.  If no new
987     * new news is found, a zero length array will be returned.  If the
988     * command fails, null will be returned.  You must add at least one
989     * newsgroup to the query, else the command will fail.  Each String
990     * in the returned array is a unique message identifier including the
991     * enclosing &lt and &gt.
992     * <p>
993     * @param query  The query restricting how to search for new news.  You
994     *    must add at least one newsgroup to the query.
995     * @return An array of String instances containing the unique message
996     *    identifiers for each new article added to the NNTP server.  If no
997     *    new news is found, a zero length array will be returned.  If the
998     *    command fails, null 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     ***/
1007    public String[] listNewNews(NewGroupsOrNewsQuery query)
1008    throws IOException
1009    {
1010        int size;
1011        String line;
1012        Vector<String> list;
1013        String[] result;
1014        BufferedReader reader;
1015
1016        if (!NNTPReply.isPositiveCompletion(newnews(
1017                                                query.getNewsgroups(), query.getDate(), query.getTime(),
1018                                                query.isGMT(), query.getDistributions())))
1019            return null;
1020
1021        list = new Vector<String>();
1022        reader = new BufferedReader(new DotTerminatedMessageReader(_reader_));
1023
1024        while ((line = reader.readLine()) != null)
1025            list.addElement(line);
1026
1027        size = list.size();
1028
1029        if (size < 1)
1030            return new String[0];
1031
1032        result = new String[size];
1033        list.copyInto(result);
1034
1035        return result;
1036    }
1037
1038    /***
1039     * There are a few NNTPClient methods that do not complete the
1040     * entire sequence of NNTP commands to complete a transaction.  These
1041     * commands require some action by the programmer after the reception
1042     * of a positive preliminary command.  After the programmer's code
1043     * completes its actions, it must call this method to receive
1044     * the completion reply from the server and verify the success of the
1045     * entire transaction.
1046     * <p>
1047     * For example
1048     * <pre>
1049     * writer = client.postArticle();
1050     * if(writer == null) // failure
1051     *   return false;
1052     * header = new SimpleNNTPHeader("foobar@foo.com", "Just testing");
1053     * header.addNewsgroup("alt.test");
1054     * writer.write(header.toString());
1055     * writer.write("This is just a test");
1056     * writer.close();
1057     * if(!client.completePendingCommand()) // failure
1058     *   return false;
1059     * </pre>
1060     * <p>
1061     * @return True if successfully completed, false if not.
1062     * @exception NNTPConnectionClosedException
1063     *      If the NNTP server prematurely closes the connection as a result
1064     *      of the client being idle or some other reason causing the server
1065     *      to send NNTP reply code 400.  This exception may be caught either
1066     *      as an IOException or independently as itself.
1067     * @exception IOException  If an I/O error occurs while either sending a
1068     *      command to the server or receiving a reply from the server.
1069     ***/
1070    public boolean completePendingCommand() throws IOException
1071    {
1072        return NNTPReply.isPositiveCompletion(getReply());
1073    }
1074
1075    /***
1076     * Post an article to the NNTP server.  This method returns a
1077     * DotTerminatedMessageWriter instance to which the article can be
1078     * written.  Null is returned if the posting attempt fails.  You
1079     * should check {@link NNTP#isAllowedToPost isAllowedToPost() }
1080     *  before trying to post.  However, a posting
1081     * attempt can fail due to malformed headers.
1082     * <p>
1083     * You must not issue any commands to the NNTP server (i.e., call any
1084     * (other methods) until you finish writing to the returned Writer
1085     * instance and close it.  The NNTP protocol uses the same stream for
1086     * issuing commands as it does for returning results.  Therefore the
1087     * returned Writer actually writes directly to the NNTP connection.
1088     * After you close the writer, you can execute new commands.  If you
1089     * do not follow these requirements your program will not work properly.
1090     * <p>
1091     * Different NNTP servers will require different header formats, but
1092     * you can use the provided
1093     * {@link org.apache.commons.net.nntp.SimpleNNTPHeader}
1094     * class to construct the bare minimum acceptable header for most
1095     * news readers.  To construct more complicated headers you should
1096     * refer to RFC 822.  When the Java Mail API is finalized, you will be
1097     * able to use it to compose fully compliant Internet text messages.
1098     * The DotTerminatedMessageWriter takes care of doubling line-leading
1099     * dots and ending the message with a single dot upon closing, so all
1100     * you have to worry about is writing the header and the message.
1101     * <p>
1102     * Upon closing the returned Writer, you need to call
1103     * {@link #completePendingCommand  completePendingCommand() }
1104     * to finalize the posting and verify its success or failure from
1105     * the server reply.
1106     * <p>
1107     * @return A DotTerminatedMessageWriter to which the article (including
1108     *      header) can be written.  Returns null if the command fails.
1109     * @exception IOException  If an I/O error occurs while either sending a
1110     *      command to the server or receiving a reply from the server.
1111     ***/
1112
1113    public Writer postArticle() throws IOException
1114    {
1115        if (!NNTPReply.isPositiveIntermediate(post()))
1116            return null;
1117
1118        return new DotTerminatedMessageWriter(_writer_);
1119    }
1120
1121
1122    public Writer forwardArticle(String articleId) throws IOException
1123    {
1124        if (!NNTPReply.isPositiveIntermediate(ihave(articleId)))
1125            return null;
1126
1127        return new DotTerminatedMessageWriter(_writer_);
1128    }
1129
1130
1131    /***
1132     * Logs out of the news server gracefully by sending the QUIT command.
1133     * However, you must still disconnect from the server before you can open
1134     * a new connection.
1135     * <p>
1136     * @return True if successfully completed, false if not.
1137     * @exception IOException  If an I/O error occurs while either sending a
1138     *      command to the server or receiving a reply from the server.
1139     ***/
1140    public boolean logout() throws IOException
1141    {
1142        return NNTPReply.isPositiveCompletion(quit());
1143    }
1144
1145
1146    /**
1147     * Log into a news server by sending the AUTHINFO USER/AUTHINFO
1148     * PASS command sequence. This is usually sent in response to a
1149     * 480 reply code from the NNTP server.
1150     * <p>
1151     * @param username a valid username
1152     * @param password the corresponding password
1153     * @return True for successful login, false for a failure
1154     * @throws IOException
1155     */
1156    public boolean authenticate(String username, String password)
1157        throws IOException
1158    {
1159        int replyCode = authinfoUser(username);
1160
1161        if (replyCode == NNTPReply.MORE_AUTH_INFO_REQUIRED)
1162            {
1163                replyCode = authinfoPass(password);
1164
1165                if (replyCode == NNTPReply.AUTHENTICATION_ACCEPTED)
1166                    {
1167                        _isAllowedToPost = true;
1168                        return true;
1169                    }
1170            }
1171        return false;
1172    }
1173
1174    /***
1175     * Private implementation of XOVER functionality.
1176     *
1177     * See {@link NNTP#xover}
1178     * for legal agument formats. Alternatively, read RFC 2980 :-)
1179     * <p>
1180     * @param articleRange
1181     * @return Returns a DotTerminatedMessageReader if successful, null
1182     *         otherwise
1183     * @exception IOException
1184     */
1185    private Reader __retrieveArticleInfo(String articleRange)
1186        throws IOException
1187    {
1188        if (!NNTPReply.isPositiveCompletion(xover(articleRange)))
1189            return null;
1190
1191        return new DotTerminatedMessageReader(_reader_);
1192    }
1193
1194    /**
1195     * Return article headers for a specified post.
1196     * <p>
1197     * @param articleNumber the article to retrieve headers for
1198     * @return a DotTerminatedReader if successful, null otherwise
1199     * @throws IOException
1200     */
1201    public Reader retrieveArticleInfo(int articleNumber) throws IOException
1202    {
1203        return __retrieveArticleInfo(Integer.toString(articleNumber));
1204    }
1205
1206    /**
1207     * Return article headers for all articles between lowArticleNumber
1208     * and highArticleNumber, inclusively.
1209     * <p>
1210     * @param lowArticleNumber
1211     * @param highArticleNumber
1212     * @return a DotTerminatedReader if successful, null otherwise
1213     * @throws IOException
1214     */
1215    public Reader retrieveArticleInfo(int lowArticleNumber,
1216                                      int highArticleNumber)
1217        throws IOException
1218    {
1219        return
1220            __retrieveArticleInfo(lowArticleNumber + "-" +
1221                                             highArticleNumber);
1222    }
1223
1224    /***
1225     * Private implementation of XHDR functionality.
1226     *
1227     * See {@link NNTP#xhdr}
1228     * for legal agument formats. Alternatively, read RFC 1036.
1229     * <p>
1230     * @param header
1231     * @param articleRange
1232     * @return Returns a DotTerminatedMessageReader if successful, null
1233     *         otherwise
1234     * @exception IOException
1235     */
1236    private Reader __retrieveHeader(String header, String articleRange)
1237        throws IOException
1238    {
1239        if (!NNTPReply.isPositiveCompletion(xhdr(header, articleRange)))
1240            return null;
1241
1242        return new DotTerminatedMessageReader(_reader_);
1243    }
1244
1245    /**
1246     * Return an article header for a specified post.
1247     * <p>
1248     * @param header the header to retrieve
1249     * @param articleNumber the article to retrieve the header for
1250     * @return a DotTerminatedReader if successful, null otherwise
1251     * @throws IOException
1252     */
1253    public Reader retrieveHeader(String header, int articleNumber)
1254        throws IOException
1255    {
1256        return __retrieveHeader(header, Integer.toString(articleNumber));
1257    }
1258
1259    /**
1260     * Return an article header for all articles between lowArticleNumber
1261     * and highArticleNumber, inclusively.
1262     * <p>
1263     * @param header
1264     * @param lowArticleNumber
1265     * @param highArticleNumber
1266     * @return a DotTerminatedReader if successful, null otherwise
1267     * @throws IOException
1268     */
1269    public Reader retrieveHeader(String header, int lowArticleNumber,
1270                                 int highArticleNumber)
1271        throws IOException
1272    {
1273        return
1274            __retrieveHeader(header,lowArticleNumber + "-" + highArticleNumber);
1275    }
1276}
1277
1278
1279/* Emacs configuration
1280 * Local variables:        **
1281 * mode:             java  **
1282 * c-basic-offset:   4     **
1283 * indent-tabs-mode: nil   **
1284 * End:                    **
1285 */