001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.compress.harmony.unpack200; 018 019import java.io.BufferedInputStream; 020import java.io.ByteArrayInputStream; 021import java.io.ByteArrayOutputStream; 022import java.io.DataOutputStream; 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.OutputStream; 026import java.io.OutputStreamWriter; 027import java.io.PrintWriter; 028import java.nio.charset.Charset; 029import java.util.ArrayList; 030import java.util.HashSet; 031import java.util.List; 032import java.util.Set; 033import java.util.TimeZone; 034import java.util.jar.JarEntry; 035import java.util.jar.JarOutputStream; 036import java.util.zip.CRC32; 037import java.util.zip.GZIPInputStream; 038import java.util.zip.ZipEntry; 039 040import org.apache.commons.compress.harmony.pack200.Codec; 041import org.apache.commons.compress.harmony.pack200.Pack200Exception; 042import org.apache.commons.compress.harmony.unpack200.bytecode.Attribute; 043import org.apache.commons.compress.harmony.unpack200.bytecode.CPClass; 044import org.apache.commons.compress.harmony.unpack200.bytecode.CPField; 045import org.apache.commons.compress.harmony.unpack200.bytecode.CPMethod; 046import org.apache.commons.compress.harmony.unpack200.bytecode.CPUTF8; 047import org.apache.commons.compress.harmony.unpack200.bytecode.ClassConstantPool; 048import org.apache.commons.compress.harmony.unpack200.bytecode.ClassFile; 049import org.apache.commons.compress.harmony.unpack200.bytecode.ClassFileEntry; 050import org.apache.commons.compress.harmony.unpack200.bytecode.InnerClassesAttribute; 051import org.apache.commons.compress.harmony.unpack200.bytecode.SourceFileAttribute; 052 053/** 054 * A Pack200 archive consists of one or more segments. Each segment is stand-alone, in the sense that every segment has 055 * the magic number header; thus, every segment is also a valid archive. However, it is possible to combine 056 * (non-GZipped) archives into a single large archive by concatenation alone. Thus all the hard work in unpacking an 057 * archive falls to understanding a segment. 058 * 059 * The first component of a segment is the header; this contains (amongst other things) the expected counts of constant 060 * pool entries, which in turn defines how many values need to be read from the stream. Because values are variable 061 * width (see {@link Codec}), it is not possible to calculate the start of the next segment, although one of the header 062 * values does hint at the size of the segment if non-zero, which can be used for buffering purposes. 063 * 064 * Note that this does not perform any buffering of the input stream; each value will be read on a byte-by-byte basis. 065 * It does not perform GZip decompression automatically; both of these are expected to be done by the caller if the 066 * stream has the magic header for GZip streams ({@link GZIPInputStream#GZIP_MAGIC}). In any case, if GZip decompression 067 * is being performed the input stream will be buffered at a higher level, and thus this can read on a byte-oriented 068 * basis. 069 */ 070public class Segment { 071 072 public static final int LOG_LEVEL_VERBOSE = 2; 073 074 public static final int LOG_LEVEL_STANDARD = 1; 075 076 public static final int LOG_LEVEL_QUIET = 0; 077 078 private SegmentHeader header; 079 080 private CpBands cpBands; 081 082 private AttrDefinitionBands attrDefinitionBands; 083 084 private IcBands icBands; 085 086 private ClassBands classBands; 087 088 private BcBands bcBands; 089 090 private FileBands fileBands; 091 092 private boolean overrideDeflateHint; 093 094 private boolean deflateHint; 095 096 private boolean doPreRead; 097 098 private int logLevel; 099 100 private PrintWriter logStream; 101 102 private byte[][] classFilesContents; 103 104 private boolean[] fileDeflate; 105 106 private boolean[] fileIsClass; 107 108 private InputStream internalBuffer; 109 110 private ClassFile buildClassFile(final int classNum) { 111 final ClassFile classFile = new ClassFile(); 112 final int[] major = classBands.getClassVersionMajor(); 113 final int[] minor = classBands.getClassVersionMinor(); 114 if (major != null) { 115 classFile.major = major[classNum]; 116 classFile.minor = minor[classNum]; 117 } else { 118 classFile.major = header.getDefaultClassMajorVersion(); 119 classFile.minor = header.getDefaultClassMinorVersion(); 120 } 121 // build constant pool 122 final ClassConstantPool cp = classFile.pool; 123 final int fullNameIndexInCpClass = classBands.getClassThisInts()[classNum]; 124 final String fullName = cpBands.getCpClass()[fullNameIndexInCpClass]; 125 // SourceFile attribute 126 int i = fullName.lastIndexOf("/") + 1; // if lastIndexOf==-1, then 127 // -1+1=0, so str.substring(0) 128 // == str 129 130 // Get the source file attribute 131 final List<Attribute> classAttributes = classBands.getClassAttributes()[classNum]; 132 SourceFileAttribute sourceFileAttribute = null; 133 for (final Attribute classAttribute : classAttributes) { 134 if (classAttribute.isSourceFileAttribute()) { 135 sourceFileAttribute = ((SourceFileAttribute) classAttribute); 136 } 137 } 138 139 if (sourceFileAttribute == null) { 140 // If we don't have a source file attribute yet, we need 141 // to infer it from the class. 142 final AttributeLayout SOURCE_FILE = attrDefinitionBands.getAttributeDefinitionMap() 143 .getAttributeLayout(AttributeLayout.ATTRIBUTE_SOURCE_FILE, AttributeLayout.CONTEXT_CLASS); 144 if (SOURCE_FILE.matches(classBands.getRawClassFlags()[classNum])) { 145 int firstDollar = -1; 146 for (int index = 0; index < fullName.length(); index++) { 147 if (fullName.charAt(index) <= '$') { 148 firstDollar = index; 149 } 150 } 151 String fileName = null; 152 153 if (firstDollar > -1 && (i <= firstDollar)) { 154 fileName = fullName.substring(i, firstDollar) + ".java"; 155 } else { 156 fileName = fullName.substring(i) + ".java"; 157 } 158 sourceFileAttribute = new SourceFileAttribute(cpBands.cpUTF8Value(fileName, false)); 159 classFile.attributes = new Attribute[] {(Attribute) cp.add(sourceFileAttribute)}; 160 } else { 161 classFile.attributes = new Attribute[] {}; 162 } 163 } else { 164 classFile.attributes = new Attribute[] {(Attribute) cp.add(sourceFileAttribute)}; 165 } 166 167 // If we see any class attributes, add them to the class's attributes 168 // that will 169 // be written out. Keep SourceFileAttributes out since we just 170 // did them above. 171 final List<Attribute> classAttributesWithoutSourceFileAttribute = new ArrayList<>(classAttributes.size()); 172 for (int index = 0; index < classAttributes.size(); index++) { 173 final Attribute attrib = classAttributes.get(index); 174 if (!attrib.isSourceFileAttribute()) { 175 classAttributesWithoutSourceFileAttribute.add(attrib); 176 } 177 } 178 final Attribute[] originalAttributes = classFile.attributes; 179 classFile.attributes = new Attribute[originalAttributes.length 180 + classAttributesWithoutSourceFileAttribute.size()]; 181 System.arraycopy(originalAttributes, 0, classFile.attributes, 0, originalAttributes.length); 182 for (int index = 0; index < classAttributesWithoutSourceFileAttribute.size(); index++) { 183 final Attribute attrib = (classAttributesWithoutSourceFileAttribute.get(index)); 184 cp.add(attrib); 185 classFile.attributes[originalAttributes.length + index] = attrib; 186 } 187 188 // this/superclass 189 final ClassFileEntry cfThis = cp.add(cpBands.cpClassValue(fullNameIndexInCpClass)); 190 final ClassFileEntry cfSuper = cp.add(cpBands.cpClassValue(classBands.getClassSuperInts()[classNum])); 191 // add interfaces 192 final ClassFileEntry[] cfInterfaces = new ClassFileEntry[classBands.getClassInterfacesInts()[classNum].length]; 193 for (i = 0; i < cfInterfaces.length; i++) { 194 cfInterfaces[i] = cp.add(cpBands.cpClassValue(classBands.getClassInterfacesInts()[classNum][i])); 195 } 196 // add fields 197 final ClassFileEntry[] cfFields = new ClassFileEntry[classBands.getClassFieldCount()[classNum]]; 198 // fieldDescr and fieldFlags used to create this 199 for (i = 0; i < cfFields.length; i++) { 200 final int descriptorIndex = classBands.getFieldDescrInts()[classNum][i]; 201 final int nameIndex = cpBands.getCpDescriptorNameInts()[descriptorIndex]; 202 final int typeIndex = cpBands.getCpDescriptorTypeInts()[descriptorIndex]; 203 final CPUTF8 name = cpBands.cpUTF8Value(nameIndex); 204 final CPUTF8 descriptor = cpBands.cpSignatureValue(typeIndex); 205 cfFields[i] = cp.add(new CPField(name, descriptor, classBands.getFieldFlags()[classNum][i], 206 classBands.getFieldAttributes()[classNum][i])); 207 } 208 // add methods 209 final ClassFileEntry[] cfMethods = new ClassFileEntry[classBands.getClassMethodCount()[classNum]]; 210 // methodDescr and methodFlags used to create this 211 for (i = 0; i < cfMethods.length; i++) { 212 final int descriptorIndex = classBands.getMethodDescrInts()[classNum][i]; 213 final int nameIndex = cpBands.getCpDescriptorNameInts()[descriptorIndex]; 214 final int typeIndex = cpBands.getCpDescriptorTypeInts()[descriptorIndex]; 215 final CPUTF8 name = cpBands.cpUTF8Value(nameIndex); 216 final CPUTF8 descriptor = cpBands.cpSignatureValue(typeIndex); 217 cfMethods[i] = cp.add(new CPMethod(name, descriptor, classBands.getMethodFlags()[classNum][i], 218 classBands.getMethodAttributes()[classNum][i])); 219 } 220 cp.addNestedEntries(); 221 222 // add inner class attribute (if required) 223 boolean addInnerClassesAttr = false; 224 final IcTuple[] icLocal = getClassBands().getIcLocal()[classNum]; 225 final boolean icLocalSent = icLocal != null; 226 final InnerClassesAttribute innerClassesAttribute = new InnerClassesAttribute("InnerClasses"); 227 final IcTuple[] icRelevant = getIcBands().getRelevantIcTuples(fullName, cp); 228 final List<IcTuple> ic_stored = computeIcStored(icLocal, icRelevant); 229 for (final IcTuple icStored : ic_stored) { 230 final int innerClassIndex = icStored.thisClassIndex(); 231 final int outerClassIndex = icStored.outerClassIndex(); 232 final int simpleClassNameIndex = icStored.simpleClassNameIndex(); 233 234 final String innerClassString = icStored.thisClassString(); 235 final String outerClassString = icStored.outerClassString(); 236 final String simpleClassName = icStored.simpleClassName(); 237 238 CPClass innerClass = null; 239 CPUTF8 innerName = null; 240 CPClass outerClass = null; 241 242 innerClass = innerClassIndex != -1 ? cpBands.cpClassValue(innerClassIndex) 243 : cpBands.cpClassValue(innerClassString); 244 if (!icStored.isAnonymous()) { 245 innerName = simpleClassNameIndex != -1 ? cpBands.cpUTF8Value(simpleClassNameIndex) 246 : cpBands.cpUTF8Value(simpleClassName); 247 } 248 249 if (icStored.isMember()) { 250 outerClass = outerClassIndex != -1 ? cpBands.cpClassValue(outerClassIndex) 251 : cpBands.cpClassValue(outerClassString); 252 } 253 final int flags = icStored.F; 254 innerClassesAttribute.addInnerClassesEntry(innerClass, outerClass, innerName, flags); 255 addInnerClassesAttr = true; 256 } 257 // If ic_local is sent and it's empty, don't add 258 // the inner classes attribute. 259 if (icLocalSent && (icLocal.length == 0)) { 260 addInnerClassesAttr = false; 261 } 262 263 // If ic_local is not sent and ic_relevant is empty, 264 // don't add the inner class attribute. 265 if (!icLocalSent && (icRelevant.length == 0)) { 266 addInnerClassesAttr = false; 267 } 268 269 if (addInnerClassesAttr) { 270 // Need to add the InnerClasses attribute to the 271 // existing classFile attributes. 272 final Attribute[] originalAttrs = classFile.attributes; 273 final Attribute[] newAttrs = new Attribute[originalAttrs.length + 1]; 274 System.arraycopy(originalAttrs, 0, newAttrs, 0, originalAttrs.length); 275 newAttrs[newAttrs.length - 1] = innerClassesAttribute; 276 classFile.attributes = newAttrs; 277 cp.addWithNestedEntries(innerClassesAttribute); 278 } 279 // sort CP according to cp_All 280 cp.resolve(this); 281 // NOTE the indexOf is only valid after the cp.resolve() 282 // build up remainder of file 283 classFile.accessFlags = (int) classBands.getClassFlags()[classNum]; 284 classFile.thisClass = cp.indexOf(cfThis); 285 classFile.superClass = cp.indexOf(cfSuper); 286 // TODO placate format of file for writing purposes 287 classFile.interfaces = new int[cfInterfaces.length]; 288 for (i = 0; i < cfInterfaces.length; i++) { 289 classFile.interfaces[i] = cp.indexOf(cfInterfaces[i]); 290 } 291 classFile.fields = cfFields; 292 classFile.methods = cfMethods; 293 return classFile; 294 } 295 296 /** 297 * Given an ic_local and an ic_relevant, use them to calculate what should be added as ic_stored. 298 * 299 * @param icLocal IcTuple[] array of local transmitted tuples 300 * @param icRelevant IcTuple[] array of relevant tuples 301 * @return List of tuples to be stored. If ic_local is null or empty, the values returned may not be correct. The 302 * caller will have to determine if this is the case. 303 */ 304 private List<IcTuple> computeIcStored(final IcTuple[] icLocal, final IcTuple[] icRelevant) { 305 final List<IcTuple> result = new ArrayList<>(icRelevant.length); 306 final List<IcTuple> duplicates = new ArrayList<>(icRelevant.length); 307 final Set<IcTuple> isInResult = new HashSet<>(icRelevant.length); 308 309 // need to compute: 310 // result = ic_local XOR ic_relevant 311 312 // add ic_local 313 if (icLocal != null) { 314 for (final IcTuple element : icLocal) { 315 if (isInResult.add(element)) { 316 result.add(element); 317 } 318 } 319 } 320 321 // add ic_relevant 322 for (final IcTuple element : icRelevant) { 323 if (isInResult.add(element)) { 324 result.add(element); 325 } else { 326 duplicates.add(element); 327 } 328 } 329 330 // eliminate "duplicates" 331 duplicates.forEach(result::remove); 332 333 return result; 334 } 335 336 protected AttrDefinitionBands getAttrDefinitionBands() { 337 return attrDefinitionBands; 338 } 339 340 protected ClassBands getClassBands() { 341 return classBands; 342 } 343 344 public SegmentConstantPool getConstantPool() { 345 return cpBands.getConstantPool(); 346 } 347 348 protected CpBands getCpBands() { 349 return cpBands; 350 } 351 352 protected IcBands getIcBands() { 353 return icBands; 354 } 355 356 public SegmentHeader getSegmentHeader() { 357 return header; 358 } 359 360 public void log(final int logLevel, final String message) { 361 if (this.logLevel >= logLevel) { 362 logStream.println(message); 363 } 364 } 365 366 /** 367 * Override the archive's deflate hint with the given boolean 368 * 369 * @param deflateHint - the deflate hint to use 370 */ 371 public void overrideDeflateHint(final boolean deflateHint) { 372 this.overrideDeflateHint = true; 373 this.deflateHint = deflateHint; 374 } 375 376 /** 377 * This performs the actual work of parsing against a non-static instance of Segment. This method is intended to run 378 * concurrently for multiple segments. 379 * 380 * @throws IOException if a problem occurs during reading from the underlying stream 381 * @throws Pack200Exception if a problem occurs with an unexpected value or unsupported codec 382 */ 383 private void parseSegment() throws IOException, Pack200Exception { 384 385 header.unpack(); 386 cpBands.unpack(); 387 attrDefinitionBands.unpack(); 388 icBands.unpack(); 389 classBands.unpack(); 390 bcBands.unpack(); 391 fileBands.unpack(); 392 393 int classNum = 0; 394 final int numberOfFiles = header.getNumberOfFiles(); 395 final String[] fileName = fileBands.getFileName(); 396 final int[] fileOptions = fileBands.getFileOptions(); 397 final SegmentOptions options = header.getOptions(); 398 399 classFilesContents = new byte[numberOfFiles][]; 400 fileDeflate = new boolean[numberOfFiles]; 401 fileIsClass = new boolean[numberOfFiles]; 402 403 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 404 final DataOutputStream dos = new DataOutputStream(bos); 405 406 for (int i = 0; i < numberOfFiles; i++) { 407 String name = fileName[i]; 408 409 final boolean nameIsEmpty = (name == null) || name.equals(""); 410 final boolean isClass = (fileOptions[i] & 2) == 2 || nameIsEmpty; 411 if (isClass && nameIsEmpty) { 412 name = cpBands.getCpClass()[classBands.getClassThisInts()[classNum]] + ".class"; 413 fileName[i] = name; 414 } 415 416 if (!overrideDeflateHint) { 417 fileDeflate[i] = (fileOptions[i] & 1) == 1 || options.shouldDeflate(); 418 } else { 419 fileDeflate[i] = deflateHint; 420 } 421 422 fileIsClass[i] = isClass; 423 424 if (isClass) { 425 final ClassFile classFile = buildClassFile(classNum); 426 classFile.write(dos); 427 dos.flush(); 428 429 classFilesContents[classNum] = bos.toByteArray(); 430 bos.reset(); 431 432 classNum++; 433 } 434 } 435 } 436 437 /** 438 * This performs reading the data from the stream into non-static instance of Segment. After the completion of this 439 * method stream can be freed. 440 * 441 * @param in the input stream to read from 442 * @throws IOException if a problem occurs during reading from the underlying stream 443 * @throws Pack200Exception if a problem occurs with an unexpected value or unsupported codec 444 */ 445 private void readSegment(final InputStream in) throws IOException, Pack200Exception { 446 log(LOG_LEVEL_VERBOSE, "-------"); 447 cpBands = new CpBands(this); 448 cpBands.read(in); 449 attrDefinitionBands = new AttrDefinitionBands(this); 450 attrDefinitionBands.read(in); 451 icBands = new IcBands(this); 452 icBands.read(in); 453 classBands = new ClassBands(this); 454 classBands.read(in); 455 bcBands = new BcBands(this); 456 bcBands.read(in); 457 fileBands = new FileBands(this); 458 fileBands.read(in); 459 460 fileBands.processFileBits(); 461 } 462 463 public void setLogLevel(final int logLevel) { 464 this.logLevel = logLevel; 465 } 466 467 public void setLogStream(final OutputStream logStream) { 468 this.logStream = new PrintWriter(new OutputStreamWriter(logStream, Charset.defaultCharset()), false); 469 } 470 471 public void setPreRead(final boolean value) { 472 doPreRead = value; 473 } 474 475 /** 476 * Unpacks a packed stream (either .pack. or .pack.gz) into a corresponding JarOuputStream. 477 * 478 * @param in a packed stream. 479 * @param out output stream. 480 * @throws Pack200Exception if there is a problem unpacking 481 * @throws IOException if there is a problem with I/O during unpacking 482 */ 483 public void unpack(final InputStream in, final JarOutputStream out) throws IOException, Pack200Exception { 484 unpackRead(in); 485 unpackProcess(); 486 unpackWrite(out); 487 } 488 489 void unpackProcess() throws IOException, Pack200Exception { 490 if (internalBuffer != null) { 491 readSegment(internalBuffer); 492 } 493 parseSegment(); 494 } 495 496 /* 497 * Package-private accessors for unpacking stages 498 */ 499 void unpackRead(InputStream in) throws IOException, Pack200Exception { 500 if (!in.markSupported()) { 501 in = new BufferedInputStream(in); 502 } 503 504 header = new SegmentHeader(this); 505 header.read(in); 506 507 final int size = (int) header.getArchiveSize() - header.getArchiveSizeOffset(); 508 509 if (doPreRead && header.getArchiveSize() != 0) { 510 final byte[] data = new byte[size]; 511 in.read(data); 512 internalBuffer = new BufferedInputStream(new ByteArrayInputStream(data)); 513 } else { 514 readSegment(in); 515 } 516 } 517 518 void unpackWrite(final JarOutputStream out) throws IOException { 519 writeJar(out); 520 if (logStream != null) { 521 logStream.close(); 522 } 523 } 524 525 /** 526 * Writes the segment to an output stream. The output stream should be pre-buffered for efficiency. Also takes the 527 * same input stream for reading, since the file bits may not be loaded and thus just copied from one stream to 528 * another. Doesn't close the output stream when finished, in case there are more entries (e.g. further segments) to 529 * be written. 530 * 531 * @param out the JarOutputStream to write data to 532 * @throws IOException if an error occurs while reading or writing to the streams 533 */ 534 public void writeJar(final JarOutputStream out) throws IOException { 535 final String[] fileName = fileBands.getFileName(); 536 final int[] fileModtime = fileBands.getFileModtime(); 537 final long[] fileSize = fileBands.getFileSize(); 538 final byte[][] fileBits = fileBands.getFileBits(); 539 540 // now write the files out 541 int classNum = 0; 542 final int numberOfFiles = header.getNumberOfFiles(); 543 final long archiveModtime = header.getArchiveModtime(); 544 545 for (int i = 0; i < numberOfFiles; i++) { 546 final String name = fileName[i]; 547 // For Pack200 archives, modtime is in seconds 548 // from the epoch. JarEntries need it to be in 549 // milliseconds from the epoch. 550 // Even though we're adding two longs and multiplying 551 // by 1000, we won't overflow because both longs are 552 // always under 2^32. 553 final long modtime = 1000 * (archiveModtime + fileModtime[i]); 554 final boolean deflate = fileDeflate[i]; 555 556 final JarEntry entry = new JarEntry(name); 557 if (deflate) { 558 entry.setMethod(ZipEntry.DEFLATED); 559 } else { 560 entry.setMethod(ZipEntry.STORED); 561 final CRC32 crc = new CRC32(); 562 if (fileIsClass[i]) { 563 crc.update(classFilesContents[classNum]); 564 entry.setSize(classFilesContents[classNum].length); 565 } else { 566 crc.update(fileBits[i]); 567 entry.setSize(fileSize[i]); 568 } 569 entry.setCrc(crc.getValue()); 570 } 571 // On Windows at least, need to correct for timezone 572 entry.setTime(modtime - TimeZone.getDefault().getRawOffset()); 573 out.putNextEntry(entry); 574 575 // write to output stream 576 if (fileIsClass[i]) { 577 entry.setSize(classFilesContents[classNum].length); 578 out.write(classFilesContents[classNum]); 579 classNum++; 580 } else { 581 entry.setSize(fileSize[i]); 582 out.write(fileBits[i]); 583 } 584 } 585 } 586 587}