View Javadoc

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