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.IOException;
21import java.io.InputStream;
22import java.io.OutputStream;
23import java.nio.ByteBuffer;
24import java.nio.CharBuffer;
25import java.nio.charset.CharsetEncoder;
26import java.nio.charset.CoderResult;
27import java.nio.charset.StandardCharsets;
28import java.util.HashMap;
29import java.util.Iterator;
30import java.util.Map;
31import libcore.io.Streams;
32
33/**
34 * The {@code Manifest} class is used to obtain attribute information for a
35 * {@code JarFile} and its entries.
36 */
37public class Manifest implements Cloneable {
38    static final int LINE_LENGTH_LIMIT = 72;
39
40    private static final byte[] LINE_SEPARATOR = new byte[] { '\r', '\n' };
41
42    private static final byte[] VALUE_SEPARATOR = new byte[] { ':', ' ' };
43
44    private final Attributes mainAttributes;
45    private final HashMap<String, Attributes> entries;
46
47    static final class Chunk {
48        final int start;
49        final int end;
50
51        Chunk(int start, int end) {
52            this.start = start;
53            this.end = end;
54        }
55    }
56
57    private HashMap<String, Chunk> chunks;
58
59    /**
60     * The end of the main attributes section in the manifest is needed in
61     * verification.
62     */
63    private int mainEnd;
64
65    /**
66     * Creates a new {@code Manifest} instance.
67     */
68    public Manifest() {
69        entries = new HashMap<String, Attributes>();
70        mainAttributes = new Attributes();
71    }
72
73    /**
74     * Creates a new {@code Manifest} instance using the attributes obtained
75     * from the input stream.
76     *
77     * @param is
78     *            {@code InputStream} to parse for attributes.
79     * @throws IOException
80     *             if an IO error occurs while creating this {@code Manifest}
81     */
82    public Manifest(InputStream is) throws IOException {
83        this();
84        read(Streams.readFully(is));
85    }
86
87    /**
88     * Creates a new {@code Manifest} instance. The new instance will have the
89     * same attributes as those found in the parameter {@code Manifest}.
90     *
91     * @param man
92     *            {@code Manifest} instance to obtain attributes from.
93     */
94    @SuppressWarnings("unchecked")
95    public Manifest(Manifest man) {
96        mainAttributes = (Attributes) man.mainAttributes.clone();
97        entries = (HashMap<String, Attributes>) ((HashMap<String, Attributes>) man
98                .getEntries()).clone();
99    }
100
101    Manifest(byte[] manifestBytes, boolean readChunks) throws IOException {
102        this();
103        if (readChunks) {
104            chunks = new HashMap<String, Chunk>();
105        }
106        read(manifestBytes);
107    }
108
109    /**
110     * Resets the both the main attributes as well as the entry attributes
111     * associated with this {@code Manifest}.
112     */
113    public void clear() {
114        entries.clear();
115        mainAttributes.clear();
116    }
117
118    /**
119     * Returns the {@code Attributes} associated with the parameter entry
120     * {@code name}.
121     *
122     * @param name
123     *            the name of the entry to obtain {@code Attributes} from.
124     * @return the Attributes for the entry or {@code null} if the entry does
125     *         not exist.
126     */
127    public Attributes getAttributes(String name) {
128        return getEntries().get(name);
129    }
130
131    /**
132     * Returns a map containing the {@code Attributes} for each entry in the
133     * {@code Manifest}.
134     *
135     * @return the map of entry attributes.
136     */
137    public Map<String, Attributes> getEntries() {
138        return entries;
139    }
140
141    /**
142     * Returns the main {@code Attributes} of the {@code JarFile}.
143     *
144     * @return main {@code Attributes} associated with the source {@code
145     *         JarFile}.
146     */
147    public Attributes getMainAttributes() {
148        return mainAttributes;
149    }
150
151    /**
152     * Creates a copy of this {@code Manifest}. The returned {@code Manifest}
153     * will equal the {@code Manifest} from which it was cloned.
154     *
155     * @return a copy of this instance.
156     */
157    @Override
158    public Object clone() {
159        return new Manifest(this);
160    }
161
162    /**
163     * Writes this {@code Manifest}'s name/attributes pairs to the given {@code OutputStream}.
164     * The {@code MANIFEST_VERSION} or {@code SIGNATURE_VERSION} attribute must be set before
165     * calling this method, or no attributes will be written.
166     *
167     * @throws IOException
168     *             If an error occurs writing the {@code Manifest}.
169     */
170    public void write(OutputStream os) throws IOException {
171        write(this, os);
172    }
173
174    /**
175     * Merges name/attribute pairs read from the input stream {@code is} into this manifest.
176     *
177     * @param is
178     *            The {@code InputStream} to read from.
179     * @throws IOException
180     *             If an error occurs reading the manifest.
181     */
182    public void read(InputStream is) throws IOException {
183        read(Streams.readFullyNoClose(is));
184    }
185
186    private void read(byte[] buf) throws IOException {
187        if (buf.length == 0) {
188            return;
189        }
190
191        ManifestReader im = new ManifestReader(buf, mainAttributes);
192        mainEnd = im.getEndOfMainSection();
193        im.readEntries(entries, chunks);
194    }
195
196    /**
197     * Returns the hash code for this instance.
198     *
199     * @return this {@code Manifest}'s hashCode.
200     */
201    @Override
202    public int hashCode() {
203        return mainAttributes.hashCode() ^ getEntries().hashCode();
204    }
205
206    /**
207     * Determines if the receiver is equal to the parameter object. Two {@code
208     * Manifest}s are equal if they have identical main attributes as well as
209     * identical entry attributes.
210     *
211     * @param o
212     *            the object to compare against.
213     * @return {@code true} if the manifests are equal, {@code false} otherwise
214     */
215    @Override
216    public boolean equals(Object o) {
217        if (o == null) {
218            return false;
219        }
220        if (o.getClass() != this.getClass()) {
221            return false;
222        }
223        if (!mainAttributes.equals(((Manifest) o).mainAttributes)) {
224            return false;
225        }
226        return getEntries().equals(((Manifest) o).getEntries());
227    }
228
229    Chunk getChunk(String name) {
230        return chunks.get(name);
231    }
232
233    void removeChunks() {
234        chunks = null;
235    }
236
237    int getMainAttributesEnd() {
238        return mainEnd;
239    }
240
241    /**
242     * Writes out the attribute information of the specified manifest to the
243     * specified {@code OutputStream}
244     *
245     * @param manifest
246     *            the manifest to write out.
247     * @param out
248     *            The {@code OutputStream} to write to.
249     * @throws IOException
250     *             If an error occurs writing the {@code Manifest}.
251     */
252    static void write(Manifest manifest, OutputStream out) throws IOException {
253        CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
254        ByteBuffer buffer = ByteBuffer.allocate(LINE_LENGTH_LIMIT);
255
256        Attributes.Name versionName = Attributes.Name.MANIFEST_VERSION;
257        String version = manifest.mainAttributes.getValue(versionName);
258        if (version == null) {
259            versionName = Attributes.Name.SIGNATURE_VERSION;
260            version = manifest.mainAttributes.getValue(versionName);
261        }
262        if (version != null) {
263            writeEntry(out, versionName, version, encoder, buffer);
264            Iterator<?> entries = manifest.mainAttributes.keySet().iterator();
265            while (entries.hasNext()) {
266                Attributes.Name name = (Attributes.Name) entries.next();
267                if (!name.equals(versionName)) {
268                    writeEntry(out, name, manifest.mainAttributes.getValue(name), encoder, buffer);
269                }
270            }
271        }
272        out.write(LINE_SEPARATOR);
273        Iterator<String> i = manifest.getEntries().keySet().iterator();
274        while (i.hasNext()) {
275            String key = i.next();
276            writeEntry(out, Attributes.Name.NAME, key, encoder, buffer);
277            Attributes attributes = manifest.entries.get(key);
278            Iterator<?> entries = attributes.keySet().iterator();
279            while (entries.hasNext()) {
280                Attributes.Name name = (Attributes.Name) entries.next();
281                writeEntry(out, name, attributes.getValue(name), encoder, buffer);
282            }
283            out.write(LINE_SEPARATOR);
284        }
285    }
286
287    private static void writeEntry(OutputStream os, Attributes.Name name,
288            String value, CharsetEncoder encoder, ByteBuffer bBuf) throws IOException {
289        String nameString = name.getName();
290        os.write(nameString.getBytes(StandardCharsets.US_ASCII));
291        os.write(VALUE_SEPARATOR);
292
293        encoder.reset();
294        bBuf.clear().limit(LINE_LENGTH_LIMIT - nameString.length() - 2);
295
296        CharBuffer cBuf = CharBuffer.wrap(value);
297
298        while (true) {
299            CoderResult r = encoder.encode(cBuf, bBuf, true);
300            if (CoderResult.UNDERFLOW == r) {
301                r = encoder.flush(bBuf);
302            }
303            os.write(bBuf.array(), bBuf.arrayOffset(), bBuf.position());
304            os.write(LINE_SEPARATOR);
305            if (CoderResult.UNDERFLOW == r) {
306                break;
307            }
308            os.write(' ');
309            bBuf.clear().limit(LINE_LENGTH_LIMIT - 1);
310        }
311    }
312}
313