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