Add download request tokens and enforce destination clear am: 2043f70acb am: 8763c625a3
am: fc156f357a
Change-Id: I929fd3306a7bb8bbc2eeb31eb1732c99ec0fb743
diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java
index 79ee37a..ee81fd0 100644
--- a/telephony/java/android/telephony/MbmsDownloadManager.java
+++ b/telephony/java/android/telephony/MbmsDownloadManager.java
@@ -388,21 +388,10 @@
tempRootDirectory.mkdirs();
setTempFileRootDirectory(tempRootDirectory);
}
-
request.setAppName(mDownloadAppName);
- // Check if the request is a multipart download. If so, validate that the destination is
- // a directory that exists.
- // TODO: figure out what qualifies a request as a multipart download request.
- if (request.getSourceUri().getLastPathSegment() != null &&
- request.getSourceUri().getLastPathSegment().contains("*")) {
- File toFile = new File(request.getDestinationUri().getSchemeSpecificPart());
- if (!toFile.isDirectory()) {
- throw new IllegalArgumentException("Multipart download must specify valid " +
- "destination directory.");
- }
- }
- // TODO: check to make sure destination is clear
- // TODO: write download request token
+
+ checkValidDownloadDestination(request);
+ writeDownloadRequestToken(request);
try {
downloadService.download(request, callback);
} catch (RemoteException e) {
@@ -435,6 +424,7 @@
* <li>ERROR_MSDC_UNKNOWN_REQUEST</li>
*/
public int cancelDownload(DownloadRequest downloadRequest) {
+ // TODO: don't forget to delete the token
return 0;
}
@@ -518,4 +508,54 @@
Log.i(LOG_TAG, "Remote exception while disposing of service");
}
}
+
+ private void writeDownloadRequestToken(DownloadRequest request) {
+ // TODO: figure out when this token eventually gets deleted
+ File tempFileLocation = MbmsUtils.getEmbmsTempFileDirForRequest(mContext, request);
+ if (!tempFileLocation.exists()) {
+ tempFileLocation.mkdirs();
+ }
+ String downloadTokenFileName = request.getHash()
+ + MbmsDownloadReceiver.DOWNLOAD_TOKEN_SUFFIX;
+ File token = new File(tempFileLocation, downloadTokenFileName);
+ if (token.exists()) {
+ Log.w(LOG_TAG, "Download token " + downloadTokenFileName + " already exists");
+ return;
+ }
+ try {
+ if (!token.createNewFile()) {
+ throw new RuntimeException("Failed to create download token for request "
+ + request);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to create download token for request " + request
+ + " due to IOException " + e);
+ }
+ }
+
+ /**
+ * Verifies the following:
+ * If a request is multi-part,
+ * 1. Destination Uri must exist and be a directory
+ * 2. Directory specified must contain no files.
+ * Otherwise
+ * 1. The file specified by the destination Uri must not exist.
+ */
+ private void checkValidDownloadDestination(DownloadRequest request) {
+ File toFile = new File(request.getDestinationUri().getSchemeSpecificPart());
+ if (request.isMultipartDownload()) {
+ if (!toFile.isDirectory()) {
+ throw new IllegalArgumentException("Multipart download must specify valid " +
+ "destination directory.");
+ }
+ if (toFile.listFiles().length > 0) {
+ throw new IllegalArgumentException("Destination directory must be clear of all " +
+ "files.");
+ }
+ } else {
+ if (toFile.exists()) {
+ throw new IllegalArgumentException("Destination file must not exist.");
+ }
+ }
+ }
}
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
index c561741..907b0cb 100644
--- a/telephony/java/android/telephony/mbms/DownloadRequest.java
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -20,15 +20,22 @@
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Base64;
import java.lang.IllegalStateException;
import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
/**
* A Parcelable class describing a pending Cell-Broadcast download request
* @hide
*/
public class DownloadRequest implements Parcelable {
+ // Version code used to keep token calculation consistent.
+ private static final int CURRENT_VERSION = 1;
+
/** @hide */
public static class Builder {
private int id;
@@ -37,6 +44,7 @@
private Uri dest;
private int subscriptionId;
private String appIntent;
+ private int version = CURRENT_VERSION;
public Builder setId(int id) {
this.id = id;
@@ -68,9 +76,14 @@
return this;
}
+ public Builder setVersion(int version) {
+ this.version = version;
+ return this;
+ }
+
public DownloadRequest build() {
return new DownloadRequest(id, serviceInfo, source, dest,
- subscriptionId, appIntent, null);
+ subscriptionId, appIntent, null, version);
}
}
@@ -80,11 +93,12 @@
private final Uri destinationUri;
private final int subscriptionId;
private final String serializedResultIntentForApp;
+ private final int version;
private String appName; // not the Android app Name, the embms app name
private DownloadRequest(int id, FileServiceInfo serviceInfo,
Uri source, Uri dest,
- int sub, String appIntent, String name) {
+ int sub, String appIntent, String name, int version) {
downloadId = id;
fileServiceInfo = serviceInfo;
sourceUri = source;
@@ -92,6 +106,7 @@
subscriptionId = sub;
serializedResultIntentForApp = appIntent;
appName = name;
+ this.version = version;
}
public static DownloadRequest copy(DownloadRequest other) {
@@ -106,6 +121,7 @@
subscriptionId = dr.subscriptionId;
serializedResultIntentForApp = dr.serializedResultIntentForApp;
appName = dr.appName;
+ version = dr.version;
}
private DownloadRequest(Parcel in) {
@@ -116,6 +132,7 @@
subscriptionId = in.readInt();
serializedResultIntentForApp = in.readString();
appName = in.readString();
+ version = in.readInt();
}
public int describeContents() {
@@ -130,6 +147,7 @@
out.writeInt(subscriptionId);
out.writeString(serializedResultIntentForApp);
out.writeString(appName);
+ out.writeInt(version);
}
public int getDownloadId() {
@@ -172,6 +190,10 @@
return appName;
}
+ public int getVersion() {
+ return version;
+ }
+
public static final Parcelable.Creator<DownloadRequest> CREATOR =
new Parcelable.Creator<DownloadRequest>() {
public DownloadRequest createFromParcel(Parcel in) {
@@ -181,4 +203,35 @@
return new DownloadRequest[size];
}
};
+
+ /**
+ * @hide
+ */
+ public boolean isMultipartDownload() {
+ // TODO: figure out what qualifies a request as a multipart download request.
+ return getSourceUri().getLastPathSegment() != null &&
+ getSourceUri().getLastPathSegment().contains("*");
+ }
+
+ /**
+ * Retrieves the hash string that should be used as the filename when storing a token for
+ * this DownloadRequest.
+ * @hide
+ */
+ public String getHash() {
+ MessageDigest digest;
+ try {
+ digest = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("Could not get sha256 hash object");
+ }
+ if (version >= 1) {
+ // Hash the source URI, destination URI, and the app intent
+ digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8));
+ digest.update(destinationUri.toString().getBytes(StandardCharsets.UTF_8));
+ digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
+ }
+ // Add updates for future versions here
+ return Base64.encodeToString(digest.digest(), Base64.URL_SAFE | Base64.NO_WRAP);
+ }
}
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
index b51c367..6ff177c 100644
--- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -41,6 +41,7 @@
public class MbmsDownloadReceiver extends BroadcastReceiver {
private static final String LOG_TAG = "MbmsDownloadReceiver";
private static final String TEMP_FILE_SUFFIX = ".embms.temp";
+ public static final String DOWNLOAD_TOKEN_SUFFIX = ".download_token";
private static final int MAX_TEMP_FILE_RETRIES = 5;
public static final String MBMS_FILE_PROVIDER_META_DATA_KEY = "mbms-file-provider-authority";
@@ -50,7 +51,7 @@
@Override
public void onReceive(Context context, Intent intent) {
- if (!verifyIntentContents(intent)) {
+ if (!verifyIntentContents(context, intent)) {
setResultCode(1 /* TODO: define error constants */);
return;
}
@@ -69,7 +70,7 @@
// TODO: Add handling for ACTION_CLEANUP
}
- private boolean verifyIntentContents(Intent intent) {
+ private boolean verifyIntentContents(Context context, Intent intent) {
if (MbmsDownloadManager.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
if (!intent.hasExtra(MbmsDownloadManager.EXTRA_RESULT)) {
Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
@@ -93,8 +94,19 @@
"temp file. Ignoring.");
return false;
}
+ DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
+ String expectedTokenFileName = request.getHash() + DOWNLOAD_TOKEN_SUFFIX;
+ File expectedTokenFile = new File(
+ MbmsUtils.getEmbmsTempFileDirForRequest(context, request),
+ expectedTokenFileName);
+ if (!expectedTokenFile.exists()) {
+ Log.w(LOG_TAG, "Supplied download request does not match a token that we have. " +
+ "Expected " + expectedTokenFile);
+ return false;
+ }
return true;
} else if (MbmsDownloadManager.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
+ // TODO: get rid of the request argument for a file descriptor request.
if (!intent.hasExtra(MbmsDownloadManager.EXTRA_REQUEST)) {
Log.w(LOG_TAG, "Temp file request not include the associated request. Ignoring.");
return false;
@@ -112,7 +124,6 @@
private void moveDownloadedFile(Context context, Intent intent) {
DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
- // TODO: check request against token
Intent intentForApp = request.getIntentForApp();
int result = intent.getIntExtra(MbmsDownloadManager.EXTRA_RESULT,
@@ -149,7 +160,6 @@
}
private void cleanupPostMove(Context context, Intent intent) {
- // TODO: account for in-use temp files
DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
if (request == null) {
Log.w(LOG_TAG, "Intent does not include a DownloadRequest. Ignoring.");
@@ -199,7 +209,7 @@
private ArrayList<UriPathPair> generateFreshTempFiles(Context context, DownloadRequest request,
int freshFdCount) {
- File tempFileDir = getEmbmsTempFileDirForRequest(context, request);
+ File tempFileDir = MbmsUtils.getEmbmsTempFileDirForRequest(context, request);
if (!tempFileDir.exists()) {
tempFileDir.mkdirs();
}
@@ -345,24 +355,14 @@
return false;
}
- if (!MbmsUtils.isContainedIn(getEmbmsTempFileDirForRequest(context, request), tempFile)) {
+ if (!MbmsUtils.isContainedIn(
+ MbmsUtils.getEmbmsTempFileDirForRequest(context, request), tempFile)) {
return false;
}
return true;
}
- /**
- * Returns a File linked to the directory used to store temp files for this request
- */
- private static File getEmbmsTempFileDirForRequest(Context context, DownloadRequest request) {
- File embmsTempFileDir = MbmsTempFileProvider.getEmbmsTempFileDir(context);
-
- // TODO: better naming scheme for temp file dirs
- String tempFileDirName = String.valueOf(request.getFileServiceInfo().getServiceId());
- return new File(embmsTempFileDir, tempFileDirName);
- }
-
private String getFileProviderAuthorityCached(Context context) {
if (mFileProviderAuthorityCache != null) {
return mFileProviderAuthorityCache;
diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java
index 7d47275..b332681 100644
--- a/telephony/java/android/telephony/mbms/MbmsUtils.java
+++ b/telephony/java/android/telephony/mbms/MbmsUtils.java
@@ -85,4 +85,14 @@
context.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
+
+ /**
+ * Returns a File linked to the directory used to store temp files for this request
+ */
+ public static File getEmbmsTempFileDirForRequest(Context context, DownloadRequest request) {
+ File embmsTempFileDir = MbmsTempFileProvider.getEmbmsTempFileDir(context);
+
+ String tempFileDirName = String.valueOf(request.getFileServiceInfo().getServiceId());
+ return new File(embmsTempFileDir, tempFileDirName);
+ }
}