am cc167f03: (-s ours) am bb611587: (-s ours) Import translations. DO NOT MERGE
[android/platform/packages/providers/DownloadProvider.git] / src / com / android / providers / downloads / Helpers.java
index 3320555..eb07139 100644 (file)
 
 package com.android.providers.downloads;
 
+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;
@@ -66,90 +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 {
-                new File(path).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
@@ -295,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.
@@ -324,28 +323,86 @@ 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) {
-        filename = filename.replaceFirst("/+", "/"); // normalize leading slashes
-        return filename.startsWith(Environment.getDownloadCacheDirectory().toString())
-                || filename.startsWith(downloadsDataDir.toString())
-                || filename.startsWith(Environment.getExternalStorageDirectory().toString());
+    static boolean isFilenameValid(Context context, File file) {
+        final File[] whitelist;
+        try {
+            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 (File testDir : whitelist) {
+            if (FileUtils.contains(testDir, file)) {
+                return true;
+            }
+        }
+
+        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);
+        }
     }
 
     /**