1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package android.telephony.mbms; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.ContentProvider; 22 import android.content.ContentResolver; 23 import android.content.ContentValues; 24 import android.content.Context; 25 import android.content.SharedPreferences; 26 import android.content.pm.ProviderInfo; 27 import android.database.Cursor; 28 import android.net.Uri; 29 import android.os.ParcelFileDescriptor; 30 import android.telephony.MbmsDownloadSession; 31 32 import java.io.File; 33 import java.io.FileNotFoundException; 34 import java.io.IOException; 35 import java.util.Objects; 36 37 /** 38 * @hide 39 */ 40 public class MbmsTempFileProvider extends ContentProvider { 41 public static final String TEMP_FILE_ROOT_PREF_FILE_NAME = "MbmsTempFileRootPrefs"; 42 public static final String TEMP_FILE_ROOT_PREF_NAME = "mbms_temp_file_root"; 43 44 private String mAuthority; 45 private Context mContext; 46 47 @Override onCreate()48 public boolean onCreate() { 49 return true; 50 } 51 52 @Override query(@onNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder)53 public Cursor query(@NonNull Uri uri, @Nullable String[] projection, 54 @Nullable String selection, @Nullable String[] selectionArgs, 55 @Nullable String sortOrder) { 56 throw new UnsupportedOperationException("No querying supported"); 57 } 58 59 @Override getType(@onNull Uri uri)60 public String getType(@NonNull Uri uri) { 61 // EMBMS temp files can contain arbitrary content. 62 return "application/octet-stream"; 63 } 64 65 @Override insert(@onNull Uri uri, @Nullable ContentValues values)66 public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { 67 throw new UnsupportedOperationException("No inserting supported"); 68 } 69 70 @Override delete(@onNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs)71 public int delete(@NonNull Uri uri, @Nullable String selection, 72 @Nullable String[] selectionArgs) { 73 throw new UnsupportedOperationException("No deleting supported"); 74 } 75 76 @Override update(@onNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs)77 public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String 78 selection, @Nullable String[] selectionArgs) { 79 throw new UnsupportedOperationException("No updating supported"); 80 } 81 82 @Override openFile(Uri uri, String mode)83 public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { 84 // ContentProvider has already checked granted permissions 85 final File file = getFileForUri(mContext, mAuthority, uri); 86 final int fileMode = ParcelFileDescriptor.parseMode(mode); 87 return ParcelFileDescriptor.open(file, fileMode); 88 } 89 90 @Override attachInfo(Context context, ProviderInfo info)91 public void attachInfo(Context context, ProviderInfo info) { 92 super.attachInfo(context, info); 93 94 // Correctness check our security 95 if (info.exported) { 96 throw new SecurityException("Provider must not be exported"); 97 } 98 if (!info.grantUriPermissions) { 99 throw new SecurityException("Provider must grant uri permissions"); 100 } 101 102 mAuthority = info.authority; 103 mContext = context; 104 } 105 getUriForFile(Context context, String authority, File file)106 public static Uri getUriForFile(Context context, String authority, File file) { 107 // Get the canonical path of the temp file 108 String filePath; 109 try { 110 filePath = file.getCanonicalPath(); 111 } catch (IOException e) { 112 throw new IllegalArgumentException("Could not get canonical path for file " + file); 113 } 114 115 // Make sure the temp file is contained in the temp file directory as configured in the 116 // manifest 117 File tempFileDir = getEmbmsTempFileDir(context); 118 if (!MbmsUtils.isContainedIn(tempFileDir, file)) { 119 throw new IllegalArgumentException("File " + file + " is not contained in the temp " + 120 "file directory, which is " + tempFileDir); 121 } 122 123 // Get the canonical path of the temp file directory 124 String tempFileDirPath; 125 try { 126 tempFileDirPath = tempFileDir.getCanonicalPath(); 127 } catch (IOException e) { 128 throw new RuntimeException( 129 "Could not get canonical path for temp file root dir " + tempFileDir); 130 } 131 132 // Start at first char of path under temp file directory 133 String pathFragment; 134 if (tempFileDirPath.endsWith("/")) { 135 pathFragment = filePath.substring(tempFileDirPath.length()); 136 } else { 137 pathFragment = filePath.substring(tempFileDirPath.length() + 1); 138 } 139 140 String encodedPath = Uri.encode(pathFragment); 141 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 142 .authority(authority).encodedPath(encodedPath).build(); 143 } 144 getFileForUri(Context context, String authority, Uri uri)145 public static File getFileForUri(Context context, String authority, Uri uri) 146 throws FileNotFoundException { 147 if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { 148 throw new IllegalArgumentException("Uri must have scheme content"); 149 } 150 if (!Objects.equals(authority, uri.getAuthority())) { 151 throw new IllegalArgumentException("Uri does not have a matching authority: " + 152 authority + ", " + uri.getAuthority()); 153 } 154 155 String relPath = Uri.decode(uri.getEncodedPath()); 156 File file; 157 File tempFileDir; 158 159 try { 160 tempFileDir = getEmbmsTempFileDir(context).getCanonicalFile(); 161 file = new File(tempFileDir, relPath).getCanonicalFile(); 162 } catch (IOException e) { 163 throw new FileNotFoundException("Could not resolve paths"); 164 } 165 166 if (!file.getPath().startsWith(tempFileDir.getPath())) { 167 throw new SecurityException("Resolved path jumped beyond configured root"); 168 } 169 170 return file; 171 } 172 173 /** 174 * Returns a File for the directory used to store temp files for this app 175 */ getEmbmsTempFileDir(Context context)176 public static File getEmbmsTempFileDir(Context context) { 177 SharedPreferences prefs = context.getSharedPreferences(TEMP_FILE_ROOT_PREF_FILE_NAME, 0); 178 String storedTempFileRoot = prefs.getString(TEMP_FILE_ROOT_PREF_NAME, null); 179 try { 180 if (storedTempFileRoot != null) { 181 return new File(storedTempFileRoot).getCanonicalFile(); 182 } else { 183 return new File(context.getFilesDir(), 184 MbmsDownloadSession.DEFAULT_TOP_LEVEL_TEMP_DIRECTORY).getCanonicalFile(); 185 } 186 } catch (IOException e) { 187 throw new RuntimeException("Unable to canonicalize temp file root path " + e); 188 } 189 } 190 } 191