From 75868b5aa4a831723f18e121a77b7ac56a2418d5 Mon Sep 17 00:00:00 2001 From: Daniel Applebaum Date: Sun, 25 Oct 2009 02:58:26 +0000 Subject: [PATCH] Enhancements to WebDAV (Exchange) capabilities: 1) Automatically add / separators if not supplied by user. Fixes Issue 290 2) Enable Move and Copy. 3) Enable setting a message to unread state. 4) Set authentication header for downloading and sending messages, so that those functions work with sites using Basic authentication. 5) Don't swallow log Exceptions. Instead, allow Exceptions to percolate up to higher levels so that they can be logged into K9mail-errors. 6) Provide appendMessages function, so that Drafts get stored on the server. 7) Enable server-side message deletion, using user-selected Trash folder. --- src/com/android/email/mail/Store.java | 8 + .../android/email/mail/store/WebDavStore.java | 562 ++++++++++++------ .../email/mail/transport/WebDavTransport.java | 80 +-- 3 files changed, 395 insertions(+), 255 deletions(-) diff --git a/src/com/android/email/mail/Store.java b/src/com/android/email/mail/Store.java index e8cf01734..b8d7390ba 100644 --- a/src/com/android/email/mail/Store.java +++ b/src/com/android/email/mail/Store.java @@ -89,6 +89,14 @@ public abstract class Store { public boolean isPushCapable() { return false; } + public boolean isSendCapable() + { + return false; + } + + public void sendMessages(Message[] messages) throws MessagingException + { + } public Pusher getPusher(PushReceiver receiver, List names) { diff --git a/src/com/android/email/mail/store/WebDavStore.java b/src/com/android/email/mail/store/WebDavStore.java index 2d22b3dfb..c155d717c 100644 --- a/src/com/android/email/mail/store/WebDavStore.java +++ b/src/com/android/email/mail/store/WebDavStore.java @@ -1,5 +1,6 @@ package com.android.email.mail.store; +import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -10,6 +11,7 @@ import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; +import java.net.URLEncoder; import java.security.GeneralSecurityException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; @@ -65,7 +67,10 @@ import com.android.email.mail.Message; import com.android.email.mail.MessageRetrievalListener; import com.android.email.mail.MessagingException; import com.android.email.mail.Store; +import com.android.email.mail.Folder.FolderType; +import com.android.email.mail.Folder.OpenMode; import com.android.email.mail.internet.MimeMessage; +import com.android.email.mail.transport.EOLConvertingOutputStream; import com.android.email.mail.transport.TrustedSocketFactory; /** @@ -97,6 +102,9 @@ public class WebDavStore extends Store { private URI mUri; /* Stores the Uniform Resource Indicator with all connection info */ private String mRedirectUrl; private String mAuthString; + private static String DAV_MAIL_SEND_FOLDER = "##DavMailSubmissionURI##"; + private static String DAV_MAIL_TMP_FOLDER = "drafts"; + private CookieStore mAuthCookies; /* Stores cookies from authentication */ private boolean mAuthenticated = false; /* Stores authentication state */ @@ -175,15 +183,13 @@ public class WebDavStore extends Store { } } } - - if (mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED || - mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED || - mConnectionSecurity == CONNECTION_SECURITY_TLS_OPTIONAL || - mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) { - this.mUrl = "https://" + mHost + ":" + mUri.getPort() + mPath; - } else { - this.mUrl = "http://" + mHost + ":" + mUri.getPort() + mPath; + String path = mPath; + if (path.length() > 0 && path.startsWith("/") == false) + { + path = "/" + mPath; } + + this.mUrl = getRoot() + path; if (mUri.getUserInfo() != null) { String[] userInfoParts = mUri.getUserInfo().split(":", 2); @@ -202,6 +208,21 @@ public class WebDavStore extends Store { mSecure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED; mAuthString = "Basic " + Utility.base64Encode(mUsername + ":" + mPassword); } + + private String getRoot() + { + String root; + if (mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED || + mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED || + mConnectionSecurity == CONNECTION_SECURITY_TLS_OPTIONAL || + mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) { + root = "https"; + } else { + root = "http"; + } + root += "://" + mHost + ":" + mUri.getPort(); + return root; + } @Override @@ -230,7 +251,9 @@ public class WebDavStore extends Store { urlLength = folderUrls.length; for (int i = 0; i < urlLength; i++) { +// Log.i(Email.LOG_TAG, "folderUrls[" + i + "] = '" + folderUrls[i]); String[] urlParts = folderUrls[i].split("/"); +// Log.i(Email.LOG_TAG, "urlParts = " + urlParts); String folderName = urlParts[urlParts.length - 1]; String fullPathName = ""; WebDavFolder wdFolder; @@ -253,7 +276,7 @@ public class WebDavStore extends Store { } } - wdFolder = new WebDavFolder(folderName); + wdFolder = new WebDavFolder(this, folderName); wdFolder.setUrl(folderUrls[i]); folderList.add(wdFolder); this.mFolderList.put(folderName, wdFolder); @@ -267,11 +290,27 @@ public class WebDavStore extends Store { WebDavFolder folder; if ((folder = this.mFolderList.get(name)) == null) { - folder = new WebDavFolder(name); + folder = new WebDavFolder(this, name); } return folder; } + + public Folder getSendSpoolFolder() throws MessagingException + { + return getFolder(DAV_MAIL_SEND_FOLDER); + } + + @Override + public boolean isMoveCapable() { + return true; + } + + @Override + public boolean isCopyCapable() + { + return true; + } /*************************************************************** * WebDAV XML Request body retrieval functions @@ -384,7 +423,7 @@ public class WebDavStore extends Store { return buffer.toString(); } - private String getMarkMessagesReadXml(String[] urls) { + private String getMarkMessagesReadXml(String[] urls, boolean read) { StringBuffer buffer = new StringBuffer(600); buffer.append("\r\n"); buffer.append("\r\n"); @@ -395,12 +434,32 @@ public class WebDavStore extends Store { buffer.append("\r\n"); buffer.append("\r\n"); buffer.append(" \r\n"); - buffer.append(" 1\r\n"); + buffer.append(" " + (read ? "1" : "0") + "\r\n"); buffer.append(" \r\n"); buffer.append("\r\n"); buffer.append("\r\n"); return buffer.toString(); } + + // For flag: + http://www.devnewsgroups.net/group/microsoft.public.exchange.development/topic27175.aspx + //"1" & _ + + private String getMoveOrCopyMessagesReadXml(String[] urls, boolean isMove) { + + String action = (isMove ? "move" : "copy"); + StringBuffer buffer = new StringBuffer(600); + buffer.append("\r\n"); + buffer.append("\r\n"); + buffer.append("\r\n"); + for (int i = 0, count = urls.length; i < count; i++) { + buffer.append(" "+urls[i]+"\r\n"); + } + buffer.append("\r\n"); + + buffer.append("\r\n"); + return buffer.toString(); + } /*************************************************************** * Authentication related methods @@ -417,7 +476,7 @@ public class WebDavStore extends Store { //this.mAuthCookies = doAuthentication(this.mUsername, this.mPassword, this.mUrl); } catch (IOException ioe) { Log.e(Email.LOG_TAG, "Error during authentication: " + ioe + "\nStack: " + processException(ioe)); - this.mAuthCookies = null; + throw new MessagingException("Error during authentication", ioe); } if (this.mAuthCookies == null) { @@ -483,7 +542,6 @@ public class WebDavStore extends Store { String url = this.mUrl; String username = this.mUsername; String password = this.mPassword; - CookieStore cookies = null; String[] urlParts = url.split("/"); String finalUrl = ""; String loginUrl = new String(); @@ -524,7 +582,6 @@ public class WebDavStore extends Store { if (this.mAuthPath == null || this.mAuthPath.equals("") || this.mAuthPath.equals("/")) { - HttpGet httpget = new HttpGet(finalUrl); httpclient.addRequestInterceptor(new HttpRequestInterceptor() { public void process(HttpRequest request, HttpContext context) @@ -571,8 +628,6 @@ public class WebDavStore extends Store { } } - /* Post Method */ - HttpPost httppost = new HttpPost(loginUrl); /** Build the POST data to use */ ArrayList pairs = new ArrayList(); @@ -622,6 +677,7 @@ public class WebDavStore extends Store { } catch (UnsupportedEncodingException uee) { Log.e(Email.LOG_TAG, "Error encoding POST data for authentication: " + uee + "\nTrace: " + processException(uee)); + throw new MessagingException("Error encoding POST data for authentication", uee); } } catch (SSLException e) { throw new CertificateValidationException(e.getMessage(), e); @@ -654,6 +710,7 @@ public class WebDavStore extends Store { reg = mHttpClient.getConnectionManager().getSchemeRegistry(); try { + // Log.i(Email.LOG_TAG, "getHttpClient mHost = " + mHost); s = new Scheme("https", new TrustedSocketFactory(mHost, mSecure), 443); } catch (NoSuchAlgorithmException nsa) { Log.e(Email.LOG_TAG, "NoSuchAlgorithmException in getHttpClient: " + nsa); @@ -700,27 +757,6 @@ public class WebDavStore extends Store { */ return mHttpClient; } - - private boolean checkAuth() { - DefaultHttpClient httpclient = mHttpClient; - HttpResponse response; - HttpGet httpget = new HttpGet(mUrl); - try { - response = httpclient.execute(httpget); - } catch (IOException ioe) { - Log.e(Email.LOG_TAG, "Error checking authentication status"); - return false; - } - - HttpEntity entity = response.getEntity(); - int statusCode = response.getStatusLine().getStatusCode(); - - if (statusCode == 401) { - return true; - } - - return false; - } public DefaultHttpClient getTrustedHttpClient() throws KeyManagementException, NoSuchAlgorithmException{ if (mHttpClient == null) { @@ -754,13 +790,8 @@ public class WebDavStore extends Store { return istream; } - try { - httpclient = getHttpClient(); - } catch (MessagingException me) { - Log.e(Email.LOG_TAG, "Generated MessagingException getting HttpClient: " + me); - return istream; - } - + httpclient = getHttpClient(); + try { int statusCode = -1; StringEntity messageEntity = null; @@ -818,12 +849,19 @@ public class WebDavStore extends Store { } } catch (UnsupportedEncodingException uee) { Log.e(Email.LOG_TAG, "UnsupportedEncodingException: " + uee + "\nTrace: " + processException(uee)); + throw new MessagingException("UnsupportedEncodingException", uee); } catch (IOException ioe) { Log.e(Email.LOG_TAG, "IOException: " + ioe + "\nTrace: " + processException(ioe)); + throw new MessagingException("IOException", ioe); } return istream; } + + public String getAuthString() + { + return mAuthString; + } /** * Performs an httprequest to the supplied url using the supplied method. @@ -838,81 +876,55 @@ public class WebDavStore extends Store { private DataSet processRequest(String url, String method, String messageBody, HashMap headers, boolean needsParsing) throws MessagingException { DataSet dataset = new DataSet(); - DefaultHttpClient httpclient; - + if (Email.DEBUG) + { + Log.v(Email.LOG_TAG, "processRequest url = '" + url + "', method = '" + method + "', messageBody = '" + messageBody + "'"); + } + if (url == null || method == null) { return dataset; } + getHttpClient(); + try { - httpclient = getHttpClient(); - } catch (MessagingException me) { - Log.e(Email.LOG_TAG, "Generated MessagingException getting HttpClient: " + me); - return dataset; - } - - try { - /* - int statusCode = -1; - */ StringEntity messageEntity = null; - /* - HttpGeneric httpmethod = new HttpGeneric(url); - HttpResponse response; - HttpEntity entity; - */ if (messageBody != null) { messageEntity = new StringEntity(messageBody); messageEntity.setContentType("text/xml"); // httpmethod.setEntity(messageEntity); } - /* - for (String headerName : headers.keySet()) { - httpmethod.setHeader(headerName, headers.get(headerName)); - } - - httpmethod.setMethod(method); - - response = httpclient.execute(httpmethod); - statusCode = response.getStatusLine().getStatusCode(); - - entity = response.getEntity(); - - if (statusCode < 200 || - statusCode > 300) { - throw new IOException("Error during request processing: "+ - response.getStatusLine().toString()+ "\n\n"+ - getHttpRequestResponse(messageEntity, entity)); - } - - if (entity != null &&*/ InputStream istream = sendRequest(url, method, messageEntity, headers, true); if (istream != null && needsParsing) { try { - /*InputStream istream = entity.getContent();*/ SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser sp = spf.newSAXParser(); XMLReader xr = sp.getXMLReader(); WebDavHandler myHandler = new WebDavHandler(); xr.setContentHandler(myHandler); + xr.parse(new InputSource(istream)); dataset = myHandler.getDataSet(); } catch (SAXException se) { Log.e(Email.LOG_TAG, "SAXException in processRequest() " + se + "\nTrace: " + processException(se)); + throw new MessagingException("SAXException in processRequest() ", se); } catch (ParserConfigurationException pce) { Log.e(Email.LOG_TAG, "ParserConfigurationException in processRequest() " + pce + "\nTrace: " + processException(pce)); + throw new MessagingException("ParserConfigurationException in processRequest() ", pce); } istream.close(); } } catch (UnsupportedEncodingException uee) { Log.e(Email.LOG_TAG, "UnsupportedEncodingException: " + uee + "\nTrace: " + processException(uee)); + throw new MessagingException("UnsupportedEncodingException in processRequest() ", uee); } catch (IOException ioe) { Log.e(Email.LOG_TAG, "IOException: " + ioe + "\nTrace: " + processException(ioe)); + throw new MessagingException("IOException in processRequest() ", ioe); } return dataset; @@ -931,6 +943,39 @@ public class WebDavStore extends Store { return baos.toString(); } + @Override + public boolean isSendCapable() + { + return true; + } + + @Override + public void sendMessages(Message[] messages) throws MessagingException + { + WebDavFolder tmpFolder = (WebDavStore.WebDavFolder)getFolder(DAV_MAIL_TMP_FOLDER); + try + { + tmpFolder.open(OpenMode.READ_WRITE); + Message[] retMessages = tmpFolder.appendWebDavMessages(messages); + + tmpFolder.moveMessages(retMessages, getSendSpoolFolder()); + } + finally + { + if (tmpFolder != null) + { + try + { + tmpFolder.close(false); + } + catch (MessagingException me) + { + Log.e(Email.LOG_TAG, "Caught MessagingException while closing folder " + tmpFolder.getName()); + } + } + } + } + /************************************************************************* * Helper and Inner classes */ @@ -940,58 +985,66 @@ public class WebDavStore extends Store { */ class WebDavFolder extends Folder { private String mName; - private String mLocalUsername; private String mFolderUrl; private boolean mIsOpen = false; private int mMessageCount = 0; private int mUnreadMessageCount = 0; + private WebDavStore store; - public WebDavFolder(String name) { - String[] userParts; - String encodedName = new String(); - try { - String[] urlParts = name.split("/"); - String url = ""; - for (int i = 0, count = urlParts.length; i < count; i++) { - if (i != 0) { - url = url + "/" + java.net.URLEncoder.encode(urlParts[i], "UTF-8"); - } else { - url = java.net.URLEncoder.encode(urlParts[i], "UTF-8"); - } - } - encodedName = url; - } catch (UnsupportedEncodingException uee) { - Log.e(Email.LOG_TAG, "UnsupportedEncodingException URLEncoding folder name, skipping encoded"); - encodedName = name; - } - - encodedName = encodedName.replaceAll("\\+", "%20"); + protected WebDavStore getStore() + { + return store; + } + + + public WebDavFolder(WebDavStore nStore, String name) { + store = nStore; this.mName = name; - userParts = WebDavStore.this.mUsername.split("/", 2); - if (userParts.length > 1) { - this.mLocalUsername = userParts[1]; - } else { - this.mLocalUsername = WebDavStore.this.mUsername; - } - - /** - * In some instances, it is possible that our folder objects have been collected, - * but getPersonalNamespaces() isn't called again (ex. Android destroys the email client). - * Perform an authentication to get the appropriate URLs in place again - */ - try { - getHttpClient(); - } catch (MessagingException me) { - Log.e(Email.LOG_TAG, "MessagingException during authentication for WebDavFolder: " + me); - return; - } - - if (encodedName.equals("INBOX")) { - encodedName = "Inbox"; - } - this.mFolderUrl = WebDavStore.this.mUrl + encodedName; + if (DAV_MAIL_SEND_FOLDER.equals(name)) + { + this.mFolderUrl = getUrl() + "/" + name +"/"; + } + else + { + String encodedName = new String(); + try { + String[] urlParts = name.split("/"); + String url = ""; + for (int i = 0, count = urlParts.length; i < count; i++) { + if (i != 0) { + url = url + "/" + java.net.URLEncoder.encode(urlParts[i], "UTF-8"); + } else { + url = java.net.URLEncoder.encode(urlParts[i], "UTF-8"); + } + } + encodedName = url; + } catch (UnsupportedEncodingException uee) { + Log.e(Email.LOG_TAG, "UnsupportedEncodingException URLEncoding folder name, skipping encoded"); + encodedName = name; + } + + encodedName = encodedName.replaceAll("\\+", "%20"); + + /** + * In some instances, it is possible that our folder objects have been collected, + * but getPersonalNamespaces() isn't called again (ex. Android destroys the email client). + * Perform an authentication to get the appropriate URLs in place again + */ + // TODO: danapple0 - huh? + //getHttpClient(); + + if (encodedName.equals("INBOX")) { + encodedName = "Inbox"; + } + this.mFolderUrl = WebDavStore.this.mUrl; + if (WebDavStore.this.mUrl.endsWith("/") == false) + { + this.mFolderUrl += "/"; + } + this.mFolderUrl += encodedName; + } } public void setUrl(String url) { @@ -1006,8 +1059,57 @@ public class WebDavStore extends Store { this.mIsOpen = true; } + + @Override + public void copyMessages(Message[] messages, Folder folder) throws MessagingException { + moveOrCopyMessages(messages, folder, false); + } + + @Override + public void moveMessages(Message[] messages, Folder folder) throws MessagingException { + moveOrCopyMessages(messages, folder, true); + } + + private void moveOrCopyMessages(Message[] messages, Folder folder, boolean isMove) throws MessagingException { + + if (folder instanceof WebDavFolder == false) + { + throw new MessagingException("moveMessages passed non-WebDavFolder"); + } + String[] uids = new String[messages.length]; - private int getMessageCount(boolean read, CookieStore authCookies) { + for (int i = 0, count = messages.length; i < count; i++) { + uids[i] = messages[i].getUid(); + } + String messageBody = new String(); + HashMap headers = new HashMap(); + HashMap uidToUrl = getMessageUrls(uids); + String[] urls = new String[uids.length]; + + for (int i = 0, count = uids.length; i < count; i++) { + urls[i] = uidToUrl.get(uids[i]); + if (urls[i] == null && messages[i] instanceof WebDavMessage) + { + WebDavMessage wdMessage = (WebDavMessage)messages[i]; + urls[i] = wdMessage.getUrl(); + } + } + + messageBody = getMoveOrCopyMessagesReadXml(urls, isMove); + WebDavFolder destFolder = (WebDavFolder)store.getFolder(folder.getName()); + headers.put("Destination", destFolder.mFolderUrl); + headers.put("Brief", "t"); + headers.put("If-Match", "*"); + String action = (isMove ? "BMOVE" : "BCOPY"); + Log.i(Email.LOG_TAG, "Moving " + messages.length + " messages to " + destFolder.mFolderUrl); + + + + processRequest(mFolderUrl, action, messageBody, headers, false); + + } + + private int getMessageCount(boolean read, CookieStore authCookies) throws MessagingException { String isRead; int messageCount = 0; DataSet dataset = new DataSet(); @@ -1022,14 +1124,11 @@ public class WebDavStore extends Store { messageBody = getMessageCountXml(isRead); headers.put("Brief", "t"); - try { - dataset = processRequest(this.mFolderUrl, "SEARCH", messageBody, headers); - if (dataset != null) { - messageCount = dataset.getMessageCount(); - } - } catch (MessagingException me) { - messageCount = 0; + dataset = processRequest(this.mFolderUrl, "SEARCH", messageBody, headers); + if (dataset != null) { + messageCount = dataset.getMessageCount(); } + return messageCount; } @@ -1107,7 +1206,9 @@ public class WebDavStore extends Store { /** Reverse the message range since 0 index is newest */ start = this.mMessageCount - end; - end = this.mMessageCount - prevStart; + end = start + (end - prevStart); + + //end = this.mMessageCount - prevStart; if (start < 0 || end < 0 || end < start) { throw new MessagingException(String.format("Invalid message set %d %d", start, end)); @@ -1176,7 +1277,7 @@ public class WebDavStore extends Store { return messages; } - private HashMap getMessageUrls(String[] uids) { + private HashMap getMessageUrls(String[] uids) throws MessagingException { HashMap uidToUrl = new HashMap(); HashMap headers = new HashMap(); DataSet dataset = new DataSet(); @@ -1186,39 +1287,35 @@ public class WebDavStore extends Store { messageBody = getMessageUrlsXml(uids); headers.put("Brief", "t"); - try { - dataset = processRequest(this.mFolderUrl, "SEARCH", messageBody, headers); - uidToUrl = dataset.getUidToUrl(); - } catch (MessagingException me) { - Log.e(Email.LOG_TAG, "MessagingException from processRequest in getMessageUrls(): " + me); - } - + dataset = processRequest(this.mFolderUrl, "SEARCH", messageBody, headers); + uidToUrl = dataset.getUidToUrl(); + return uidToUrl; } @Override public void fetch(Message[] messages, FetchProfile fp, MessageRetrievalListener listener) throws MessagingException { - HashMap uidToReadStatus = new HashMap(); if (messages == null || messages.length == 0) { return; } - /** - * Fetch message flag info for the array - */ - if (fp.contains(FetchProfile.Item.FLAGS)) { - fetchFlags(messages, listener); - } - /** * Fetch message envelope information for the array */ if (fp.contains(FetchProfile.Item.ENVELOPE)) { fetchEnvelope(messages, listener); } + /** + * Fetch message flag info for the array + */ + if (fp.contains(FetchProfile.Item.FLAGS)) { + fetchFlags(messages, listener); + } + + if (fp.contains(FetchProfile.Item.BODY_SANE)) { fetchMessages(messages, listener, FETCH_BODY_SANE_SUGGESTED_SIZE / 76); @@ -1228,24 +1325,24 @@ public class WebDavStore extends Store { fetchMessages(messages, listener, -1); } - if (fp.contains(FetchProfile.Item.STRUCTURE)) { - for (int i = 0, count = messages.length; i < count; i++) { - if (!(messages[i] instanceof WebDavMessage)) { - throw new MessagingException("WebDavStore fetch called with non-WebDavMessage"); - } - WebDavMessage wdMessage = (WebDavMessage) messages[i]; - - if (listener != null) { - listener.messageStarted(wdMessage.getUid(), i, count); - } - - wdMessage.setBody(null); - - if (listener != null) { - listener.messageFinished(wdMessage, i, count); - } - } - } +// if (fp.contains(FetchProfile.Item.STRUCTURE)) { +// for (int i = 0, count = messages.length; i < count; i++) { +// if (!(messages[i] instanceof WebDavMessage)) { +// throw new MessagingException("WebDavStore fetch called with non-WebDavMessage"); +// } +// WebDavMessage wdMessage = (WebDavMessage) messages[i]; +// +// if (listener != null) { +// listener.messageStarted(wdMessage.getUid(), i, count); +// } +// +// wdMessage.setBody(null); +// +// if (listener != null) { +// listener.messageFinished(wdMessage, i, count); +// } +// } +// } } /** @@ -1278,18 +1375,22 @@ public class WebDavStore extends Store { */ if (wdMessage.getUrl().equals("")) { wdMessage.setUrl(getMessageUrls(new String[] {wdMessage.getUid()}).get(wdMessage.getUid())); + Log.i(Email.LOG_TAG, "Fetching messages with UID = '" + wdMessage.getUid() + "', URL = '" + wdMessage.getUrl() + "'"); if (wdMessage.getUrl().equals("")) { throw new MessagingException("Unable to get URL for message"); } } try { + Log.i(Email.LOG_TAG, "Fetching message with UID = '" + wdMessage.getUid() + "', URL = '" + wdMessage.getUrl() + "'"); HttpGet httpget = new HttpGet(new URI(wdMessage.getUrl())); HttpResponse response; HttpEntity entity; httpget.setHeader("translate", "f"); - + if (mAuthString != null && mAuthenticated) { + httpget.setHeader("Authorization", mAuthString); + } response = httpclient.execute(httpget); statusCode = response.getStatusLine().getStatusCode(); @@ -1308,7 +1409,6 @@ public class WebDavStore extends Store { StringBuffer buffer = new StringBuffer(); String tempText = new String(); String resultText = new String(); - String bodyBoundary = ""; BufferedReader reader; int currentLines = 0; @@ -1333,10 +1433,13 @@ public class WebDavStore extends Store { } catch (IllegalArgumentException iae) { Log.e(Email.LOG_TAG, "IllegalArgumentException caught " + iae + "\nTrace: " + processException(iae)); + throw new MessagingException("IllegalArgumentException caught", iae); } catch (URISyntaxException use) { Log.e(Email.LOG_TAG, "URISyntaxException caught " + use + "\nTrace: " + processException(use)); + throw new MessagingException("URISyntaxException caught", use); } catch (IOException ioe) { Log.e(Email.LOG_TAG, "Non-success response code loading message, response code was " + statusCode + "\nURL: " + wdMessage.getUrl() + "\nError: " + ioe.getMessage() + "\nTrace: " + processException(ioe)); + throw new MessagingException("Failure code " + statusCode, ioe); } if (listener != null) { @@ -1497,14 +1600,14 @@ public class WebDavStore extends Store { Flag flag = flags[i]; if (flag == Flag.SEEN) { - markServerMessagesRead(uids); + markServerMessagesRead(uids, value); } else if (flag == Flag.DELETED) { deleteServerMessages(uids); } } } - private void markServerMessagesRead(String[] uids) throws MessagingException { + private void markServerMessagesRead(String[] uids, boolean read) throws MessagingException { String messageBody = new String(); HashMap headers = new HashMap(); HashMap uidToUrl = getMessageUrls(uids); @@ -1515,7 +1618,7 @@ public class WebDavStore extends Store { urls[i] = uidToUrl.get(uids[i]); } - messageBody = getMarkMessagesReadXml(urls); + messageBody = getMarkMessagesReadXml(urls, read); headers.put("Brief", "t"); headers.put("If-Match", "*"); @@ -1553,10 +1656,90 @@ public class WebDavStore extends Store { return finalUrl; } - + @Override public void appendMessages(Message[] messages) throws MessagingException { - Log.e(Email.LOG_TAG, "appendMessages() not implmented"); + appendWebDavMessages(messages); + } + + public Message[] appendWebDavMessages(Message[] messages) throws MessagingException { + + Message[] retMessages = new Message[messages.length]; + int ind = 0; + + DefaultHttpClient httpclient = getHttpClient(); + + for (Message message : messages) + { + HttpGeneric httpmethod; + HttpResponse response; + StringEntity bodyEntity; + int statusCode; + + try { + String subject; + + try { + subject = message.getSubject(); + } catch (MessagingException e) { + Log.e(Email.LOG_TAG, "MessagingException while retrieving Subject: " + e); + subject = ""; + } + ByteArrayOutputStream out; + try { + out = new ByteArrayOutputStream(message.getSize()); + } catch (MessagingException e) { + Log.e(Email.LOG_TAG, "MessagingException while getting size of message: " + e); + out = new ByteArrayOutputStream(); + } + open(OpenMode.READ_WRITE); + message.writeTo( + new EOLConvertingOutputStream( + new BufferedOutputStream(out, 1024))); + + bodyEntity = new StringEntity(out.toString(), "UTF-8"); + bodyEntity.setContentType("message/rfc822"); + + String messageURL = mFolderUrl; + if (messageURL.endsWith("/") == false) + { + messageURL += "/"; + } + messageURL += URLEncoder.encode(subject + ".eml"); + + Log.i(Email.LOG_TAG, "Uploading message to " + mFolderUrl); + + httpmethod = new HttpGeneric(messageURL); + httpmethod.setMethod("PUT"); + httpmethod.setEntity(bodyEntity); + + String mAuthString = getAuthString(); + + if (mAuthString != null ) { + httpmethod.setHeader("Authorization", mAuthString); + } + + response = httpclient.execute(httpmethod); + statusCode = response.getStatusLine().getStatusCode(); + + if (statusCode < 200 || + statusCode > 300) { + throw new IOException("Sending Message: Error while trying to append message to " + messageURL + " " + + response.getStatusLine().toString()+ "\n\n"+ + WebDavStore.getHttpRequestResponse(bodyEntity, response.getEntity())); + } + WebDavMessage retMessage = new WebDavMessage(message.getUid(), this); + + retMessage.setUrl(messageURL); + retMessages[ind++] = retMessage; + } + catch (Exception e) + { + throw new MessagingException("Unable to append", e); + } + + } + return retMessages; } @Override @@ -1589,6 +1772,7 @@ public class WebDavStore extends Store { class WebDavMessage extends MimeMessage { private String mUrl = new String(); + WebDavMessage(String uid, Folder folder) throws MessagingException { this.mUid = uid; this.mFolder = folder; @@ -1672,6 +1856,16 @@ public class WebDavStore extends Store { } } + + @Override + public void delete(String trashFolderName) throws MessagingException + { + WebDavFolder wdFolder = (WebDavFolder)getFolder(); + Log.i(Email.LOG_TAG, "Deleting message by moving to " + trashFolderName); + wdFolder.moveMessages(new Message[] { this }, wdFolder.getStore().getFolder(trashFolderName)); + + } + @Override public void setFlag(Flag flag, boolean set) throws MessagingException { super.setFlag(flag, set); @@ -1757,6 +1951,7 @@ public class WebDavStore extends Store { public void addHeader(String field, String value) { String headerName = mHeaderMappings.get(field); + //Log.i(Email.LOG_TAG, "header " + headerName + " = '" + value + "'"); if (headerName != null) { this.mMessageHeaders.put(mHeaderMappings.get(field), value); @@ -1986,6 +2181,11 @@ public class WebDavStore extends Store { */ public HttpGeneric(final String uri) { super(); + + if (Email.DEBUG) + { + Log.v(Email.LOG_TAG, "Starting uri = '" + uri + "'"); + } String[] urlParts = uri.split("/"); int length = urlParts.length; @@ -2015,8 +2215,14 @@ public class WebDavStore extends Store { url = urlParts[i]; } } - + if (Email.DEBUG) + { + Log.v(Email.LOG_TAG, "url = '" + url + "' length = " + url.length() + + ", end = '" + end + "' length = " + end.length()); + } url = url + "/" + end; + + Log.i(Email.LOG_TAG, "url = " + url); setURI(URI.create(url)); } diff --git a/src/com/android/email/mail/transport/WebDavTransport.java b/src/com/android/email/mail/transport/WebDavTransport.java index f2508c946..d63b16796 100644 --- a/src/com/android/email/mail/transport/WebDavTransport.java +++ b/src/com/android/email/mail/transport/WebDavTransport.java @@ -41,6 +41,7 @@ import android.util.Log; import com.android.email.Email; import com.android.email.PeekableInputStream; +import com.android.email.Utility; import com.android.email.codec.binary.Base64; import com.android.email.mail.Address; import com.android.email.mail.AuthenticationFailedException; @@ -94,90 +95,15 @@ public class WebDavTransport extends Transport { Log.d(Email.LOG_TAG, ">>> open called on WebDavTransport "); store.getHttpClient(); } - -// public void sendMessage(Message message) throws MessagingException { -// Address[] from = message.getFrom(); -// -// } public void close() { } - public String generateTempURI(String subject) { - String encodedSubject = URLEncoder.encode(subject); - return store.getUrl() + "/drafts/" + encodedSubject + ".eml"; - } - public String generateSendURI() { - return store.getUrl() + "/##DavMailSubmissionURI##/"; - } - public void sendMessage(Message message) throws MessagingException { - Log.d(Email.LOG_TAG, ">>> sendMessage called."); + + store.sendMessages(new Message[] { message }); - DefaultHttpClient httpclient; - httpclient = store.getHttpClient(); - HttpGeneric httpmethod; - HttpResponse response; - StringEntity bodyEntity; - int statusCode; - String subject; - ByteArrayOutputStream out; - try { - try { - subject = message.getSubject(); - } catch (MessagingException e) { - Log.e(Email.LOG_TAG, "MessagingException while retrieving Subject: " + e); - subject = ""; - } - try { - out = new ByteArrayOutputStream(message.getSize()); - } catch (MessagingException e) { - Log.e(Email.LOG_TAG, "MessagingException while getting size of message: " + e); - out = new ByteArrayOutputStream(); - } - open(); - message.writeTo( - new EOLConvertingOutputStream( - new BufferedOutputStream(out, 1024))); - - bodyEntity = new StringEntity(out.toString(), "UTF-8"); - bodyEntity.setContentType("message/rfc822"); - - httpmethod = store.new HttpGeneric(generateTempURI(subject)); - httpmethod.setMethod("PUT"); - httpmethod.setEntity(bodyEntity); - - response = httpclient.execute(httpmethod); - statusCode = response.getStatusLine().getStatusCode(); - - if (statusCode < 200 || - statusCode > 300) { - throw new IOException("Sending Message: Error while trying to upload to drafts folder: "+ - response.getStatusLine().toString()+ "\n\n"+ - WebDavStore.getHttpRequestResponse(bodyEntity, response.getEntity())); - } - httpmethod = store.new HttpGeneric(generateTempURI(subject)); - httpmethod.setMethod("MOVE"); - httpmethod.setHeader("Destination", generateSendURI()); - - response = httpclient.execute(httpmethod); - statusCode = response.getStatusLine().getStatusCode(); - - if (statusCode < 200 || - statusCode > 300) { - throw new IOException("Sending Message: Error while trying to move finished draft to Outbox: "+ - response.getStatusLine().toString()+ "\n\n"+ - WebDavStore.getHttpRequestResponse(null, response.getEntity())); - } - - } catch (UnsupportedEncodingException uee) { - Log.e(Email.LOG_TAG, "UnsupportedEncodingException in sendMessage() " + uee); - } catch (IOException ioe) { - Log.e(Email.LOG_TAG, "IOException in sendMessage() " + ioe); - throw new MessagingException("Unable to send message"+ioe.getMessage(), ioe); - } - Log.d(Email.LOG_TAG, ">>> getMessageCount finished"); } }