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