diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/ZipException.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/ZipException.java new file mode 100644 index 0000000000000000000000000000000000000000..a70624243e3c929e54023bde140290e7638863fe --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/error/ZipException.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package com.ohos.hapsigntool.error; + +import java.io.IOException; + +/** + * Zip exception for programs. + * + * @since 2023/12/07 + */ +public class ZipException extends IOException { + /** + * new ZipException + * + * @param message exception message + */ + public ZipException(String message) { + super(message); + } + + /** + * new ZipException + * + * @param message exception message + * @param e exception + */ + public ZipException(String message, Exception e) { + super(message, e); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/CentralDirectory.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/CentralDirectory.java new file mode 100644 index 0000000000000000000000000000000000000000..66501ee2b317923ea02777411cce1080fb78058c --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/CentralDirectory.java @@ -0,0 +1,401 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.hap.entity.zip; + +import com.ohos.hapsigntool.error.ZipException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; + +/** + * resolve zip CentralDirectory data + * + * @since 2023/12/02 + */ +class CentralDirectory { + /** + * central directory invariable bytes length + */ + public static final int CD_LENGTH = 46; + + /** + * 4 bytes , central directory signature + */ + public static final int SIGNATURE = 0x02014b50; + + /** + * 2 bytes + */ + private short version; + + /** + * 2 bytes + */ + private short versionExtra; + + + /** + * 2 bytes + */ + private short flag; + + /** + * 2 bytes + */ + private short method; + + /** + * 2 bytes + */ + private short lastTime; + + /** + * 2 bytes + */ + private short lastDate; + + /** + * 4 bytes + */ + private int crc32; + + /** + * 4 bytes + */ + private long compressedSize; + + /** + * 4 bytes + */ + private long unCompressedSize; + + /** + * 2 bytes + */ + private int fileNameLength; + + /** + * 2 bytes + */ + private int extraLength; + + /** + * 2 bytes + */ + private int commentLength; + + /** + * 2 bytes + */ + private int diskNumStart; + + + /** + * 2 bytes + */ + private short internalFile; + + + /** + * 4 bytes + */ + private int externalFile; + + + /** + * 4 bytes + */ + private long offset; + + /** + * n bytes + */ + private String fileName; + + /** + * n bytes + */ + private byte[] extraData; + + /** + * n bytes + */ + private String comment; + + private int length; + + /** + * init Central Directory + * + * @param bytes Full Central Directory bytes + * @param offset One Central Directory offset + * @return CentralDirectory + * @throws ZipException read Central Directory exception + */ + public static CentralDirectory initCentralDirectory(byte[] bytes, int offset) throws ZipException { + if (bytes.length < offset) { + throw new ZipException("find zip central directory failed"); + } + CentralDirectory cd = new CentralDirectory(); + int byteLength = bytes.length - offset; + ByteBuffer bf = ByteBuffer.allocate(byteLength); + bf.put(bytes, offset, byteLength); + bf.order(ByteOrder.LITTLE_ENDIAN); + bf.flip(); + if (bf.getInt() != SIGNATURE) { + throw new ZipException("find zip central directory failed"); + } + + cd.setVersion(bf.getShort()); + cd.setVersionExtra(bf.getShort()); + cd.setFlag(bf.getShort()); + cd.setMethod(bf.getShort()); + cd.setLastTime(bf.getShort()); + cd.setLastDate(bf.getShort()); + cd.setCrc32(bf.getInt()); + cd.setCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf)); + cd.setUnCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf)); + cd.setFileNameLength(UnsignedDecimalUtil.getUnsignedShort(bf)); + cd.setExtraLength(UnsignedDecimalUtil.getUnsignedShort(bf)); + cd.setCommentLength(UnsignedDecimalUtil.getUnsignedShort(bf)); + cd.setDiskNumStart(UnsignedDecimalUtil.getUnsignedShort(bf)); + cd.setInternalFile(bf.getShort()); + cd.setExternalFile(bf.getInt()); + cd.setOffset(UnsignedDecimalUtil.getUnsignedInt(bf)); + if (cd.getFileNameLength() > 0) { + byte[] readFileName = new byte[cd.getFileNameLength()]; + bf.get(readFileName); + cd.setFileName(new String(readFileName, StandardCharsets.UTF_8)); + } + if (cd.getExtraLength() > 0) { + byte[] extra = new byte[cd.getExtraLength()]; + bf.get(extra); + cd.setExtraData(extra); + } + if (cd.getCommentLength() > 0) { + byte[] readComment = new byte[cd.getCommentLength()]; + bf.get(readComment); + cd.setComment(new String(readComment, StandardCharsets.UTF_8)); + } + cd.setLength(CD_LENGTH + cd.getFileNameLength() + cd.getExtraLength() + cd.getCommentLength()); + return cd; + } + + /** + * change Central Directory to bytes + * + * @return bytes + */ + public byte[] toBytes() { + ByteBuffer bf = ByteBuffer.allocate(length).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(SIGNATURE); + UnsignedDecimalUtil.setUnsignedShort(bf, version); + UnsignedDecimalUtil.setUnsignedShort(bf, versionExtra); + UnsignedDecimalUtil.setUnsignedShort(bf, flag); + UnsignedDecimalUtil.setUnsignedShort(bf, method); + UnsignedDecimalUtil.setUnsignedShort(bf, lastTime); + UnsignedDecimalUtil.setUnsignedShort(bf, lastDate); + UnsignedDecimalUtil.setUnsignedInt(bf, crc32); + UnsignedDecimalUtil.setUnsignedInt(bf, compressedSize); + UnsignedDecimalUtil.setUnsignedInt(bf, unCompressedSize); + UnsignedDecimalUtil.setUnsignedShort(bf, fileNameLength); + UnsignedDecimalUtil.setUnsignedShort(bf, extraLength); + UnsignedDecimalUtil.setUnsignedShort(bf, commentLength); + UnsignedDecimalUtil.setUnsignedShort(bf, diskNumStart); + UnsignedDecimalUtil.setUnsignedShort(bf, internalFile); + UnsignedDecimalUtil.setUnsignedInt(bf, externalFile); + UnsignedDecimalUtil.setUnsignedInt(bf, offset); + if (fileNameLength > 0) { + bf.put(fileName.getBytes(StandardCharsets.UTF_8)); + } + if (extraLength > 0) { + bf.put(extraData); + } + if (commentLength > 0) { + bf.put(comment.getBytes(StandardCharsets.UTF_8)); + } + return bf.array(); + } + + public static int getCdLength() { + return CD_LENGTH; + } + + public static int getSIGNATURE() { + return SIGNATURE; + } + + public short getVersion() { + return version; + } + + public void setVersion(short version) { + this.version = version; + } + + public short getVersionExtra() { + return versionExtra; + } + + public void setVersionExtra(short versionExtra) { + this.versionExtra = versionExtra; + } + + public short getFlag() { + return flag; + } + + public void setFlag(short flag) { + this.flag = flag; + } + + public short getMethod() { + return method; + } + + public void setMethod(short method) { + this.method = method; + } + + public short getLastTime() { + return lastTime; + } + + public void setLastTime(short lastTime) { + this.lastTime = lastTime; + } + + public short getLastDate() { + return lastDate; + } + + public void setLastDate(short lastDate) { + this.lastDate = lastDate; + } + + public int getCrc32() { + return crc32; + } + + public void setCrc32(int crc32) { + this.crc32 = crc32; + } + + public long getCompressedSize() { + return compressedSize; + } + + public void setCompressedSize(long compressedSize) { + this.compressedSize = compressedSize; + } + + public long getUnCompressedSize() { + return unCompressedSize; + } + + public void setUnCompressedSize(long unCompressedSize) { + this.unCompressedSize = unCompressedSize; + } + + public int getFileNameLength() { + return fileNameLength; + } + + public void setFileNameLength(int fileNameLength) { + this.fileNameLength = fileNameLength; + } + + public int getExtraLength() { + return extraLength; + } + + public void setExtraLength(int extraLength) { + this.extraLength = extraLength; + } + + public int getCommentLength() { + return commentLength; + } + + public void setCommentLength(int commentLength) { + this.commentLength = commentLength; + } + + public int getDiskNumStart() { + return diskNumStart; + } + + public void setDiskNumStart(int diskNumStart) { + this.diskNumStart = diskNumStart; + } + + public short getInternalFile() { + return internalFile; + } + + public void setInternalFile(short internalFile) { + this.internalFile = internalFile; + } + + public int getExternalFile() { + return externalFile; + } + + public void setExternalFile(int externalFile) { + this.externalFile = externalFile; + } + + public long getOffset() { + return offset; + } + + public void setOffset(long offset) { + this.offset = offset; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public byte[] getExtraData() { + return extraData; + } + + public void setExtraData(byte[] extraData) { + this.extraData = extraData; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/DataDescriptor.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/DataDescriptor.java new file mode 100644 index 0000000000000000000000000000000000000000..fc3da08e386e35a732344a75f776aef52f96a881 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/DataDescriptor.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.hap.entity.zip; + +import com.ohos.hapsigntool.error.ZipException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * resolve zip DataDescriptor data + * + * @since 2023/12/02 + */ +public class DataDescriptor { + /** + * DataDescriptor invariable bytes length + */ + public static final int DES_LENGTH = 16; + + /** + * 4 bytes , DataDescriptor signature + */ + public static final int SIGNATURE = 0x08074b50; + + /** + * 4 bytes + */ + private int crc32; + + /** + * 4 bytes + */ + private long compressedSize; + + /** + * 4 bytes + */ + private long unCompressedSize; + + /** + * init Central Directory + * + * @param bytes DataDescriptor bytes + * @return DataDescriptor + * @throws ZipException read data descriptor exception + */ + public static DataDescriptor initDataDescriptor(byte[] bytes) throws ZipException { + if (bytes.length != DES_LENGTH) { + throw new ZipException("read Data Descriptor failed"); + } + ByteBuffer bf = ByteBuffer.allocate(bytes.length); + bf.put(bytes); + bf.order(ByteOrder.LITTLE_ENDIAN); + bf.flip(); + DataDescriptor data = new DataDescriptor(); + if (bf.getInt() != SIGNATURE) { + throw new ZipException("read Data Descriptor failed"); + } + data.setCrc32(bf.getInt()); + data.setCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf)); + data.setUnCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf)); + return data; + } + + /** + * change DataDescriptor to bytes + * + * @return bytes + */ + public byte[] toBytes() { + ByteBuffer bf = ByteBuffer.allocate(DES_LENGTH).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(SIGNATURE); + bf.putInt(crc32); + UnsignedDecimalUtil.setUnsignedInt(bf, compressedSize); + UnsignedDecimalUtil.setUnsignedInt(bf, unCompressedSize); + return bf.array(); + } + + public static int getDesLength() { + return DES_LENGTH; + } + + public static int getSIGNATURE() { + return SIGNATURE; + } + + public int getCrc32() { + return crc32; + } + + public void setCrc32(int crc32) { + this.crc32 = crc32; + } + + public long getCompressedSize() { + return compressedSize; + } + + public void setCompressedSize(long compressedSize) { + this.compressedSize = compressedSize; + } + + public long getUnCompressedSize() { + return unCompressedSize; + } + + public void setUnCompressedSize(long unCompressedSize) { + this.unCompressedSize = unCompressedSize; + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/EndOfCentralDirectory.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/EndOfCentralDirectory.java new file mode 100644 index 0000000000000000000000000000000000000000..dfc2ba1475700f8860a1594611594144a24b7062 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/EndOfCentralDirectory.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.hap.entity.zip; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; + +/** + * resolve zip EndOfCentralDirectory data + * + * @since 2023/12/04 + */ +class EndOfCentralDirectory { + /** + * EndOfCentralDirectory invariable bytes length + */ + public static final int EOCD_LENGTH = 22; + + /** + * 4 bytes , central directory signature + */ + public static final int SIGNATURE = 0x06054b50; + + /** + * 2 bytes + */ + private int diskNum; + + /** + * 2 bytes + */ + private int cDStartDiskNum; + + /** + * 2 bytes + */ + private int thisDiskCDNum; + + /** + * 2 bytes + */ + private int cDTotal; + + /** + * 4 bytes + */ + private long cDSize; + + /** + * 4 bytes + */ + private long offset; + + /** + * 2 bytes + */ + private int commentLength; + + /** + * n bytes + */ + private String comment; + + private int length; + + /** + * init End Of Central Directory + * + * @param bytes End Of Central Directory bytes + * @return End Of Central Directory + */ + public static EndOfCentralDirectory initEOCDByBytes(byte[] bytes) { + EndOfCentralDirectory eocd = new EndOfCentralDirectory(); + ByteBuffer bf = ByteBuffer.allocate(bytes.length); + bf.put(bytes); + bf.order(ByteOrder.LITTLE_ENDIAN); + bf.flip(); + if (bf.getInt() != SIGNATURE) { + return null; + } + eocd.setDiskNum(UnsignedDecimalUtil.getUnsignedShort(bf)); + eocd.setcDStartDiskNum(UnsignedDecimalUtil.getUnsignedShort(bf)); + eocd.setThisDiskCDNum(UnsignedDecimalUtil.getUnsignedShort(bf)); + eocd.setcDTotal(UnsignedDecimalUtil.getUnsignedShort(bf)); + eocd.setcDSize(UnsignedDecimalUtil.getUnsignedInt(bf)); + eocd.setOffset(UnsignedDecimalUtil.getUnsignedInt(bf)); + eocd.setCommentLength(UnsignedDecimalUtil.getUnsignedShort(bf)); + if (eocd.getCommentLength() > 0) { + byte[] readComment = new byte[eocd.getCommentLength()]; + bf.get(readComment); + eocd.setComment(new String(readComment, StandardCharsets.UTF_8)); + } + eocd.setLength(EOCD_LENGTH + eocd.getCommentLength()); + if (bf.remaining() != 0) { + return null; + } + return eocd; + } + + /** + * change End Of Central Directory to bytes + * + * @return bytes + */ + public byte[] toBytes() { + ByteBuffer bf = ByteBuffer.allocate(length).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(SIGNATURE); + UnsignedDecimalUtil.setUnsignedShort(bf, diskNum); + UnsignedDecimalUtil.setUnsignedShort(bf, cDStartDiskNum); + UnsignedDecimalUtil.setUnsignedShort(bf, thisDiskCDNum); + UnsignedDecimalUtil.setUnsignedShort(bf, cDTotal); + UnsignedDecimalUtil.setUnsignedInt(bf, cDSize); + UnsignedDecimalUtil.setUnsignedInt(bf, offset); + UnsignedDecimalUtil.setUnsignedShort(bf, commentLength); + if (commentLength > 0) { + bf.put(comment.getBytes(StandardCharsets.UTF_8)); + } + return bf.array(); + } + + public static int getEocdLength() { + return EOCD_LENGTH; + } + + public static int getSIGNATURE() { + return SIGNATURE; + } + + public int getDiskNum() { + return diskNum; + } + + public void setDiskNum(int diskNum) { + this.diskNum = diskNum; + } + + public int getcDStartDiskNum() { + return cDStartDiskNum; + } + + public void setcDStartDiskNum(int cDStartDiskNum) { + this.cDStartDiskNum = cDStartDiskNum; + } + + public int getThisDiskCDNum() { + return thisDiskCDNum; + } + + public void setThisDiskCDNum(int thisDiskCDNum) { + this.thisDiskCDNum = thisDiskCDNum; + } + + public int getcDTotal() { + return cDTotal; + } + + public void setcDTotal(int cDTotal) { + this.cDTotal = cDTotal; + } + + public long getcDSize() { + return cDSize; + } + + public void setcDSize(long cDSize) { + this.cDSize = cDSize; + } + + public long getOffset() { + return offset; + } + + public void setOffset(long offset) { + this.offset = offset; + } + + public int getCommentLength() { + return commentLength; + } + + public void setCommentLength(int commentLength) { + this.commentLength = commentLength; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/UnsignedDecimalUtil.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/UnsignedDecimalUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..fe804fd7dfbbc1c13ea3d61c629f8f7d36ec705f --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/UnsignedDecimalUtil.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.hap.entity.zip; + +import java.nio.ByteBuffer; + +/** + * Unsigned Decimal Util + * + * @since 2023/12/09 + */ +public class UnsignedDecimalUtil { + private static final int BIT_SIZE = 8; + + private static final int DOUBLE_BIT_SIZE = 16; + + private static final int TRIPLE_BIT_SIZE = 24; + + /** + * get unsigned int to long + * + * @param bf byteBuffer + * @return long + */ + public static long getUnsignedInt(ByteBuffer bf) { + int i = bf.getInt(); + if (i >= 0) { + return i; + } + return i & 0xFFFFFFFFL; + } + + /** + * get unsigned short to int + * + * @param bf byteBuffer + * @return int + */ + public static int getUnsignedShort(ByteBuffer bf) { + short s = bf.getShort(); + if (s >= 0) { + return s; + } + return s & 0xFFFF; + } + + /** + * set long to unsigned int + * + * @param bf byteBuffer + * @param l long + */ + public static void setUnsignedInt(ByteBuffer bf, long l) { + byte[] bytes = { + (byte) (l & 0xff), + (byte) ((l >> BIT_SIZE) & 0xff), + (byte) ((l >> DOUBLE_BIT_SIZE) & 0xff), + (byte) ((l >> TRIPLE_BIT_SIZE) & 0xff), + }; + bf.put(bytes); + } + + /** + * set int to unsigned short + * + * @param bf byteBuffer + * @param i int + */ + public static void setUnsignedShort(ByteBuffer bf, int i) { + byte[] bytes = { + (byte) (i & 0xff), + (byte) ((i >> BIT_SIZE) & 0xff), + }; + bf.put(bytes); + } +} diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/Zip.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/Zip.java new file mode 100644 index 0000000000000000000000000000000000000000..54851e15075ce4604d547a709a2696c64310925b --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/Zip.java @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.hap.entity.zip; + +import com.ohos.hapsigntool.error.ZipException; +import com.ohos.hapsigntool.utils.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * resolve zip data + * + * @since 2023/12/02 + */ +public class Zip { + private static final Map suffixRegex = new HashMap() {{ + put("so", "\\w+\\.so(\\.[0-9]*)*$"); + put("abc", "\\w+\\.abc$"); + put("an", "\\w+\\.an$"); + }}; + + private List zipEntries; + + private long signingOffset; + + private byte[] signingBlock; + + private long cDOffset; + + private long eOCDOffset; + + private EndOfCentralDirectory endOfCentralDirectory; + + private String file; + + private final short unCompressMethod = 0; + + /** + * create Zip by file + * + * @param file file + * @throws IOException read file exception + */ + public Zip(File file) throws IOException { + this.file = file.getPath(); + if (!file.exists()) { + throw new ZipException("read zip file failed"); + } + // 1. get eocd data + endOfCentralDirectory = getZipEndOfCentralDirectory(file); + // 2. use eocd's cd offset, get cd data + getZipCentralDirectory(file); + // 3. use cd's entry offset and file size, get entry data + getZipEntries(file); + // 4. file all data - eocd - cd - entry = sign block + signingBlock = getSigningBlock(file); + } + + private EndOfCentralDirectory getZipEndOfCentralDirectory(File file) throws IOException { + if (file.length() < EndOfCentralDirectory.EOCD_LENGTH) { + throw new ZipException("find zip eocd failed"); + } + + // try to read EOCD without comment + int eocdMaxLength = EndOfCentralDirectory.EOCD_LENGTH; + eOCDOffset = file.length() - eocdMaxLength; + byte[] bytes = FileUtils.readFileByOffsetAndLength(file, eOCDOffset, eocdMaxLength); + EndOfCentralDirectory eocd = EndOfCentralDirectory.initEOCDByBytes(bytes); + if (eocd != null) { + return eocd; + } + + // try to search EOCD with comment + int maxCommentLength = 65535; + eocdMaxLength = EndOfCentralDirectory.EOCD_LENGTH + maxCommentLength; + eOCDOffset = file.length() - eocdMaxLength; + bytes = FileUtils.readFileByOffsetAndLength(file, eOCDOffset, eocdMaxLength); + for (int start = 0; start < eocdMaxLength; start++) { + eocd = EndOfCentralDirectory.initEOCDByBytes(bytes); + if (eocd != null) { + eOCDOffset += start; + return eocd; + } + } + throw new ZipException("read zip failed: can not find eocd in file"); + } + + private void getZipCentralDirectory(File file) throws IOException { + zipEntries = new ArrayList<>(endOfCentralDirectory.getcDTotal()); + cDOffset = endOfCentralDirectory.getOffset(); + // read full central directory bytes + byte[] cdBytes = FileUtils.readFileByOffsetAndLength(file, cDOffset, endOfCentralDirectory.getcDSize()); + if (cdBytes.length < CentralDirectory.CD_LENGTH) { + throw new ZipException("find zip cd failed"); + } + int offset = 0; + // one by one format central directory + while (offset < cdBytes.length) { + CentralDirectory cd = CentralDirectory.initCentralDirectory(cdBytes, offset); + ZipEntry entry = new ZipEntry(); + entry.setCentralDirectory(cd); + zipEntries.add(entry); + offset += cd.getLength(); + } + } + + private byte[] getSigningBlock(File file) throws IOException { + return FileUtils.readFileByOffsetAndLength(file, signingOffset, cDOffset - signingOffset); + } + + private void getZipEntries(File file) throws IOException { + // use central directory data, find entry data + for (ZipEntry entry : zipEntries) { + CentralDirectory cd = entry.getCentralDirectory(); + long offset = cd.getOffset(); + long fileSize = cd.getCompressedSize(); + short flag = cd.getFlag(); + short mask = 0x08; + // set desc null flag + boolean hasDesc = (flag & mask) != 0; + entry.setZipEntryData(ZipEntryData.initZipEntry(file, offset, fileSize, hasDesc)); + } + ZipEntry endEntry = zipEntries.get(zipEntries.size() - 1); + CentralDirectory endCD = endEntry.getCentralDirectory(); + ZipEntryData endEntryData = endEntry.getZipEntryData(); + signingOffset = endCD.getOffset() + endEntryData.getLength(); + } + + /** + * output zip to zip file + * + * @param file file path + * @throws IOException write exception + */ + public void toFile(String file) throws IOException { + File f = new File(file); + if (!f.exists()) { + f.createNewFile(); + } + FileUtils.write(new byte[]{}, f); + for (ZipEntry entry : zipEntries) { + ZipEntryData zipEntryData = entry.getZipEntryData(); + FileUtils.writeByteToOutFile(zipEntryData.getZipEntryHeader().toBytes(), file); + boolean isSuccess = FileUtils.appendWriteFileByOffsetToFile(this.file, file, + zipEntryData.getFileOffset(), zipEntryData.getFileSize()); + if (!isSuccess) { + throw new ZipException("write zip data failed"); + } + if (zipEntryData.getDataDescriptor() != null) { + FileUtils.writeByteToOutFile(zipEntryData.getDataDescriptor().toBytes(), file); + } + } + FileUtils.writeByteToOutFile(signingBlock, file); + for (ZipEntry entry : zipEntries) { + CentralDirectory cd = entry.getCentralDirectory(); + FileUtils.writeByteToOutFile(cd.toBytes(), file); + } + FileUtils.writeByteToOutFile(endOfCentralDirectory.toBytes(), file); + } + + /** + * alignment uncompress entry + * + * @throws ZipException alignment exception + */ + public void alignment(int alignment) throws ZipException { + sort(); + boolean is4KAlign = true; + for (ZipEntry entry : zipEntries) { + ZipEntryData zipEntryData = entry.getZipEntryData(); + short method = zipEntryData.getZipEntryHeader().getMethod(); + if (method != unCompressMethod) { + // only align uncompressed entry. + break; + } + int alignBytes; + if (isRunnableFile(zipEntryData.getZipEntryHeader().getFileName())) { + // .abc and .so file align 4096 byte. + alignBytes = 4096; + } else { + // the first file after runnable file, align 4096 byte. + if (is4KAlign) { + alignBytes = 4096; + is4KAlign = false; + } else { + // normal file align 4 byte. + alignBytes = alignment; + } + } + int add = entry.alignment(alignBytes); + if (add > 0) { + resetOffset(); + } + } + } + + /** + * sort uncompress entry in the front. + */ + private void sort() { + int unCompressOffset = 0; + int compressOffset = zipEntries.size() - 1; + int pointer = 0; + // sort uncompress file (so, abc, an) - other uncompress file - compress file + while (pointer <= compressOffset) { + ZipEntry entry = zipEntries.get(pointer); + if (isRunnableFile(entry.getZipEntryData().getZipEntryHeader().getFileName()) + && entry.getZipEntryData().getZipEntryHeader().getMethod() == unCompressMethod) { + ZipEntry temp = zipEntries.get(unCompressOffset); + zipEntries.set(unCompressOffset, zipEntries.get(pointer)); + zipEntries.set(pointer, temp); + unCompressOffset++; + pointer++; + continue; + } + if (entry.getZipEntryData().getZipEntryHeader().getMethod() != unCompressMethod) { + ZipEntry temp = zipEntries.get(compressOffset); + zipEntries.set(compressOffset, zipEntries.get(pointer)); + zipEntries.set(pointer, temp); + compressOffset--; + continue; + } + pointer++; + } + resetOffset(); + } + + private void resetOffset() { + long offset = 0; + long cdLength = 0; + for (ZipEntry entry : zipEntries) { + entry.getCentralDirectory().setOffset(offset); + offset += entry.getZipEntryData().getLength(); + cdLength += entry.getCentralDirectory().getLength(); + } + offset += signingBlock.length; + cDOffset = offset; + endOfCentralDirectory.setOffset(offset); + offset += cdLength; + eOCDOffset = offset; + } + + /** + * regex filename + * + * @param name filename + * @return boolean + */ + public static boolean isRunnableFile(String name) { + for (String regex : suffixRegex.values()) { + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(name); + if (matcher.matches()) { + return true; + } + } + return false; + } + + public List getZipEntries() { + return zipEntries; + } + + public void setZipEntries(List zipEntries) { + this.zipEntries = zipEntries; + } + + public long getSigningOffset() { + return signingOffset; + } + + public void setSigningOffset(long signingOffset) { + this.signingOffset = signingOffset; + } + + public byte[] getSigningBlock() { + return signingBlock; + } + + public void setSigningBlock(byte[] signingBlock) { + this.signingBlock = signingBlock; + } + + public long getCDOffset() { + return cDOffset; + } + + public void setCDOffset(long cDOffset) { + this.cDOffset = cDOffset; + } + + public long getEOCDOffset() { + return eOCDOffset; + } + + public void setEOCDOffset(long eOCDOffset) { + this.eOCDOffset = eOCDOffset; + } + + public EndOfCentralDirectory getEndOfCentralDirectory() { + return endOfCentralDirectory; + } + + public void setEndOfCentralDirectory(EndOfCentralDirectory endOfCentralDirectory) { + this.endOfCentralDirectory = endOfCentralDirectory; + } + + public String getFile() { + return file; + } + + public void setFile(String file) { + this.file = file; + } + + public short getUnCompressMethod() { + return unCompressMethod; + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/ZipEntry.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/ZipEntry.java new file mode 100644 index 0000000000000000000000000000000000000000..e1b0ec94bbcc4e1538b7ade5233aadc684951d38 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/ZipEntry.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.hap.entity.zip; + +import com.ohos.hapsigntool.error.ZipException; + +import java.util.Arrays; + +/** + * ZipEntry and CentralDirectory data + * + * @since 2023/12/02 + */ +class ZipEntry { + private ZipEntryData zipEntryData; + + private CentralDirectory centralDirectory; + + /** + * alignment one entry + * + * @param alignNum need align bytes length + * @return add bytes length + * @throws ZipException alignment exception + */ + public int alignment(int alignNum) throws ZipException { + long add = (zipEntryData.getZipEntryHeader().getLength() + centralDirectory.getOffset()) % alignNum; + if (add == 0) { + return 0; + } + int newExtraLength = zipEntryData.getZipEntryHeader().getExtraLength() + (int) add; + if (newExtraLength > Short.MAX_VALUE) { + throw new ZipException("can not align " + zipEntryData.getZipEntryHeader().getFileName()); + } + zipEntryData.getZipEntryHeader().setExtraLength((short) newExtraLength); + byte[] oldExtraData = zipEntryData.getZipEntryHeader().getExtraData(); + byte[] newExtra; + if (oldExtraData == null) { + newExtra = new byte[newExtraLength]; + } else { + newExtra = Arrays.copyOf(oldExtraData, newExtraLength); + } + zipEntryData.getZipEntryHeader().setExtraData(newExtra); + int newLength = ZipEntryHeader.HEADER_LENGTH + zipEntryData.getZipEntryHeader().getFileNameLength() + + newExtraLength; + if (zipEntryData.getZipEntryHeader().getLength() + add != newLength) { + throw new ZipException("can not align " + zipEntryData.getZipEntryHeader().getFileName()); + } + zipEntryData.getZipEntryHeader().setLength(newLength); + zipEntryData.setLength(zipEntryData.getLength() + add); + return (int) add; + } + + public ZipEntryData getZipEntryData() { + return zipEntryData; + } + + public void setZipEntryData(ZipEntryData zipEntryData) { + this.zipEntryData = zipEntryData; + } + + public CentralDirectory getCentralDirectory() { + return centralDirectory; + } + + public void setCentralDirectory(CentralDirectory centralDirectory) { + this.centralDirectory = centralDirectory; + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/ZipEntryData.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/ZipEntryData.java new file mode 100644 index 0000000000000000000000000000000000000000..fb1d25ec7903fcebd07240c1bdc8d6388d2c2c46 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/ZipEntryData.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.hap.entity.zip; + +import com.ohos.hapsigntool.utils.FileUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +/** + * resolve zip ZipEntry data + * + * @since 2023/12/04 + */ +class ZipEntryData { + private ZipEntryHeader zipEntryHeader; + + private long fileOffset; + + private long fileSize; + + private DataDescriptor dataDescriptor; + + private long length; + + public ZipEntryHeader getZipEntryHeader() { + return zipEntryHeader; + } + + /** + * init zip entry by file + * + * @param file zip file + * @param entryOffset entry start offset + * @param compress compress file size + * @param hasDesc has data descriptor + * @return zip entry + * @throws IOException read zip exception + */ + public static ZipEntryData initZipEntry(File file, long entryOffset, long compress, boolean hasDesc) + throws IOException { + try (FileInputStream input = new FileInputStream(file)) { + long offset = entryOffset; + // read entry header by file and offset. + byte[] headBytes = FileUtils.readInputByOffsetAndLength(input, entryOffset, ZipEntryHeader.HEADER_LENGTH); + ZipEntryHeader entryHeader = ZipEntryHeader.initZipEntryHeader(headBytes); + offset += ZipEntryHeader.HEADER_LENGTH; + + // read entry file name and extra by offset. + int nameAndExtraLength = entryHeader.getFileNameLength() + entryHeader.getExtraLength(); + byte[] nameAndExtra = FileUtils.readInputByLength(input, nameAndExtraLength); + entryHeader.setNameAndExtra(nameAndExtra); + offset += nameAndExtraLength; + + // skip file data , save file offset and size. + ZipEntryData entry = new ZipEntryData(); + entry.setFileOffset(offset); + entry.setFileSize(compress); + input.skip(compress); + + long entryLength = entryHeader.getLength() + compress; + if (hasDesc) { + // if entry has data descriptor, read entry data descriptor. + byte[] desBytes = FileUtils.readInputByLength(input, DataDescriptor.DES_LENGTH); + DataDescriptor dataDesc = DataDescriptor.initDataDescriptor(desBytes); + entryLength += DataDescriptor.DES_LENGTH; + entry.setDataDescriptor(dataDesc); + } + entry.setZipEntryHeader(entryHeader); + entry.setLength(entryLength); + return entry; + } + } + + public void setZipEntryHeader(ZipEntryHeader zipEntryHeader) { + this.zipEntryHeader = zipEntryHeader; + } + + public DataDescriptor getDataDescriptor() { + return dataDescriptor; + } + + public void setDataDescriptor(DataDescriptor dataDescriptor) { + this.dataDescriptor = dataDescriptor; + } + + public long getFileOffset() { + return fileOffset; + } + + public void setFileOffset(long fileOffset) { + this.fileOffset = fileOffset; + } + + public long getFileSize() { + return fileSize; + } + + public void setFileSize(long fileSize) { + this.fileSize = fileSize; + } + + public long getLength() { + return length; + } + + public void setLength(long length) { + this.length = length; + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/ZipEntryHeader.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/ZipEntryHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..bf921df6ddd46e088d2f3787096685d5abe5f357 --- /dev/null +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/entity/zip/ZipEntryHeader.java @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.hapsigntool.hap.entity.zip; + +import com.ohos.hapsigntool.error.ZipException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; + +/** + * resolve zip ZipEntryHeader data + * + * @since 2023/12/02 + */ +class ZipEntryHeader { + /** + * ZipEntryHeader invariable bytes length + */ + public static final int HEADER_LENGTH = 30; + + /** + * 4 bytes , entry header signature + */ + public static final int SIGNATURE = 0x04034b50; + + /** + * 2 bytes + */ + private short version; + + /** + * 2 bytes + */ + private short flag; + + /** + * 2 bytes + */ + private short method; + + /** + * 2 bytes + */ + private short lastTime; + + /** + * 2 bytes + */ + private short lastDate; + + /** + * 4 bytes + */ + private int crc32; + + /** + * 4 bytes + */ + private long compressedSize; + + /** + * 4 bytes + */ + private long unCompressedSize; + + /** + * 2 bytes + */ + private int fileNameLength; + + /** + * 2 bytes + */ + private int extraLength; + + /** + * n bytes + */ + private String fileName; + + /** + * n bytes + */ + private byte[] extraData; + + private int length; + + /** + * init Zip Entry Header + * + * @param bytes ZipEntryHeader bytes + * @return ZipEntryHeader + * @throws ZipException read entry header exception + */ + public static ZipEntryHeader initZipEntryHeader(byte[] bytes) throws ZipException { + ZipEntryHeader entryHeader = new ZipEntryHeader(); + ByteBuffer bf = ByteBuffer.allocate(bytes.length); + bf.put(bytes); + bf.order(ByteOrder.LITTLE_ENDIAN); + bf.flip(); + if (bf.getInt() != ZipEntryHeader.SIGNATURE) { + throw new ZipException("find zip entry head failed"); + } + entryHeader.setVersion(bf.getShort()); + entryHeader.setFlag(bf.getShort()); + entryHeader.setMethod(bf.getShort()); + entryHeader.setLastTime(bf.getShort()); + entryHeader.setLastDate(bf.getShort()); + entryHeader.setCrc32(bf.getInt()); + entryHeader.setCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf)); + entryHeader.setUnCompressedSize(UnsignedDecimalUtil.getUnsignedInt(bf)); + entryHeader.setFileNameLength(UnsignedDecimalUtil.getUnsignedShort(bf)); + entryHeader.setExtraLength(UnsignedDecimalUtil.getUnsignedShort(bf)); + entryHeader.setLength(HEADER_LENGTH + entryHeader.getFileNameLength() + entryHeader.getExtraLength()); + return entryHeader; + } + + /** + * set entry header name and extra + * + * @param bytes name and extra bytes + */ + public void setNameAndExtra(byte[] bytes) { + ByteBuffer bf = ByteBuffer.allocate(bytes.length); + bf.put(bytes); + bf.order(ByteOrder.LITTLE_ENDIAN); + bf.flip(); + if (fileNameLength > 0) { + byte[] nameBytes = new byte[fileNameLength]; + bf.get(nameBytes); + this.fileName = new String(nameBytes, StandardCharsets.UTF_8); + } + if (extraLength > 0) { + byte[] extra = new byte[extraLength]; + bf.get(extra); + this.extraData = extra; + } + } + + /** + * change Zip Entry Header to bytes + * + * @return bytes + */ + public byte[] toBytes() { + ByteBuffer bf = ByteBuffer.allocate(length).order(ByteOrder.LITTLE_ENDIAN); + bf.putInt(SIGNATURE); + bf.putShort(version); + bf.putShort(flag); + bf.putShort(method); + bf.putShort(lastTime); + bf.putShort(lastDate); + bf.putInt(crc32); + UnsignedDecimalUtil.setUnsignedInt(bf, compressedSize); + UnsignedDecimalUtil.setUnsignedInt(bf, unCompressedSize); + UnsignedDecimalUtil.setUnsignedShort(bf, fileNameLength); + UnsignedDecimalUtil.setUnsignedShort(bf, extraLength); + if (fileNameLength > 0) { + bf.put(fileName.getBytes(StandardCharsets.UTF_8)); + } + if (extraLength > 0) { + bf.put(extraData); + } + return bf.array(); + } + + public static int getHeaderLength() { + return HEADER_LENGTH; + } + + public static int getSIGNATURE() { + return SIGNATURE; + } + + public short getVersion() { + return version; + } + + public void setVersion(short version) { + this.version = version; + } + + public short getFlag() { + return flag; + } + + public void setFlag(short flag) { + this.flag = flag; + } + + public short getMethod() { + return method; + } + + public void setMethod(short method) { + this.method = method; + } + + public short getLastTime() { + return lastTime; + } + + public void setLastTime(short lastTime) { + this.lastTime = lastTime; + } + + public short getLastDate() { + return lastDate; + } + + public void setLastDate(short lastDate) { + this.lastDate = lastDate; + } + + public int getCrc32() { + return crc32; + } + + public void setCrc32(int crc32) { + this.crc32 = crc32; + } + + public long getCompressedSize() { + return compressedSize; + } + + public void setCompressedSize(long compressedSize) { + this.compressedSize = compressedSize; + } + + public long getUnCompressedSize() { + return unCompressedSize; + } + + public void setUnCompressedSize(long unCompressedSize) { + this.unCompressedSize = unCompressedSize; + } + + public int getFileNameLength() { + return fileNameLength; + } + + public void setFileNameLength(int fileNameLength) { + this.fileNameLength = fileNameLength; + } + + public int getExtraLength() { + return extraLength; + } + + public void setExtraLength(int extraLength) { + this.extraLength = extraLength; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public byte[] getExtraData() { + return extraData; + } + + public void setExtraData(byte[] extraData) { + this.extraData = extraData; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } +} \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java index c181bdaf9b50fb31269996830bf6d853f4db1f95..817c4f3d8c40a74d8e679b18c068ac1e00359126 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java @@ -29,6 +29,7 @@ import com.ohos.hapsigntool.codesigning.sign.CodeSigning; import com.ohos.hapsigntool.error.CustomException; import com.ohos.hapsigntool.hap.config.SignerConfig; import com.ohos.hapsigntool.hap.entity.SigningBlock; +import com.ohos.hapsigntool.hap.entity.zip.Zip; import com.ohos.hapsigntool.hap.exception.HapFormatException; import com.ohos.hapsigntool.hap.exception.InvalidParamsException; import com.ohos.hapsigntool.hap.exception.MissingParamsException; @@ -504,14 +505,9 @@ public abstract class SignProvider { */ private void copyFileAndAlignment(File input, File tmpOutput, int alignment) throws IOException, HapFormatException { - try (JarFile inputJar = new JarFile(input, false); - FileOutputStream outputFile = new FileOutputStream(tmpOutput); - JarOutputStream outputJar = new JarOutputStream(outputFile)) { - long timestamp = TIMESTAMP; - timestamp -= TimeZone.getDefault().getOffset(timestamp); - outputJar.setLevel(COMPRESSION_MODE); - SignHap.copyFiles(inputJar, outputJar, timestamp, alignment); - } + Zip zip = new Zip(input); + zip.alignment(alignment); + zip.toFile(tmpOutput.getPath()); } /** diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java index fdb240afa7961e4a24b4172a1f809e121f65c576..c322e91440374534dfbaae03e81588dbc45e802b 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java @@ -18,6 +18,7 @@ package com.ohos.hapsigntool.utils; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.ohos.hapsigntool.error.ERROR; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -42,27 +43,31 @@ import java.nio.file.Files; * @since 2021/12/28 */ public final class FileUtils { - /** * LOGGER. */ private static final Logger LOGGER = LogManager.getLogger(FileUtils.class); + /** * add GSON static. */ public static final Gson GSON = (new GsonBuilder()).disableHtmlEscaping().create(); + /** * add GSON_PRETTY_PRINT static. */ public static final Gson GSON_PRETTY_PRINT = (new GsonBuilder()).disableHtmlEscaping().setPrettyPrinting().create(); + /** * File reader block size */ - public static final int FILE_BUFFER_BLOCK = 4096; + public static final int FILE_BUFFER_BLOCK = 1024 * 1024; + /** * File end */ public static final int FILE_END = -1; + /** * Expected split string length */ @@ -96,7 +101,7 @@ public final class FileUtils { * @throws IOException Read failed */ public static byte[] readFile(File file) throws IOException { - return read(new FileInputStream(file)); + return read(Files.newInputStream(file.toPath())); } /** @@ -119,6 +124,74 @@ public final class FileUtils { } } + /** + * Read byte from input file. + * + * @param file input file + * @param offset offset + * @param length length + * @return data bytes + */ + public static byte[] readFileByOffsetAndLength(File file, long offset, long length) throws IOException { + try (FileInputStream input = new FileInputStream(file)) { + return readInputByOffsetAndLength(input, offset, length); + } + } + + /** + * Read byte from input stream. + * + * @param input input stream + * @param offset offset + * @param length length + * @return data bytes + * @throws IOException read exception + */ + public static byte[] readInputByOffsetAndLength(InputStream input, long offset, long length) throws IOException { + long skip = input.skip(offset); + if (skip < offset) { + throw new IOException("can not read bytes by offset"); + } + return readInputByLength(input, length); + } + + /** + * Read byte from input stream. + * + * @param input InputStream + * @param length length + * @return data bytes + */ + public static byte[] readInputByLength(InputStream input, long length) throws IOException { + try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { + writeInputToOutPut(input, output, length); + return output.toByteArray(); + } + } + + /** + * write input to output by length + */ + private static void writeInputToOutPut(InputStream input, OutputStream output, long length) throws IOException { + byte[] buffer; + if (length > FILE_BUFFER_BLOCK) { + long times = length / FILE_BUFFER_BLOCK; + long remainder = length % FILE_BUFFER_BLOCK; + buffer = new byte[FILE_BUFFER_BLOCK]; + for (int i = 0; i < times; i++) { + int read = input.read(buffer); + output.write(buffer, 0, read); + } + byte[] suffix = new byte[(int) remainder]; + int read = input.read(suffix); + output.write(suffix, 0, read); + } else { + buffer = new byte[(int) length]; + int read = input.read(buffer); + output.write(buffer, 0, read); + } + } + /** * Out put content to file. * @@ -134,6 +207,34 @@ public final class FileUtils { } } + /** + * Write data in file to output stream + * + * @param inFile input file path. + * @param outFile output file path. + * @param offset file read offset + * @param size file read size + * @return true, if write successfully. + */ + public static boolean appendWriteFileByOffsetToFile(String inFile, String outFile, long offset, long size) { + if (StringUtils.isEmpty(outFile)) { + return false; + } + File inputFile = new File(inFile); + File outPutFile = new File(outFile); + try (FileInputStream fis = new FileInputStream(inputFile); + FileOutputStream fos = new FileOutputStream(outPutFile, true)) { + fis.skip(offset); + writeInputToOutPut(fis, fos, size); + return true; + } catch (FileNotFoundException e) { + LOGGER.error("Failed to get input stream object."); + } catch (IOException e) { + LOGGER.error("Failed to read or write data."); + } + return false; + } + /** * Check file exist or not. * @@ -250,13 +351,13 @@ public final class FileUtils { /** * Write byte array data to output file. * - * @param signHeadByte byte array data. + * @param bytes byte array data. * @param outFile output file path. * @return true, if write successfully. */ - public static boolean writeByteToOutFile(byte[] signHeadByte, String outFile) { + public static boolean writeByteToOutFile(byte[] bytes, String outFile) { try (OutputStream ops = new FileOutputStream(outFile, true)) { - ops.write(signHeadByte, 0, signHeadByte.length); + ops.write(bytes, 0, bytes.length); ops.flush(); return true; } catch (FileNotFoundException e) { @@ -289,6 +390,7 @@ public final class FileUtils { /** * Open an input stream of input file safely. + * * @param file input file. * @return an input stream of input file * @throws IOException file is a directory or can't be read.