18a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak/*
28a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Licensed to the Apache Software Foundation (ASF) under one or more
38a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * contributor license agreements.  See the NOTICE file distributed with
48a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * this work for additional information regarding copyright ownership.
58a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * The ASF licenses this file to You under the Apache License, Version 2.0
68a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * (the "License"); you may not use this file except in compliance with
78a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * the License.  You may obtain a copy of the License at
88a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak *
98a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak *     http://www.apache.org/licenses/LICENSE-2.0
108a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak *
118a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Unless required by applicable law or agreed to in writing, software
128a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * distributed under the License is distributed on an "AS IS" BASIS,
138a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
148a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * See the License for the specific language governing permissions and
158a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * limitations under the License.
168a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */
178a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
188a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakpackage android.util.jar;
198a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
208a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.io.ByteArrayOutputStream;
218a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.io.IOException;
228a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.nio.charset.StandardCharsets;
238a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.HashMap;
248a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.Map;
258a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakimport java.util.jar.Attributes;
268a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
278a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak/**
288a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * Reads a JAR file manifest. The specification is here:
298a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak * http://java.sun.com/javase/6/docs/technotes/guides/jar/jar.html
308a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak */
318a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniakclass StrictJarManifestReader {
328a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    // There are relatively few unique attribute names,
338a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    // but a manifest might have thousands of entries.
348a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    private final HashMap<String, Attributes.Name> attributeNameCache = new HashMap<String, Attributes.Name>();
358a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
368a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    private final ByteArrayOutputStream valueBuffer = new ByteArrayOutputStream(80);
378a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
388a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    private final byte[] buf;
398a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
408a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    private final int endOfMainSection;
418a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
428a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    private int pos;
438a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
448a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    private Attributes.Name name;
458a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
468a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    private String value;
478a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
488a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    private int consecutiveLineBreaks = 0;
498a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
508a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    public StrictJarManifestReader(byte[] buf, Attributes main) throws IOException {
518a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        this.buf = buf;
528a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        while (readHeader()) {
538a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            main.put(name, value);
548a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        }
558a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        this.endOfMainSection = pos;
568a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    }
578a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
588a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    public void readEntries(Map<String, Attributes> entries, Map<String, StrictJarManifest.Chunk> chunks) throws IOException {
598a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        int mark = pos;
608a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        while (readHeader()) {
618a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            if (!Attributes.Name.NAME.equals(name)) {
628a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                throw new IOException("Entry is not named");
638a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            }
648a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            String entryNameValue = value;
658a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
668a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            Attributes entry = entries.get(entryNameValue);
678a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            if (entry == null) {
688a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                entry = new Attributes(12);
698a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            }
708a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
718a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            while (readHeader()) {
728a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                entry.put(name, value);
738a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            }
748a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
758a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            if (chunks != null) {
768a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                if (chunks.get(entryNameValue) != null) {
778a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                    // TODO A bug: there might be several verification chunks for
788a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                    // the same name. I believe they should be used to update
798a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                    // signature in order of appearance; there are two ways to fix
808a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                    // this: either use a list of chunks, or decide on used
818a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                    // signature algorithm in advance and reread the chunks while
828a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                    // updating the signature; for now a defensive error is thrown
838a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                    throw new IOException("A jar verifier does not support more than one entry with the same name");
848a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                }
858a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                chunks.put(entryNameValue, new StrictJarManifest.Chunk(mark, pos));
868a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                mark = pos;
878a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            }
888a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
898a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            entries.put(entryNameValue, entry);
908a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        }
918a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    }
928a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
938a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    public int getEndOfMainSection() {
948a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        return endOfMainSection;
958a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    }
968a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
978a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    /**
988a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak     * Read a single line from the manifest buffer.
998a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak     */
1008a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    private boolean readHeader() throws IOException {
1018a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        if (consecutiveLineBreaks > 1) {
1028a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            // break a section on an empty line
1038a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            consecutiveLineBreaks = 0;
1048a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            return false;
1058a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        }
1068a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        readName();
1078a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        consecutiveLineBreaks = 0;
1088a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        readValue();
1098a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        // if the last line break is missed, the line
1108a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        // is ignored by the reference implementation
1118a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        return consecutiveLineBreaks > 0;
1128a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    }
1138a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
1148a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    private void readName() throws IOException {
1158a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        int mark = pos;
1168a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
1178a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        while (pos < buf.length) {
1188a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            if (buf[pos++] != ':') {
1198a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                continue;
1208a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            }
1218a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
1228a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            String nameString = new String(buf, mark, pos - mark - 1, StandardCharsets.US_ASCII);
1238a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
1248a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            if (buf[pos++] != ' ') {
1258a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                throw new IOException(String.format("Invalid value for attribute '%s'", nameString));
1268a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            }
1278a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
1288a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            try {
1298a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                name = attributeNameCache.get(nameString);
1308a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                if (name == null) {
1318a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                    name = new Attributes.Name(nameString);
1328a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                    attributeNameCache.put(nameString, name);
1338a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                }
1348a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            } catch (IllegalArgumentException e) {
1358a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                // new Attributes.Name() throws IllegalArgumentException but we declare IOException
1368a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                throw new IOException(e.getMessage());
1378a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            }
1388a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            return;
1398a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        }
1408a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    }
1418a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
1428a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    private void readValue() throws IOException {
1438a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        boolean lastCr = false;
1448a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        int mark = pos;
1458a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        int last = pos;
1468a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        valueBuffer.reset();
1478a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        while (pos < buf.length) {
1488a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            byte next = buf[pos++];
1498a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            switch (next) {
1508a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            case 0:
1518a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                throw new IOException("NUL character in a manifest");
1528a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            case '\n':
1538a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                if (lastCr) {
1548a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                    lastCr = false;
1558a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                } else {
1568a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                    consecutiveLineBreaks++;
1578a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                }
1588a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                continue;
1598a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            case '\r':
1608a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                lastCr = true;
1618a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                consecutiveLineBreaks++;
1628a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                continue;
1638a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            case ' ':
1648a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                if (consecutiveLineBreaks == 1) {
1658a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                    valueBuffer.write(buf, mark, last - mark);
1668a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                    mark = pos;
1678a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                    consecutiveLineBreaks = 0;
1688a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                    continue;
1698a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                }
1708a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            }
1718a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
1728a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            if (consecutiveLineBreaks >= 1) {
1738a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                pos--;
1748a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak                break;
1758a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            }
1768a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak            last = pos;
1778a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        }
1788a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak
1798a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        valueBuffer.write(buf, mark, last - mark);
1808a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        // A bit frustrating that that Charset.forName will be called
1818a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        // again.
1828a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak        value = valueBuffer.toString(StandardCharsets.UTF_8.name());
1838a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak    }
1848a7c1606d88873c5a1b5764c16cb046b6f2275b2Przemyslaw Szczepaniak}
185