Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2043f70a authored by Hall Liu's avatar Hall Liu
Browse files

Add download request tokens and enforce destination clear

Start writing tokens for each download request after calling download(),
and check agains them when a completed-file intent is received with a
download request extra.
Also start checking that the download destination is clear.

Change-Id: I533fe2ba49ac56bed5eab44670fe8e9a273b1ffb
parent e601160a
Loading
Loading
Loading
Loading
+54 −14
Original line number Diff line number Diff line
@@ -388,21 +388,10 @@ public class MbmsDownloadManager {
            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 @@ public class MbmsDownloadManager {
     * <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 @@ public class MbmsDownloadManager {
            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.");
            }
        }
    }
}
+55 −2
Original line number Diff line number Diff line
@@ -20,15 +20,22 @@ import android.content.Intent;
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 @@ public class DownloadRequest implements Parcelable {
        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 @@ public class DownloadRequest implements Parcelable {
            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 @@ public class DownloadRequest implements Parcelable {
    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 @@ public class DownloadRequest implements Parcelable {
        subscriptionId = sub;
        serializedResultIntentForApp = appIntent;
        appName = name;
        this.version = version;
    }

    public static DownloadRequest copy(DownloadRequest other) {
@@ -106,6 +121,7 @@ public class DownloadRequest implements Parcelable {
        subscriptionId = dr.subscriptionId;
        serializedResultIntentForApp = dr.serializedResultIntentForApp;
        appName = dr.appName;
        version = dr.version;
    }

    private DownloadRequest(Parcel in) {
@@ -116,6 +132,7 @@ public class DownloadRequest implements Parcelable {
        subscriptionId = in.readInt();
        serializedResultIntentForApp = in.readString();
        appName = in.readString();
        version = in.readInt();
    }

    public int describeContents() {
@@ -130,6 +147,7 @@ public class DownloadRequest implements Parcelable {
        out.writeInt(subscriptionId);
        out.writeString(serializedResultIntentForApp);
        out.writeString(appName);
        out.writeInt(version);
    }

    public int getDownloadId() {
@@ -172,6 +190,10 @@ public class DownloadRequest implements Parcelable {
        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 @@ public class DownloadRequest implements Parcelable {
            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);
    }
}
+17 −17
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import java.util.UUID;
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 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {

    @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 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
        // 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 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
                        "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 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {

    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 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
    }

    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 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {

    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 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
            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;
+10 −0
Original line number Diff line number Diff line
@@ -85,4 +85,14 @@ public class MbmsUtils {

        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);
    }
}