Fix problem with unencoded data in URI sent to EAS
* We need to encode the itemId and collectionId for SmartForward and
SmartReply
* Add unit test for the fix
* Small change + test to EasSyncService usage of URI encoding to use
a non-deprecated method
Bug: 2787725
Change-Id: I428b308b56cc359b8cdd9e42bc3f42c65b6797dc
diff --git a/src/com/android/exchange/EasOutboxService.java b/src/com/android/exchange/EasOutboxService.java
index 34b6d7f..b0b39b7 100644
--- a/src/com/android/exchange/EasOutboxService.java
+++ b/src/com/android/exchange/EasOutboxService.java
@@ -36,6 +36,7 @@
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
+import android.net.Uri;
import android.os.RemoteException;
import java.io.File;
@@ -71,6 +72,11 @@
}
}
+ /*package*/ String generateSmartSendCmd(boolean reply, String itemId, String collectionId) {
+ return (reply ? "SmartReply" : "SmartForward") + "&ItemId=" + Uri.encode(itemId) +
+ "&CollectionId=" + Uri.encode(collectionId);
+ }
+
/**
* Send a single message via EAS
* Note that we mark messages SEND_FAILED when there is a permanent failure, rather than an
@@ -130,11 +136,12 @@
new InputStreamEntity(inputStream, tmpFile.length());
// Create the appropriate command and POST it to the server
- String cmd = "SendMail&SaveInSent=T";
+ String cmd = "SendMail";
if (smartSend) {
- cmd = reply ? "SmartReply" : "SmartForward";
- cmd += "&ItemId=" + itemId + "&CollectionId=" + collectionId + "&SaveInSent=T";
+ cmd = generateSmartSendCmd(reply, itemId, collectionId);
}
+ cmd += "&SaveInSent=T";
+
userLog("Send cmd: " + cmd);
HttpResponse resp = sendHttpClientPost(cmd, inputEntity, SEND_MAIL_TIMEOUT);
diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java
index a4464dc..f58604c 100644
--- a/src/com/android/exchange/EasSyncService.java
+++ b/src/com/android/exchange/EasSyncService.java
@@ -77,6 +77,7 @@
import android.content.Context;
import android.content.Entity;
import android.database.Cursor;
+import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -94,7 +95,6 @@
import java.io.InputStream;
import java.lang.Thread.State;
import java.net.URI;
-import java.net.URLEncoder;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.HashMap;
@@ -185,7 +185,7 @@
protected String mDeviceId = null;
/*package*/ String mDeviceType = "Android";
/*package*/ String mAuthString = null;
- private String mCmdString = null;
+ /*package*/ String mCmdString = null;
public String mHostAddress;
public String mUserName;
public String mPassword;
@@ -1099,17 +1099,16 @@
* in all HttpPost commands. This should be called if these strings are null, or if mUserName
* and/or mPassword are changed
*/
- @SuppressWarnings("deprecation")
private void cacheAuthAndCmdString() {
- String safeUserName = URLEncoder.encode(mUserName);
+ String safeUserName = Uri.encode(mUserName);
String cs = mUserName + ':' + mPassword;
mAuthString = "Basic " + Base64.encodeToString(cs.getBytes(), Base64.NO_WRAP);
mCmdString = "&User=" + safeUserName + "&DeviceId=" + mDeviceId +
"&DeviceType=" + mDeviceType;
}
- private String makeUriString(String cmd, String extra) throws IOException {
- // Cache the authentication string and the command string
+ /*package*/ String makeUriString(String cmd, String extra) throws IOException {
+ // Cache the authentication string and the command string
if (mAuthString == null || mCmdString == null) {
cacheAuthAndCmdString();
}
diff --git a/tests/src/com/android/exchange/EasOutboxServiceTests.java b/tests/src/com/android/exchange/EasOutboxServiceTests.java
new file mode 100644
index 0000000..df6bd6c
--- /dev/null
+++ b/tests/src/com/android/exchange/EasOutboxServiceTests.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.exchange;
+
+import com.android.email.provider.EmailContent.Mailbox;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+
+/**
+ * You can run this entire test case with:
+ * runtest -c com.android.exchange.EasOutboxServiceTests email
+ */
+
+public class EasOutboxServiceTests extends AndroidTestCase {
+
+ Context mMockContext;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mMockContext = getContext();
+ }
+
+ public void testGenerateSmartSendCmd() {
+ EasOutboxService svc = new EasOutboxService(mMockContext, new Mailbox());
+ // Test encoding of collection id
+ String cmd = svc.generateSmartSendCmd(true, "1339085683659694034", "Mail:^f");
+ assertEquals("SmartReply&ItemId=1339085683659694034&CollectionId=Mail%3A%5Ef", cmd);
+ // Test encoding of item id
+ cmd = svc.generateSmartSendCmd(false, "14:3", "6");
+ assertEquals("SmartForward&ItemId=14%3A3&CollectionId=6", cmd);
+ }
+}
diff --git a/tests/src/com/android/exchange/EasSyncServiceTests.java b/tests/src/com/android/exchange/EasSyncServiceTests.java
index 2f070d5..320a1d1 100644
--- a/tests/src/com/android/exchange/EasSyncServiceTests.java
+++ b/tests/src/com/android/exchange/EasSyncServiceTests.java
@@ -25,6 +25,7 @@
import android.content.Context;
import android.test.AndroidTestCase;
+import android.util.Base64;
import java.io.File;
import java.io.IOException;
@@ -35,6 +36,12 @@
*/
public class EasSyncServiceTests extends AndroidTestCase {
+ static private final String USER = "user";
+ static private final String PASSWORD = "password";
+ static private final String HOST = "xxx.host.zzz";
+ static private final String ID = "id";
+ static private final String TYPE = "type";
+
Context mMockContext;
@Override
@@ -130,4 +137,39 @@
assertEquals(Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE,
Eas.getProtocolVersionDouble(Eas.SUPPORTED_PROTOCOL_EX2007_SP1));
}
+
+ private EasSyncService setupService(String user) {
+ EasSyncService svc = new EasSyncService();
+ svc.mUserName = user;
+ svc.mPassword = PASSWORD;
+ svc.mDeviceId = ID;
+ svc.mDeviceType = TYPE;
+ svc.mHostAddress = HOST;
+ return svc;
+ }
+
+ public void testMakeUriString() throws IOException {
+ // Simple user name and command
+ EasSyncService svc = setupService(USER);
+ String uriString = svc.makeUriString("OPTIONS", null);
+ // These next two should now be cached
+ assertNotNull(svc.mAuthString);
+ assertNotNull(svc.mCmdString);
+ assertEquals("Basic " + Base64.encodeToString((USER+":"+PASSWORD).getBytes(),
+ Base64.NO_WRAP), svc.mAuthString);
+ assertEquals("&User=" + USER + "&DeviceId=" + ID + "&DeviceType=" + TYPE, svc.mCmdString);
+ assertEquals("https://" + HOST + "/Microsoft-Server-ActiveSync?Cmd=OPTIONS" +
+ svc.mCmdString, uriString);
+ // User name that requires encoding
+ String user = "name_with_underscore@foo%bar.com";
+ svc = setupService(user);
+ uriString = svc.makeUriString("OPTIONS", null);
+ assertEquals("Basic " + Base64.encodeToString((user+":"+PASSWORD).getBytes(),
+ Base64.NO_WRAP), svc.mAuthString);
+ String safeUserName = "name_with_underscore%40foo%25bar.com";
+ assertEquals("&User=" + safeUserName + "&DeviceId=" + ID + "&DeviceType=" + TYPE,
+ svc.mCmdString);
+ assertEquals("https://" + HOST + "/Microsoft-Server-ActiveSync?Cmd=OPTIONS" +
+ svc.mCmdString, uriString);
+ }
}