/*
 * Copyright (C) 2017 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 android.telephony.mbms;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.telephony.MbmsDownloadSession;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Objects;

@hide
/** * @hide */
public class MbmsTempFileProvider extends ContentProvider { public static final String TEMP_FILE_ROOT_PREF_FILE_NAME = "MbmsTempFileRootPrefs"; public static final String TEMP_FILE_ROOT_PREF_NAME = "mbms_temp_file_root"; private String mAuthority; private Context mContext; @Override public boolean onCreate() { return true; } @Override public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { throw new UnsupportedOperationException("No querying supported"); } @Override public String getType(@NonNull Uri uri) { // EMBMS temp files can contain arbitrary content. return "application/octet-stream"; } @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { throw new UnsupportedOperationException("No inserting supported"); } @Override public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { throw new UnsupportedOperationException("No deleting supported"); } @Override public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { throw new UnsupportedOperationException("No updating supported"); } @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { // ContentProvider has already checked granted permissions final File file = getFileForUri(mContext, mAuthority, uri); final int fileMode = ParcelFileDescriptor.parseMode(mode); return ParcelFileDescriptor.open(file, fileMode); } @Override public void attachInfo(Context context, ProviderInfo info) { super.attachInfo(context, info); // Sanity check our security if (info.exported) { throw new SecurityException("Provider must not be exported"); } if (!info.grantUriPermissions) { throw new SecurityException("Provider must grant uri permissions"); } mAuthority = info.authority; mContext = context; } public static Uri getUriForFile(Context context, String authority, File file) { // Get the canonical path of the temp file String filePath; try { filePath = file.getCanonicalPath(); } catch (IOException e) { throw new IllegalArgumentException("Could not get canonical path for file " + file); } // Make sure the temp file is contained in the temp file directory as configured in the // manifest File tempFileDir = getEmbmsTempFileDir(context); if (!MbmsUtils.isContainedIn(tempFileDir, file)) { throw new IllegalArgumentException("File " + file + " is not contained in the temp " + "file directory, which is " + tempFileDir); } // Get the canonical path of the temp file directory String tempFileDirPath; try { tempFileDirPath = tempFileDir.getCanonicalPath(); } catch (IOException e) { throw new RuntimeException( "Could not get canonical path for temp file root dir " + tempFileDir); } // Start at first char of path under temp file directory String pathFragment; if (tempFileDirPath.endsWith("/")) { pathFragment = filePath.substring(tempFileDirPath.length()); } else { pathFragment = filePath.substring(tempFileDirPath.length() + 1); } String encodedPath = Uri.encode(pathFragment); return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) .authority(authority).encodedPath(encodedPath).build(); } public static File getFileForUri(Context context, String authority, Uri uri) throws FileNotFoundException { if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { throw new IllegalArgumentException("Uri must have scheme content"); } if (!Objects.equals(authority, uri.getAuthority())) { throw new IllegalArgumentException("Uri does not have a matching authority: " + authority + ", " + uri.getAuthority()); } String relPath = Uri.decode(uri.getEncodedPath()); File file; File tempFileDir; try { tempFileDir = getEmbmsTempFileDir(context).getCanonicalFile(); file = new File(tempFileDir, relPath).getCanonicalFile(); } catch (IOException e) { throw new FileNotFoundException("Could not resolve paths"); } if (!file.getPath().startsWith(tempFileDir.getPath())) { throw new SecurityException("Resolved path jumped beyond configured root"); } return file; }
Returns a File for the directory used to store temp files for this app
/** * Returns a File for the directory used to store temp files for this app */
public static File getEmbmsTempFileDir(Context context) { SharedPreferences prefs = context.getSharedPreferences(TEMP_FILE_ROOT_PREF_FILE_NAME, 0); String storedTempFileRoot = prefs.getString(TEMP_FILE_ROOT_PREF_NAME, null); try { if (storedTempFileRoot != null) { return new File(storedTempFileRoot).getCanonicalFile(); } else { return new File(context.getFilesDir(), MbmsDownloadSession.DEFAULT_TOP_LEVEL_TEMP_DIRECTORY).getCanonicalFile(); } } catch (IOException e) { throw new RuntimeException("Unable to canonicalize temp file root path " + e); } } }