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

Commit de9ec006 authored by Tomasz Mikolajewski's avatar Tomasz Mikolajewski
Browse files

Normalize paths for files in archives.

Always use leading slashes for paths, for simpler logic.

Bug: 31783726
Test: All tests pass.
Change-Id: I41f03880234086b6da0b85e726414c3550c7ce1f
parent 5d8ea8df
Loading
Loading
Loading
Loading
+43 −31
Original line number Original line Diff line number Diff line
@@ -109,45 +109,48 @@ public class Archive implements Closeable {


        // Build the tree structure in memory.
        // Build the tree structure in memory.
        mTree = new HashMap<String, List<ZipEntry>>();
        mTree = new HashMap<String, List<ZipEntry>>();
        mTree.put("/", new ArrayList<ZipEntry>());


        mEntries = new HashMap<String, ZipEntry>();
        mEntries = new HashMap<String, ZipEntry>();
        ZipEntry entry;
        ZipEntry entry;
        final List<? extends ZipEntry> entries = Collections.list(mZipFile.entries());
        final List<? extends ZipEntry> entries = Collections.list(mZipFile.entries());
        final Stack<ZipEntry> stack = new Stack<>();
        final Stack<ZipEntry> stack = new Stack<>();
        String entryPath;
        for (int i = entries.size() - 1; i >= 0; i--) {
        for (int i = entries.size() - 1; i >= 0; i--) {
            entry = entries.get(i);
            entry = entries.get(i);
            if (entry.isDirectory() != entry.getName().endsWith("/")) {
            if (entry.isDirectory() != entry.getName().endsWith("/")) {
                throw new IOException(
                throw new IOException(
                        "Directories must have a trailing slash, and files must not.");
                        "Directories must have a trailing slash, and files must not.");
            }
            }
            if (mEntries.containsKey(entry.getName())) {
            entryPath = getEntryPath(entry);
            if (mEntries.containsKey(entryPath)) {
                throw new IOException("Multiple entries with the same name are not supported.");
                throw new IOException("Multiple entries with the same name are not supported.");
            }
            }
            mEntries.put(entry.getName(), entry);
            mEntries.put(entryPath, entry);
            if (entry.isDirectory()) {
            if (entry.isDirectory()) {
                mTree.put(entry.getName(), new ArrayList<ZipEntry>());
                mTree.put(entryPath, new ArrayList<ZipEntry>());
            }
            }
            if (!"/".equals(entryPath)) { // Skip root, as it doesn't have a parent.
                stack.push(entry);
                stack.push(entry);
            }
            }
        }


        int delimiterIndex;
        int delimiterIndex;
        String parentPath;
        String parentPath;
        ZipEntry parentEntry;
        ZipEntry parentEntry;
        List<ZipEntry> parentList;
        List<ZipEntry> parentList;


        // Go through all directories recursively and build a tree structure.
        while (stack.size() > 0) {
        while (stack.size() > 0) {
            entry = stack.pop();
            entry = stack.pop();


            delimiterIndex = entry.getName().lastIndexOf('/', entry.isDirectory()
            entryPath = getEntryPath(entry);
                    ? entry.getName().length() - 2 : entry.getName().length() - 1);
            delimiterIndex = entryPath.lastIndexOf('/', entry.isDirectory()
            parentPath =
                    ? entryPath.length() - 2 : entryPath.length() - 1);
                    delimiterIndex != -1 ? entry.getName().substring(0, delimiterIndex) + "/" : "/";
            parentPath = entryPath.substring(0, delimiterIndex) + "/";

            parentList = mTree.get(parentPath);
            parentList = mTree.get(parentPath);


            if (parentList == null) {
            if (parentList == null) {
                parentEntry = mEntries.get(parentPath);
                if (parentEntry == null) {
                // The ZIP file doesn't contain all directories leading to the entry.
                // The ZIP file doesn't contain all directories leading to the entry.
                // It's rare, but can happen in a valid ZIP archive. In such case create a
                // It's rare, but can happen in a valid ZIP archive. In such case create a
                // fake ZipEntry and add it on top of the stack to process it next.
                // fake ZipEntry and add it on top of the stack to process it next.
@@ -155,8 +158,11 @@ public class Archive implements Closeable {
                parentEntry.setSize(0);
                parentEntry.setSize(0);
                parentEntry.setTime(entry.getTime());
                parentEntry.setTime(entry.getTime());
                mEntries.put(parentPath, parentEntry);
                mEntries.put(parentPath, parentEntry);

                if (!"/".equals(parentPath)) {
                    stack.push(parentEntry);
                    stack.push(parentEntry);
                }
                }

                parentList = new ArrayList<ZipEntry>();
                parentList = new ArrayList<ZipEntry>();
                mTree.put(parentPath, parentList);
                mTree.put(parentPath, parentList);
            }
            }
@@ -165,6 +171,19 @@ public class Archive implements Closeable {
        }
        }
    }
    }


    /**
     * Returns a valid, normalized path for an entry.
     */
    public static String getEntryPath(ZipEntry entry) {
        Preconditions.checkArgument(entry.isDirectory() == entry.getName().endsWith("/"),
                "Ill-formated ZIP-file.");
        if (entry.getName().startsWith("/")) {
            return entry.getName();
        } else {
            return "/" + entry.getName();
        }
    }

    /**
    /**
     * Returns true if the file descriptor is seekable.
     * Returns true if the file descriptor is seekable.
     * @param descriptor File descriptor to check.
     * @param descriptor File descriptor to check.
@@ -296,25 +315,18 @@ public class Archive implements Closeable {
            return false;
            return false;
        }
        }


        // TODO: Include the fake '/' entry in mEntries, and remove this check.
        // Fake entries are for directories which do not have entries in the ZIP
        // archive, but are reachable. Eg. /a, /a/b and /a/b/c for a single-entry
        // archive containing /a/b/c/file.txt.
        if (parsedParentId.mPath.equals("/")) {
            return true;
        }

        final ZipEntry parentEntry = mEntries.get(parsedParentId.mPath);
        final ZipEntry parentEntry = mEntries.get(parsedParentId.mPath);
        if (parentEntry == null || !parentEntry.isDirectory()) {
        if (parentEntry == null || !parentEntry.isDirectory()) {
            return false;
            return false;
        }
        }


        final String parentPath = entry.getName();

        // Add a trailing slash even if it's not a directory, so it's easy to check if the
        // Add a trailing slash even if it's not a directory, so it's easy to check if the
        // entry is a descendant.
        // entry is a descendant.
        final String pathWithSlash = entry.isDirectory() ? entry.getName() : entry.getName() + "/";
        String pathWithSlash = entry.isDirectory() ? getEntryPath(entry)
        return pathWithSlash.startsWith(parentPath) && !parentPath.equals(pathWithSlash);
                : getEntryPath(entry) + "/";

        return pathWithSlash.startsWith(parsedParentId.mPath) &&
                !parsedParentId.mPath.equals(pathWithSlash);
    }
    }


    /**
    /**
@@ -489,7 +501,7 @@ public class Archive implements Closeable {


    private void addCursorRow(MatrixCursor cursor, ZipEntry entry) {
    private void addCursorRow(MatrixCursor cursor, ZipEntry entry) {
        final MatrixCursor.RowBuilder row = cursor.newRow();
        final MatrixCursor.RowBuilder row = cursor.newRow();
        final ArchiveId parsedId = new ArchiveId(mArchiveUri, entry.getName());
        final ArchiveId parsedId = new ArchiveId(mArchiveUri, getEntryPath(entry));
        row.add(Document.COLUMN_DOCUMENT_ID, parsedId.toDocumentId());
        row.add(Document.COLUMN_DOCUMENT_ID, parsedId.toDocumentId());


        final File file = new File(entry.getName());
        final File file = new File(entry.getName());
+26 −26
Original line number Original line Diff line number Diff line
@@ -157,7 +157,7 @@ public class ArchiveTest extends AndroidTestCase {
                new ArchiveId(ARCHIVE_URI, "/").toDocumentId(), null, null);
                new ArchiveId(ARCHIVE_URI, "/").toDocumentId(), null, null);


        assertTrue(cursor.moveToFirst());
        assertTrue(cursor.moveToFirst());
        assertEquals(new ArchiveId(ARCHIVE_URI, "dir1/").toDocumentId(),
        assertEquals(new ArchiveId(ARCHIVE_URI, "/dir1/").toDocumentId(),
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
        assertEquals("dir1",
        assertEquals("dir1",
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
@@ -168,7 +168,7 @@ public class ArchiveTest extends AndroidTestCase {


        assertTrue(cursor.moveToNext());
        assertTrue(cursor.moveToNext());
        assertEquals(
        assertEquals(
                new ArchiveId(ARCHIVE_URI, "dir2/").toDocumentId(),
                new ArchiveId(ARCHIVE_URI, "/dir2/").toDocumentId(),
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
        assertEquals("dir2",
        assertEquals("dir2",
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
@@ -179,7 +179,7 @@ public class ArchiveTest extends AndroidTestCase {


        assertTrue(cursor.moveToNext());
        assertTrue(cursor.moveToNext());
        assertEquals(
        assertEquals(
                new ArchiveId(ARCHIVE_URI, "file1.txt").toDocumentId(),
                new ArchiveId(ARCHIVE_URI, "/file1.txt").toDocumentId(),
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
        assertEquals("file1.txt",
        assertEquals("file1.txt",
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
@@ -192,11 +192,11 @@ public class ArchiveTest extends AndroidTestCase {


        // Check if querying children works too.
        // Check if querying children works too.
        final Cursor childCursor = mArchive.queryChildDocuments(
        final Cursor childCursor = mArchive.queryChildDocuments(
                new ArchiveId(ARCHIVE_URI, "dir1/").toDocumentId(), null, null);
                new ArchiveId(ARCHIVE_URI, "/dir1/").toDocumentId(), null, null);


        assertTrue(childCursor.moveToFirst());
        assertTrue(childCursor.moveToFirst());
        assertEquals(
        assertEquals(
                new ArchiveId(ARCHIVE_URI, "dir1/cherries.txt").toDocumentId(),
                new ArchiveId(ARCHIVE_URI, "/dir1/cherries.txt").toDocumentId(),
                childCursor.getString(childCursor.getColumnIndexOrThrow(
                childCursor.getString(childCursor.getColumnIndexOrThrow(
                        Document.COLUMN_DOCUMENT_ID)));
                        Document.COLUMN_DOCUMENT_ID)));
        assertEquals("cherries.txt",
        assertEquals("cherries.txt",
@@ -216,7 +216,7 @@ public class ArchiveTest extends AndroidTestCase {


        assertTrue(cursor.moveToFirst());
        assertTrue(cursor.moveToFirst());
        assertEquals(
        assertEquals(
                new ArchiveId(ARCHIVE_URI, "dir1/").toDocumentId(),
                new ArchiveId(ARCHIVE_URI, "/dir1/").toDocumentId(),
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
        assertEquals("dir1",
        assertEquals("dir1",
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
@@ -227,11 +227,11 @@ public class ArchiveTest extends AndroidTestCase {
        assertFalse(cursor.moveToNext());
        assertFalse(cursor.moveToNext());


        final Cursor childCursor = mArchive.queryChildDocuments(
        final Cursor childCursor = mArchive.queryChildDocuments(
                new ArchiveId(ARCHIVE_URI, "dir1/").toDocumentId(), null, null);
                new ArchiveId(ARCHIVE_URI, "/dir1/").toDocumentId(), null, null);


        assertTrue(childCursor.moveToFirst());
        assertTrue(childCursor.moveToFirst());
        assertEquals(
        assertEquals(
                new ArchiveId(ARCHIVE_URI, "dir1/dir2/").toDocumentId(),
                new ArchiveId(ARCHIVE_URI, "/dir1/dir2/").toDocumentId(),
                childCursor.getString(childCursor.getColumnIndexOrThrow(
                childCursor.getString(childCursor.getColumnIndexOrThrow(
                        Document.COLUMN_DOCUMENT_ID)));
                        Document.COLUMN_DOCUMENT_ID)));
        assertEquals("dir2",
        assertEquals("dir2",
@@ -245,12 +245,12 @@ public class ArchiveTest extends AndroidTestCase {
        assertFalse(childCursor.moveToNext());
        assertFalse(childCursor.moveToNext());


        final Cursor childCursor2 = mArchive.queryChildDocuments(
        final Cursor childCursor2 = mArchive.queryChildDocuments(
                new ArchiveId(ARCHIVE_URI, "dir1/dir2/").toDocumentId(),
                new ArchiveId(ARCHIVE_URI, "/dir1/dir2/").toDocumentId(),
                null, null);
                null, null);


        assertTrue(childCursor2.moveToFirst());
        assertTrue(childCursor2.moveToFirst());
        assertEquals(
        assertEquals(
                new ArchiveId(ARCHIVE_URI, "dir1/dir2/cherries.txt").toDocumentId(),
                new ArchiveId(ARCHIVE_URI, "/dir1/dir2/cherries.txt").toDocumentId(),
                childCursor2.getString(childCursor.getColumnIndexOrThrow(
                childCursor2.getString(childCursor.getColumnIndexOrThrow(
                        Document.COLUMN_DOCUMENT_ID)));
                        Document.COLUMN_DOCUMENT_ID)));
        assertFalse(childCursor2.moveToNext());
        assertFalse(childCursor2.moveToNext());
@@ -263,7 +263,7 @@ public class ArchiveTest extends AndroidTestCase {


        assertTrue(cursor.moveToFirst());
        assertTrue(cursor.moveToFirst());
        assertEquals(
        assertEquals(
                new ArchiveId(ARCHIVE_URI, "dir1/").toDocumentId(),
                new ArchiveId(ARCHIVE_URI, "/dir1/").toDocumentId(),
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
        assertEquals("dir1",
        assertEquals("dir1",
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
@@ -274,11 +274,11 @@ public class ArchiveTest extends AndroidTestCase {
        assertFalse(cursor.moveToNext());
        assertFalse(cursor.moveToNext());


        final Cursor childCursor = mArchive.queryChildDocuments(
        final Cursor childCursor = mArchive.queryChildDocuments(
                new ArchiveId(ARCHIVE_URI, "dir1/").toDocumentId(), null, null);
                new ArchiveId(ARCHIVE_URI, "/dir1/").toDocumentId(), null, null);


        assertTrue(childCursor.moveToFirst());
        assertTrue(childCursor.moveToFirst());
        assertEquals(
        assertEquals(
                new ArchiveId(ARCHIVE_URI, "dir1/dir2/").toDocumentId(),
                new ArchiveId(ARCHIVE_URI, "/dir1/dir2/").toDocumentId(),
                childCursor.getString(childCursor.getColumnIndexOrThrow(
                childCursor.getString(childCursor.getColumnIndexOrThrow(
                        Document.COLUMN_DOCUMENT_ID)));
                        Document.COLUMN_DOCUMENT_ID)));
        assertEquals("dir2",
        assertEquals("dir2",
@@ -292,7 +292,7 @@ public class ArchiveTest extends AndroidTestCase {


        assertTrue(childCursor.moveToNext());
        assertTrue(childCursor.moveToNext());
        assertEquals(
        assertEquals(
                new ArchiveId(ARCHIVE_URI, "dir1/dir3/").toDocumentId(),
                new ArchiveId(ARCHIVE_URI, "/dir1/dir3/").toDocumentId(),
                childCursor.getString(childCursor.getColumnIndexOrThrow(
                childCursor.getString(childCursor.getColumnIndexOrThrow(
                        Document.COLUMN_DOCUMENT_ID)));
                        Document.COLUMN_DOCUMENT_ID)));
        assertEquals("dir3",
        assertEquals("dir3",
@@ -306,12 +306,12 @@ public class ArchiveTest extends AndroidTestCase {
        assertFalse(cursor.moveToNext());
        assertFalse(cursor.moveToNext());


        final Cursor childCursor2 = mArchive.queryChildDocuments(
        final Cursor childCursor2 = mArchive.queryChildDocuments(
                new ArchiveId(ARCHIVE_URI, "dir1/dir2/").toDocumentId(),
                new ArchiveId(ARCHIVE_URI, "/dir1/dir2/").toDocumentId(),
                null, null);
                null, null);
        assertFalse(childCursor2.moveToFirst());
        assertFalse(childCursor2.moveToFirst());


        final Cursor childCursor3 = mArchive.queryChildDocuments(
        final Cursor childCursor3 = mArchive.queryChildDocuments(
                new ArchiveId(ARCHIVE_URI, "dir1/dir3/").toDocumentId(),
                new ArchiveId(ARCHIVE_URI, "/dir1/dir3/").toDocumentId(),
                null, null);
                null, null);
        assertFalse(childCursor3.moveToFirst());
        assertFalse(childCursor3.moveToFirst());
    }
    }
@@ -319,34 +319,34 @@ public class ArchiveTest extends AndroidTestCase {
    public void testGetDocumentType() throws IOException {
    public void testGetDocumentType() throws IOException {
        loadArchive(getNonSeekableDescriptor(R.raw.archive));
        loadArchive(getNonSeekableDescriptor(R.raw.archive));
        assertEquals(Document.MIME_TYPE_DIR, mArchive.getDocumentType(
        assertEquals(Document.MIME_TYPE_DIR, mArchive.getDocumentType(
                new ArchiveId(ARCHIVE_URI, "dir1/").toDocumentId()));
                new ArchiveId(ARCHIVE_URI, "/dir1/").toDocumentId()));
        assertEquals("text/plain", mArchive.getDocumentType(
        assertEquals("text/plain", mArchive.getDocumentType(
                new ArchiveId(ARCHIVE_URI, "file1.txt").toDocumentId()));
                new ArchiveId(ARCHIVE_URI, "/file1.txt").toDocumentId()));
    }
    }


    public void testIsChildDocument() throws IOException {
    public void testIsChildDocument() throws IOException {
        loadArchive(getNonSeekableDescriptor(R.raw.archive));
        loadArchive(getNonSeekableDescriptor(R.raw.archive));
        final String documentId = new ArchiveId(ARCHIVE_URI, "/").toDocumentId();
        final String documentId = new ArchiveId(ARCHIVE_URI, "/").toDocumentId();
        assertTrue(mArchive.isChildDocument(documentId,
        assertTrue(mArchive.isChildDocument(documentId,
                new ArchiveId(ARCHIVE_URI, "dir1/").toDocumentId()));
                new ArchiveId(ARCHIVE_URI, "/dir1/").toDocumentId()));
        assertFalse(mArchive.isChildDocument(documentId,
        assertFalse(mArchive.isChildDocument(documentId,
                new ArchiveId(ARCHIVE_URI, "this-does-not-exist").toDocumentId()));
                new ArchiveId(ARCHIVE_URI, "/this-does-not-exist").toDocumentId()));
        assertTrue(mArchive.isChildDocument(
        assertTrue(mArchive.isChildDocument(
                new ArchiveId(ARCHIVE_URI, "dir1/").toDocumentId(),
                new ArchiveId(ARCHIVE_URI, "/dir1/").toDocumentId(),
                new ArchiveId(ARCHIVE_URI, "dir1/cherries.txt").toDocumentId()));
                new ArchiveId(ARCHIVE_URI, "/dir1/cherries.txt").toDocumentId()));
        assertTrue(mArchive.isChildDocument(documentId,
        assertTrue(mArchive.isChildDocument(documentId,
                new ArchiveId(ARCHIVE_URI, "dir1/cherries.txt").toDocumentId()));
                new ArchiveId(ARCHIVE_URI, "/dir1/cherries.txt").toDocumentId()));
    }
    }


    public void testQueryDocument() throws IOException {
    public void testQueryDocument() throws IOException {
        loadArchive(getNonSeekableDescriptor(R.raw.archive));
        loadArchive(getNonSeekableDescriptor(R.raw.archive));
        final Cursor cursor = mArchive.queryDocument(
        final Cursor cursor = mArchive.queryDocument(
                new ArchiveId(ARCHIVE_URI, "dir2/strawberries.txt").toDocumentId(),
                new ArchiveId(ARCHIVE_URI, "/dir2/strawberries.txt").toDocumentId(),
                null);
                null);


        assertTrue(cursor.moveToFirst());
        assertTrue(cursor.moveToFirst());
        assertEquals(
        assertEquals(
                new ArchiveId(ARCHIVE_URI, "dir2/strawberries.txt").toDocumentId(),
                new ArchiveId(ARCHIVE_URI, "/dir2/strawberries.txt").toDocumentId(),
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
        assertEquals("strawberries.txt",
        assertEquals("strawberries.txt",
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
                cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
@@ -369,7 +369,7 @@ public class ArchiveTest extends AndroidTestCase {
    // Common part of testOpenDocument and testOpenDocument_NonSeekable.
    // Common part of testOpenDocument and testOpenDocument_NonSeekable.
    void commonTestOpenDocument() throws IOException {
    void commonTestOpenDocument() throws IOException {
        final ParcelFileDescriptor descriptor = mArchive.openDocument(
        final ParcelFileDescriptor descriptor = mArchive.openDocument(
                new ArchiveId(ARCHIVE_URI, "dir2/strawberries.txt").toDocumentId(),
                new ArchiveId(ARCHIVE_URI, "/dir2/strawberries.txt").toDocumentId(),
                "r", null /* signal */);
                "r", null /* signal */);
        try (final ParcelFileDescriptor.AutoCloseInputStream inputStream =
        try (final ParcelFileDescriptor.AutoCloseInputStream inputStream =
                new ParcelFileDescriptor.AutoCloseInputStream(descriptor)) {
                new ParcelFileDescriptor.AutoCloseInputStream(descriptor)) {