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