1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package java.util.jar;
19
20import java.io.ByteArrayInputStream;
21import java.io.IOException;
22import java.io.InputStream;
23import java.io.OutputStream;
24import java.lang.reflect.Field;
25import java.nio.ByteBuffer;
26import java.nio.CharBuffer;
27import java.nio.charset.CharsetEncoder;
28import java.nio.charset.CoderResult;
29import java.nio.charset.StandardCharsets;
30import java.util.HashMap;
31import java.util.Iterator;
32import java.util.Map;
33import libcore.io.Streams;
34
35/**
36 * The {@code Manifest} class is used to obtain attribute information for a
37 * {@code JarFile} and its entries.
38 */
39public class Manifest implements Cloneable {
40    static final int LINE_LENGTH_LIMIT = 72;
41
42    private static final byte[] LINE_SEPARATOR = new byte[] { '\r', '\n' };
43
44    private static final byte[] VALUE_SEPARATOR = new byte[] { ':', ' ' };
45
46    private static final Field BAIS_BUF = getByteArrayInputStreamField("buf");
47    private static final Field BAIS_POS = getByteArrayInputStreamField("pos");
48
49    private static Field getByteArrayInputStreamField(String name) {
50        try {
51            Field f = ByteArrayInputStream.class.getDeclaredField(name);
52            f.setAccessible(true);
53            return f;
54        } catch (Exception ex) {
55            throw new AssertionError(ex);
56        }
57    }
58
59    private Attributes mainAttributes = new Attributes();
60
61    private HashMap<String, Attributes> entries = new HashMap<String, Attributes>();
62
63    static class Chunk {
64        int start;
65        int end;
66
67        Chunk(int start, int end) {
68            this.start = start;
69            this.end = end;
70        }
71    }
72
73    private HashMap<String, Chunk> chunks;
74
75    /**
76     * The end of the main attributes section in the manifest is needed in
77     * verification.
78     */
79    private int mainEnd;
80
81    /**
82     * Creates a new {@code Manifest} instance.
83     */
84    public Manifest() {
85    }
86
87    /**
88     * Creates a new {@code Manifest} instance using the attributes obtained
89     * from the input stream.
90     *
91     * @param is
92     *            {@code InputStream} to parse for attributes.
93     * @throws IOException
94     *             if an IO error occurs while creating this {@code Manifest}
95     */
96    public Manifest(InputStream is) throws IOException {
97        read(is);
98    }
99
100    /**
101     * Creates a new {@code Manifest} instance. The new instance will have the
102     * same attributes as those found in the parameter {@code Manifest}.
103     *
104     * @param man
105     *            {@code Manifest} instance to obtain attributes from.
106     */
107    @SuppressWarnings("unchecked")
108    public Manifest(Manifest man) {
109        mainAttributes = (Attributes) man.mainAttributes.clone();
110        entries = (HashMap<String, Attributes>) ((HashMap<String, Attributes>) man
111                .getEntries()).clone();
112    }
113
114    Manifest(InputStream is, boolean readChunks) throws IOException {
115        if (readChunks) {
116            chunks = new HashMap<String, Chunk>();
117        }
118        read(is);
119    }
120
121    /**
122     * Resets the both the main attributes as well as the entry attributes
123     * associated with this {@code Manifest}.
124     */
125    public void clear() {
126        entries.clear();
127        mainAttributes.clear();
128    }
129
130    /**
131     * Returns the {@code Attributes} associated with the parameter entry
132     * {@code name}.
133     *
134     * @param name
135     *            the name of the entry to obtain {@code Attributes} from.
136     * @return the Attributes for the entry or {@code null} if the entry does
137     *         not exist.
138     */
139    public Attributes getAttributes(String name) {
140        return getEntries().get(name);
141    }
142
143    /**
144     * Returns a map containing the {@code Attributes} for each entry in the
145     * {@code Manifest}.
146     *
147     * @return the map of entry attributes.
148     */
149    public Map<String, Attributes> getEntries() {
150        return entries;
151    }
152
153    /**
154     * Returns the main {@code Attributes} of the {@code JarFile}.
155     *
156     * @return main {@code Attributes} associated with the source {@code
157     *         JarFile}.
158     */
159    public Attributes getMainAttributes() {
160        return mainAttributes;
161    }
162
163    /**
164     * Creates a copy of this {@code Manifest}. The returned {@code Manifest}
165     * will equal the {@code Manifest} from which it was cloned.
166     *
167     * @return a copy of this instance.
168     */
169    @Override
170    public Object clone() {
171        return new Manifest(this);
172    }
173
174    /**
175     * Writes this {@code Manifest}'s name/attributes pairs to the given {@code OutputStream}.
176     * The {@code MANIFEST_VERSION} or {@code SIGNATURE_VERSION} attribute must be set before
177     * calling this method, or no attributes will be written.
178     *
179     * @throws IOException
180     *             If an error occurs writing the {@code Manifest}.
181     */
182    public void write(OutputStream os) throws IOException {
183        write(this, os);
184    }
185
186    /**
187     * Merges name/attribute pairs read from the input stream {@code is} into this manifest.
188     *
189     * @param is
190     *            The {@code InputStream} to read from.
191     * @throws IOException
192     *             If an error occurs reading the manifest.
193     */
194    public void read(InputStream is) throws IOException {
195        byte[] buf;
196        if (is instanceof ByteArrayInputStream) {
197            buf = exposeByteArrayInputStreamBytes((ByteArrayInputStream) is);
198        } else {
199            buf = Streams.readFullyNoClose(is);
200        }
201
202        if (buf.length == 0) {
203            return;
204        }
205
206        // a workaround for HARMONY-5662
207        // replace EOF and NUL with another new line
208        // which does not trigger an error
209        byte b = buf[buf.length - 1];
210        if (b == 0 || b == 26) {
211            buf[buf.length - 1] = '\n';
212        }
213
214        ManifestReader im = new ManifestReader(buf, mainAttributes);
215        mainEnd = im.getEndOfMainSection();
216        im.readEntries(entries, chunks);
217    }
218
219    /**
220     * Returns a byte[] containing all the bytes from a ByteArrayInputStream.
221     * Where possible, this returns the actual array rather than a copy.
222     */
223    private static byte[] exposeByteArrayInputStreamBytes(ByteArrayInputStream bais) {
224        byte[] buffer;
225        synchronized (bais) {
226            byte[] buf;
227            int pos;
228            try {
229                buf = (byte[]) BAIS_BUF.get(bais);
230                pos = BAIS_POS.getInt(bais);
231            } catch (IllegalAccessException iae) {
232                throw new AssertionError(iae);
233            }
234            int available = bais.available();
235            if (pos == 0 && buf.length == available) {
236                buffer = buf;
237            } else {
238                buffer = new byte[available];
239                System.arraycopy(buf, pos, buffer, 0, available);
240            }
241            bais.skip(available);
242        }
243        return buffer;
244    }
245
246    /**
247     * Returns the hash code for this instance.
248     *
249     * @return this {@code Manifest}'s hashCode.
250     */
251    @Override
252    public int hashCode() {
253        return mainAttributes.hashCode() ^ getEntries().hashCode();
254    }
255
256    /**
257     * Determines if the receiver is equal to the parameter object. Two {@code
258     * Manifest}s are equal if they have identical main attributes as well as
259     * identical entry attributes.
260     *
261     * @param o
262     *            the object to compare against.
263     * @return {@code true} if the manifests are equal, {@code false} otherwise
264     */
265    @Override
266    public boolean equals(Object o) {
267        if (o == null) {
268            return false;
269        }
270        if (o.getClass() != this.getClass()) {
271            return false;
272        }
273        if (!mainAttributes.equals(((Manifest) o).mainAttributes)) {
274            return false;
275        }
276        return getEntries().equals(((Manifest) o).getEntries());
277    }
278
279    Chunk getChunk(String name) {
280        return chunks.get(name);
281    }
282
283    void removeChunks() {
284        chunks = null;
285    }
286
287    int getMainAttributesEnd() {
288        return mainEnd;
289    }
290
291    /**
292     * Writes out the attribute information of the specified manifest to the
293     * specified {@code OutputStream}
294     *
295     * @param manifest
296     *            the manifest to write out.
297     * @param out
298     *            The {@code OutputStream} to write to.
299     * @throws IOException
300     *             If an error occurs writing the {@code Manifest}.
301     */
302    static void write(Manifest manifest, OutputStream out) throws IOException {
303        CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
304        ByteBuffer buffer = ByteBuffer.allocate(LINE_LENGTH_LIMIT);
305
306        Attributes.Name versionName = Attributes.Name.MANIFEST_VERSION;
307        String version = manifest.mainAttributes.getValue(versionName);
308        if (version == null) {
309            versionName = Attributes.Name.SIGNATURE_VERSION;
310            version = manifest.mainAttributes.getValue(versionName);
311        }
312        if (version != null) {
313            writeEntry(out, versionName, version, encoder, buffer);
314            Iterator<?> entries = manifest.mainAttributes.keySet().iterator();
315            while (entries.hasNext()) {
316                Attributes.Name name = (Attributes.Name) entries.next();
317                if (!name.equals(versionName)) {
318                    writeEntry(out, name, manifest.mainAttributes.getValue(name), encoder, buffer);
319                }
320            }
321        }
322        out.write(LINE_SEPARATOR);
323        Iterator<String> i = manifest.getEntries().keySet().iterator();
324        while (i.hasNext()) {
325            String key = i.next();
326            writeEntry(out, Attributes.Name.NAME, key, encoder, buffer);
327            Attributes attributes = manifest.entries.get(key);
328            Iterator<?> entries = attributes.keySet().iterator();
329            while (entries.hasNext()) {
330                Attributes.Name name = (Attributes.Name) entries.next();
331                writeEntry(out, name, attributes.getValue(name), encoder, buffer);
332            }
333            out.write(LINE_SEPARATOR);
334        }
335    }
336
337    private static void writeEntry(OutputStream os, Attributes.Name name,
338            String value, CharsetEncoder encoder, ByteBuffer bBuf) throws IOException {
339        String nameString = name.getName();
340        os.write(nameString.getBytes(StandardCharsets.US_ASCII));
341        os.write(VALUE_SEPARATOR);
342
343        encoder.reset();
344        bBuf.clear().limit(LINE_LENGTH_LIMIT - nameString.length() - 2);
345
346        CharBuffer cBuf = CharBuffer.wrap(value);
347
348        while (true) {
349            CoderResult r = encoder.encode(cBuf, bBuf, true);
350            if (CoderResult.UNDERFLOW == r) {
351                r = encoder.flush(bBuf);
352            }
353            os.write(bBuf.array(), bBuf.arrayOffset(), bBuf.position());
354            os.write(LINE_SEPARATOR);
355            if (CoderResult.UNDERFLOW == r) {
356                break;
357            }
358            os.write(' ');
359            bBuf.clear().limit(LINE_LENGTH_LIMIT - 1);
360        }
361    }
362}
363