1 package coursier.bootstrap.launcher.jar; 2 /* 3 * Copyright 2012-2018 the original author or authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * 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 import java.io.IOException; 19 20 /** 21 * A ZIP File "End of central directory record" (EOCD). 22 * 23 * @author Phillip Webb 24 * @author Andy Wilkinson 25 * @see <a href="http://en.wikipedia.org/wiki/Zip_%28file_format%29">Zip File Format</a> 26 */ 27 class CentralDirectoryEndRecord { 28 29 private static final int MINIMUM_SIZE = 22; 30 31 private static final int MAXIMUM_COMMENT_LENGTH = 0xFFFF; 32 33 private static final int MAXIMUM_SIZE = MINIMUM_SIZE + MAXIMUM_COMMENT_LENGTH; 34 35 private static final int SIGNATURE = 0x06054b50; 36 37 private static final int COMMENT_LENGTH_OFFSET = 20; 38 39 private static final int READ_BLOCK_SIZE = 256; 40 41 private byte[] block; 42 43 private int offset; 44 45 private int size; 46 47 /** 48 * Create a new {@link CentralDirectoryEndRecord} instance from the specified 49 * {@link RandomAccessData}, searching backwards from the end until a valid block is 50 * located. 51 * @param data the source data 52 * @throws IOException in case of I/O errors 53 */ CentralDirectoryEndRecord(RandomAccessData data)54 CentralDirectoryEndRecord(RandomAccessData data) throws IOException { 55 this.block = createBlockFromEndOfData(data, READ_BLOCK_SIZE); 56 this.size = MINIMUM_SIZE; 57 this.offset = this.block.length - this.size; 58 while (!isValid()) { 59 this.size++; 60 if (this.size > this.block.length) { 61 if (this.size >= MAXIMUM_SIZE || this.size > data.getSize()) { 62 throw new IOException("Unable to find ZIP central directory " 63 + "records after reading " + this.size + " bytes"); 64 } 65 this.block = createBlockFromEndOfData(data, this.size + READ_BLOCK_SIZE); 66 } 67 this.offset = this.block.length - this.size; 68 } 69 } 70 createBlockFromEndOfData(RandomAccessData data, int size)71 private byte[] createBlockFromEndOfData(RandomAccessData data, int size) 72 throws IOException { 73 int length = (int) Math.min(data.getSize(), size); 74 return data.read(data.getSize() - length, length); 75 } 76 isValid()77 private boolean isValid() { 78 if (this.block.length < MINIMUM_SIZE 79 || Bytes.littleEndianValue(this.block, this.offset + 0, 4) != SIGNATURE) { 80 return false; 81 } 82 // Total size must be the structure size + comment 83 long commentLength = Bytes.littleEndianValue(this.block, 84 this.offset + COMMENT_LENGTH_OFFSET, 2); 85 return this.size == MINIMUM_SIZE + commentLength; 86 } 87 88 /** 89 * Returns the location in the data that the archive actually starts. For most files 90 * the archive data will start at 0, however, it is possible to have prefixed bytes 91 * (often used for startup scripts) at the beginning of the data. 92 * @param data the source data 93 * @return the offset within the data where the archive begins 94 */ getStartOfArchive(RandomAccessData data)95 public long getStartOfArchive(RandomAccessData data) { 96 long length = Bytes.littleEndianValue(this.block, this.offset + 12, 4); 97 long specifiedOffset = Bytes.littleEndianValue(this.block, this.offset + 16, 4); 98 long actualOffset = data.getSize() - this.size - length; 99 return actualOffset - specifiedOffset; 100 } 101 102 /** 103 * Return the bytes of the "Central directory" based on the offset indicated in this 104 * record. 105 * @param data the source data 106 * @return the central directory data 107 */ getCentralDirectory(RandomAccessData data)108 public RandomAccessData getCentralDirectory(RandomAccessData data) { 109 long offset = Bytes.littleEndianValue(this.block, this.offset + 16, 4); 110 long length = Bytes.littleEndianValue(this.block, this.offset + 12, 4); 111 return data.getSubsection(offset, length); 112 } 113 114 /** 115 * Return the number of ZIP entries in the file. 116 * @return the number of records in the zip 117 */ getNumberOfRecords()118 public int getNumberOfRecords() { 119 long numberOfRecords = Bytes.littleEndianValue(this.block, this.offset + 10, 2); 120 if (numberOfRecords == 0xFFFF) { 121 throw new IllegalStateException("Zip64 archives are not supported"); 122 } 123 return (int) numberOfRecords; 124 } 125 126 } 127