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