diff options
Diffstat (limited to 'src/com/android/providers/downloads/Helpers.java')
-rw-r--r-- | src/com/android/providers/downloads/Helpers.java | 219 |
1 files changed, 125 insertions, 94 deletions
diff --git a/src/com/android/providers/downloads/Helpers.java b/src/com/android/providers/downloads/Helpers.java index 3562dac7..eb071395 100644 --- a/src/com/android/providers/downloads/Helpers.java +++ b/src/com/android/providers/downloads/Helpers.java @@ -21,6 +21,7 @@ import static com.android.providers.downloads.Constants.TAG; import android.content.Context; import android.net.Uri; import android.os.Environment; +import android.os.FileUtils; import android.os.SystemClock; import android.provider.Downloads; import android.util.Log; @@ -68,98 +69,79 @@ public class Helpers { /** * Creates a filename (where the file should be saved) from info about a download. + * This file will be touched to reserve it. */ - static String generateSaveFile( - Context context, - String url, - String hint, - String contentDisposition, - String contentLocation, - String mimeType, - int destination, - long contentLength, - StorageManager storageManager) throws StopRequestException { - if (contentLength < 0) { - contentLength = 0; - } - String path; - File base = null; + static String generateSaveFile(Context context, String url, String hint, + String contentDisposition, String contentLocation, String mimeType, int destination) + throws IOException { + + final File parent; + final File[] parentTest; + String name = null; + if (destination == Downloads.Impl.DESTINATION_FILE_URI) { - path = Uri.parse(hint).getPath(); + final File file = new File(Uri.parse(hint).getPath()); + parent = file.getParentFile().getAbsoluteFile(); + parentTest = new File[] { parent }; + name = file.getName(); } else { - base = storageManager.locateDestinationDirectory(mimeType, destination, - contentLength); - path = chooseFilename(url, hint, contentDisposition, contentLocation, - destination); + parent = getRunningDestinationDirectory(context, destination); + parentTest = new File[] { + parent, + getSuccessDestinationDirectory(context, destination) + }; + name = chooseFilename(url, hint, contentDisposition, contentLocation); + } + + // Ensure target directories are ready + for (File test : parentTest) { + if (!(test.isDirectory() || test.mkdirs())) { + throw new IOException("Failed to create parent for " + test); + } } - storageManager.verifySpace(destination, path, contentLength); + if (DownloadDrmHelper.isDrmConvertNeeded(mimeType)) { - path = DownloadDrmHelper.modifyDrmFwLockFileExtension(path); + name = DownloadDrmHelper.modifyDrmFwLockFileExtension(name); } - path = getFullPath(path, mimeType, destination, base); - return path; - } - static String getFullPath(String filename, String mimeType, int destination, File base) - throws StopRequestException { - String extension = null; - int dotIndex = filename.lastIndexOf('.'); - boolean missingExtension = dotIndex < 0 || dotIndex < filename.lastIndexOf('/'); + final String prefix; + final String suffix; + final int dotIndex = name.lastIndexOf('.'); + final boolean missingExtension = dotIndex < 0; if (destination == Downloads.Impl.DESTINATION_FILE_URI) { // Destination is explicitly set - do not change the extension if (missingExtension) { - extension = ""; + prefix = name; + suffix = ""; } else { - extension = filename.substring(dotIndex); - filename = filename.substring(0, dotIndex); + prefix = name.substring(0, dotIndex); + suffix = name.substring(dotIndex); } } else { // Split filename between base and extension // Add an extension if filename does not have one if (missingExtension) { - extension = chooseExtensionFromMimeType(mimeType, true); + prefix = name; + suffix = chooseExtensionFromMimeType(mimeType, true); } else { - extension = chooseExtensionFromFilename(mimeType, destination, filename, dotIndex); - filename = filename.substring(0, dotIndex); + prefix = name.substring(0, dotIndex); + suffix = chooseExtensionFromFilename(mimeType, destination, name, dotIndex); } } - boolean recoveryDir = Constants.RECOVERY_DIRECTORY.equalsIgnoreCase(filename + extension); - - if (base != null) { - filename = base.getPath() + File.separator + filename; - } - - if (Constants.LOGVV) { - Log.v(Constants.TAG, "target file: " + filename + extension); - } - synchronized (sUniqueLock) { - final String path = chooseUniqueFilenameLocked( - destination, filename, extension, recoveryDir); + name = generateAvailableFilenameLocked(parentTest, prefix, suffix); // Claim this filename inside lock to prevent other threads from // clobbering us. We're not paranoid enough to use O_EXCL. - try { - File file = new File(path); - File parent = file.getParentFile(); - - // Make sure the parent directories exists before generates new file - if (parent != null && !parent.exists()) { - parent.mkdirs(); - } - - file.createNewFile(); - } catch (IOException e) { - throw new StopRequestException(Downloads.Impl.STATUS_FILE_ERROR, - "Failed to create target file " + path, e); - } - return path; + final File file = new File(parent, name); + file.createNewFile(); + return file.getAbsolutePath(); } } private static String chooseFilename(String url, String hint, String contentDisposition, - String contentLocation, int destination) { + String contentLocation) { String filename = null; // First, try to use the hint from the application, if there's one @@ -305,18 +287,25 @@ public class Helpers { return extension; } - private static String chooseUniqueFilenameLocked(int destination, String filename, - String extension, boolean recoveryDir) throws StopRequestException { - String fullFilename = filename + extension; - if (!new File(fullFilename).exists() - && (!recoveryDir || - (destination != Downloads.Impl.DESTINATION_CACHE_PARTITION && - destination != Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION && - destination != Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE && - destination != Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING))) { - return fullFilename; - } - filename = filename + Constants.FILENAME_SEQUENCE_SEPARATOR; + private static boolean isFilenameAvailableLocked(File[] parents, String name) { + if (Constants.RECOVERY_DIRECTORY.equalsIgnoreCase(name)) return false; + + for (File parent : parents) { + if (new File(parent, name).exists()) { + return false; + } + } + + return true; + } + + private static String generateAvailableFilenameLocked( + File[] parents, String prefix, String suffix) throws IOException { + String name = prefix + suffix; + if (isFilenameAvailableLocked(parents, name)) { + return name; + } + /* * This number is used to generate partially randomized filenames to avoid * collisions. @@ -334,39 +323,38 @@ public class Helpers { int sequence = 1; for (int magnitude = 1; magnitude < 1000000000; magnitude *= 10) { for (int iteration = 0; iteration < 9; ++iteration) { - fullFilename = filename + sequence + extension; - if (!new File(fullFilename).exists()) { - return fullFilename; - } - if (Constants.LOGVV) { - Log.v(Constants.TAG, "file with sequence number " + sequence + " exists"); + name = prefix + Constants.FILENAME_SEQUENCE_SEPARATOR + sequence + suffix; + if (isFilenameAvailableLocked(parents, name)) { + return name; } sequence += sRandom.nextInt(magnitude) + 1; } } - throw new StopRequestException(Downloads.Impl.STATUS_FILE_ERROR, - "failed to generate an unused filename on internal download storage"); + + throw new IOException("Failed to generate an available filename"); } /** - * Checks whether the filename looks legitimate + * Checks whether the filename looks legitimate for security purposes. This + * prevents us from opening files that aren't actually downloads. */ - static boolean isFilenameValid(String filename, File downloadsDataDir) { - final String[] whitelist; + static boolean isFilenameValid(Context context, File file) { + final File[] whitelist; try { - filename = new File(filename).getCanonicalPath(); - whitelist = new String[] { - downloadsDataDir.getCanonicalPath(), - Environment.getDownloadCacheDirectory().getCanonicalPath(), - Environment.getExternalStorageDirectory().getCanonicalPath(), + file = file.getCanonicalFile(); + whitelist = new File[] { + context.getFilesDir().getCanonicalFile(), + context.getCacheDir().getCanonicalFile(), + Environment.getDownloadCacheDirectory().getCanonicalFile(), + Environment.getExternalStorageDirectory().getCanonicalFile(), }; } catch (IOException e) { Log.w(TAG, "Failed to resolve canonical path: " + e); return false; } - for (String test : whitelist) { - if (filename.startsWith(test)) { + for (File testDir : whitelist) { + if (FileUtils.contains(testDir, file)) { return true; } } @@ -374,6 +362,49 @@ public class Helpers { return false; } + public static File getRunningDestinationDirectory(Context context, int destination) + throws IOException { + return getDestinationDirectory(context, destination, true); + } + + public static File getSuccessDestinationDirectory(Context context, int destination) + throws IOException { + return getDestinationDirectory(context, destination, false); + } + + private static File getDestinationDirectory(Context context, int destination, boolean running) + throws IOException { + switch (destination) { + case Downloads.Impl.DESTINATION_CACHE_PARTITION: + case Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE: + case Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING: + if (running) { + return context.getFilesDir(); + } else { + return context.getCacheDir(); + } + + case Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION: + if (running) { + return new File(Environment.getDownloadCacheDirectory(), + Constants.DIRECTORY_CACHE_RUNNING); + } else { + return Environment.getDownloadCacheDirectory(); + } + + case Downloads.Impl.DESTINATION_EXTERNAL: + final File target = new File( + Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS); + if (!target.isDirectory() && target.mkdirs()) { + throw new IOException("unable to create external downloads directory"); + } + return target; + + default: + throw new IllegalStateException("unexpected destination: " + destination); + } + } + /** * Checks whether this looks like a legitimate selection parameter */ |