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