1 /* 2 * Copyright (C) 2015 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 com.android.messaging.util; 18 19 import android.content.ContentResolver; 20 import android.content.Context; 21 import android.net.Uri; 22 import android.os.Environment; 23 import android.os.ParcelFileDescriptor; 24 import android.text.TextUtils; 25 26 import com.android.messaging.Factory; 27 import com.android.messaging.R; 28 import com.google.common.io.Files; 29 30 import java.io.File; 31 import java.io.IOException; 32 import java.nio.file.Path; 33 import java.nio.file.Paths; 34 import java.text.SimpleDateFormat; 35 import java.util.Date; 36 import java.util.Locale; 37 38 public class FileUtil { 39 /** Returns a new file name, ensuring that such a file does not already exist. */ getNewFile(File directory, String extension, String fileNameFormat)40 private static synchronized File getNewFile(File directory, String extension, 41 String fileNameFormat) throws IOException { 42 final Date date = new Date(System.currentTimeMillis()); 43 final SimpleDateFormat dateFormat = new SimpleDateFormat(fileNameFormat); 44 final String numberedFileNameFormat = dateFormat.format(date) + "_%02d" + "." + extension; 45 for (int i = 1; i <= 99; i++) { // Only save 99 of the same file name. 46 final String newName = String.format(Locale.US, numberedFileNameFormat, i); 47 File testFile = new File(directory, newName); 48 if (!testFile.exists()) { 49 testFile.createNewFile(); 50 return testFile; 51 } 52 } 53 LogUtil.e(LogUtil.BUGLE_TAG, "Too many duplicate file names: " + numberedFileNameFormat); 54 return null; 55 } 56 57 /** 58 * Creates an unused name to use for creating a new file. The format happens to be similar 59 * to that used by the Android camera application. 60 * 61 * @param directory directory that the file should be saved to 62 * @param contentType of the media being saved 63 * @return file name to be used for creating the new file. The caller is responsible for 64 * actually creating the file. 65 */ getNewFile(File directory, String contentType)66 public static File getNewFile(File directory, String contentType) throws IOException { 67 String fileExtension = ContentType.getExtensionFromMimeType(contentType); 68 final Context context = Factory.get().getApplicationContext(); 69 String fileNameFormat = context.getString(ContentType.isImageType(contentType) 70 ? R.string.new_image_file_name_format : R.string.new_file_name_format); 71 return getNewFile(directory, fileExtension, fileNameFormat); 72 } 73 74 /** Delete everything below and including root */ removeFileOrDirectory(File root)75 public static void removeFileOrDirectory(File root) { 76 removeFileOrDirectoryExcept(root, null); 77 } 78 79 /** Delete everything below and including root except for the given file */ removeFileOrDirectoryExcept(File root, File exclude)80 public static void removeFileOrDirectoryExcept(File root, File exclude) { 81 if (root.exists()) { 82 if (root.isDirectory()) { 83 for (File file : root.listFiles()) { 84 if (exclude == null || !file.equals(exclude)) { 85 removeFileOrDirectoryExcept(file, exclude); 86 } 87 } 88 root.delete(); 89 } else if (root.isFile()) { 90 root.delete(); 91 } 92 } 93 } 94 95 /** 96 * Move all files and folders under a directory into the target. 97 */ moveAllContentUnderDirectory(File sourceDir, File targetDir)98 public static void moveAllContentUnderDirectory(File sourceDir, File targetDir) { 99 if (sourceDir.isDirectory() && targetDir.isDirectory()) { 100 if (isSameOrSubDirectory(sourceDir, targetDir)) { 101 LogUtil.e(LogUtil.BUGLE_TAG, "Can't move directory content since the source " + 102 "directory is a parent of the target"); 103 return; 104 } 105 for (File file : sourceDir.listFiles()) { 106 if (file.isDirectory()) { 107 final File dirTarget = new File(targetDir, file.getName()); 108 dirTarget.mkdirs(); 109 moveAllContentUnderDirectory(file, dirTarget); 110 } else { 111 try { 112 final File fileTarget = new File(targetDir, file.getName()); 113 Files.move(file, fileTarget); 114 } catch (IOException e) { 115 LogUtil.e(LogUtil.BUGLE_TAG, "Failed to move files", e); 116 // Try proceed with the next file. 117 } 118 } 119 } 120 } 121 } 122 123 // Checks if the file is in /data, and don't allow any app to send personal information. 124 // We're told it's possible to create world readable hardlinks to other apps private data 125 // so we ban all /data file uris. isInPrivateDir(Uri uri)126 public static boolean isInPrivateDir(Uri uri) { 127 return isFileUriInPrivateDir(uri) || isContentUriInPrivateDir(uri); 128 } 129 isFileUriInPrivateDir(Uri uri)130 private static boolean isFileUriInPrivateDir(Uri uri) { 131 if (!UriUtil.isFileUri(uri)) { 132 return false; 133 } 134 final File file = new File(uri.getPath()); 135 return FileUtil.isSameOrSubDirectory(Environment.getDataDirectory(), file); 136 } 137 isContentUriInPrivateDir(Uri uri)138 private static boolean isContentUriInPrivateDir(Uri uri) { 139 if (!uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { 140 return false; 141 } 142 try { 143 Context context = Factory.get().getApplicationContext(); 144 ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r"); 145 int fd = pfd.getFd(); 146 // Use the file descriptor to find out the read file path through symbolic link. 147 Path fdPath = Paths.get("/proc/self/fd/" + fd); 148 Path filePath = java.nio.file.Files.readSymbolicLink(fdPath); 149 pfd.close(); 150 return FileUtil.isSameOrSubDirectory(Environment.getDataDirectory(), filePath.toFile()); 151 } catch (Exception e) { 152 return false; 153 } 154 } 155 156 /** 157 * Checks, whether the child directory is the same as, or a sub-directory of the base 158 * directory. 159 */ isSameOrSubDirectory(File base, File child)160 private static boolean isSameOrSubDirectory(File base, File child) { 161 try { 162 base = base.getCanonicalFile(); 163 child = child.getCanonicalFile(); 164 File parentFile = child; 165 while (parentFile != null) { 166 if (base.equals(parentFile)) { 167 return true; 168 } 169 parentFile = parentFile.getParentFile(); 170 } 171 return false; 172 } catch (IOException ex) { 173 LogUtil.e(LogUtil.BUGLE_TAG, "Error while accessing file", ex); 174 return false; 175 } 176 } 177 } 178