private String executeSimpleCommand(String command, boolean sensitive) throws MessagingException { try { open(Folder.OPEN_MODE_RW); if (command != null) { if (K9MailLib.isDebug() && DEBUG_PROTOCOL_POP3) { if (sensitive && !K9MailLib.isDebugSensitive()) { Log.d(LOG_TAG, ">>> " + "[Command Hidden, Enable Sensitive Debug Logging To Show]"); } else { Log.d(LOG_TAG, ">>> " + command); } } writeLine(command); } String response = readLine(); if (response.length() == 0 || response.charAt(0) != '+') { throw new Pop3ErrorResponse(response); } return response; } catch (MessagingException me) { throw me; } catch (Exception e) { closeIO(); throw new MessagingException("Unable to execute POP3 command", e); } }
private void indexMessage(int msgNum, Pop3Message message) { if (K9MailLib.isDebug() && DEBUG_PROTOCOL_POP3) { Log.d(LOG_TAG, "Adding index for UID " + message.getUid() + " to msgNum " + msgNum); } mMsgNumToMsgMap.put(msgNum, message); mUidToMsgMap.put(message.getUid(), message); mUidToMsgNumMap.put(message.getUid(), msgNum); }
private void indexUids(List<String> uids) throws MessagingException, IOException { Set<String> unindexedUids = new HashSet<String>(); for (String uid : uids) { if (mUidToMsgMap.get(uid) == null) { if (K9MailLib.isDebug() && DEBUG_PROTOCOL_POP3) { Log.d(LOG_TAG, "Need to index UID " + uid); } unindexedUids.add(uid); } } if (unindexedUids.isEmpty()) { return; } /* * If we are missing uids in the cache the only sure way to * get them is to do a full UIDL list. A possible optimization * would be trying UIDL for the latest X messages and praying. */ String response = executeSimpleCommand(UIDL_COMMAND); while ((response = readLine()) != null) { if (response.equals(".")) { break; } String[] uidParts = response.split(" +"); // Ignore messages without a unique-id if (uidParts.length >= 2) { Integer msgNum = Integer.valueOf(uidParts[0]); String msgUid = uidParts[1]; if (unindexedUids.contains(msgUid)) { if (K9MailLib.isDebug() && DEBUG_PROTOCOL_POP3) { Log.d(LOG_TAG, "Got msgNum " + msgNum + " for UID " + msgUid); } Pop3Message message = mUidToMsgMap.get(msgUid); if (message == null) { message = new Pop3Message(msgUid, this); } indexMessage(msgNum, message); } } } }
List<ImapResponse> readStatusResponse( String tag, String commandToLog, String logId, UntaggedHandler untaggedHandler) throws IOException, NegativeImapResponseException { List<ImapResponse> responses = new ArrayList<ImapResponse>(); ImapResponse response; do { response = readResponse(); if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) { Log.v(LOG_TAG, logId + "<<<" + response); } if (response.getTag() != null && !response.getTag().equalsIgnoreCase(tag)) { Log.w( LOG_TAG, "After sending tag " + tag + ", got tag response from previous command " + response + " for " + logId); Iterator<ImapResponse> responseIterator = responses.iterator(); while (responseIterator.hasNext()) { ImapResponse delResponse = responseIterator.next(); if (delResponse.getTag() != null || delResponse.size() < 2 || (!equalsIgnoreCase(delResponse.get(1), Responses.EXISTS) && !equalsIgnoreCase(delResponse.get(1), Responses.EXPUNGE))) { responseIterator.remove(); } } response = null; continue; } if (untaggedHandler != null) { untaggedHandler.handleAsyncUntaggedResponse(response); } responses.add(response); } while (response == null || response.getTag() == null); if (response.size() < 1 || !equalsIgnoreCase(response.get(0), Responses.OK)) { throw new NegativeImapResponseException( "Command: " + commandToLog + "; response: " + response.toString(), response.getAlertText()); } return responses; }
private String readLine() throws IOException { StringBuilder sb = new StringBuilder(); int d = mIn.read(); if (d == -1) { throw new IOException("End of stream reached while trying to read line."); } do { if (((char) d) == '\r') { continue; } else if (((char) d) == '\n') { break; } else { sb.append((char) d); } } while ((d = mIn.read()) != -1); String ret = sb.toString(); if (K9MailLib.isDebug() && DEBUG_PROTOCOL_POP3) { Log.d(LOG_TAG, "<<< " + ret); } return ret; }
private int getMessageCount(boolean read) throws MessagingException { String isRead; int messageCount = 0; Map<String, String> headers = new HashMap<String, String>(); String messageBody; if (read) { isRead = "True"; } else { isRead = "False"; } messageBody = store.getMessageCountXml(isRead); headers.put("Brief", "t"); DataSet dataset = store.processRequest(this.mFolderUrl, "SEARCH", messageBody, headers); if (dataset != null) { messageCount = dataset.getMessageCount(); } if (K9MailLib.isDebug() && DEBUG_PROTOCOL_WEBDAV) { Log.v(LOG_TAG, "Counted messages and webdav returned: " + messageCount); } return messageCount; }
void autoconfigureFolders(final ImapConnection connection) throws IOException, MessagingException { if (!connection.hasCapability(Capabilities.SPECIAL_USE)) { if (K9MailLib.isDebug()) { Log.d(LOG_TAG, "No detected folder auto-configuration methods."); } return; } if (K9MailLib.isDebug()) { Log.d(LOG_TAG, "Folder auto-configuration: Using RFC6154/SPECIAL-USE."); } String command = String.format( "LIST (SPECIAL-USE) \"\" %s", ImapUtility.encodeString(getCombinedPrefix() + "*")); List<ImapResponse> responses = connection.executeSimpleCommand(command); List<ListResponse> listResponses = ListResponse.parseList(responses); for (ListResponse listResponse : listResponses) { String decodedFolderName; try { decodedFolderName = folderNameCodec.decode(listResponse.getName()); } catch (CharacterCodingException e) { Log.w( LOG_TAG, "Folder name not correctly encoded with the UTF-7 variant " + "as defined by RFC 3501: " + listResponse.getName(), e); // We currently just skip folders with malformed names. continue; } if (pathDelimiter == null) { pathDelimiter = listResponse.getHierarchyDelimiter(); combinedPrefix = null; } if (listResponse.hasAttribute("\\Archive") || listResponse.hasAttribute("\\All")) { mStoreConfig.setArchiveFolderName(decodedFolderName); if (K9MailLib.isDebug()) { Log.d(LOG_TAG, "Folder auto-configuration detected Archive folder: " + decodedFolderName); } } else if (listResponse.hasAttribute("\\Drafts")) { mStoreConfig.setDraftsFolderName(decodedFolderName); if (K9MailLib.isDebug()) { Log.d(LOG_TAG, "Folder auto-configuration detected Drafts folder: " + decodedFolderName); } } else if (listResponse.hasAttribute("\\Sent")) { mStoreConfig.setSentFolderName(decodedFolderName); if (K9MailLib.isDebug()) { Log.d(LOG_TAG, "Folder auto-configuration detected Sent folder: " + decodedFolderName); } } else if (listResponse.hasAttribute("\\Junk")) { mStoreConfig.setSpamFolderName(decodedFolderName); if (K9MailLib.isDebug()) { Log.d(LOG_TAG, "Folder auto-configuration detected Spam folder: " + decodedFolderName); } } else if (listResponse.hasAttribute("\\Trash")) { mStoreConfig.setTrashFolderName(decodedFolderName); if (K9MailLib.isDebug()) { Log.d(LOG_TAG, "Folder auto-configuration detected Trash folder: " + decodedFolderName); } } } }
@Override public void onCreate() { if (K9.DEVELOPER_MODE) { StrictMode.enableDefaults(); } PRNGFixes.apply(); super.onCreate(); app = this; sIsDebuggable = ((getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0); K9MailLib.setDebugStatus( new K9MailLib.DebugStatus() { @Override public boolean enabled() { return DEBUG; } @Override public boolean debugSensitive() { return DEBUG_SENSITIVE; } }); checkCachedDatabaseVersion(); Preferences prefs = Preferences.getPreferences(this); loadPrefs(prefs); /* * We have to give MimeMessage a temp directory because File.createTempFile(String, String) * doesn't work in Android and MimeMessage does not have access to a Context. */ BinaryTempFileBody.setTempDirectory(getCacheDir()); LocalKeyStore.setKeyStoreLocation(getDir("KeyStore", MODE_PRIVATE).toString()); /* * Enable background sync of messages */ setServicesEnabled(this); registerReceivers(); MessagingController.getInstance(this) .addListener( new MessagingListener() { private void broadcastIntent( String action, Account account, String folder, Message message) { try { Uri uri = Uri.parse( "email://messages/" + account.getAccountNumber() + "/" + Uri.encode(folder) + "/" + Uri.encode(message.getUid())); Intent intent = new Intent(action, uri); intent.putExtra(K9.Intents.EmailReceived.EXTRA_ACCOUNT, account.getDescription()); intent.putExtra(K9.Intents.EmailReceived.EXTRA_FOLDER, folder); intent.putExtra(K9.Intents.EmailReceived.EXTRA_SENT_DATE, message.getSentDate()); intent.putExtra( K9.Intents.EmailReceived.EXTRA_FROM, Address.toString(message.getFrom())); intent.putExtra( K9.Intents.EmailReceived.EXTRA_TO, Address.toString(message.getRecipients(Message.RecipientType.TO))); intent.putExtra( K9.Intents.EmailReceived.EXTRA_CC, Address.toString(message.getRecipients(Message.RecipientType.CC))); intent.putExtra( K9.Intents.EmailReceived.EXTRA_BCC, Address.toString(message.getRecipients(Message.RecipientType.BCC))); intent.putExtra(K9.Intents.EmailReceived.EXTRA_SUBJECT, message.getSubject()); intent.putExtra( K9.Intents.EmailReceived.EXTRA_FROM_SELF, account.isAnIdentity(message.getFrom())); K9.this.sendBroadcast(intent); if (K9.DEBUG) Log.d( K9.LOG_TAG, "Broadcasted: action=" + action + " account=" + account.getDescription() + " folder=" + folder + " message uid=" + message.getUid()); } catch (MessagingException e) { Log.w( K9.LOG_TAG, "Error: action=" + action + " account=" + account.getDescription() + " folder=" + folder + " message uid=" + message.getUid()); } } private void updateUnreadWidget() { try { UnreadWidgetProvider.updateUnreadCount(K9.this); } catch (Exception e) { if (K9.DEBUG) { Log.e(LOG_TAG, "Error while updating unread widget(s)", e); } } } @Override public void synchronizeMailboxRemovedMessage( Account account, String folder, Message message) { broadcastIntent( K9.Intents.EmailReceived.ACTION_EMAIL_DELETED, account, folder, message); updateUnreadWidget(); } @Override public void messageDeleted(Account account, String folder, Message message) { broadcastIntent( K9.Intents.EmailReceived.ACTION_EMAIL_DELETED, account, folder, message); updateUnreadWidget(); } @Override public void synchronizeMailboxNewMessage( Account account, String folder, Message message) { broadcastIntent( K9.Intents.EmailReceived.ACTION_EMAIL_RECEIVED, account, folder, message); updateUnreadWidget(); } @Override public void folderStatusChanged( Account account, String folderName, int unreadMessageCount) { updateUnreadWidget(); // let observers know a change occurred Intent intent = new Intent(K9.Intents.EmailReceived.ACTION_REFRESH_OBSERVER, null); intent.putExtra(K9.Intents.EmailReceived.EXTRA_ACCOUNT, account.getDescription()); intent.putExtra(K9.Intents.EmailReceived.EXTRA_FOLDER, folderName); K9.this.sendBroadcast(intent); } }); notifyObservers(); }
/** * Fetches the body of the given message, limiting the downloaded data to the specified number * of lines if possible. * * <p>If lines is -1 the entire message is fetched. This is implemented with RETR for lines = -1 * or TOP for any other value. If the server does not support TOP, RETR is used instead. */ private void fetchBody(Pop3Message message, int lines) throws IOException, MessagingException { String response = null; // Try hard to use the TOP command if we're not asked to download the whole message. if (lines != -1 && (!mTopNotSupported || mCapabilities.top)) { try { if (K9MailLib.isDebug() && DEBUG_PROTOCOL_POP3 && !mCapabilities.top) { Log.d( LOG_TAG, "This server doesn't support the CAPA command. " + "Checking to see if the TOP command is supported nevertheless."); } response = executeSimpleCommand( String.format( Locale.US, TOP_COMMAND + " %d %d", mUidToMsgNumMap.get(message.getUid()), lines)); // TOP command is supported. Remember this for the next time. mCapabilities.top = true; } catch (Pop3ErrorResponse e) { if (mCapabilities.top) { // The TOP command should be supported but something went wrong. throw e; } else { if (K9MailLib.isDebug() && DEBUG_PROTOCOL_POP3) { Log.d( LOG_TAG, "The server really doesn't support the TOP " + "command. Using RETR instead."); } // Don't try to use the TOP command again. mTopNotSupported = true; } } } if (response == null) { executeSimpleCommand( String.format(Locale.US, RETR_COMMAND + " %d", mUidToMsgNumMap.get(message.getUid()))); } try { message.parse(new Pop3ResponseInputStream(mIn)); // TODO: if we've received fewer lines than requested we also have the complete message. if (lines == -1 || !mCapabilities.top) { message.setFlag(Flag.X_DOWNLOADED_FULL, true); } } catch (MessagingException me) { /* * If we're only downloading headers it's possible * we'll get a broken MIME message which we're not * real worried about. If we've downloaded the body * and can't parse it we need to let the user know. */ if (lines == -1) { throw me; } } }