1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.apksig.internal.jar;
18 
19 import java.nio.charset.StandardCharsets;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collections;
23 import java.util.List;
24 import java.util.jar.Attributes;
25 
26 /**
27  * JAR manifest and signature file parser.
28  *
29  * <p>These files consist of a main section followed by individual sections. Individual sections
30  * are named, their names referring to JAR entries.
31  *
32  * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#JAR_Manifest">JAR Manifest format</a>
33  */
34 public class ManifestParser {
35 
36     private final byte[] mManifest;
37     private int mOffset;
38     private int mEndOffset;
39 
40     private byte[] mBufferedLine;
41 
42     /**
43      * Constructs a new {@code ManifestParser} with the provided input.
44      */
ManifestParser(byte[] data)45     public ManifestParser(byte[] data) {
46         this(data, 0, data.length);
47     }
48 
49     /**
50      * Constructs a new {@code ManifestParser} with the provided input.
51      */
ManifestParser(byte[] data, int offset, int length)52     public ManifestParser(byte[] data, int offset, int length) {
53         mManifest = data;
54         mOffset = offset;
55         mEndOffset = offset + length;
56     }
57 
58     /**
59      * Returns the remaining sections of this file.
60      */
readAllSections()61     public List<Section> readAllSections() {
62         List<Section> sections = new ArrayList<>();
63         Section section;
64         while ((section = readSection()) != null) {
65             sections.add(section);
66         }
67         return sections;
68     }
69 
70     /**
71      * Returns the next section from this file or {@code null} if end of file has been reached.
72      */
readSection()73     public Section readSection() {
74         // Locate the first non-empty line
75         int sectionStartOffset;
76         String attr;
77         do {
78             sectionStartOffset = mOffset;
79             attr = readAttribute();
80             if (attr == null) {
81                 return null;
82             }
83         } while (attr.length() == 0);
84         List<Attribute> attrs = new ArrayList<>();
85         attrs.add(parseAttr(attr));
86 
87         // Read attributes until end of section reached
88         while (true) {
89             attr = readAttribute();
90             if ((attr == null) || (attr.length() == 0)) {
91                 // End of section
92                 break;
93             }
94             attrs.add(parseAttr(attr));
95         }
96 
97         int sectionEndOffset = mOffset;
98         int sectionSizeBytes = sectionEndOffset - sectionStartOffset;
99 
100         return new Section(sectionStartOffset, sectionSizeBytes, attrs);
101     }
102 
parseAttr(String attr)103     private static Attribute parseAttr(String attr) {
104         // Name is separated from value by a semicolon followed by a single SPACE character.
105         // This permits trailing spaces in names and leading and trailing spaces in values.
106         // Some APK obfuscators take advantage of this fact. We thus need to preserve these unusual
107         // spaces to be able to parse such obfuscated APKs.
108         int delimiterIndex = attr.indexOf(": ");
109         if (delimiterIndex == -1) {
110             return new Attribute(attr, "");
111         } else {
112             return new Attribute(
113                     attr.substring(0, delimiterIndex),
114                     attr.substring(delimiterIndex + ": ".length()));
115         }
116     }
117 
118     /**
119      * Returns the next attribute or empty {@code String} if end of section has been reached or
120      * {@code null} if end of input has been reached.
121      */
readAttribute()122     private String readAttribute() {
123         byte[] bytes = readAttributeBytes();
124         if (bytes == null) {
125             return null;
126         } else if (bytes.length == 0) {
127             return "";
128         } else {
129             return new String(bytes, StandardCharsets.UTF_8);
130         }
131     }
132 
133     /**
134      * Returns the next attribute or empty array if end of section has been reached or {@code null}
135      * if end of input has been reached.
136      */
readAttributeBytes()137     private byte[] readAttributeBytes() {
138         // Check whether end of section was reached during previous invocation
139         if ((mBufferedLine != null) && (mBufferedLine.length == 0)) {
140             mBufferedLine = null;
141             return EMPTY_BYTE_ARRAY;
142         }
143 
144         // Read the next line
145         byte[] line = readLine();
146         if (line == null) {
147             // End of input
148             if (mBufferedLine != null) {
149                 byte[] result = mBufferedLine;
150                 mBufferedLine = null;
151                 return result;
152             }
153             return null;
154         }
155 
156         // Consume the read line
157         if (line.length == 0) {
158             // End of section
159             if (mBufferedLine != null) {
160                 byte[] result = mBufferedLine;
161                 mBufferedLine = EMPTY_BYTE_ARRAY;
162                 return result;
163             }
164             return EMPTY_BYTE_ARRAY;
165         }
166         byte[] attrLine;
167         if (mBufferedLine == null) {
168             attrLine = line;
169         } else {
170             if ((line.length == 0) || (line[0] != ' ')) {
171                 // The most common case: buffered line is a full attribute
172                 byte[] result = mBufferedLine;
173                 mBufferedLine = line;
174                 return result;
175             }
176             attrLine = mBufferedLine;
177             mBufferedLine = null;
178             attrLine = concat(attrLine, line, 1, line.length - 1);
179         }
180 
181         // Everything's buffered in attrLine now. mBufferedLine is null
182 
183         // Read more lines
184         while (true) {
185             line = readLine();
186             if (line == null) {
187                 // End of input
188                 return attrLine;
189             } else if (line.length == 0) {
190                 // End of section
191                 mBufferedLine = EMPTY_BYTE_ARRAY; // return "end of section" next time
192                 return attrLine;
193             }
194             if (line[0] == ' ') {
195                 // Continuation line
196                 attrLine = concat(attrLine, line, 1, line.length - 1);
197             } else {
198                 // Next attribute
199                 mBufferedLine = line;
200                 return attrLine;
201             }
202         }
203     }
204 
205     private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
206 
concat(byte[] arr1, byte[] arr2, int offset2, int length2)207     private static byte[] concat(byte[] arr1, byte[] arr2, int offset2, int length2) {
208         byte[] result = new byte[arr1.length + length2];
209         System.arraycopy(arr1, 0, result, 0, arr1.length);
210         System.arraycopy(arr2, offset2, result, arr1.length, length2);
211         return result;
212     }
213 
214     /**
215      * Returns the next line (without line delimiter characters) or {@code null} if end of input has
216      * been reached.
217      */
readLine()218     private byte[] readLine() {
219         if (mOffset >= mEndOffset) {
220             return null;
221         }
222         int startOffset = mOffset;
223         int newlineStartOffset = -1;
224         int newlineEndOffset = -1;
225         for (int i = startOffset; i < mEndOffset; i++) {
226             byte b = mManifest[i];
227             if (b == '\r') {
228                 newlineStartOffset = i;
229                 int nextIndex = i + 1;
230                 if ((nextIndex < mEndOffset) && (mManifest[nextIndex] == '\n')) {
231                     newlineEndOffset = nextIndex + 1;
232                     break;
233                 }
234                 newlineEndOffset = nextIndex;
235                 break;
236             } else if (b == '\n') {
237                 newlineStartOffset = i;
238                 newlineEndOffset = i + 1;
239                 break;
240             }
241         }
242         if (newlineStartOffset == -1) {
243             newlineStartOffset = mEndOffset;
244             newlineEndOffset = mEndOffset;
245         }
246         mOffset = newlineEndOffset;
247 
248         if (newlineStartOffset == startOffset) {
249             return EMPTY_BYTE_ARRAY;
250         }
251         return Arrays.copyOfRange(mManifest, startOffset, newlineStartOffset);
252     }
253 
254 
255     /**
256      * Attribute.
257      */
258     public static class Attribute {
259         private final String mName;
260         private final String mValue;
261 
262         /**
263          * Constructs a new {@code Attribute} with the provided name and value.
264          */
Attribute(String name, String value)265         public Attribute(String name, String value) {
266             mName = name;
267             mValue = value;
268         }
269 
270         /**
271          * Returns this attribute's name.
272          */
getName()273         public String getName() {
274             return mName;
275         }
276 
277         /**
278          * Returns this attribute's value.
279          */
getValue()280         public String getValue() {
281             return mValue;
282         }
283     }
284 
285     /**
286      * Section.
287      */
288     public static class Section {
289         private final int mStartOffset;
290         private final int mSizeBytes;
291         private final String mName;
292         private final List<Attribute> mAttributes;
293 
294         /**
295          * Constructs a new {@code Section}.
296          *
297          * @param startOffset start offset (in bytes) of the section in the input file
298          * @param sizeBytes size (in bytes) of the section in the input file
299          * @param attrs attributes contained in the section
300          */
Section(int startOffset, int sizeBytes, List<Attribute> attrs)301         public Section(int startOffset, int sizeBytes, List<Attribute> attrs) {
302             mStartOffset = startOffset;
303             mSizeBytes = sizeBytes;
304             String sectionName = null;
305             if (!attrs.isEmpty()) {
306                 Attribute firstAttr = attrs.get(0);
307                 if ("Name".equalsIgnoreCase(firstAttr.getName())) {
308                     sectionName = firstAttr.getValue();
309                 }
310             }
311             mName = sectionName;
312             mAttributes = Collections.unmodifiableList(new ArrayList<>(attrs));
313         }
314 
getName()315         public String getName() {
316             return mName;
317         }
318 
319         /**
320          * Returns the offset (in bytes) at which this section starts in the input.
321          */
getStartOffset()322         public int getStartOffset() {
323             return mStartOffset;
324         }
325 
326         /**
327          * Returns the size (in bytes) of this section in the input.
328          */
getSizeBytes()329         public int getSizeBytes() {
330             return mSizeBytes;
331         }
332 
333         /**
334          * Returns this section's attributes, in the order in which they appear in the input.
335          */
getAttributes()336         public List<Attribute> getAttributes() {
337             return mAttributes;
338         }
339 
340         /**
341          * Returns the value of the specified attribute in this section or {@code null} if this
342          * section does not contain a matching attribute.
343          */
getAttributeValue(Attributes.Name name)344         public String getAttributeValue(Attributes.Name name) {
345             return getAttributeValue(name.toString());
346         }
347 
348         /**
349          * Returns the value of the specified attribute in this section or {@code null} if this
350          * section does not contain a matching attribute.
351          *
352          * @param name name of the attribute. Attribute names are case-insensitive.
353          */
getAttributeValue(String name)354         public String getAttributeValue(String name) {
355             for (Attribute attr : mAttributes) {
356                 if (attr.getName().equalsIgnoreCase(name)) {
357                     return attr.getValue();
358                 }
359             }
360             return null;
361         }
362     }
363 }
364