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