content: /*
   * 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
   */
  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
       */
      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);
          }
      }
  }
  