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