Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4f5e8b3c authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Valid filenames have length limits!

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
parent 47b872d9
Loading
Loading
Loading
Loading
+12 −7
Original line number Diff line number Diff line
@@ -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;
+26 −0
Original line number Diff line number Diff line
@@ -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));
+12 −0
Original line number Diff line number Diff line
@@ -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"));