NNTPStore.java
上传用户:huihesys
上传日期:2007-01-04
资源大小:3877k
文件大小:34k
源码类别:

WEB邮件程序

开发平台:

C/C++

  1. /*
  2.  * NNTPStore.java
  3.  * Copyright (C) 1999 dog <dog@dog.net.uk>
  4.  * 
  5.  * This library is free software; you can redistribute it and/or
  6.  * modify it under the terms of the GNU Lesser General Public
  7.  * License as published by the Free Software Foundation; either
  8.  * version 2 of the License, or (at your option) any later version.
  9.  * 
  10.  * This library is distributed in the hope that it will be useful,
  11.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13.  * Lesser General Public License for more details.
  14.  * 
  15.  * You should have received a copy of the GNU Lesser General Public
  16.  * License along with this library; if not, write to the Free Software
  17.  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  18.  * 
  19.  * You may retrieve the latest version of this library from
  20.  * http://www.dog.net.uk/knife/
  21.  */
  22. package dog.mail.nntp;
  23. import java.io.*;
  24. import java.net.*;
  25. import java.util.*;
  26. import javax.mail.*;
  27. import javax.mail.event.*;
  28. import javax.mail.internet.*;
  29. import dog.mail.util.*;
  30. import dog.util.*;
  31. /**
  32.  * The storage class implementing the NNTP Usenet news protocol.
  33.  *
  34.  * @author dog@dog.net.uk
  35.  * @version 1.2
  36.  */
  37. public class NNTPStore extends Store implements StatusSource {
  38. /**
  39.  * The default NNTP port.
  40.  */
  41. public static final int DEFAULT_PORT = 119;
  42. static int fetchsize = 1024;
  43. Socket socket;
  44. CRLFInputStream in;
  45. CRLFOutputStream out;
  46. String hostname;
  47. String response;
  48.     static final int HELP = 100;
  49.     static final int READY = 200;
  50.     static final int READ_ONLY = 201;
  51.     static final int STREAMING_OK = 203;
  52.     static final int CLOSING = 205;
  53. static final int GROUP_SELECTED = 211; // or list of article numbers follows
  54. static final int LISTING = 215; // list of newsgroups follows
  55. static final int ARTICLE_RETRIEVED_BOTH = 220;
  56. static final int ARTICLE_RETRIEVED_HEAD = 221; // or header follows
  57. static final int ARTICLE_RETRIEVED_BODY = 222;
  58. static final int ARTICLE_RETRIEVED = 223;
  59. static final int LISTING_OVERVIEW = 224;
  60. static final int LISTING_ARTICLES = 230; // list of new articles by message-id follows
  61. static final int LISTING_NEW = 231; // list of new newsgroups follows
  62. static final int ARTICLE_POSTED = 240; // article posted ok
  63. static final int AUTHINFO_OK = 281;
  64. static final int SEND_ARTICLE = 340; // send article to be posted. End with <CR-LF>.<CR-LF>
  65. static final int SEND_AUTHINFOPASS = 381; // send password (response to user name)
  66.     static final int SERVICE_DISCONTINUED = 400;
  67. static final int NO_SUCH_GROUP = 411;
  68. static final int NO_GROUP_SELECTED = 412; // no newsgroup has been selected
  69. static final int NO_ARTICLE_SELECTED = 420; // no current article has been selected
  70. static final int NO_SUCH_ARTICLE_IN_GROUP = 423; // no such article number in this group
  71. static final int NO_SUCH_ARTICLE = 430; // no such article found
  72. static final int POSTING_NOT_ALLOWED = 440; // posting not allowed
  73. static final int POSTING_FAILED = 441; // posting failed
  74.     static final int COMMAND_NOT_RECOGNIZED = 500;
  75.     static final int COMMAND_SYNTAX_ERROR = 501;
  76.     static final int PERMISSION_DENIED = 502;
  77.     static final int SERVER_ERROR = 503;
  78. boolean postingAllowed = false;
  79.     Root root;
  80. Newsgroup current;
  81.     Date lastNewGroup;
  82. Hashtable newsgroups = new Hashtable(); // hashtable of newsgroups by name
  83. Hashtable articles = new Hashtable(); // hashtable of articles by message-id
  84. Vector statusListeners = new Vector();
  85. /**
  86.  * Constructor.
  87.  */
  88. public NNTPStore(Session session, URLName urlname) {
  89. super(session, urlname);
  90. String ccs = session.getProperty("mail.nntp.fetchsize");
  91. if (ccs!=null) try { fetchsize = Math.max(Integer.parseInt(ccs), 1024); } catch (NumberFormatException e) {}
  92. }
  93. /**
  94.  * Connects to the NNTP server and authenticates with the specified parameters.
  95.  */
  96. protected boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
  97. if (port<0) port = DEFAULT_PORT;
  98. if (host==null)
  99. return false;
  100. try {
  101.             boolean debug = session.getDebug();
  102. hostname = host;
  103. if (debug)
  104. System.err.println("DEBUG: nntp: opening connection to "+hostname);
  105. socket = new Socket(host, port);
  106. in = new CRLFInputStream(new BufferedInputStream(socket.getInputStream()), fetchsize);
  107. out = new CRLFOutputStream(new BufferedOutputStream(socket.getOutputStream()));
  108. switch (getResponse()) {
  109. case READY:
  110. postingAllowed = true;
  111. case READ_ONLY:
  112. //StringTokenizer st = new StringTokenizer(response);
  113. //if (st.hasMoreTokens()) hostname = st.nextToken();
  114. break;
  115. default:
  116. throw new MessagingException("unexpected server response: "+response);
  117. }
  118. send("MODE READER"); // newsreader extension
  119. switch (getResponse()) {
  120. case READY:
  121. postingAllowed = true;
  122. case READ_ONLY:
  123. break;
  124. }
  125. // NNTP basic authentication
  126. // introduced by Volker Schmidt <vs75@gmx.de>
  127. // NOTE: need to handle multiple varities of authentication (kerberos, etc)
  128. if (username!=null && password!=null) {
  129. send("AUTHINFO USER "+username);
  130. if (getResponse()!=SEND_AUTHINFOPASS)
  131. throw new AuthenticationFailedException(response);
  132. send("AUTHINFO PASS "+password);
  133. if (getResponse()!=AUTHINFO_OK)
  134. throw new AuthenticationFailedException(response);
  135. }
  136. // end authentication
  137. if (debug)
  138. System.err.println("DEBUG: nntp: connected to "+hostname+", posting is "+(postingAllowed ? "" : "not ")+"allowed");
  139. addStore(this);
  140. readNewsrc();
  141. return true;
  142. } catch(UnknownHostException e) {
  143. throw new MessagingException("unknown host", e);
  144. } catch(IOException e) {
  145. throw new MessagingException("I/O error", e);
  146. }
  147. }
  148. /**
  149.  * Closes the connection.
  150.  */
  151. public synchronized void close() throws MessagingException {
  152. if (socket!=null)
  153. try {
  154. boolean debug = session.getDebug();
  155. if (debug)
  156. System.err.println("DEBUG: nntp: closing connection to "+hostname);
  157. send("QUIT");
  158. switch (getResponse()) {
  159. case CLOSING:
  160. break;
  161. case SERVER_ERROR:
  162. if (response.toLowerCase().indexOf("timeout")>-1)
  163. break;
  164. default:
  165. throw new MessagingException("unexpected server response: "+response);
  166. }
  167. removeStore(this);
  168. socket.close();
  169. socket = null;
  170. if (debug)
  171. System.err.println("DEBUG: nntp: closed connection to "+hostname);
  172. } catch (IOException e) {
  173. // socket.close() seems to throw an exception!
  174. //throw new MessagingException("Close failed", e);
  175. }
  176. super.close();
  177. }
  178. /**
  179.  * Returns the hostname of the server.
  180.  */
  181. public String getHostName() { return hostname; }
  182. int getResponse() throws IOException {
  183. response = in.readLine();
  184. boolean debug = session.getDebug();
  185. if (debug)
  186. System.err.println("DEBUG: nntp: <"+response);
  187. if (response==null)
  188. response = SERVER_ERROR+" timeout";
  189. try {
  190. int index = response.indexOf(' ');
  191. if (index>-1) {
  192. int code = Integer.parseInt(response.substring(0, index));
  193. response = response.substring(index+1);
  194. return code;
  195. } else {
  196. int code = Integer.parseInt(response);
  197. return code;
  198. }
  199. } catch (StringIndexOutOfBoundsException e) {
  200. throw new ProtocolException("NNTP protocol exception: "+response);
  201. } catch (NumberFormatException e) {
  202. throw new ProtocolException("NNTP protocol exception: "+response);
  203. }
  204. }
  205. void send(String command) throws IOException {
  206. boolean debug = session.getDebug();
  207. if (debug)
  208. System.err.println("DEBUG: nntp: >"+command);
  209. out.write(command.getBytes());
  210. out.writeln();
  211. out.flush();
  212. }
  213.     // Opens a newsgroup.
  214. synchronized void open(Newsgroup group) throws MessagingException {
  215. if (current!=null) {
  216. if (current.equals(group))
  217. return;
  218. else
  219. close(current);
  220. }
  221. String name = group.getName();
  222. try {
  223. send("GROUP "+name);
  224.             int r = getResponse();
  225. switch (r) {
  226.   case GROUP_SELECTED:
  227. try {
  228. updateGroup(group, response);
  229. group.open = true;
  230. current = group;
  231. } catch (NumberFormatException e) {
  232. throw new MessagingException("NNTP protocol exception: "+response, e);
  233. }
  234. break;
  235.   case NO_SUCH_GROUP:
  236. throw new MessagingException(response);
  237.   case SERVER_ERROR:
  238. if (response.toLowerCase().indexOf("timeout")>-1) {
  239. close();
  240. connect();
  241. open(group);
  242. break;
  243. }
  244.   default:
  245. throw new MessagingException("unexpected server response ("+r+"): "+response);
  246. }
  247. } catch (IOException e) {
  248. throw new MessagingException("I/O error", e);
  249. }
  250. }
  251.     // Updates a newsgroup with the most recent article counts.
  252. void updateGroup(Newsgroup newsgroup, String response) throws IOException {
  253. try {
  254. StringTokenizer st = new StringTokenizer(response, " ");
  255.             newsgroup.count = Integer.parseInt(st.nextToken());
  256.             newsgroup.first = Integer.parseInt(st.nextToken());
  257. newsgroup.last = Integer.parseInt(st.nextToken());
  258. boolean debug = session.getDebug();
  259. if (debug)
  260. System.err.println("DEBUG: nntp: "+newsgroup.name+": "+newsgroup.count+" articles");
  261. } catch (NumberFormatException e) {
  262. throw new ProtocolException("NNTP protocol exception");
  263. } catch (NoSuchElementException e) {
  264. throw new ProtocolException("NNTP protocol exception");
  265. }
  266. }
  267.     // Closes a newsgroup.
  268. synchronized void close(Newsgroup group) throws MessagingException {
  269. if (current!=null && !current.equals(group)) close(current);
  270. group.open = false;
  271. }
  272.     // Returns the (approximate) number of articles in a newsgroup.
  273. synchronized int getMessageCount(Newsgroup group) throws MessagingException {
  274.         String name = group.getName();
  275. try {
  276. send("GROUP "+name);
  277. switch (getResponse()) {
  278.   case GROUP_SELECTED:
  279. try {
  280. updateGroup(group, response);
  281. current = group;
  282. return group.count;
  283. } catch(NumberFormatException e) {
  284. throw new MessagingException("NNTP protocol exception: "+response, e);
  285. }
  286.   case NO_SUCH_GROUP:
  287. throw new MessagingException("No such group");
  288.   case SERVER_ERROR:
  289. if (response.toLowerCase().indexOf("timeout")>-1) {
  290. close();
  291. connect();
  292. return getMessageCount(group);
  293. }
  294.   default:
  295. throw new MessagingException("unexpected server response: "+response);
  296. }
  297. } catch (IOException e) {
  298. throw new MessagingException("I/O error", e);
  299. }
  300. }
  301.     // Returns the headers for an article.
  302. synchronized InternetHeaders getHeaders(Article article) throws MessagingException {
  303. new Throwable().printStackTrace();
  304. String mid = article.messageId;
  305. if (mid==null) {
  306. Newsgroup group = (Newsgroup)article.getFolder();
  307. if (!current.equals(group)) open(group);
  308. mid = Integer.toString(article.getMessageNumber());
  309. }
  310. try {
  311. send("HEAD "+mid);
  312. switch (getResponse()) {
  313.   case ARTICLE_RETRIEVED_HEAD:
  314. return new InternetHeaders(new MessageInputStream(in));
  315.   case NO_GROUP_SELECTED:
  316. throw new MessagingException("No group selected");
  317.   case NO_ARTICLE_SELECTED:
  318. throw new MessagingException("No article selected");
  319.   case NO_SUCH_ARTICLE_IN_GROUP:
  320. //throw new MessagingException("No such article in group");
  321. return null;
  322.   case NO_SUCH_ARTICLE:
  323. throw new MessagingException("No such article");
  324.   case SERVER_ERROR:
  325. if (response.toLowerCase().indexOf("timeout")>-1) {
  326. close();
  327. connect();
  328. return getHeaders(article);
  329. }
  330.   default:
  331. throw new MessagingException("unexpected server response: "+response);
  332. }
  333. } catch (IOException e) {
  334. throw new MessagingException("I/O error", e);
  335. }
  336. }
  337.     // Returns the content for an article.
  338. synchronized byte[] getContent(Article article) throws MessagingException {
  339. String mid = article.messageId;
  340. if (mid==null) {
  341. Newsgroup group = (Newsgroup)article.getFolder();
  342. if (!current.equals(group)) open(group);
  343. mid = Integer.toString(article.getMessageNumber());
  344. }
  345. try {
  346. send("BODY "+mid);
  347. switch (getResponse()) {
  348.   case ARTICLE_RETRIEVED_BODY:
  349. boolean debug = session.getDebug();
  350. if (debug)
  351. System.out.println("DEBUG: nntp: retrieving body");
  352. int max = fetchsize, len;
  353. byte b[] = new byte[max];
  354. MessageInputStream min = new MessageInputStream(in, debug);
  355. ByteArrayOutputStream bout = new ByteArrayOutputStream();
  356. while ((len = min.read(b, 0, max))!=-1)
  357. bout.write(b, 0, len);
  358. if (debug)
  359. System.out.println("DEBUG: nntp: done retrieving body");
  360. return bout.toByteArray();
  361.   case NO_GROUP_SELECTED:
  362. throw new MessagingException("No group selected");
  363.   case NO_ARTICLE_SELECTED:
  364. throw new MessagingException("No article selected");
  365.   case NO_SUCH_ARTICLE_IN_GROUP:
  366. throw new MessagingException("No such article in group");
  367.   case NO_SUCH_ARTICLE:
  368. throw new MessagingException("No such article");
  369.   case SERVER_ERROR:
  370. if (response.toLowerCase().indexOf("timeout")>-1) {
  371. close();
  372. connect();
  373. return getContent(article);
  374. }
  375.   default:
  376. throw new MessagingException("unexpected server response: "+response);
  377. }
  378. } catch (IOException e) {
  379. throw new MessagingException("I/O error", e);
  380. }
  381. }
  382. /**
  383.  * Post an article.
  384.  * @param article the article
  385.  * @param addresses the addresses to post to.
  386.  * @exception MessagingException if a messaging exception occurred or there were no newsgroup recipients
  387.  */
  388. public void postArticle(Message article, Address[] addresses) throws MessagingException {
  389. Vector v = new Vector();
  390. for (int i=0; i<addresses.length; i++) { // get unique newsgroup addresses
  391. if (addresses[i] instanceof NewsAddress && !v.contains(addresses[i]))
  392. v.addElement(addresses[i]);
  393. }
  394. NewsAddress[] a = new NewsAddress[v.size()]; v.copyInto(a);
  395. if (a.length==0) throw new MessagingException("No newsgroups specified as recipients");
  396. for (int i=0; i<a.length; i++)
  397. post(article, a[i]);
  398. }
  399.     // Posts an article to the specified newsgroup.
  400. synchronized void post(Message article, NewsAddress address) throws MessagingException {
  401. String group = address.getNewsgroup();
  402. try {
  403. send("POST");
  404. switch (getResponse()) {
  405.   case SEND_ARTICLE:
  406. if (session.getDebug())
  407. article.writeTo(System.out);
  408. MessageOutputStream mout = new MessageOutputStream(out);
  409. article.writeTo(mout);
  410. out.write("n.n".getBytes());
  411. out.flush();
  412. switch (getResponse()) {
  413.  case ARTICLE_POSTED:
  414. break;
  415.  case POSTING_FAILED:
  416. throw new MessagingException("Posting failed: "+response);
  417.  default:
  418. throw new MessagingException(response);
  419. }
  420. break;
  421.   case POSTING_NOT_ALLOWED:
  422. throw new MessagingException("Posting not allowed");
  423.   case POSTING_FAILED:
  424. throw new MessagingException("Posting failed");
  425.   case SERVER_ERROR:
  426. if (response.toLowerCase().indexOf("timeout")>-1) {
  427. connect();
  428. post(article, address);
  429. break;
  430. }
  431.   default:
  432. throw new MessagingException("unexpected server response: "+response);
  433. }
  434. } catch (IOException e) {
  435. throw new MessagingException("I/O error", e);
  436. }
  437. }
  438. // Attempts to discover which newsgroups exist and which articles have been read.
  439. void readNewsrc() {
  440. try {
  441. File newsrc = new File(System.getProperty("user.home")+File.separator+".newsrc-"+getHostName());
  442. if (!newsrc.exists())
  443. newsrc = new File(System.getProperty("user.home")+File.separator+".newsrc");
  444. BufferedReader reader = new BufferedReader(new FileReader(newsrc));
  445. String line;
  446. while ((line = reader.readLine())!=null) {
  447. StringTokenizer st = new StringTokenizer(line, ":!", true);
  448. try {
  449. String name = st.nextToken();
  450. Newsgroup group = new Newsgroup(this, name);
  451. group.subscribed = ":".equals(st.nextToken());
  452. if (st.hasMoreTokens())
  453. group.newsrcline = st.nextToken().trim();
  454. newsgroups.put(name, group);
  455. } catch (NoSuchElementException e) {}
  456. }
  457. } catch (FileNotFoundException e) {
  458. } catch (IOException e) {
  459. } catch (SecurityException e) { // not allowed to read file
  460. }
  461. }
  462. // Returns the newsgroups available in this store via the specified listing command.
  463. Newsgroup[] getNewsgroups(String command, boolean retry) throws MessagingException {
  464.         Vector vector = new Vector();
  465. try {
  466. send(command);
  467. switch (getResponse()) {
  468.   case LISTING:
  469. String line;
  470. for (line=in.readLine(); line!=null && !".".equals(line); line = in.readLine()) {
  471. StringTokenizer st = new StringTokenizer(line, " ");
  472. String name = st.nextToken();
  473. int last = Integer.parseInt(st.nextToken());
  474. int first = Integer.parseInt(st.nextToken());
  475. boolean posting = ("y".equals(st.nextToken().toLowerCase()));
  476. Newsgroup group = (Newsgroup)getFolder(name);
  477. group.first = first;
  478. group.last = last;
  479. group.postingAllowed = posting;
  480. vector.addElement(group);
  481. }
  482. break;
  483.   case SERVER_ERROR:
  484. if (!retry) {
  485. if (response.toLowerCase().indexOf("timeout")>-1) {
  486. close();
  487. connect();
  488. return getNewsgroups(command, true);
  489. } else
  490. return getNewsgroups("LIST", false); // backward compatibility with rfc977
  491. }
  492.   default:
  493. throw new MessagingException(command+" failed: "+response);
  494. }
  495. } catch (IOException e) {
  496. throw new MessagingException(command+" failed", e);
  497. } catch (NumberFormatException e) {
  498. throw new MessagingException(command+" failed", e);
  499. }
  500. Newsgroup[] groups = new Newsgroup[vector.size()]; vector.copyInto(groups);
  501. return groups;
  502. }
  503. // Returns the articles for a newsgroup.
  504. Article[] getArticles(Newsgroup newsgroup) throws MessagingException {
  505. try {
  506. return getOverview(newsgroup);
  507. } catch (MessagingException e) { // try rfc977
  508. return getNewArticles(newsgroup, new Date(0L));
  509. }
  510. }
  511. /**
  512.  * Returns the newsgroups added to this store since the specified date.
  513.      * @exception MessagingException if a messaging error occurred
  514.  */
  515. Newsgroup[] getNewFolders(Date date) throws MessagingException {
  516.         String datetime = getDateTimeString(date);
  517. Vector vector = new Vector();
  518. try {
  519. send("NEWGROUPS "+datetime);
  520. switch (getResponse()) {
  521.   case LISTING_NEW:
  522. String line;
  523. for (line=in.readLine(); line!=null && !".".equals(line); line = in.readLine()) {
  524. StringTokenizer st = new StringTokenizer(line, " ");
  525. String name = st.nextToken();
  526. int last = Integer.parseInt(st.nextToken());
  527. int first = Integer.parseInt(st.nextToken());
  528. boolean posting = ("y".equals(st.nextToken().toLowerCase()));
  529. Newsgroup group = (Newsgroup)getFolder(name);
  530. group.first = first;
  531. group.last = last;
  532. group.postingAllowed = posting;
  533. vector.addElement(group);
  534. }
  535. break;
  536.   default:
  537. throw new MessagingException("Listing failed: "+response);
  538. }
  539. } catch (IOException e) {
  540. throw new MessagingException("Listing failed", e);
  541. } catch (NumberFormatException e) {
  542. throw new MessagingException("Listing failed", e);
  543. }
  544. Newsgroup[] groups = new Newsgroup[vector.size()]; vector.copyInto(groups);
  545. return groups;
  546. }
  547.     // Returns the articles added to the specified newsgroup since the specified date.
  548. Article[] getNewArticles(Newsgroup newsgroup, Date date) throws MessagingException {
  549.         String command = "NEWNEWS "+newsgroup.getName()+" "+getDateTimeString(date);
  550. Vector vector = new Vector();
  551. try {
  552. send(command);
  553. switch (getResponse()) {
  554.   case LISTING_ARTICLES:
  555. String line;
  556. for (line=in.readLine(); line!=null && !".".equals(line); line = in.readLine()) {
  557. Message article = getArticle(newsgroup, line);
  558. vector.addElement(article);
  559. }
  560. break;
  561.   default:
  562. throw new MessagingException(command+" failed: "+response);
  563. }
  564. } catch (IOException e) {
  565. throw new MessagingException(command+" failed", e);
  566. } catch (NumberFormatException e) {
  567. throw new MessagingException(command+" failed", e);
  568. }
  569. Article[] articles = new Article[vector.size()]; vector.copyInto(articles);
  570. return articles;
  571. }
  572. // Returns a GMT date-time formatted string for the specified date,
  573. // suitable as an argument to NEWGROUPS and NEWNEWS commands.
  574. String getDateTimeString(Date date) {
  575. Calendar calendar = Calendar.getInstance();
  576. calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
  577. calendar.setTime(date);
  578. StringBuffer buffer = new StringBuffer();
  579. int field;
  580. String ZERO = "0"; // leading zero
  581. field = calendar.get(Calendar.YEAR)%100; buffer.append((field<10) ? ZERO+field : Integer.toString(field));
  582. field = calendar.get(Calendar.MONTH)+1; buffer.append((field<10) ? ZERO+field : Integer.toString(field));
  583. field = calendar.get(Calendar.DAY_OF_MONTH); buffer.append((field<10) ? ZERO+field : Integer.toString(field));
  584.         buffer.append(" ");
  585. field = calendar.get(Calendar.HOUR_OF_DAY); buffer.append((field<10) ? ZERO+field : Integer.toString(field));
  586. field = calendar.get(Calendar.MINUTE); buffer.append((field<10) ? ZERO+field : Integer.toString(field));
  587. field = calendar.get(Calendar.SECOND); buffer.append((field<10) ? ZERO+field : Integer.toString(field));
  588. buffer.append(" GMT");
  589. return buffer.toString();
  590. }
  591. /**
  592.  * Returns the root folder.
  593.  */
  594. public Folder getDefaultFolder() throws MessagingException {
  595. synchronized (this) {
  596. if (root==null) root = new Root(this);
  597. }
  598. return root;
  599. }
  600. /**
  601.  * Returns the newsgroup with the specified name.
  602.  */
  603. public Folder getFolder(String name) throws MessagingException {
  604. return getNewsgroup(name);
  605. }
  606. /**
  607.  * Returns the newsgroup specified as part of a URLName.
  608.  */
  609. public Folder getFolder(URLName urlname) throws MessagingException {
  610. String group = urlname.getFile();
  611. int hashIndex = group.indexOf('#');
  612.         if (hashIndex>-1) group = group.substring(0, hashIndex);
  613. return getNewsgroup(group);
  614. }
  615. Newsgroup getNewsgroup(String name) {
  616. Newsgroup newsgroup = (Newsgroup)newsgroups.get(name);
  617. if (newsgroup==null) {
  618. newsgroup = new Newsgroup(this, name);
  619. newsgroups.put(name, newsgroup);
  620. }
  621. return newsgroup;
  622. }
  623. // Returns the article with the specified message-id for the newsgroup.
  624. Article getArticle(Newsgroup newsgroup, String mid) throws MessagingException {
  625. Article article = (Article)articles.get(mid);
  626. if (article==null) {
  627. article = new Article(newsgroup, mid);
  628. articles.put(mid, article);
  629. }
  630. return article;
  631. }
  632. // -- NNTP extensions --
  633. int[] getArticleNumbers(Newsgroup newsgroup) throws MessagingException {
  634. String command = "LISTGROUP "+newsgroup.getName();
  635. Vector vector = new Vector();
  636. synchronized (this) {
  637. try {
  638. send(command);
  639. switch (getResponse()) {
  640.   case GROUP_SELECTED:
  641. String line;
  642. for (line=in.readLine(); line!=null && !".".equals(line); line = in.readLine()) {
  643. vector.addElement(line);
  644. }
  645. break;
  646.   case NO_GROUP_SELECTED:
  647.   case PERMISSION_DENIED:
  648.   default:
  649. throw new MessagingException(command+" failed: "+response);
  650. }
  651. } catch (IOException e) {
  652. throw new MessagingException(command+" failed", e);
  653. } catch (NumberFormatException e) {
  654. throw new MessagingException(command+" failed", e);
  655. }
  656. }
  657. int[] numbers = new int[vector.size()];
  658. for (int i=0; i<numbers.length; i++)
  659. numbers[i] = Integer.parseInt((String)vector.elementAt(i));
  660. return numbers;
  661. }
  662. private String[] overviewFormat, strippedOverviewFormat;
  663. String[] getOverviewFormat(boolean strip) throws MessagingException {
  664.         if (overviewFormat==null) {
  665. String command = "LIST OVERVIEW.FMT";
  666. synchronized (this) {
  667. Vector vector = new Vector(), vector2 = new Vector();
  668. try {
  669. send(command);
  670. switch (getResponse()) {
  671.  case LISTING:
  672. String line;
  673. for (line=in.readLine(); line!=null && !".".equals(line); line = in.readLine()) {
  674. vector.addElement(line);
  675. vector2.addElement(line.substring(0, line.indexOf(':')));
  676. }
  677. break;
  678.  case SERVER_ERROR:
  679.  default:
  680. throw new MessagingException(command+" failed: "+response);
  681. }
  682. } catch (IOException e) {
  683. throw new MessagingException(command+" failed", e);
  684. } catch (NumberFormatException e) {
  685. throw new MessagingException(command+" failed", e);
  686. }
  687. overviewFormat = new String[vector.size()]; vector.copyInto(overviewFormat);
  688. strippedOverviewFormat = new String[vector2.size()]; vector2.copyInto(strippedOverviewFormat);
  689. if (session.getDebug()) {
  690. StringBuffer buffer = new StringBuffer();
  691. buffer.append("DEBUG: nntp: overview format: ");
  692. for (int i=0; i<strippedOverviewFormat.length;i++) {
  693. if (i>0)
  694. buffer.append(", ");
  695. buffer.append(strippedOverviewFormat[i]);
  696. }
  697. System.out.println(buffer.toString());
  698. }
  699. }
  700. }
  701. if (strip)
  702. return strippedOverviewFormat;
  703. else
  704. return overviewFormat;
  705. }
  706.     // Prefetches header information for the specified messages, if possible
  707. Article[] getOverview(Newsgroup newsgroup) throws MessagingException {
  708.         String name = newsgroup.getName();
  709.         String[] format = getOverviewFormat(false);
  710. String command = "GROUP "+name;
  711. if (current==null || !name.equals(current.getName())) { // select the group
  712. synchronized (this) {
  713. try {
  714. send(command);
  715. switch (getResponse()) {
  716.   case GROUP_SELECTED:
  717. try {
  718. updateGroup(newsgroup, response);
  719. current = newsgroup;
  720. } catch(NumberFormatException e) {
  721. throw new MessagingException("NNTP protocol exception: "+response, e);
  722. }
  723.   case NO_SUCH_GROUP:
  724. throw new MessagingException("No such group");
  725.   case SERVER_ERROR:
  726. if (response.toLowerCase().indexOf("timeout")>-1) {
  727. close();
  728. connect();
  729. return getOverview(newsgroup);
  730. }
  731.   default:
  732. throw new MessagingException(command+" failed: "+response);
  733. }
  734. } catch (IOException e) {
  735. throw new MessagingException(command+" failed", e);
  736. }
  737. }
  738. }
  739. command = "XOVER "+newsgroup.first+"-"+newsgroup.last;
  740. Vector av = new Vector(Math.max(newsgroup.last-newsgroup.first, 10));
  741. synchronized (this) {
  742. try {
  743. send(command);
  744. switch (getResponse()) {
  745.   case LISTING_OVERVIEW:
  746. String line;
  747. int count = 0, length = (newsgroup.last-newsgroup.first);
  748. processStatusEvent(new StatusEvent(this, StatusEvent.OPERATION_START, "Retrieving messages", 0, length, 0));
  749. for (line=in.readLine(); line!=null && !".".equals(line); line = in.readLine()) {
  750. int tabIndex = line.indexOf('t');
  751. if (tabIndex>-1) {
  752. int msgnum = Integer.parseInt(line.substring(0, tabIndex));
  753.                             Article article = new Article(newsgroup, msgnum);
  754. article.addXoverHeaders(getOverviewHeaders(format, line, tabIndex));
  755. av.addElement(article);
  756. } else
  757. throw new ProtocolException("Invalid overview line format");
  758. if ((++count%50) == 0)
  759. processStatusEvent(new StatusEvent(this, StatusEvent.OPERATION_UPDATE, "Retrieved "+count+" of "+length+" messages", 0, length, count));
  760. }
  761. processStatusEvent(new StatusEvent(this, StatusEvent.OPERATION_END, "Done", 0, length, length));
  762. break;
  763.   case NO_ARTICLE_SELECTED:
  764.   case PERMISSION_DENIED:
  765. break;
  766.   case NO_GROUP_SELECTED:
  767.   case SERVER_ERROR:
  768.   default:
  769. throw new MessagingException(command+" failed: "+response);
  770. }
  771. } catch (IOException e) {
  772. throw new MessagingException(command+" failed", e);
  773. } catch (NumberFormatException e) {
  774. throw new MessagingException(command+" failed", e);
  775. }
  776. }
  777. Article[] articles = new Article[av.size()]; av.copyInto(articles);
  778. return articles;
  779. }
  780.     // Returns an InternetHeaders object representing the headers stored in an xover response line.
  781. InternetHeaders getOverviewHeaders(String[] format, String line, int startIndex) {
  782.         InternetHeaders headers = new InternetHeaders();
  783. for (int i=0; i<format.length; i++) {
  784. int colonIndex = format[i].indexOf(':');
  785. String key = format[i].substring(0, colonIndex);
  786. boolean full = "full".equals(format[i].substring(colonIndex+1, format[i].length()));
  787. int tabIndex = line.indexOf('t', startIndex+1);
  788. if (tabIndex<0) tabIndex = line.length();
  789. String value = line.substring(startIndex+1, tabIndex);
  790. if (full)
  791. value = value.substring(value.indexOf(':')+1).trim();
  792. headers.addHeader(key, value);
  793. startIndex = tabIndex;
  794. }
  795. return headers;
  796. }
  797. boolean validateOverviewHeader(String key) throws MessagingException {
  798. String[] format = getOverviewFormat(true);
  799. for (int i=0; i<format.length; i++) {
  800. if (key.equalsIgnoreCase(format[i]))
  801. return true;
  802. }
  803. return false;
  804. }
  805. public void addStatusListener(StatusListener l) {
  806. synchronized (statusListeners) {
  807. statusListeners.addElement(l);
  808. }
  809. }
  810. public void removeStatusListener(StatusListener l) {
  811. synchronized (statusListeners) {
  812. statusListeners.removeElement(l);
  813. }
  814. }
  815. protected void processStatusEvent(StatusEvent event) {
  816.         StatusListener[] listeners;
  817. synchronized (statusListeners) {
  818. listeners = new StatusListener[statusListeners.size()];
  819. statusListeners.copyInto(listeners);
  820. }
  821. switch (event.getType()) {
  822.   case StatusEvent.OPERATION_START:
  823. for (int i=0; i<listeners.length; i++)
  824. listeners[i].statusOperationStarted(event);
  825. break;
  826.   case StatusEvent.OPERATION_UPDATE:
  827. for (int i=0; i<listeners.length; i++)
  828. listeners[i].statusProgressUpdate(event);
  829. break;
  830.   case StatusEvent.OPERATION_END:
  831. for (int i=0; i<listeners.length; i++)
  832. listeners[i].statusOperationEnded(event);
  833.             break;
  834. }
  835. }
  836. /*
  837.  * Manages multiplexing of store connections.
  838.  */
  839. static Hashtable stores;
  840. static void addStore(NNTPStore store) {
  841. if (stores==null)
  842. stores = new Hashtable();
  843. stores.put(store.socket, store);
  844. }
  845. static void removeStore(NNTPStore store) {
  846. stores.remove(store.socket);
  847. }
  848. static NNTPStore getStore(InetAddress address, int port) {
  849. if (stores==null)
  850. return null;
  851. for (Enumeration enum = stores.keys(); enum.hasMoreElements(); ) {
  852. Socket ss = (Socket)enum.nextElement();
  853. InetAddress sa = ss.getInetAddress();
  854. int sp = ss.getPort();
  855. if (sp==port && sa.equals(address))
  856. return (NNTPStore)stores.get(ss);
  857. }
  858. return null;
  859. }
  860. /**
  861.  * The root holds the newsgroups in an NNTPStore.
  862.  */
  863. class Root extends Folder {
  864. /**
  865.  * Constructor.
  866.  */
  867. protected Root(Store store) { super(store); }
  868. /**
  869.  * Returns the name of this folder.
  870.  */
  871. public String getName() { return "/"; }
  872. /**
  873.  * Returns the full name of this folder.
  874.  */
  875. public String getFullName() { return getName(); }
  876. /**
  877.  * Returns the type of this folder.
  878.  */
  879. public int getType() throws MessagingException { return HOLDS_FOLDERS; }
  880. /**
  881.  * Indicates whether this folder exists.
  882.  */
  883. public boolean exists() throws MessagingException { return true; }
  884. /**
  885.  * Indicates whether this folder contains any new articles.
  886.  */
  887. public boolean hasNewMessages() throws MessagingException { return false; }
  888. /**
  889.  * Opens this folder.
  890.  */
  891. public void open(int mode) throws MessagingException {
  892. if (mode!=this.READ_ONLY) throw new MessagingException("Folder is read-only");
  893. }
  894. /**
  895.  * Closes this folder.
  896.  */
  897. public void close(boolean expunge) throws MessagingException {}
  898. /**
  899.  * Expunges this folder.
  900.  */
  901. public Message[] expunge() throws MessagingException { return new Message[0]; }
  902. /**
  903.  * Indicates whether this folder is open.
  904.  */
  905. public boolean isOpen() { return true; }
  906. /**
  907.  * Returns the permanent flags for this folder.
  908.  */
  909. public Flags getPermanentFlags() { return new Flags(); }
  910. /**
  911.  * Returns the number of articles in this folder.
  912.  */
  913. public int getMessageCount() throws MessagingException { return 0; }
  914. /**
  915.  * Returns the articles in this folder.
  916.  */
  917. public Message[] getMessages() throws MessagingException {
  918. throw new MessagingException("Folder can't contain messages");
  919. }
  920. /**
  921.  * Returns the specified message in this folder.
  922.  * Since NNTP articles are not stored in sequential order,
  923.  * the effect is just to reference articles returned by getMessages().
  924.  */
  925. public Message getMessage(int msgnum) throws MessagingException {
  926. throw new MessagingException("Folder can't contain messages");
  927. }
  928. /**
  929.  * Root folder is read-only.
  930.  */
  931. public void appendMessages(Message aarticle[]) throws MessagingException {
  932. throw new MessagingException("Folder is read-only");
  933. }
  934. /**
  935.  * Does nothing.
  936.  */
  937. public void fetch(Message articles[], FetchProfile fetchprofile) throws MessagingException {
  938. }
  939. /**
  940.  * This folder does not have a parent.
  941.  */
  942. public Folder getParent() throws MessagingException { return null; }
  943. /**
  944.  * Returns the newsgroups on the server.
  945.  */
  946. public Folder[] list() throws MessagingException {
  947. return getNewsgroups("LIST ACTIVE", false);
  948. }
  949. /**
  950.  * Returns the newsgroups on the server.
  951.  */
  952. public Folder[] list(String pattern) throws MessagingException {
  953. return getNewsgroups(pattern, false);
  954. }
  955. /**
  956.  * Returns the subscribed newsgroups on the server.
  957.  */
  958. public Folder[] listSubscribed() throws MessagingException {
  959. Vector groups = new Vector();
  960. for (Enumeration enum = newsgroups.elements(); enum.hasMoreElements(); ) {
  961. Newsgroup group = (Newsgroup)enum.nextElement();
  962. if (group.subscribed)
  963. groups.addElement(group);
  964. }
  965. Folder[] list = new Folder[groups.size()]; groups.copyInto(list);
  966. return list;
  967. }
  968. /**
  969.  * Returns the subscribed newsgroups on the server.
  970.  */
  971. public Folder[] listSubscribed(String pattern) throws MessagingException {
  972. return listSubscribed();
  973. }
  974. /**
  975.  * Returns the newsgroup with the specified name.
  976.  */
  977. public Folder getFolder(String name) throws MessagingException {
  978. return getNewsgroup(name);
  979. }
  980. /**
  981.  * Returns the separator character.
  982.  */
  983. public char getSeparator() throws MessagingException {
  984. return '.';
  985. }
  986. /**
  987.  * Root folders cannot be created, deleted, or renamed.
  988.  */
  989. public boolean create(int i) throws MessagingException {
  990. throw new MessagingException("Folder cannot be created");
  991. }
  992. /**
  993.  * Root folders cannot be created, deleted, or renamed.
  994.  */
  995. public boolean delete(boolean flag) throws MessagingException {
  996. throw new MessagingException("Folder cannot be deleted");
  997. }
  998. /**
  999.  * Root folders cannot be created, deleted, or renamed.
  1000.  */
  1001. public boolean renameTo(Folder folder) throws MessagingException {
  1002. throw new MessagingException("Folder cannot be renamed");
  1003. }
  1004. }
  1005. }