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