Pop3Store.java revision b203b2b1196bfd5507c83a4fe81d362de840ec0a
1/* 2 * Copyright (C) 2008 The Android Open Source Project 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 17package com.android.email.mail.store; 18 19import android.content.Context; 20import android.os.Bundle; 21import android.util.Log; 22 23import com.android.email.R; 24import com.android.email.mail.Store; 25import com.android.email.mail.transport.MailTransport; 26import com.android.email2.ui.MailActivityEmail; 27import com.android.emailcommon.Logging; 28import com.android.emailcommon.internet.MimeMessage; 29import com.android.emailcommon.mail.AuthenticationFailedException; 30import com.android.emailcommon.mail.FetchProfile; 31import com.android.emailcommon.mail.Flag; 32import com.android.emailcommon.mail.Folder; 33import com.android.emailcommon.mail.Folder.OpenMode; 34import com.android.emailcommon.mail.Message; 35import com.android.emailcommon.mail.MessagingException; 36import com.android.emailcommon.provider.Account; 37import com.android.emailcommon.provider.HostAuth; 38import com.android.emailcommon.provider.Mailbox; 39import com.android.emailcommon.service.EmailServiceProxy; 40import com.android.emailcommon.service.SearchParams; 41import com.android.emailcommon.utility.LoggingInputStream; 42import com.android.emailcommon.utility.Utility; 43import com.google.common.annotations.VisibleForTesting; 44 45import org.apache.james.mime4j.EOLConvertingInputStream; 46 47import java.io.IOException; 48import java.io.InputStream; 49import java.util.ArrayList; 50import java.util.HashMap; 51 52public class Pop3Store extends Store { 53 // All flags defining debug or development code settings must be FALSE 54 // when code is checked in or released. 55 private static boolean DEBUG_FORCE_SINGLE_LINE_UIDL = false; 56 private static boolean DEBUG_LOG_RAW_STREAM = false; 57 58 private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED }; 59 /** The name of the only mailbox available to POP3 accounts */ 60 private static final String POP3_MAILBOX_NAME = "INBOX"; 61 private final HashMap<String, Folder> mFolders = new HashMap<String, Folder>(); 62 private final Message[] mOneMessage = new Message[1]; 63 64 /** 65 * Static named constructor. 66 */ 67 public static Store newInstance(Account account, Context context) throws MessagingException { 68 return new Pop3Store(context, account); 69 } 70 71 /** 72 * Creates a new store for the given account. 73 */ 74 private Pop3Store(Context context, Account account) throws MessagingException { 75 mContext = context; 76 mAccount = account; 77 78 HostAuth recvAuth = account.getOrCreateHostAuthRecv(context); 79 mTransport = new MailTransport(context, "POP3", recvAuth); 80 String[] userInfoParts = recvAuth.getLogin(); 81 if (userInfoParts != null) { 82 mUsername = userInfoParts[0]; 83 mPassword = userInfoParts[1]; 84 } 85 } 86 87 /** 88 * For testing only. Injects a different transport. The transport should already be set 89 * up and ready to use. Do not use for real code. 90 * @param testTransport The Transport to inject and use for all future communication. 91 */ 92 /* package */ void setTransport(MailTransport testTransport) { 93 mTransport = testTransport; 94 } 95 96 @Override 97 public Folder getFolder(String name) { 98 Folder folder = mFolders.get(name); 99 if (folder == null) { 100 folder = new Pop3Folder(name); 101 mFolders.put(folder.getName(), folder); 102 } 103 return folder; 104 } 105 106 private final int[] DEFAULT_FOLDERS = { 107 Mailbox.TYPE_DRAFTS, 108 Mailbox.TYPE_OUTBOX, 109 Mailbox.TYPE_SENT, 110 Mailbox.TYPE_TRASH 111 }; 112 113 @Override 114 public Folder[] updateFolders() { 115 String inboxName = mContext.getString(R.string.mailbox_name_display_inbox); 116 Mailbox mailbox = Mailbox.getMailboxForPath(mContext, mAccount.mId, inboxName); 117 updateMailbox(mailbox, mAccount.mId, inboxName, '\0', true, Mailbox.TYPE_INBOX); 118 // Force the parent key to be "no mailbox" for the mail POP3 mailbox 119 mailbox.mParentKey = Mailbox.NO_MAILBOX; 120 if (mailbox.isSaved()) { 121 mailbox.update(mContext, mailbox.toContentValues()); 122 } else { 123 mailbox.save(mContext); 124 } 125 126 // Build default mailboxes as well, in case they're not already made. 127 for (int type : DEFAULT_FOLDERS) { 128 if (Mailbox.findMailboxOfType(mContext, mAccount.mId, type) == Mailbox.NO_MAILBOX) { 129 String name = getMailboxServerName(mContext, type); 130 mailbox = Mailbox.newSystemMailbox(mAccount.mId, type, name); 131 mailbox.save(mContext); 132 } 133 } 134 135 return new Folder[] { getFolder(inboxName) }; 136 } 137 138 139 /** 140 * Returns the server-side name for a specific mailbox. 141 * 142 * @return the resource string corresponding to the mailbox type, empty if not found. 143 */ 144 public String getMailboxServerName(Context context, int mailboxType) { 145 int resId = -1; 146 switch (mailboxType) { 147 case Mailbox.TYPE_INBOX: 148 resId = R.string.mailbox_name_server_inbox; 149 break; 150 case Mailbox.TYPE_OUTBOX: 151 resId = R.string.mailbox_name_server_outbox; 152 break; 153 case Mailbox.TYPE_DRAFTS: 154 resId = R.string.mailbox_name_server_drafts; 155 break; 156 case Mailbox.TYPE_TRASH: 157 resId = R.string.mailbox_name_server_trash; 158 break; 159 case Mailbox.TYPE_SENT: 160 resId = R.string.mailbox_name_server_sent; 161 break; 162 case Mailbox.TYPE_JUNK: 163 resId = R.string.mailbox_name_server_junk; 164 break; 165 } 166 return resId != -1 ? context.getString(resId) : ""; 167 } 168 169 /** 170 * Used by account setup to test if an account's settings are appropriate. The definition 171 * of "checked" here is simply, can you log into the account and does it meet some minimum set 172 * of feature requirements? 173 * 174 * @throws MessagingException if there was some problem with the account 175 */ 176 @Override 177 public Bundle checkSettings() throws MessagingException { 178 Pop3Folder folder = new Pop3Folder(POP3_MAILBOX_NAME); 179 Bundle bundle = null; 180 // Close any open or half-open connections - checkSettings should always be "fresh" 181 if (mTransport.isOpen()) { 182 folder.close(false); 183 } 184 try { 185 folder.open(OpenMode.READ_WRITE); 186 bundle = folder.checkSettings(); 187 } finally { 188 folder.close(false); // false == don't expunge anything 189 } 190 return bundle; 191 } 192 193 public class Pop3Folder extends Folder { 194 private final HashMap<String, Pop3Message> mUidToMsgMap 195 = new HashMap<String, Pop3Message>(); 196 private final HashMap<Integer, Pop3Message> mMsgNumToMsgMap 197 = new HashMap<Integer, Pop3Message>(); 198 private final HashMap<String, Integer> mUidToMsgNumMap = new HashMap<String, Integer>(); 199 private final String mName; 200 private int mMessageCount; 201 private Pop3Capabilities mCapabilities; 202 203 public Pop3Folder(String name) { 204 if (name.equalsIgnoreCase(POP3_MAILBOX_NAME)) { 205 mName = POP3_MAILBOX_NAME; 206 } else { 207 mName = name; 208 } 209 } 210 211 /** 212 * Used by account setup to test if an account's settings are appropriate. Here, we run 213 * an additional test to see if UIDL is supported on the server. If it's not we 214 * can't service this account. 215 * 216 * @return Bundle containing validation data (code and, if appropriate, error message) 217 * @throws MessagingException if the account is not going to be useable 218 */ 219 public Bundle checkSettings() throws MessagingException { 220 Bundle bundle = new Bundle(); 221 int result = MessagingException.NO_ERROR; 222 try { 223 UidlParser parser = new UidlParser(); 224 executeSimpleCommand("UIDL"); 225 // drain the entire output, so additional communications don't get confused. 226 String response; 227 while ((response = mTransport.readLine(false)) != null) { 228 parser.parseMultiLine(response); 229 if (parser.mEndOfMessage) { 230 break; 231 } 232 } 233 } catch (IOException ioe) { 234 mTransport.close(); 235 result = MessagingException.IOERROR; 236 bundle.putString(EmailServiceProxy.VALIDATE_BUNDLE_ERROR_MESSAGE, 237 ioe.getMessage()); 238 } 239 bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, result); 240 return bundle; 241 } 242 243 @Override 244 public synchronized void open(OpenMode mode) throws MessagingException { 245 if (mTransport.isOpen()) { 246 return; 247 } 248 249 if (!mName.equalsIgnoreCase(POP3_MAILBOX_NAME)) { 250 throw new MessagingException("Folder does not exist"); 251 } 252 253 try { 254 mTransport.open(); 255 256 // Eat the banner 257 executeSimpleCommand(null); 258 259 mCapabilities = getCapabilities(); 260 261 if (mTransport.canTryTlsSecurity()) { 262 if (mCapabilities.stls) { 263 executeSimpleCommand("STLS"); 264 mTransport.reopenTls(); 265 } else { 266 if (MailActivityEmail.DEBUG) { 267 Log.d(Logging.LOG_TAG, "TLS not supported but required"); 268 } 269 throw new MessagingException(MessagingException.TLS_REQUIRED); 270 } 271 } 272 273 try { 274 executeSensitiveCommand("USER " + mUsername, "USER /redacted/"); 275 executeSensitiveCommand("PASS " + mPassword, "PASS /redacted/"); 276 } catch (MessagingException me) { 277 if (MailActivityEmail.DEBUG) { 278 Log.d(Logging.LOG_TAG, me.toString()); 279 } 280 throw new AuthenticationFailedException(null, me); 281 } 282 } catch (IOException ioe) { 283 mTransport.close(); 284 if (MailActivityEmail.DEBUG) { 285 Log.d(Logging.LOG_TAG, ioe.toString()); 286 } 287 throw new MessagingException(MessagingException.IOERROR, ioe.toString()); 288 } 289 290 Exception statException = null; 291 try { 292 String response = executeSimpleCommand("STAT"); 293 String[] parts = response.split(" "); 294 if (parts.length < 2) { 295 statException = new IOException(); 296 } else { 297 mMessageCount = Integer.parseInt(parts[1]); 298 } 299 } catch (MessagingException me) { 300 statException = me; 301 } catch (IOException ioe) { 302 statException = ioe; 303 } catch (NumberFormatException nfe) { 304 statException = nfe; 305 } 306 if (statException != null) { 307 mTransport.close(); 308 if (MailActivityEmail.DEBUG) { 309 Log.d(Logging.LOG_TAG, statException.toString()); 310 } 311 throw new MessagingException("POP3 STAT", statException); 312 } 313 mUidToMsgMap.clear(); 314 mMsgNumToMsgMap.clear(); 315 mUidToMsgNumMap.clear(); 316 } 317 318 @Override 319 public OpenMode getMode() { 320 return OpenMode.READ_WRITE; 321 } 322 323 /** 324 * Close the folder (and the transport below it). 325 * 326 * MUST NOT return any exceptions. 327 * 328 * @param expunge If true all deleted messages will be expunged (TODO - not implemented) 329 */ 330 @Override 331 public void close(boolean expunge) { 332 try { 333 executeSimpleCommand("QUIT"); 334 } 335 catch (Exception e) { 336 // ignore any problems here - just continue closing 337 } 338 mTransport.close(); 339 } 340 341 @Override 342 public String getName() { 343 return mName; 344 } 345 346 // POP3 does not folder creation 347 @Override 348 public boolean canCreate(FolderType type) { 349 return false; 350 } 351 352 @Override 353 public boolean create(FolderType type) { 354 return false; 355 } 356 357 @Override 358 public boolean exists() { 359 return mName.equalsIgnoreCase(POP3_MAILBOX_NAME); 360 } 361 362 @Override 363 public int getMessageCount() { 364 return mMessageCount; 365 } 366 367 @Override 368 public int getUnreadMessageCount() { 369 return -1; 370 } 371 372 @Override 373 public Message getMessage(String uid) throws MessagingException { 374 if (mUidToMsgNumMap.size() == 0) { 375 try { 376 indexMsgNums(1, mMessageCount); 377 } catch (IOException ioe) { 378 mTransport.close(); 379 if (MailActivityEmail.DEBUG) { 380 Log.d(Logging.LOG_TAG, "Unable to index during getMessage " + ioe); 381 } 382 throw new MessagingException("getMessages", ioe); 383 } 384 } 385 Pop3Message message = mUidToMsgMap.get(uid); 386 return message; 387 } 388 389 @Override 390 public Pop3Message[] getMessages(int start, int end, MessageRetrievalListener listener) 391 throws MessagingException { 392 return null; 393 } 394 395 public Pop3Message[] getMessages(int end, final int limit) 396 throws MessagingException { 397 try { 398 indexMsgNums(1, end); 399 } catch (IOException ioe) { 400 mTransport.close(); 401 if (MailActivityEmail.DEBUG) { 402 Log.d(Logging.LOG_TAG, ioe.toString()); 403 } 404 throw new MessagingException("getMessages", ioe); 405 } 406 ArrayList<Message> messages = new ArrayList<Message>(); 407 for (int msgNum = end; msgNum > 0 && (messages.size() < limit); msgNum--) { 408 Pop3Message message = mMsgNumToMsgMap.get(msgNum); 409 if (message != null) { 410 messages.add(message); 411 } 412 } 413 return messages.toArray(new Pop3Message[messages.size()]); 414 } 415 416 /** 417 * Ensures that the given message set (from start to end inclusive) 418 * has been queried so that uids are available in the local cache. 419 * @param start 420 * @param end 421 * @throws MessagingException 422 * @throws IOException 423 */ 424 private void indexMsgNums(int start, int end) 425 throws MessagingException, IOException { 426 if (!mMsgNumToMsgMap.isEmpty()) { 427 return; 428 } 429 UidlParser parser = new UidlParser(); 430 if (DEBUG_FORCE_SINGLE_LINE_UIDL || (mMessageCount > 5000)) { 431 /* 432 * In extreme cases we'll do a UIDL command per message instead of a bulk 433 * download. 434 */ 435 for (int msgNum = start; msgNum <= end; msgNum++) { 436 Pop3Message message = mMsgNumToMsgMap.get(msgNum); 437 if (message == null) { 438 String response = executeSimpleCommand("UIDL " + msgNum); 439 if (!parser.parseSingleLine(response)) { 440 throw new IOException(); 441 } 442 message = new Pop3Message(parser.mUniqueId, this); 443 indexMessage(msgNum, message); 444 } 445 } 446 } else { 447 String response = executeSimpleCommand("UIDL"); 448 while ((response = mTransport.readLine(false)) != null) { 449 if (!parser.parseMultiLine(response)) { 450 throw new IOException(); 451 } 452 if (parser.mEndOfMessage) { 453 break; 454 } 455 int msgNum = parser.mMessageNumber; 456 if (msgNum >= start && msgNum <= end) { 457 Pop3Message message = mMsgNumToMsgMap.get(msgNum); 458 if (message == null) { 459 message = new Pop3Message(parser.mUniqueId, this); 460 indexMessage(msgNum, message); 461 } 462 } 463 } 464 } 465 } 466 467 /** 468 * Simple parser class for UIDL messages. 469 * 470 * <p>NOTE: In variance with RFC 1939, we allow multiple whitespace between the 471 * message-number and unique-id fields. This provides greater compatibility with some 472 * non-compliant POP3 servers, e.g. mail.comcast.net. 473 */ 474 /* package */ class UidlParser { 475 476 /** 477 * Caller can read back message-number from this field 478 */ 479 public int mMessageNumber; 480 /** 481 * Caller can read back unique-id from this field 482 */ 483 public String mUniqueId; 484 /** 485 * True if the response was "end-of-message" 486 */ 487 public boolean mEndOfMessage; 488 /** 489 * True if an error was reported 490 */ 491 public boolean mErr; 492 493 /** 494 * Construct & Initialize 495 */ 496 public UidlParser() { 497 mErr = true; 498 } 499 500 /** 501 * Parse a single-line response. This is returned from a command of the form 502 * "UIDL msg-num" and will be formatted as: "+OK msg-num unique-id" or 503 * "-ERR diagnostic text" 504 * 505 * @param response The string returned from the server 506 * @return true if the string parsed as expected (e.g. no syntax problems) 507 */ 508 public boolean parseSingleLine(String response) { 509 mErr = false; 510 if (response == null || response.length() == 0) { 511 return false; 512 } 513 char first = response.charAt(0); 514 if (first == '+') { 515 String[] uidParts = response.split(" +"); 516 if (uidParts.length >= 3) { 517 try { 518 mMessageNumber = Integer.parseInt(uidParts[1]); 519 } catch (NumberFormatException nfe) { 520 return false; 521 } 522 mUniqueId = uidParts[2]; 523 mEndOfMessage = true; 524 return true; 525 } 526 } else if (first == '-') { 527 mErr = true; 528 return true; 529 } 530 return false; 531 } 532 533 /** 534 * Parse a multi-line response. This is returned from a command of the form 535 * "UIDL" and will be formatted as: "." or "msg-num unique-id". 536 * 537 * @param response The string returned from the server 538 * @return true if the string parsed as expected (e.g. no syntax problems) 539 */ 540 public boolean parseMultiLine(String response) { 541 mErr = false; 542 if (response == null || response.length() == 0) { 543 return false; 544 } 545 char first = response.charAt(0); 546 if (first == '.') { 547 mEndOfMessage = true; 548 return true; 549 } else { 550 String[] uidParts = response.split(" +"); 551 if (uidParts.length >= 2) { 552 try { 553 mMessageNumber = Integer.parseInt(uidParts[0]); 554 } catch (NumberFormatException nfe) { 555 return false; 556 } 557 mUniqueId = uidParts[1]; 558 mEndOfMessage = false; 559 return true; 560 } 561 } 562 return false; 563 } 564 } 565 566 private void indexMessage(int msgNum, Pop3Message message) { 567 mMsgNumToMsgMap.put(msgNum, message); 568 mUidToMsgMap.put(message.getUid(), message); 569 mUidToMsgNumMap.put(message.getUid(), msgNum); 570 } 571 572 @Override 573 public Message[] getMessages(String[] uids, MessageRetrievalListener listener) { 574 throw new UnsupportedOperationException( 575 "Pop3Folder.getMessage(MessageRetrievalListener)"); 576 } 577 578 /** 579 * Fetch the items contained in the FetchProfile into the given set of 580 * Messages in as efficient a manner as possible. 581 * @param messages 582 * @param fp 583 * @throws MessagingException 584 */ 585 @Override 586 public void fetch(Message[] messages, FetchProfile fp, MessageRetrievalListener listener) 587 throws MessagingException { 588 throw new UnsupportedOperationException( 589 "Pop3Folder.fetch(Message[], FetchProfile, MessageRetrievalListener)"); 590 } 591 592 /** 593 * Fetches the body of the given message, limiting the stored data 594 * to the specified number of lines. If lines is -1 the entire message 595 * is fetched. This is implemented with RETR for lines = -1 or TOP 596 * for any other value. If the server does not support TOP it is 597 * emulated with RETR and extra lines are thrown away. 598 * 599 * @param message 600 * @param lines 601 * @param optional callback that reports progress of the fetch 602 */ 603 public void fetchBody(Pop3Message message, int lines, 604 EOLConvertingInputStream.Callback callback) throws IOException, MessagingException { 605 String response = null; 606 int messageId = mUidToMsgNumMap.get(message.getUid()); 607 if (lines == -1) { 608 // Fetch entire message 609 response = executeSimpleCommand(String.format("RETR %d", messageId)); 610 } else { 611 // Fetch partial message. Try "TOP", and fall back to slower "RETR" if necessary 612 try { 613 response = executeSimpleCommand(String.format("TOP %d %d", messageId, lines)); 614 } catch (MessagingException me) { 615 try { 616 response = executeSimpleCommand(String.format("RETR %d", messageId)); 617 } catch (MessagingException e) { 618 Log.w(Logging.LOG_TAG, "Can't read message " + messageId); 619 } 620 } 621 } 622 if (response != null) { 623 try { 624 int ok = response.indexOf("OK"); 625 if (ok > 0) { 626 try { 627 int start = ok + 3; 628 int end = response.indexOf(" ", start); 629 String intString; 630 if (end > 0) { 631 intString = response.substring(start, end); 632 } else { 633 intString = response.substring(start); 634 } 635 message.setSize(Integer.parseInt(intString)); 636 } catch (NumberFormatException e) { 637 // We tried 638 } 639 } 640 InputStream in = mTransport.getInputStream(); 641 if (DEBUG_LOG_RAW_STREAM && MailActivityEmail.DEBUG) { 642 in = new LoggingInputStream(in); 643 } 644 message.parse(new Pop3ResponseInputStream(in), callback); 645 } 646 catch (MessagingException me) { 647 /* 648 * If we're only downloading headers it's possible 649 * we'll get a broken MIME message which we're not 650 * real worried about. If we've downloaded the body 651 * and can't parse it we need to let the user know. 652 */ 653 if (lines == -1) { 654 throw me; 655 } 656 } 657 } 658 } 659 660 @Override 661 public Flag[] getPermanentFlags() { 662 return PERMANENT_FLAGS; 663 } 664 665 @Override 666 public void appendMessages(Message[] messages) { 667 } 668 669 @Override 670 public void delete(boolean recurse) { 671 } 672 673 @Override 674 public Message[] expunge() { 675 return null; 676 } 677 678 public void deleteMessage(Message message) throws MessagingException { 679 mOneMessage[0] = message; 680 setFlags(mOneMessage, PERMANENT_FLAGS, true); 681 } 682 683 @Override 684 public void setFlags(Message[] messages, Flag[] flags, boolean value) 685 throws MessagingException { 686 if (!value || !Utility.arrayContains(flags, Flag.DELETED)) { 687 /* 688 * The only flagging we support is setting the Deleted flag. 689 */ 690 return; 691 } 692 try { 693 for (Message message : messages) { 694 try { 695 String uid = message.getUid(); 696 int msgNum = mUidToMsgNumMap.get(uid); 697 executeSimpleCommand(String.format("DELE %s", msgNum)); 698 // Remove from the maps 699 mMsgNumToMsgMap.remove(msgNum); 700 mUidToMsgNumMap.remove(uid); 701 } catch (MessagingException e) { 702 // A failed deletion isn't a problem 703 } 704 } 705 } 706 catch (IOException ioe) { 707 mTransport.close(); 708 if (MailActivityEmail.DEBUG) { 709 Log.d(Logging.LOG_TAG, ioe.toString()); 710 } 711 throw new MessagingException("setFlags()", ioe); 712 } 713 } 714 715 @Override 716 public void copyMessages(Message[] msgs, Folder folder, MessageUpdateCallbacks callbacks) { 717 throw new UnsupportedOperationException("copyMessages is not supported in POP3"); 718 } 719 720 private Pop3Capabilities getCapabilities() throws IOException { 721 Pop3Capabilities capabilities = new Pop3Capabilities(); 722 try { 723 String response = executeSimpleCommand("CAPA"); 724 while ((response = mTransport.readLine(true)) != null) { 725 if (response.equals(".")) { 726 break; 727 } else if (response.equalsIgnoreCase("STLS")){ 728 capabilities.stls = true; 729 } 730 } 731 } 732 catch (MessagingException me) { 733 /* 734 * The server may not support the CAPA command, so we just eat this Exception 735 * and allow the empty capabilities object to be returned. 736 */ 737 } 738 return capabilities; 739 } 740 741 /** 742 * Send a single command and wait for a single line response. Reopens the connection, 743 * if it is closed. Leaves the connection open. 744 * 745 * @param command The command string to send to the server. 746 * @return Returns the response string from the server. 747 */ 748 private String executeSimpleCommand(String command) throws IOException, MessagingException { 749 return executeSensitiveCommand(command, null); 750 } 751 752 /** 753 * Send a single command and wait for a single line response. Reopens the connection, 754 * if it is closed. Leaves the connection open. 755 * 756 * @param command The command string to send to the server. 757 * @param sensitiveReplacement If the command includes sensitive data (e.g. authentication) 758 * please pass a replacement string here (for logging). 759 * @return Returns the response string from the server. 760 */ 761 private String executeSensitiveCommand(String command, String sensitiveReplacement) 762 throws IOException, MessagingException { 763 open(OpenMode.READ_WRITE); 764 765 if (command != null) { 766 mTransport.writeLine(command, sensitiveReplacement); 767 } 768 769 String response = mTransport.readLine(true); 770 771 if (response.length() > 1 && response.charAt(0) == '-') { 772 throw new MessagingException(response); 773 } 774 775 return response; 776 } 777 778 @Override 779 public boolean equals(Object o) { 780 if (o instanceof Pop3Folder) { 781 return ((Pop3Folder) o).mName.equals(mName); 782 } 783 return super.equals(o); 784 } 785 786 @Override 787 @VisibleForTesting 788 public boolean isOpen() { 789 return mTransport.isOpen(); 790 } 791 792 @Override 793 public Message createMessage(String uid) { 794 return new Pop3Message(uid, this); 795 } 796 797 @Override 798 public Message[] getMessages(SearchParams params, MessageRetrievalListener listener) { 799 return null; 800 } 801 } 802 803 public static class Pop3Message extends MimeMessage { 804 public Pop3Message(String uid, Pop3Folder folder) { 805 mUid = uid; 806 mFolder = folder; 807 mSize = -1; 808 } 809 810 public void setSize(int size) { 811 mSize = size; 812 } 813 814 @Override 815 public void parse(InputStream in) throws IOException, MessagingException { 816 super.parse(in); 817 } 818 819 @Override 820 public void setFlag(Flag flag, boolean set) throws MessagingException { 821 super.setFlag(flag, set); 822 mFolder.setFlags(new Message[] { this }, new Flag[] { flag }, set); 823 } 824 } 825 826 /** 827 * POP3 Capabilities as defined in RFC 2449. This is not a complete list of CAPA 828 * responses - just those that we use in this client. 829 */ 830 class Pop3Capabilities { 831 /** The STLS (start TLS) command is supported */ 832 public boolean stls; 833 834 @Override 835 public String toString() { 836 return String.format("STLS %b", stls); 837 } 838 } 839 840 // TODO figure out what is special about this and merge it into MailTransport 841 class Pop3ResponseInputStream extends InputStream { 842 private final InputStream mIn; 843 private boolean mStartOfLine = true; 844 private boolean mFinished; 845 846 public Pop3ResponseInputStream(InputStream in) { 847 mIn = in; 848 } 849 850 @Override 851 public int read() throws IOException { 852 if (mFinished) { 853 return -1; 854 } 855 int d = mIn.read(); 856 if (mStartOfLine && d == '.') { 857 d = mIn.read(); 858 if (d == '\r') { 859 mFinished = true; 860 mIn.read(); 861 return -1; 862 } 863 } 864 865 mStartOfLine = (d == '\n'); 866 867 return d; 868 } 869 } 870} 871