001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.net.nntp; 019 020import java.io.BufferedReader; 021import java.io.IOException; 022import java.io.Reader; 023import java.io.StringWriter; 024import java.io.Writer; 025import java.util.ArrayList; 026import java.util.List; 027 028import org.apache.commons.io.IOUtils; 029import org.apache.commons.net.MalformedServerReplyException; 030import org.apache.commons.net.io.DotTerminatedMessageReader; 031import org.apache.commons.net.io.DotTerminatedMessageWriter; 032import org.apache.commons.net.io.Util; 033import org.apache.commons.net.util.NetConstants; 034 035/** 036 * NNTPClient encapsulates all the functionality necessary to post and retrieve articles from an NNTP server. As with all classes derived from 037 * {@link org.apache.commons.net.SocketClient}, you must first connect to the server with {@link org.apache.commons.net.SocketClient#connect connect} before 038 * doing anything, and finally {@link org.apache.commons.net.nntp.NNTP#disconnect disconnect()} after you're completely finished interacting with the server. 039 * Remember that the {@link org.apache.commons.net.nntp.NNTP#isAllowedToPost isAllowedToPost()} method is defined in {@link org.apache.commons.net.nntp.NNTP}. 040 * <p> 041 * 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 042 * 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 043 * a {@link org.apache.commons.net.nntp.NNTPReply#SERVICE_DISCONTINUED NNTPReply.SERVICE_DISCONTINUED} response to a command. When that occurs, the NNTP class 044 * method encountering that reply will throw an {@link org.apache.commons.net.nntp.NNTPConnectionClosedException} . {@code NNTPConectionClosedException} is 045 * 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 046 * appear before the more general {@code IOException} catch block. When you encounter an 047 * {@link org.apache.commons.net.nntp.NNTPConnectionClosedException} , you must disconnect the connection with 048 * {@link org.apache.commons.net.nntp.NNTP#disconnect disconnect()} to properly clean up the system resources used by NNTP. Before disconnecting, you may check 049 * the last reply code and text with {@link org.apache.commons.net.nntp.NNTP#getReplyCode getReplyCode} and 050 * {@link org.apache.commons.net.nntp.NNTP#getReplyString getReplyString}. 051 * </p> 052 * <p> 053 * 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 054 * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the 055 * 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 056 * lenient as possible. 057 * </p> 058 * 059 * @see NNTP 060 * @see NNTPConnectionClosedException 061 * @see org.apache.commons.net.MalformedServerReplyException 062 */ 063 064public class NNTPClient extends NNTP { 065 066 /** 067 * Parse a response line from {@link #retrieveArticleInfo(long, long)}. 068 * 069 * @param line a response line 070 * @return the parsed {@link Article}, if unparseable then isDummy() will be true, and the subject will contain the raw info. 071 * @since 3.0 072 */ 073 static Article parseArticleEntry(final String line) { 074 // Extract the article information 075 // Mandatory format (from NNTP RFC 2980) is : 076 // articleNumber\tSubject\tAuthor\tDate\tID\tReference(s)\tByte Count\tLine Count 077 078 final Article article = new Article(); 079 article.setSubject(line); // in case parsing fails 080 final String[] parts = line.split("\t"); 081 if (parts.length > 6) { 082 int i = 0; 083 try { 084 article.setArticleNumber(Long.parseLong(parts[i++])); 085 article.setSubject(parts[i++]); 086 article.setFrom(parts[i++]); 087 article.setDate(parts[i++]); 088 article.setArticleId(parts[i++]); 089 article.addReference(parts[i++]); 090 } catch (final NumberFormatException e) { 091 // ignored, already handled 092 } 093 } 094 return article; 095 } 096 097 /* 098 * 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 = 099 * 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}