OSDN Git Service

Valid filenames have length limits!
authorJeff Sharkey <jsharkey@android.com>
Fri, 12 Jun 2015 02:13:37 +0000 (19:13 -0700)
committerJeff Sharkey <jsharkey@android.com>
Fri, 12 Jun 2015 02:16:27 +0000 (19:16 -0700)
ext4 filenames are at most 255 bytes.  vfat filenames are bit more
lax, but we're often saving them on ext4 through a FUSE daemon, so
limit them the same way.

Since package names are used as directory names, verify that they're
valid filenames.

Tests to verify behavior.

Bug: 18689171
Change-Id: If7df4c40d352954510b71de4ff05d78259c721ed

core/java/android/content/pm/PackageParser.java
core/java/android/os/FileUtils.java
core/tests/coretests/src/android/os/FileUtilsTest.java

index 83b0140..c92c256 100644 (file)
@@ -36,6 +36,7 @@ import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.FileUtils;
 import android.os.PatternMatcher;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -1194,7 +1195,8 @@ public class PackageParser {
         }
     }
 
-    private static String validateName(String name, boolean requiresSeparator) {
+    private static String validateName(String name, boolean requireSeparator,
+            boolean requireFilename) {
         final int N = name.length();
         boolean hasSep = false;
         boolean front = true;
@@ -1216,7 +1218,10 @@ public class PackageParser {
             }
             return "bad character '" + c + "'";
         }
-        return hasSep || !requiresSeparator
+        if (requireFilename && !FileUtils.isValidExtFilename(name)) {
+            return "Invalid filename";
+        }
+        return hasSep || !requireSeparator
                 ? null : "must have at least one '.' separator";
     }
 
@@ -1240,7 +1245,7 @@ public class PackageParser {
 
         final String packageName = attrs.getAttributeValue(null, "package");
         if (!"android".equals(packageName)) {
-            final String error = validateName(packageName, true);
+            final String error = validateName(packageName, true, true);
             if (error != null) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
                         "Invalid manifest package: " + error);
@@ -1252,7 +1257,7 @@ public class PackageParser {
             if (splitName.length() == 0) {
                 splitName = null;
             } else {
-                final String error = validateName(splitName, false);
+                final String error = validateName(splitName, false, false);
                 if (error != null) {
                     throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
                             "Invalid manifest split: " + error);
@@ -1391,7 +1396,7 @@ public class PackageParser {
         String str = sa.getNonConfigurationString(
                 com.android.internal.R.styleable.AndroidManifest_sharedUserId, 0);
         if (str != null && str.length() > 0) {
-            String nameError = validateName(str, true);
+            String nameError = validateName(str, true, false);
             if (nameError != null && !"android".equals(pkgName)) {
                 outError[0] = "<manifest> specifies bad sharedUserId name \""
                     + str + "\": " + nameError;
@@ -1973,7 +1978,7 @@ public class PackageParser {
                 return null;
             }
             String subName = proc.substring(1);
-            String nameError = validateName(subName, false);
+            String nameError = validateName(subName, false, false);
             if (nameError != null) {
                 outError[0] = "Invalid " + type + " name " + proc + " in package "
                         + pkg + ": " + nameError;
@@ -1981,7 +1986,7 @@ public class PackageParser {
             }
             return (pkg + proc).intern();
         }
-        String nameError = validateName(proc, true);
+        String nameError = validateName(proc, true, false);
         if (nameError != null && !"system".equals(proc)) {
             outError[0] = "Invalid " + type + " name " + proc + " in package "
                     + pkg + ": " + nameError;
index 917271d..864225a 100644 (file)
@@ -24,6 +24,8 @@ import android.util.Log;
 import android.util.Slog;
 import android.webkit.MimeTypeMap;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.BufferedInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -34,6 +36,7 @@ import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.Objects;
@@ -456,6 +459,7 @@ public class FileUtils {
                 res.append('_');
             }
         }
+        trimFilename(res, 255);
         return res.toString();
     }
 
@@ -504,9 +508,31 @@ public class FileUtils {
                 res.append('_');
             }
         }
+        // Even though vfat allows 255 UCS-2 chars, we might eventually write to
+        // ext4 through a FUSE layer, so use that limit.
+        trimFilename(res, 255);
+        return res.toString();
+    }
+
+    @VisibleForTesting
+    public static String trimFilename(String str, int maxBytes) {
+        final StringBuilder res = new StringBuilder(str);
+        trimFilename(res, maxBytes);
         return res.toString();
     }
 
+    private static void trimFilename(StringBuilder res, int maxBytes) {
+        byte[] raw = res.toString().getBytes(StandardCharsets.UTF_8);
+        if (raw.length > maxBytes) {
+            maxBytes -= 3;
+            while (raw.length > maxBytes) {
+                res.deleteCharAt(res.length() / 2);
+                raw = res.toString().getBytes(StandardCharsets.UTF_8);
+            }
+            res.insert(res.length() / 2, "...");
+        }
+    }
+
     public static String rewriteAfterRename(File beforeDir, File afterDir, String path) {
         if (path == null) return null;
         final File result = rewriteAfterRename(beforeDir, afterDir, new File(path));
index ee9e2e4..ac5abad 100644 (file)
@@ -232,6 +232,18 @@ public class FileUtilsTest extends AndroidTestCase {
         assertEquals("foo_bar__baz", FileUtils.buildValidFatFilename("foo?bar**baz"));
     }
 
+    public void testTrimFilename() throws Exception {
+        assertEquals("short.txt", FileUtils.trimFilename("short.txt", 16));
+        assertEquals("extrem...eme.txt", FileUtils.trimFilename("extremelylongfilename.txt", 16));
+
+        final String unicode = "a\u03C0\u03C0\u03C0\u03C0z";
+        assertEquals("a\u03C0\u03C0\u03C0\u03C0z", FileUtils.trimFilename(unicode, 10));
+        assertEquals("a\u03C0...\u03C0z", FileUtils.trimFilename(unicode, 9));
+        assertEquals("a...\u03C0z", FileUtils.trimFilename(unicode, 8));
+        assertEquals("a...\u03C0z", FileUtils.trimFilename(unicode, 7));
+        assertEquals("a...z", FileUtils.trimFilename(unicode, 6));
+    }
+
     public void testBuildUniqueFile_normal() throws Exception {
         assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test"));
         assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));