1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.net.pop3;
  17. import java.io.IOException;
  18. import java.io.Reader;
  19. import java.security.MessageDigest;
  20. import java.security.NoSuchAlgorithmException;
  21. import java.util.Enumeration;
  22. import java.util.StringTokenizer;
  23. import org.apache.commons.net.io.DotTerminatedMessageReader;
  24. /***
  25. * The POP3Client class implements the client side of the Internet POP3
  26. * Protocol defined in RFC 1939. All commands are supported, including
  27. * the APOP command which requires MD5 encryption. See RFC 1939 for
  28. * more details on the POP3 protocol.
  29. * <p>
  30. * Rather than list it separately for each method, we mention here that
  31. * every method communicating with the server and throwing an IOException
  32. * can also throw a
  33. * <a href="org.apache.commons.net.MalformedServerReplyException.html">
  34. * MalformedServerReplyException </a>, which is a subclass
  35. * of IOException. A MalformedServerReplyException will be thrown when
  36. * the reply received from the server deviates enough from the protocol
  37. * specification that it cannot be interpreted in a useful manner despite
  38. * attempts to be as lenient as possible.
  39. * <p>
  40. * <p>
  41. * @author Daniel F. Savarese
  42. * @see POP3MessageInfo
  43. * @see org.apache.commons.net.io.DotTerminatedMessageReader
  44. * @see org.apache.commons.net.MalformedServerReplyException
  45. ***/
  46. public class POP3Client extends POP3
  47. {
  48. private static POP3MessageInfo __parseStatus(String line)
  49. {
  50. int num, size;
  51. StringTokenizer tokenizer;
  52. tokenizer = new StringTokenizer(line);
  53. if (!tokenizer.hasMoreElements())
  54. return null;
  55. num = size = 0;
  56. try
  57. {
  58. num = Integer.parseInt(tokenizer.nextToken());
  59. if (!tokenizer.hasMoreElements())
  60. return null;
  61. size = Integer.parseInt(tokenizer.nextToken());
  62. }
  63. catch (NumberFormatException e)
  64. {
  65. return null;
  66. }
  67. return new POP3MessageInfo(num, size);
  68. }
  69. private static POP3MessageInfo __parseUID(String line)
  70. {
  71. int num;
  72. StringTokenizer tokenizer;
  73. tokenizer = new StringTokenizer(line);
  74. if (!tokenizer.hasMoreElements())
  75. return null;
  76. num = 0;
  77. try
  78. {
  79. num = Integer.parseInt(tokenizer.nextToken());
  80. if (!tokenizer.hasMoreElements())
  81. return null;
  82. line = tokenizer.nextToken();
  83. }
  84. catch (NumberFormatException e)
  85. {
  86. return null;
  87. }
  88. return new POP3MessageInfo(num, line);
  89. }
  90. /***
  91. * Login to the POP3 server with the given username and password. You
  92. * must first connect to the server with
  93. * <a href="org.apache.commons.net.SocketClient.html#connect"> connect </a>
  94. * before attempting to login. A login attempt is only valid if
  95. * the client is in the
  96. * <a href="org.apache.commons.net.pop3.POP3.html#AUTHORIZATION_STATE">
  97. * AUTHORIZATION_STATE </a>. After logging in, the client enters the
  98. * <a href="org.apache.commons.net.pop3.POP3.html#TRANSACTION_STATE">
  99. * TRANSACTION_STATE </a>.
  100. * <p>
  101. * @param username The account name being logged in to.
  102. * @param password The plain text password of the account.
  103. * @return True if the login attempt was successful, false if not.
  104. * @exception IOException If a network I/O error occurs in the process of
  105. * logging in.
  106. ***/
  107. public boolean login(String username, String password) throws IOException
  108. {
  109. if (getState() != AUTHORIZATION_STATE)
  110. return false;
  111. if (sendCommand(POP3Command.USER, username) != POP3Reply.OK)
  112. return false;
  113. if (sendCommand(POP3Command.PASS, password) != POP3Reply.OK)
  114. return false;
  115. setState(TRANSACTION_STATE);
  116. return true;
  117. }
  118. /***
  119. * Login to the POP3 server with the given username and authentication
  120. * information. Use this method when connecting to a server requiring
  121. * authentication using the APOP command. Because the timestamp
  122. * produced in the greeting banner varies from server to server, it is
  123. * not possible to consistently extract the information. Therefore,
  124. * after connecting to the server, you must call
  125. * <a href="org.apache.commons.net.pop3.POP3.html#getReplyString">
  126. * getReplyString </a> and parse out the timestamp information yourself.
  127. * <p>
  128. * You must first connect to the server with
  129. * <a href="org.apache.commons.net.SocketClient.html#connect"> connect </a>
  130. * before attempting to login. A login attempt is only valid if
  131. * the client is in the
  132. * <a href="org.apache.commons.net.pop3.POP3.html#AUTHORIZATION_STATE">
  133. * AUTHORIZATION_STATE </a>. After logging in, the client enters the
  134. * <a href="org.apache.commons.net.pop3.POP3.html#TRANSACTION_STATE">
  135. * TRANSACTION_STATE </a>. After connecting, you must parse out the
  136. * server specific information to use as a timestamp, and pass that
  137. * information to this method. The secret is a shared secret known
  138. * to you and the server. See RFC 1939 for more details regarding
  139. * the APOP command.
  140. * <p>
  141. * @param username The account name being logged in to.
  142. * @param timestamp The timestamp string to combine with the secret.
  143. * @param secret The shared secret which produces the MD5 digest when
  144. * combined with the timestamp.
  145. * @return True if the login attempt was successful, false if not.
  146. * @exception IOException If a network I/O error occurs in the process of
  147. * logging in.
  148. * @exception NoSuchAlgorithmException If the MD5 encryption algorithm
  149. * cannot be instantiated by the Java runtime system.
  150. ***/
  151. public boolean login(String username, String timestamp, String secret)
  152. throws IOException, NoSuchAlgorithmException
  153. {
  154. int i;
  155. byte[] digest;
  156. StringBuffer buffer, digestBuffer;
  157. MessageDigest md5;
  158. if (getState() != AUTHORIZATION_STATE)
  159. return false;
  160. md5 = MessageDigest.getInstance("MD5");
  161. timestamp += secret;
  162. digest = md5.digest(timestamp.getBytes());
  163. digestBuffer = new StringBuffer(128);
  164. for (i = 0; i < digest.length; i++)
  165. digestBuffer.append(Integer.toHexString(digest[i] & 0xff));
  166. buffer = new StringBuffer(256);
  167. buffer.append(username);
  168. buffer.append(' ');
  169. buffer.append(digestBuffer.toString());
  170. if (sendCommand(POP3Command.APOP, buffer.toString()) != POP3Reply.OK)
  171. return false;
  172. setState(TRANSACTION_STATE);
  173. return true;
  174. }
  175. /***
  176. * Logout of the POP3 server. To fully disconnect from the server
  177. * you must call
  178. * <a href="org.apache.commons.net.pop3.POP3.html#disconnect"> disconnect </a>.
  179. * A logout attempt is valid in any state. If
  180. * the client is in the
  181. * <a href="org.apache.commons.net.pop3.POP3.html#TRANSACTION_STATE">
  182. * TRANSACTION_STATE </a>, it enters the
  183. * <a href="org.apache.commons.net.pop3.POP3.html#UPDATE_STATE">
  184. * UPDATE_STATE </a> on a successful logout.
  185. * <p>
  186. * @return True if the logout attempt was successful, false if not.
  187. * @exception IOException If a network I/O error occurs in the process
  188. * of logging out.
  189. ***/
  190. public boolean logout() throws IOException
  191. {
  192. if (getState() == TRANSACTION_STATE)
  193. setState(UPDATE_STATE);
  194. sendCommand(POP3Command.QUIT);
  195. return (_replyCode == POP3Reply.OK);
  196. }
  197. /***
  198. * Send a NOOP command to the POP3 server. This is useful for keeping
  199. * a connection alive since most POP3 servers will timeout after 10
  200. * minutes of inactivity. A noop attempt will only succeed if
  201. * the client is in the
  202. * <a href="org.apache.commons.net.pop3.POP3.html#TRANSACTION_STATE">
  203. * TRANSACTION_STATE </a>.
  204. * <p>
  205. * @return True if the noop attempt was successful, false if not.
  206. * @exception IOException If a network I/O error occurs in the process of
  207. * sending the NOOP command.
  208. ***/
  209. public boolean noop() throws IOException
  210. {
  211. if (getState() == TRANSACTION_STATE)
  212. return (sendCommand(POP3Command.NOOP) == POP3Reply.OK);
  213. return false;
  214. }
  215. /***
  216. * Delete a message from the POP3 server. The message is only marked
  217. * for deletion by the server. If you decide to unmark the message, you
  218. * must issuse a <a href="#reset"> reset </a> command. Messages marked
  219. * for deletion are only deleted by the server on
  220. * <a href="#logout"> logout </a>.
  221. * A delete attempt can only succeed if the client is in the
  222. * <a href="org.apache.commons.net.pop3.POP3.html#TRANSACTION_STATE">
  223. * TRANSACTION_STATE </a>.
  224. * <p>
  225. * @param messageId The message number to delete.
  226. * @return True if the deletion attempt was successful, false if not.
  227. * @exception IOException If a network I/O error occurs in the process of
  228. * sending the delete command.
  229. ***/
  230. public boolean deleteMessage(int messageId) throws IOException
  231. {
  232. if (getState() == TRANSACTION_STATE)
  233. return (sendCommand(POP3Command.DELE, Integer.toString(messageId))
  234. == POP3Reply.OK);
  235. return false;
  236. }
  237. /***
  238. * Reset the POP3 session. This is useful for undoing any message
  239. * deletions that may have been performed. A reset attempt can only
  240. * succeed if the client is in the
  241. * <a href="org.apache.commons.net.pop3.POP3.html#TRANSACTION_STATE">
  242. * TRANSACTION_STATE </a>.
  243. * <p>
  244. * @return True if the reset attempt was successful, false if not.
  245. * @exception IOException If a network I/O error occurs in the process of
  246. * sending the reset command.
  247. ***/
  248. public boolean reset() throws IOException
  249. {
  250. if (getState() == TRANSACTION_STATE)
  251. return (sendCommand(POP3Command.RSET) == POP3Reply.OK);
  252. return false;
  253. }
  254. /***
  255. * Get the mailbox status. A status attempt can only
  256. * succeed if the client is in the
  257. * <a href="org.apache.commons.net.pop3.POP3.html#TRANSACTION_STATE">
  258. * TRANSACTION_STATE </a>. Returns a POP3MessageInfo instance
  259. * containing the number of messages in the mailbox and the total
  260. * size of the messages in bytes. Returns null if the status the
  261. * attempt fails.
  262. * <p>
  263. * @return A POP3MessageInfo instance containing the number of
  264. * messages in the mailbox and the total size of the messages
  265. * in bytes. Returns null if the status the attempt fails.
  266. * @exception IOException If a network I/O error occurs in the process of
  267. * sending the status command.
  268. ***/
  269. public POP3MessageInfo status() throws IOException
  270. {
  271. if (getState() != TRANSACTION_STATE)
  272. return null;
  273. if (sendCommand(POP3Command.STAT) != POP3Reply.OK)
  274. return null;
  275. return __parseStatus(_lastReplyLine.substring(3));
  276. }
  277. /***
  278. * List an individual message. A list attempt can only
  279. * succeed if the client is in the
  280. * <a href="org.apache.commons.net.pop3.POP3.html#TRANSACTION_STATE">
  281. * TRANSACTION_STATE </a>. Returns a POP3MessageInfo instance
  282. * containing the number of the listed message and the
  283. * size of the message in bytes. Returns null if the list
  284. * attempt fails (e.g., if the specified message number does
  285. * not exist).
  286. * <p>
  287. * @param messageId The number of the message list.
  288. * @return A POP3MessageInfo instance containing the number of the
  289. * listed message and the size of the message in bytes. Returns
  290. * null if the list attempt fails.
  291. * @exception IOException If a network I/O error occurs in the process of
  292. * sending the list command.
  293. ***/
  294. public POP3MessageInfo listMessage(int messageId) throws IOException
  295. {
  296. if (getState() != TRANSACTION_STATE)
  297. return null;
  298. if (sendCommand(POP3Command.LIST, Integer.toString(messageId))
  299. != POP3Reply.OK)
  300. return null;
  301. return __parseStatus(_lastReplyLine.substring(3));
  302. }
  303. /***
  304. * List all messages. A list attempt can only
  305. * succeed if the client is in the
  306. * <a href="org.apache.commons.net.pop3.POP3.html#TRANSACTION_STATE">
  307. * TRANSACTION_STATE </a>. Returns an array of POP3MessageInfo instances,
  308. * each containing the number of a message and its size in bytes.
  309. * If there are no messages, this method returns a zero length array.
  310. * If the list attempt fails, it returns null.
  311. * <p>
  312. * @return An array of POP3MessageInfo instances representing all messages
  313. * in the order they appear in the mailbox,
  314. * each containing the number of a message and its size in bytes.
  315. * If there are no messages, this method returns a zero length array.
  316. * If the list attempt fails, it returns null.
  317. * @exception IOException If a network I/O error occurs in the process of
  318. * sending the list command.
  319. ***/
  320. public POP3MessageInfo[] listMessages() throws IOException
  321. {
  322. POP3MessageInfo[] messages;
  323. Enumeration enum;
  324. int line;
  325. if (getState() != TRANSACTION_STATE)
  326. return null;
  327. if (sendCommand(POP3Command.LIST) != POP3Reply.OK)
  328. return null;
  329. getAdditionalReply();
  330. // This could be a zero length array if no messages present
  331. messages = new POP3MessageInfo[_replyLines.size() - 2];
  332. enum = _replyLines.elements();
  333. // Skip first line
  334. enum.nextElement();
  335. // Fetch lines.
  336. for (line = 0; line < messages.length; line++)
  337. messages[line] = __parseStatus((String)enum.nextElement());
  338. return messages;
  339. }
  340. /***
  341. * List the unique identifier for a message. A list attempt can only
  342. * succeed if the client is in the
  343. * <a href="org.apache.commons.net.pop3.POP3.html#TRANSACTION_STATE">
  344. * TRANSACTION_STATE </a>. Returns a POP3MessageInfo instance
  345. * containing the number of the listed message and the
  346. * unique identifier for that message. Returns null if the list
  347. * attempt fails (e.g., if the specified message number does
  348. * not exist).
  349. * <p>
  350. * @param messageId The number of the message list.
  351. * @return A POP3MessageInfo instance containing the number of the
  352. * listed message and the unique identifier for that message.
  353. * Returns null if the list attempt fails.
  354. * @exception IOException If a network I/O error occurs in the process of
  355. * sending the list unique identifier command.
  356. ***/
  357. public POP3MessageInfo listUniqueIdentifier(int messageId)
  358. throws IOException
  359. {
  360. if (getState() != TRANSACTION_STATE)
  361. return null;
  362. if (sendCommand(POP3Command.UIDL, Integer.toString(messageId))
  363. != POP3Reply.OK)
  364. return null;
  365. return __parseUID(_lastReplyLine.substring(3));
  366. }
  367. /***
  368. * List the unique identifiers for all messages. A list attempt can only
  369. * succeed if the client is in the
  370. * <a href="org.apache.commons.net.pop3.POP3.html#TRANSACTION_STATE">
  371. * TRANSACTION_STATE </a>. Returns an array of POP3MessageInfo instances,
  372. * each containing the number of a message and its unique identifier.
  373. * If there are no messages, this method returns a zero length array.
  374. * If the list attempt fails, it returns null.
  375. * <p>
  376. * @return An array of POP3MessageInfo instances representing all messages
  377. * in the order they appear in the mailbox,
  378. * each containing the number of a message and its unique identifier
  379. * If there are no messages, this method returns a zero length array.
  380. * If the list attempt fails, it returns null.
  381. * @exception IOException If a network I/O error occurs in the process of
  382. * sending the list unique identifier command.
  383. ***/
  384. public POP3MessageInfo[] listUniqueIdentifiers() throws IOException
  385. {
  386. POP3MessageInfo[] messages;
  387. Enumeration enum;
  388. int line;
  389. if (getState() != TRANSACTION_STATE)
  390. return null;
  391. if (sendCommand(POP3Command.UIDL) != POP3Reply.OK)
  392. return null;
  393. getAdditionalReply();
  394. // This could be a zero length array if no messages present
  395. messages = new POP3MessageInfo[_replyLines.size() - 2];
  396. enum = _replyLines.elements();
  397. // Skip first line
  398. enum.nextElement();
  399. // Fetch lines.
  400. for (line = 0; line < messages.length; line++)
  401. messages[line] = __parseUID((String)enum.nextElement());
  402. return messages;
  403. }
  404. /***
  405. * Retrieve a message from the POP3 server. A retrieve message attempt
  406. * can only succeed if the client is in the
  407. * <a href="org.apache.commons.net.pop3.POP3.html#TRANSACTION_STATE">
  408. * TRANSACTION_STATE </a>. Returns a DotTerminatedMessageReader instance
  409. * from which the entire message can be read.
  410. * Returns null if the retrieval attempt fails (e.g., if the specified
  411. * message number does not exist).
  412. * <p>
  413. * You must not issue any commands to the POP3 server (i.e., call any
  414. * other methods) until you finish reading the message from the
  415. * returned Reader instance.
  416. * The POP3 protocol uses the same stream for issuing commands as it does
  417. * for returning results. Therefore the returned Reader actually reads
  418. * directly from the POP3 connection. After the end of message has been
  419. * reached, new commands can be executed and their replies read. If
  420. * you do not follow these requirements, your program will not work
  421. * properly.
  422. * <p>
  423. * @param messageId The number of the message to fetch.
  424. * @return A DotTerminatedMessageReader instance
  425. * from which the entire message can be read.
  426. * Returns null if the retrieval attempt fails (e.g., if the specified
  427. * message number does not exist).
  428. * @exception IOException If a network I/O error occurs in the process of
  429. * sending the retrieve message command.
  430. ***/
  431. public Reader retrieveMessage(int messageId) throws IOException
  432. {
  433. if (getState() != TRANSACTION_STATE)
  434. return null;
  435. if (sendCommand(POP3Command.RETR, Integer.toString(messageId))
  436. != POP3Reply.OK)
  437. return null;
  438. return new DotTerminatedMessageReader(_reader);
  439. }
  440. /***
  441. * Retrieve only the specified top number of lines of a message from the
  442. * POP3 server. A retrieve top lines attempt
  443. * can only succeed if the client is in the
  444. * <a href="org.apache.commons.net.pop3.POP3.html#TRANSACTION_STATE">
  445. * TRANSACTION_STATE </a>. Returns a DotTerminatedMessageReader instance
  446. * from which the specified top number of lines of the message can be
  447. * read.
  448. * Returns null if the retrieval attempt fails (e.g., if the specified
  449. * message number does not exist).
  450. * <p>
  451. * You must not issue any commands to the POP3 server (i.e., call any
  452. * other methods) until you finish reading the message from the returned
  453. * Reader instance.
  454. * The POP3 protocol uses the same stream for issuing commands as it does
  455. * for returning results. Therefore the returned Reader actually reads
  456. * directly from the POP3 connection. After the end of message has been
  457. * reached, new commands can be executed and their replies read. If
  458. * you do not follow these requirements, your program will not work
  459. * properly.
  460. * <p>
  461. * @param messageId The number of the message to fetch.
  462. * @param numLines The top number of lines to fetch. This must be >= 0.
  463. * @return A DotTerminatedMessageReader instance
  464. * from which the specified top number of lines of the message can be
  465. * read.
  466. * Returns null if the retrieval attempt fails (e.g., if the specified
  467. * message number does not exist).
  468. * @exception IOException If a network I/O error occurs in the process of
  469. * sending the top command.
  470. ***/
  471. public Reader retrieveMessageTop(int messageId, int numLines)
  472. throws IOException
  473. {
  474. if (numLines < 0 || getState() != TRANSACTION_STATE)
  475. return null;
  476. if (sendCommand(POP3Command.TOP, Integer.toString(messageId) + " " +
  477. Integer.toString(numLines)) != POP3Reply.OK)
  478. return null;
  479. return new DotTerminatedMessageReader(_reader);
  480. }
  481. }