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