From 48103b2492ac469d0c94530d79d9d0593b5872b9 Mon Sep 17 00:00:00 2001 From: Wind Date: Thu, 27 Feb 2020 02:34:48 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=9C=A8android7=E4=BB=A5?= =?UTF-8?q?=E4=B8=8B=E7=9A=84=E6=89=8B=E6=9C=BA=E4=B8=8A=E7=9A=84=E5=85=BC?= =?UTF-8?q?=E5=AE=B9=E6=80=A7=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apksig/DefaultApkSignerEngine.java | 18 +- .../com/android/apksig/internal/Supplier.java | 14 + .../internal/apk/ApkSigningBlockUtils.java | 46 +- .../internal/apk/v1/V1SchemeSigner.java | 2 +- .../apksig/internal/asn1/Asn1BerParser.java | 30 +- .../apksig/internal/asn1/Asn1DerEncoder.java | 30 +- .../android/apksig/internal/util/Base64.java | 976 ++++++++++++++++++ .../internal/util/ChainedDataSource.java | 11 +- .../internal/util/VerityTreeBuilder.java | 3 +- build.gradle | 2 +- ...tor-1.0.0.jar => ManifestEditor-1.0.1.jar} | Bin 102898 -> 103057 bytes .../wind/xpatch/task/BuildAndSignApkTask.java | 6 + .../com/storm/wind/xpatch/util/FileUtils.java | 8 +- 13 files changed, 1111 insertions(+), 35 deletions(-) create mode 100644 apksigner/src/main/java/com/android/apksig/internal/Supplier.java create mode 100644 apksigner/src/main/java/com/android/apksig/internal/util/Base64.java rename xpatch/libs/{ManifestEditor-1.0.0.jar => ManifestEditor-1.0.1.jar} (87%) diff --git a/apksigner/src/main/java/com/android/apksig/DefaultApkSignerEngine.java b/apksigner/src/main/java/com/android/apksig/DefaultApkSignerEngine.java index c88239e..044254d 100644 --- a/apksigner/src/main/java/com/android/apksig/DefaultApkSignerEngine.java +++ b/apksigner/src/main/java/com/android/apksig/DefaultApkSignerEngine.java @@ -47,6 +47,7 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -436,12 +437,17 @@ public class DefaultApkSignerEngine implements ApkSignerEngine { if (V1SchemeSigner.isJarEntryDigestNeededInManifest(entry.getKey()) && isDebuggable(entryName)) { - Optional extractedDigest = - V1SchemeVerifier.getDigestsToVerify( - entry.getValue(), "-Digest", mMinSdkVersion, Integer.MAX_VALUE) - .stream() - .filter(d -> d.jcaDigestAlgorithm == alg) - .findFirst(); + Optional extractedDigest = Optional.empty(); + + Collection collection = V1SchemeVerifier.getDigestsToVerify( + entry.getValue(), "-Digest", mMinSdkVersion, Integer.MAX_VALUE); + + for (V1SchemeVerifier.NamedDigest d : collection) { + if (d.jcaDigestAlgorithm == alg) { + extractedDigest = Optional.of(d); + break; + } + } extractedDigest.ifPresent( namedDigest -> mOutputJarEntryDigests.put(entryName, namedDigest.digest)); diff --git a/apksigner/src/main/java/com/android/apksig/internal/Supplier.java b/apksigner/src/main/java/com/android/apksig/internal/Supplier.java new file mode 100644 index 0000000..cc06bbf --- /dev/null +++ b/apksigner/src/main/java/com/android/apksig/internal/Supplier.java @@ -0,0 +1,14 @@ +package com.android.apksig.internal; + +/** + * @author Windysha + */ +public interface Supplier { + + /** + * Gets a result. + * + * @return a result + */ + T get(); +} diff --git a/apksigner/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtils.java b/apksigner/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtils.java index 3ec00b2..f3d461e 100644 --- a/apksigner/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtils.java +++ b/apksigner/src/main/java/com/android/apksig/internal/apk/ApkSigningBlockUtils.java @@ -21,6 +21,7 @@ import com.android.apksig.SigningCertificateLineage; import com.android.apksig.apk.ApkFormatException; import com.android.apksig.apk.ApkSigningBlockNotFoundException; import com.android.apksig.apk.ApkUtils; +import com.android.apksig.internal.Supplier; import com.android.apksig.internal.asn1.Asn1BerParser; import com.android.apksig.internal.asn1.Asn1DecodingException; import com.android.apksig.internal.asn1.Asn1DerEncoder; @@ -60,14 +61,15 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; -import java.util.stream.Collectors; public class ApkSigningBlockUtils { @@ -418,10 +420,22 @@ public class ApkSigningBlockUtils { DataSource centralDir, DataSource eocd) throws IOException, NoSuchAlgorithmException, DigestException { Map contentDigests = new HashMap<>(); - Set oneMbChunkBasedAlgorithm = digestAlgorithms.stream() - .filter(a -> a == ContentDigestAlgorithm.CHUNKED_SHA256 || - a == ContentDigestAlgorithm.CHUNKED_SHA512) - .collect(Collectors.toSet()); + // 不使用stream,以兼容android6以及一下 +// Set oneMbChunkBasedAlgorithm = digestAlgorithms.stream() +// .filter(a -> a == ContentDigestAlgorithm.CHUNKED_SHA256 || +// a == ContentDigestAlgorithm.CHUNKED_SHA512) +// .collect(Collectors.toSet()); + + Set oneMbChunkBasedAlgorithm = new HashSet<>(); + if (digestAlgorithms != null && digestAlgorithms.size() > 0) { + for (ContentDigestAlgorithm a : digestAlgorithms) { + if (a == ContentDigestAlgorithm.CHUNKED_SHA256 || + a == ContentDigestAlgorithm.CHUNKED_SHA512) { + oneMbChunkBasedAlgorithm.add(a); + } + } + } + computeOneMbChunkContentDigests( executor, oneMbChunkBasedAlgorithm, @@ -1150,10 +1164,22 @@ public class ApkSigningBlockUtils { if (bestSigAlgorithmOnSdkVersion.isEmpty()) { throw new NoSupportedSignaturesException("No supported signature"); } - return bestSigAlgorithmOnSdkVersion.values().stream() - .sorted((sig1, sig2) -> Integer.compare( - sig1.algorithm.getId(), sig2.algorithm.getId())) - .collect(Collectors.toList()); + + // 不使用stream,以兼容android6以及一下 + Collection values = bestSigAlgorithmOnSdkVersion.values(); + Comparator cmp = new Comparator() { + @Override + public int compare(SupportedSignature sig1, SupportedSignature sig2) { + return Integer.compare(sig1.algorithm.getId(), sig2.algorithm.getId()); + } + }; + Collections.sort((List) values, cmp); + + return (List) values; +// return bestSigAlgorithmOnSdkVersion.values().stream() +// .sorted((sig1, sig2) -> Integer.compare( +// sig1.algorithm.getId(), sig2.algorithm.getId())) +// .collect(Collectors.toList()); } public static class NoSupportedSignaturesException extends Exception { diff --git a/apksigner/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java b/apksigner/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java index f900211..fa447ea 100644 --- a/apksigner/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java +++ b/apksigner/src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java @@ -46,7 +46,7 @@ import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; -import java.util.Base64; +import com.android.apksig.internal.util.Base64; import java.util.Collections; import java.util.HashSet; import java.util.List; diff --git a/apksigner/src/main/java/com/android/apksig/internal/asn1/Asn1BerParser.java b/apksigner/src/main/java/com/android/apksig/internal/asn1/Asn1BerParser.java index d4a6fb6..81aca7b 100644 --- a/apksigner/src/main/java/com/android/apksig/internal/asn1/Asn1BerParser.java +++ b/apksigner/src/main/java/com/android/apksig/internal/asn1/Asn1BerParser.java @@ -311,7 +311,12 @@ public final class Asn1BerParser { private static Asn1Type getContainerAsn1Type(Class containerClass) throws Asn1DecodingException { - Asn1Class containerAnnotation = containerClass.getDeclaredAnnotation(Asn1Class.class); + Asn1Class containerAnnotation; + try { + containerAnnotation = containerClass.getDeclaredAnnotation(Asn1Class.class); + } catch (Throwable e) { + containerAnnotation = containerClass.getAnnotation(Asn1Class.class); + } if (containerAnnotation == null) { throw new Asn1DecodingException( containerClass.getName() + " is not annotated with " @@ -531,7 +536,12 @@ public final class Asn1BerParser { Field[] declaredFields = containerClass.getDeclaredFields(); List result = new ArrayList<>(declaredFields.length); for (Field field : declaredFields) { - Asn1Field annotation = field.getDeclaredAnnotation(Asn1Field.class); + Asn1Field annotation; + try { + annotation = containerClass.getDeclaredAnnotation(Asn1Field.class); + } catch (Throwable e) { + annotation = containerClass.getAnnotation(Asn1Field.class); + } if (annotation == null) { continue; } @@ -645,8 +655,12 @@ public final class Asn1BerParser { break; case SEQUENCE: { - Asn1Class containerAnnotation = - targetType.getDeclaredAnnotation(Asn1Class.class); + Asn1Class containerAnnotation; + try { + containerAnnotation = targetType.getDeclaredAnnotation(Asn1Class.class); + } catch (Throwable e) { + containerAnnotation = targetType.getAnnotation(Asn1Class.class); + } if ((containerAnnotation != null) && (containerAnnotation.type() == Asn1Type.SEQUENCE)) { return parseSequence(dataValue, targetType); @@ -655,8 +669,12 @@ public final class Asn1BerParser { } case CHOICE: { - Asn1Class containerAnnotation = - targetType.getDeclaredAnnotation(Asn1Class.class); + Asn1Class containerAnnotation; + try { + containerAnnotation = targetType.getDeclaredAnnotation(Asn1Class.class); + } catch (Throwable e) { + containerAnnotation = targetType.getAnnotation(Asn1Class.class); + } if ((containerAnnotation != null) && (containerAnnotation.type() == Asn1Type.CHOICE)) { return parseChoice(dataValue, targetType); diff --git a/apksigner/src/main/java/com/android/apksig/internal/asn1/Asn1DerEncoder.java b/apksigner/src/main/java/com/android/apksig/internal/asn1/Asn1DerEncoder.java index 22a432f..3567a8b 100644 --- a/apksigner/src/main/java/com/android/apksig/internal/asn1/Asn1DerEncoder.java +++ b/apksigner/src/main/java/com/android/apksig/internal/asn1/Asn1DerEncoder.java @@ -53,7 +53,12 @@ public final class Asn1DerEncoder { */ public static byte[] encode(Object container) throws Asn1EncodingException { Class containerClass = container.getClass(); - Asn1Class containerAnnotation = containerClass.getDeclaredAnnotation(Asn1Class.class); + Asn1Class containerAnnotation; + try { + containerAnnotation = containerClass.getDeclaredAnnotation(Asn1Class.class); + } catch (Throwable e) { + containerAnnotation = containerClass.getAnnotation(Asn1Class.class); + } if (containerAnnotation == null) { throw new Asn1EncodingException( containerClass.getName() + " not annotated with " + Asn1Class.class.getName()); @@ -216,7 +221,12 @@ public final class Asn1DerEncoder { Field[] declaredFields = containerClass.getDeclaredFields(); List result = new ArrayList<>(declaredFields.length); for (Field field : declaredFields) { - Asn1Field annotation = field.getDeclaredAnnotation(Asn1Field.class); + Asn1Field annotation; + try { + annotation = field.getDeclaredAnnotation(Asn1Field.class); + } catch (Throwable e) { + annotation = field.getAnnotation(Asn1Field.class); + } if (annotation == null) { continue; } @@ -560,8 +570,12 @@ public final class Asn1DerEncoder { break; case SEQUENCE: { - Asn1Class containerAnnotation = - sourceType.getDeclaredAnnotation(Asn1Class.class); + Asn1Class containerAnnotation; + try { + containerAnnotation = sourceType.getDeclaredAnnotation(Asn1Class.class); + } catch (Throwable e) { + containerAnnotation = sourceType.getAnnotation(Asn1Class.class); + } if ((containerAnnotation != null) && (containerAnnotation.type() == Asn1Type.SEQUENCE)) { return toSequence(source); @@ -570,8 +584,12 @@ public final class Asn1DerEncoder { } case CHOICE: { - Asn1Class containerAnnotation = - sourceType.getDeclaredAnnotation(Asn1Class.class); + Asn1Class containerAnnotation; + try { + containerAnnotation = sourceType.getDeclaredAnnotation(Asn1Class.class); + } catch (Throwable e) { + containerAnnotation = sourceType.getAnnotation(Asn1Class.class); + } if ((containerAnnotation != null) && (containerAnnotation.type() == Asn1Type.CHOICE)) { return toChoice(source); diff --git a/apksigner/src/main/java/com/android/apksig/internal/util/Base64.java b/apksigner/src/main/java/com/android/apksig/internal/util/Base64.java new file mode 100644 index 0000000..01dc54a --- /dev/null +++ b/apksigner/src/main/java/com/android/apksig/internal/util/Base64.java @@ -0,0 +1,976 @@ +package com.android.apksig.internal.util; + +import java.io.FilterOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Objects; + +/** + * This class consists exclusively of static methods for obtaining + * encoders and decoders for the Base64 encoding scheme. The + * implementation of this class supports the following types of Base64 + * as specified in + * RFC 4648 and + * RFC 2045. + * + *
    + *
  • Basic + *

    Uses "The Base64 Alphabet" as specified in Table 1 of + * RFC 4648 and RFC 2045 for encoding and decoding operation. + * The encoder does not add any line feed (line separator) + * character. The decoder rejects data that contains characters + * outside the base64 alphabet.

  • + * + *
  • URL and Filename safe + *

    Uses the "URL and Filename safe Base64 Alphabet" as specified + * in Table 2 of RFC 4648 for encoding and decoding. The + * encoder does not add any line feed (line separator) character. + * The decoder rejects data that contains characters outside the + * base64 alphabet.

  • + * + *
  • MIME + *

    Uses the "The Base64 Alphabet" as specified in Table 1 of + * RFC 2045 for encoding and decoding operation. The encoded output + * must be represented in lines of no more than 76 characters each + * and uses a carriage return {@code '\r'} followed immediately by + * a linefeed {@code '\n'} as the line separator. No line separator + * is added to the end of the encoded output. All line separators + * or other characters not found in the base64 alphabet table are + * ignored in decoding operation.

  • + *
+ * + *

Unless otherwise noted, passing a {@code null} argument to a + * method of this class will cause a {@link java.lang.NullPointerException + * NullPointerException} to be thrown. + * + * @author Xueming Shen + * @since 1.8 + */ + +public class Base64 { + + private Base64() {} + + /** + * Returns a {@link Encoder} that encodes using the + * Basic type base64 encoding scheme. + * + * @return A Base64 encoder. + */ + public static Encoder getEncoder() { + return Encoder.RFC4648; + } + + /** + * Returns a {@link Encoder} that encodes using the + * URL and Filename safe type base64 + * encoding scheme. + * + * @return A Base64 encoder. + */ + public static Encoder getUrlEncoder() { + return Encoder.RFC4648_URLSAFE; + } + + /** + * Returns a {@link Encoder} that encodes using the + * MIME type base64 encoding scheme. + * + * @return A Base64 encoder. + */ + public static Encoder getMimeEncoder() { + return Encoder.RFC2045; + } + + /** + * Returns a {@link Encoder} that encodes using the + * MIME type base64 encoding scheme + * with specified line length and line separators. + * + * @param lineLength + * the length of each output line (rounded down to nearest multiple + * of 4). If {@code lineLength <= 0} the output will not be separated + * in lines + * @param lineSeparator + * the line separator for each output line + * + * @return A Base64 encoder. + * + * @throws IllegalArgumentException if {@code lineSeparator} includes any + * character of "The Base64 Alphabet" as specified in Table 1 of + * RFC 2045. + */ + public static Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) { + Objects.requireNonNull(lineSeparator); + int[] base64 = Decoder.fromBase64; + for (byte b : lineSeparator) { + if (base64[b & 0xff] != -1) + throw new IllegalArgumentException( + "Illegal base64 line separator character 0x" + Integer.toString(b, 16)); + } + if (lineLength <= 0) { + return Encoder.RFC4648; + } + return new Encoder(false, lineSeparator, lineLength >> 2 << 2, true); + } + + /** + * Returns a {@link Decoder} that decodes using the + * Basic type base64 encoding scheme. + * + * @return A Base64 decoder. + */ + public static Decoder getDecoder() { + return Decoder.RFC4648; + } + + /** + * Returns a {@link Decoder} that decodes using the + * URL and Filename safe type base64 + * encoding scheme. + * + * @return A Base64 decoder. + */ + public static Decoder getUrlDecoder() { + return Decoder.RFC4648_URLSAFE; + } + + /** + * Returns a {@link Decoder} that decodes using the + * MIME type base64 decoding scheme. + * + * @return A Base64 decoder. + */ + public static Decoder getMimeDecoder() { + return Decoder.RFC2045; + } + + /** + * This class implements an encoder for encoding byte data using + * the Base64 encoding scheme as specified in RFC 4648 and RFC 2045. + * + *

Instances of {@link Encoder} class are safe for use by + * multiple concurrent threads. + * + *

Unless otherwise noted, passing a {@code null} argument to + * a method of this class will cause a + * {@link java.lang.NullPointerException NullPointerException} to + * be thrown. + * + * @see Decoder + * @since 1.8 + */ + public static class Encoder { + + private final byte[] newline; + private final int linemax; + private final boolean isURL; + private final boolean doPadding; + + private Encoder(boolean isURL, byte[] newline, int linemax, boolean doPadding) { + this.isURL = isURL; + this.newline = newline; + this.linemax = linemax; + this.doPadding = doPadding; + } + + /** + * This array is a lookup table that translates 6-bit positive integer + * index values into their "Base64 Alphabet" equivalents as specified + * in "Table 1: The Base64 Alphabet" of RFC 2045 (and RFC 4648). + */ + private static final char[] toBase64 = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' + }; + + /** + * It's the lookup table for "URL and Filename safe Base64" as specified + * in Table 2 of the RFC 4648, with the '+' and '/' changed to '-' and + * '_'. This table is used when BASE64_URL is specified. + */ + private static final char[] toBase64URL = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' + }; + + private static final int MIMELINEMAX = 76; + private static final byte[] CRLF = new byte[] {'\r', '\n'}; + + static final Encoder RFC4648 = new Encoder(false, null, -1, true); + static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1, true); + static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX, true); + + private final int outLength(int srclen) { + int len = 0; + if (doPadding) { + len = 4 * ((srclen + 2) / 3); + } else { + int n = srclen % 3; + len = 4 * (srclen / 3) + (n == 0 ? 0 : n + 1); + } + if (linemax > 0) // line separators + len += (len - 1) / linemax * newline.length; + return len; + } + + /** + * Encodes all bytes from the specified byte array into a newly-allocated + * byte array using the {@link Base64} encoding scheme. The returned byte + * array is of the length of the resulting bytes. + * + * @param src + * the byte array to encode + * @return A newly-allocated byte array containing the resulting + * encoded bytes. + */ + public byte[] encode(byte[] src) { + int len = outLength(src.length); // dst array size + byte[] dst = new byte[len]; + int ret = encode0(src, 0, src.length, dst); + if (ret != dst.length) + return Arrays.copyOf(dst, ret); + return dst; + } + + /** + * Encodes all bytes from the specified byte array using the + * {@link Base64} encoding scheme, writing the resulting bytes to the + * given output byte array, starting at offset 0. + * + *

It is the responsibility of the invoker of this method to make + * sure the output byte array {@code dst} has enough space for encoding + * all bytes from the input byte array. No bytes will be written to the + * output byte array if the output byte array is not big enough. + * + * @param src + * the byte array to encode + * @param dst + * the output byte array + * @return The number of bytes written to the output byte array + * + * @throws IllegalArgumentException if {@code dst} does not have enough + * space for encoding all input bytes. + */ + public int encode(byte[] src, byte[] dst) { + int len = outLength(src.length); // dst array size + if (dst.length < len) + throw new IllegalArgumentException( + "Output byte array is too small for encoding all input bytes"); + return encode0(src, 0, src.length, dst); + } + + /** + * Encodes the specified byte array into a String using the {@link Base64} + * encoding scheme. + * + *

This method first encodes all input bytes into a base64 encoded + * byte array and then constructs a new String by using the encoded byte + * array and the {@link java.nio.charset.StandardCharsets#ISO_8859_1 + * ISO-8859-1} charset. + * + *

In other words, an invocation of this method has exactly the same + * effect as invoking + * {@code new String(encode(src), StandardCharsets.ISO_8859_1)}. + * + * @param src + * the byte array to encode + * @return A String containing the resulting Base64 encoded characters + */ + @SuppressWarnings("deprecation") + public String encodeToString(byte[] src) { + byte[] encoded = encode(src); + return new String(encoded, 0, 0, encoded.length); + } + + /** + * Encodes all remaining bytes from the specified byte buffer into + * a newly-allocated ByteBuffer using the {@link Base64} encoding + * scheme. + * + * Upon return, the source buffer's position will be updated to + * its limit; its limit will not have been changed. The returned + * output buffer's position will be zero and its limit will be the + * number of resulting encoded bytes. + * + * @param buffer + * the source ByteBuffer to encode + * @return A newly-allocated byte buffer containing the encoded bytes. + */ + public ByteBuffer encode(ByteBuffer buffer) { + int len = outLength(buffer.remaining()); + byte[] dst = new byte[len]; + int ret = 0; + if (buffer.hasArray()) { + ret = encode0(buffer.array(), + buffer.arrayOffset() + buffer.position(), + buffer.arrayOffset() + buffer.limit(), + dst); + buffer.position(buffer.limit()); + } else { + byte[] src = new byte[buffer.remaining()]; + buffer.get(src); + ret = encode0(src, 0, src.length, dst); + } + if (ret != dst.length) + dst = Arrays.copyOf(dst, ret); + return ByteBuffer.wrap(dst); + } + + /** + * Wraps an output stream for encoding byte data using the {@link Base64} + * encoding scheme. + * + *

It is recommended to promptly close the returned output stream after + * use, during which it will flush all possible leftover bytes to the underlying + * output stream. Closing the returned output stream will close the underlying + * output stream. + * + * @param os + * the output stream. + * @return the output stream for encoding the byte data into the + * specified Base64 encoded format + */ + public OutputStream wrap(OutputStream os) { + Objects.requireNonNull(os); + return new EncOutputStream(os, isURL ? toBase64URL : toBase64, + newline, linemax, doPadding); + } + + /** + * Returns an encoder instance that encodes equivalently to this one, + * but without adding any padding character at the end of the encoded + * byte data. + * + *

The encoding scheme of this encoder instance is unaffected by + * this invocation. The returned encoder instance should be used for + * non-padding encoding operation. + * + * @return an equivalent encoder that encodes without adding any + * padding character at the end + */ + public Encoder withoutPadding() { + if (!doPadding) + return this; + return new Encoder(isURL, newline, linemax, false); + } + + private int encode0(byte[] src, int off, int end, byte[] dst) { + char[] base64 = isURL ? toBase64URL : toBase64; + int sp = off; + int slen = (end - off) / 3 * 3; + int sl = off + slen; + if (linemax > 0 && slen > linemax / 4 * 3) + slen = linemax / 4 * 3; + int dp = 0; + while (sp < sl) { + int sl0 = Math.min(sp + slen, sl); + for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) { + int bits = (src[sp0++] & 0xff) << 16 | + (src[sp0++] & 0xff) << 8 | + (src[sp0++] & 0xff); + dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f]; + dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f]; + dst[dp0++] = (byte)base64[(bits >>> 6) & 0x3f]; + dst[dp0++] = (byte)base64[bits & 0x3f]; + } + int dlen = (sl0 - sp) / 3 * 4; + dp += dlen; + sp = sl0; + if (dlen == linemax && sp < end) { + for (byte b : newline){ + dst[dp++] = b; + } + } + } + if (sp < end) { // 1 or 2 leftover bytes + int b0 = src[sp++] & 0xff; + dst[dp++] = (byte)base64[b0 >> 2]; + if (sp == end) { + dst[dp++] = (byte)base64[(b0 << 4) & 0x3f]; + if (doPadding) { + dst[dp++] = '='; + dst[dp++] = '='; + } + } else { + int b1 = src[sp++] & 0xff; + dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)]; + dst[dp++] = (byte)base64[(b1 << 2) & 0x3f]; + if (doPadding) { + dst[dp++] = '='; + } + } + } + return dp; + } + } + + /** + * This class implements a decoder for decoding byte data using the + * Base64 encoding scheme as specified in RFC 4648 and RFC 2045. + * + *

The Base64 padding character {@code '='} is accepted and + * interpreted as the end of the encoded byte data, but is not + * required. So if the final unit of the encoded byte data only has + * two or three Base64 characters (without the corresponding padding + * character(s) padded), they are decoded as if followed by padding + * character(s). If there is a padding character present in the + * final unit, the correct number of padding character(s) must be + * present, otherwise {@code IllegalArgumentException} ( + * {@code IOException} when reading from a Base64 stream) is thrown + * during decoding. + * + *

Instances of {@link Decoder} class are safe for use by + * multiple concurrent threads. + * + *

Unless otherwise noted, passing a {@code null} argument to + * a method of this class will cause a + * {@link java.lang.NullPointerException NullPointerException} to + * be thrown. + * + * @see Encoder + * @since 1.8 + */ + public static class Decoder { + + private final boolean isURL; + private final boolean isMIME; + + private Decoder(boolean isURL, boolean isMIME) { + this.isURL = isURL; + this.isMIME = isMIME; + } + + /** + * Lookup table for decoding unicode characters drawn from the + * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into + * their 6-bit positive integer equivalents. Characters that + * are not in the Base64 alphabet but fall within the bounds of + * the array are encoded to -1. + * + */ + private static final int[] fromBase64 = new int[256]; + static { + Arrays.fill(fromBase64, -1); + for (int i = 0; i < Encoder.toBase64.length; i++) + fromBase64[Encoder.toBase64[i]] = i; + fromBase64['='] = -2; + } + + /** + * Lookup table for decoding "URL and Filename safe Base64 Alphabet" + * as specified in Table2 of the RFC 4648. + */ + private static final int[] fromBase64URL = new int[256]; + + static { + Arrays.fill(fromBase64URL, -1); + for (int i = 0; i < Encoder.toBase64URL.length; i++) + fromBase64URL[Encoder.toBase64URL[i]] = i; + fromBase64URL['='] = -2; + } + + static final Decoder RFC4648 = new Decoder(false, false); + static final Decoder RFC4648_URLSAFE = new Decoder(true, false); + static final Decoder RFC2045 = new Decoder(false, true); + + /** + * Decodes all bytes from the input byte array using the {@link Base64} + * encoding scheme, writing the results into a newly-allocated output + * byte array. The returned byte array is of the length of the resulting + * bytes. + * + * @param src + * the byte array to decode + * + * @return A newly-allocated byte array containing the decoded bytes. + * + * @throws IllegalArgumentException + * if {@code src} is not in valid Base64 scheme + */ + public byte[] decode(byte[] src) { + byte[] dst = new byte[outLength(src, 0, src.length)]; + int ret = decode0(src, 0, src.length, dst); + if (ret != dst.length) { + dst = Arrays.copyOf(dst, ret); + } + return dst; + } + + /** + * Decodes a Base64 encoded String into a newly-allocated byte array + * using the {@link Base64} encoding scheme. + * + *

An invocation of this method has exactly the same effect as invoking + * {@code decode(src.getBytes(StandardCharsets.ISO_8859_1))} + * + * @param src + * the string to decode + * + * @return A newly-allocated byte array containing the decoded bytes. + * + * @throws IllegalArgumentException + * if {@code src} is not in valid Base64 scheme + */ + public byte[] decode(String src) { + return decode(src.getBytes(StandardCharsets.ISO_8859_1)); + } + + /** + * Decodes all bytes from the input byte array using the {@link Base64} + * encoding scheme, writing the results into the given output byte array, + * starting at offset 0. + * + *

It is the responsibility of the invoker of this method to make + * sure the output byte array {@code dst} has enough space for decoding + * all bytes from the input byte array. No bytes will be be written to + * the output byte array if the output byte array is not big enough. + * + *

If the input byte array is not in valid Base64 encoding scheme + * then some bytes may have been written to the output byte array before + * IllegalargumentException is thrown. + * + * @param src + * the byte array to decode + * @param dst + * the output byte array + * + * @return The number of bytes written to the output byte array + * + * @throws IllegalArgumentException + * if {@code src} is not in valid Base64 scheme, or {@code dst} + * does not have enough space for decoding all input bytes. + */ + public int decode(byte[] src, byte[] dst) { + int len = outLength(src, 0, src.length); + if (dst.length < len) + throw new IllegalArgumentException( + "Output byte array is too small for decoding all input bytes"); + return decode0(src, 0, src.length, dst); + } + + /** + * Decodes all bytes from the input byte buffer using the {@link Base64} + * encoding scheme, writing the results into a newly-allocated ByteBuffer. + * + *

Upon return, the source buffer's position will be updated to + * its limit; its limit will not have been changed. The returned + * output buffer's position will be zero and its limit will be the + * number of resulting decoded bytes + * + *

{@code IllegalArgumentException} is thrown if the input buffer + * is not in valid Base64 encoding scheme. The position of the input + * buffer will not be advanced in this case. + * + * @param buffer + * the ByteBuffer to decode + * + * @return A newly-allocated byte buffer containing the decoded bytes + * + * @throws IllegalArgumentException + * if {@code src} is not in valid Base64 scheme. + */ + public ByteBuffer decode(ByteBuffer buffer) { + int pos0 = buffer.position(); + try { + byte[] src; + int sp, sl; + if (buffer.hasArray()) { + src = buffer.array(); + sp = buffer.arrayOffset() + buffer.position(); + sl = buffer.arrayOffset() + buffer.limit(); + buffer.position(buffer.limit()); + } else { + src = new byte[buffer.remaining()]; + buffer.get(src); + sp = 0; + sl = src.length; + } + byte[] dst = new byte[outLength(src, sp, sl)]; + return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst)); + } catch (IllegalArgumentException iae) { + buffer.position(pos0); + throw iae; + } + } + + /** + * Returns an input stream for decoding {@link Base64} encoded byte stream. + * + *

The {@code read} methods of the returned {@code InputStream} will + * throw {@code IOException} when reading bytes that cannot be decoded. + * + *

Closing the returned input stream will close the underlying + * input stream. + * + * @param is + * the input stream + * + * @return the input stream for decoding the specified Base64 encoded + * byte stream + */ + public InputStream wrap(InputStream is) { + Objects.requireNonNull(is); + return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME); + } + + private int outLength(byte[] src, int sp, int sl) { + int[] base64 = isURL ? fromBase64URL : fromBase64; + int paddings = 0; + int len = sl - sp; + if (len == 0) + return 0; + if (len < 2) { + if (isMIME && base64[0] == -1) + return 0; + throw new IllegalArgumentException( + "Input byte[] should at least have 2 bytes for base64 bytes"); + } + if (isMIME) { + // scan all bytes to fill out all non-alphabet. a performance + // trade-off of pre-scan or Arrays.copyOf + int n = 0; + while (sp < sl) { + int b = src[sp++] & 0xff; + if (b == '=') { + len -= (sl - sp + 1); + break; + } + if ((b = base64[b]) == -1) + n++; + } + len -= n; + } else { + if (src[sl - 1] == '=') { + paddings++; + if (src[sl - 2] == '=') + paddings++; + } + } + if (paddings == 0 && (len & 0x3) != 0) + paddings = 4 - (len & 0x3); + return 3 * ((len + 3) / 4) - paddings; + } + + private int decode0(byte[] src, int sp, int sl, byte[] dst) { + int[] base64 = isURL ? fromBase64URL : fromBase64; + int dp = 0; + int bits = 0; + int shiftto = 18; // pos of first byte of 4-byte atom + while (sp < sl) { + int b = src[sp++] & 0xff; + if ((b = base64[b]) < 0) { + if (b == -2) { // padding byte '=' + // = shiftto==18 unnecessary padding + // x= shiftto==12 a dangling single x + // x to be handled together with non-padding case + // xx= shiftto==6&&sp==sl missing last = + // xx=y shiftto==6 last is not = + if (shiftto == 6 && (sp == sl || src[sp++] != '=') || + shiftto == 18) { + throw new IllegalArgumentException( + "Input byte array has wrong 4-byte ending unit"); + } + break; + } + if (isMIME) // skip if for rfc2045 + continue; + else + throw new IllegalArgumentException( + "Illegal base64 character " + + Integer.toString(src[sp - 1], 16)); + } + bits |= (b << shiftto); + shiftto -= 6; + if (shiftto < 0) { + dst[dp++] = (byte)(bits >> 16); + dst[dp++] = (byte)(bits >> 8); + dst[dp++] = (byte)(bits); + shiftto = 18; + bits = 0; + } + } + // reached end of byte array or hit padding '=' characters. + if (shiftto == 6) { + dst[dp++] = (byte)(bits >> 16); + } else if (shiftto == 0) { + dst[dp++] = (byte)(bits >> 16); + dst[dp++] = (byte)(bits >> 8); + } else if (shiftto == 12) { + // dangling single "x", incorrectly encoded. + throw new IllegalArgumentException( + "Last unit does not have enough valid bits"); + } + // anything left is invalid, if is not MIME. + // if MIME, ignore all non-base64 character + while (sp < sl) { + if (isMIME && base64[src[sp++]] < 0) + continue; + throw new IllegalArgumentException( + "Input byte array has incorrect ending byte at " + sp); + } + return dp; + } + } + + /* + * An output stream for encoding bytes into the Base64. + */ + private static class EncOutputStream extends FilterOutputStream { + + private int leftover = 0; + private int b0, b1, b2; + private boolean closed = false; + + private final char[] base64; // byte->base64 mapping + private final byte[] newline; // line separator, if needed + private final int linemax; + private final boolean doPadding;// whether or not to pad + private int linepos = 0; + + EncOutputStream(OutputStream os, char[] base64, + byte[] newline, int linemax, boolean doPadding) { + super(os); + this.base64 = base64; + this.newline = newline; + this.linemax = linemax; + this.doPadding = doPadding; + } + + @Override + public void write(int b) throws IOException { + byte[] buf = new byte[1]; + buf[0] = (byte)(b & 0xff); + write(buf, 0, 1); + } + + private void checkNewline() throws IOException { + if (linepos == linemax) { + out.write(newline); + linepos = 0; + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (closed) + throw new IOException("Stream is closed"); + if (off < 0 || len < 0 || len > b.length - off) + throw new ArrayIndexOutOfBoundsException(); + if (len == 0) + return; + if (leftover != 0) { + if (leftover == 1) { + b1 = b[off++] & 0xff; + len--; + if (len == 0) { + leftover++; + return; + } + } + b2 = b[off++] & 0xff; + len--; + checkNewline(); + out.write(base64[b0 >> 2]); + out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); + out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]); + out.write(base64[b2 & 0x3f]); + linepos += 4; + } + int nBits24 = len / 3; + leftover = len - (nBits24 * 3); + while (nBits24-- > 0) { + checkNewline(); + int bits = (b[off++] & 0xff) << 16 | + (b[off++] & 0xff) << 8 | + (b[off++] & 0xff); + out.write(base64[(bits >>> 18) & 0x3f]); + out.write(base64[(bits >>> 12) & 0x3f]); + out.write(base64[(bits >>> 6) & 0x3f]); + out.write(base64[bits & 0x3f]); + linepos += 4; + } + if (leftover == 1) { + b0 = b[off++] & 0xff; + } else if (leftover == 2) { + b0 = b[off++] & 0xff; + b1 = b[off++] & 0xff; + } + } + + @Override + public void close() throws IOException { + if (!closed) { + closed = true; + if (leftover == 1) { + checkNewline(); + out.write(base64[b0 >> 2]); + out.write(base64[(b0 << 4) & 0x3f]); + if (doPadding) { + out.write('='); + out.write('='); + } + } else if (leftover == 2) { + checkNewline(); + out.write(base64[b0 >> 2]); + out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); + out.write(base64[(b1 << 2) & 0x3f]); + if (doPadding) { + out.write('='); + } + } + leftover = 0; + out.close(); + } + } + } + + /* + * An input stream for decoding Base64 bytes + */ + private static class DecInputStream extends InputStream { + + private final InputStream is; + private final boolean isMIME; + private final int[] base64; // base64 -> byte mapping + private int bits = 0; // 24-bit buffer for decoding + private int nextin = 18; // next available "off" in "bits" for input; + // -> 18, 12, 6, 0 + private int nextout = -8; // next available "off" in "bits" for output; + // -> 8, 0, -8 (no byte for output) + private boolean eof = false; + private boolean closed = false; + + DecInputStream(InputStream is, int[] base64, boolean isMIME) { + this.is = is; + this.base64 = base64; + this.isMIME = isMIME; + } + + private byte[] sbBuf = new byte[1]; + + @Override + public int read() throws IOException { + return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (closed) + throw new IOException("Stream is closed"); + if (eof && nextout < 0) // eof and no leftover + return -1; + if (off < 0 || len < 0 || len > b.length - off) + throw new IndexOutOfBoundsException(); + int oldOff = off; + if (nextout >= 0) { // leftover output byte(s) in bits buf + do { + if (len == 0) + return off - oldOff; + b[off++] = (byte)(bits >> nextout); + len--; + nextout -= 8; + } while (nextout >= 0); + bits = 0; + } + while (len > 0) { + int v = is.read(); + if (v == -1) { + eof = true; + if (nextin != 18) { + if (nextin == 12) + throw new IOException("Base64 stream has one un-decoded dangling byte."); + // treat ending xx/xxx without padding character legal. + // same logic as v == '=' below + b[off++] = (byte)(bits >> (16)); + len--; + if (nextin == 0) { // only one padding byte + if (len == 0) { // no enough output space + bits >>= 8; // shift to lowest byte + nextout = 0; + } else { + b[off++] = (byte) (bits >> 8); + } + } + } + if (off == oldOff) + return -1; + else + return off - oldOff; + } + if (v == '=') { // padding byte(s) + // = shiftto==18 unnecessary padding + // x= shiftto==12 dangling x, invalid unit + // xx= shiftto==6 && missing last '=' + // xx=y or last is not '=' + if (nextin == 18 || nextin == 12 || + nextin == 6 && is.read() != '=') { + throw new IOException("Illegal base64 ending sequence:" + nextin); + } + b[off++] = (byte)(bits >> (16)); + len--; + if (nextin == 0) { // only one padding byte + if (len == 0) { // no enough output space + bits >>= 8; // shift to lowest byte + nextout = 0; + } else { + b[off++] = (byte) (bits >> 8); + } + } + eof = true; + break; + } + if ((v = base64[v]) == -1) { + if (isMIME) // skip if for rfc2045 + continue; + else + throw new IOException("Illegal base64 character " + + Integer.toString(v, 16)); + } + bits |= (v << nextin); + if (nextin == 0) { + nextin = 18; // clear for next + nextout = 16; + while (nextout >= 0) { + b[off++] = (byte)(bits >> nextout); + len--; + nextout -= 8; + if (len == 0 && nextout >= 0) { // don't clean "bits" + return off - oldOff; + } + } + bits = 0; + } else { + nextin -= 6; + } + } + return off - oldOff; + } + + @Override + public int available() throws IOException { + if (closed) + throw new IOException("Stream is closed"); + return is.available(); // TBD: + } + + @Override + public void close() throws IOException { + if (!closed) { + closed = true; + is.close(); + } + } + } +} + diff --git a/apksigner/src/main/java/com/android/apksig/internal/util/ChainedDataSource.java b/apksigner/src/main/java/com/android/apksig/internal/util/ChainedDataSource.java index a0baf1a..d62065c 100644 --- a/apksigner/src/main/java/com/android/apksig/internal/util/ChainedDataSource.java +++ b/apksigner/src/main/java/com/android/apksig/internal/util/ChainedDataSource.java @@ -18,6 +18,7 @@ package com.android.apksig.internal.util; import com.android.apksig.util.DataSink; import com.android.apksig.util.DataSource; + import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -31,7 +32,13 @@ public class ChainedDataSource implements DataSource { public ChainedDataSource(DataSource... sources) { mSources = sources; - mTotalSize = Arrays.stream(sources).mapToLong(src -> src.size()).sum(); + long size = 0; + if (sources != null && sources.length > 0) { + for (DataSource src : sources) { + size = size + src.size(); + } + } + mTotalSize = size; } @Override @@ -86,7 +93,7 @@ public class ChainedDataSource implements DataSource { ByteBuffer buffer = ByteBuffer.allocate(size); for (; i < mSources.length && buffer.hasRemaining(); i++) { long sizeToCopy = Math.min(mSources[i].size() - offset, buffer.remaining()); - mSources[i].copyTo(offset, Math.toIntExact(sizeToCopy), buffer); + mSources[i].copyTo(offset, (int) (sizeToCopy), buffer); offset = 0; // may not be zero for the first source, but reset after that. } buffer.rewind(); diff --git a/apksigner/src/main/java/com/android/apksig/internal/util/VerityTreeBuilder.java b/apksigner/src/main/java/com/android/apksig/internal/util/VerityTreeBuilder.java index 75497a7..68a095a 100644 --- a/apksigner/src/main/java/com/android/apksig/internal/util/VerityTreeBuilder.java +++ b/apksigner/src/main/java/com/android/apksig/internal/util/VerityTreeBuilder.java @@ -155,8 +155,7 @@ public class VerityTreeBuilder { levelOffset[0] = 0; for (int i = 0; i < levelSize.size(); i++) { // We don't support verity tree if it is larger then Integer.MAX_VALUE. - levelOffset[i + 1] = levelOffset[i] + Math.toIntExact( - levelSize.get(levelSize.size() - i - 1)); + levelOffset[i + 1] = levelOffset[i] + (levelSize.get(levelSize.size() - i - 1)).intValue(); } return levelOffset; } diff --git a/build.gradle b/build.gradle index b29656a..a83f81e 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ allprojects { apply plugin: 'maven' apply plugin: 'idea' apply plugin: 'eclipse' - version = '3.1' + version = '3.2' } defaultTasks('clean','distZip') diff --git a/xpatch/libs/ManifestEditor-1.0.0.jar b/xpatch/libs/ManifestEditor-1.0.1.jar similarity index 87% rename from xpatch/libs/ManifestEditor-1.0.0.jar rename to xpatch/libs/ManifestEditor-1.0.1.jar index cbb4c1aae44fb18317688cc3fd73870815eece17..e1664cce25a75fe34ed79b4a6fe3c44e22423bde 100644 GIT binary patch delta 7107 zcmZWtbzBr{+ukKsT2exg?k-7b1CWvu3F%(I0O=KwU8Hf5AyuT*gEUB|Gzfws(jg^M zihzRh&CY`7^?dWsJiqI@`?+W4p66>0A}J3+X{bv`L))OyoS#5^tQ067hBCnToSlD@a^h))5X? zA8NenTFw>4sQ92&(ATnRQ*zle`LkT-%$(O$#TgR3_sVoQ+v)y|(vQS4X^WE(ykQyO zg1CFRI$W9%0`3rrLBKPjXgIWxMTC<=i`tV5gjArcp@&Ov-zWkGQ>J=wnGwhjh0~P3 zr<+6Ig;<0rp~VvaAYQ*#u%8%0qQ$Y~(1JsW3i4Ghsgj46`KbCHzbIAj!7oBJi||_F z+HC}o)_2`Ycr7&pRXh@I6o{8OG^4>IbQY0#^^9xeWH>GCb4O0--tWjq2<{4Y$59Nr zkIiynQBK5IeJ2VU+;073X&&-C0fT*~g24o_G+p!{%Z4&%{;ejtBt=@|X6t1%qqDzVej9b`9Y5ZS(F;@^^9qL_4@0Nlv;Y3q^ABvjW5eW2>%D0D zA+pO1zNscCi9D`YH)1pTP7~IP7b3$8!!z$;zm_<3&EHdIB`%7t+tuyS2>8eca7@pM}GL=Gmauu$G1f8!ALMrHl-g!DQmFM=S&j17k48c0> zYpGFl!?d9n&KPKIS&*t0%xNq0ng-jnFcTY)YwcWBK=rPkdgg&t;-UUrIh?OnKi!iS z5&7lbN-PTB;bTMw_$y7rwodG5#j10J91%EQ#HlB6!Cn&+>hmaf)3FJeh`8hg&~tvKar->h$m zHS5_FQIbPhY4~-^6IB)$8_FuKjq>LW-{ubN@!P-oNLsi=(pJ#ljPFKI4}bjCfX73X zJjjpt2{l8c1*OH2)a+*ojn+)=-x=)s`NLvSj*xF4%&KvQ#)0LdP~L|@N0Zzuy^dmX zCjRdLVMo1A0cJC6{`Nk~(^E3+Lq)H)sYRDGZ9lkUQSEA9!p&#r@_8AZ;?P2_pW>vS zWDD(Brnqb>GDj{ynK+&DO-BRpo=yIm(S+R@(Yl87N7g%^hSl9zXA35L&;rzO7Z#3aB~@vxdN6@w0W<( zX+knO=E0ApWyc6BRm~uBGd`puL7?vUb=M@y;i0Li$)~f7dwI&C?aEb-)|5>h0mMyj zH!Yc`tw%*m)=;Ac1Rli}?CJ8(`Sn63Gl^=QpAm&GmKg}G(_(uljofaGe82qE>kYe4y$* zvFDqS`o3&pUlc<%I&|3Id%Ne-#B0s3j-gllLaQ=YyU*|6Dv@BJkF52fV zy`iSejW{pG^I(>^utvNrs@81luvkI@VM(lDY zMVUw1&dc!R1a3RYkY%>Vd_BczHRgf+9vDI+$75=ZbQiLpw9r~CY7~1Qc}|2>j?W?H zs?y@OK8KJi!?FS1V)mLo<5%w$VQ#;S($kY$ai_@`u>ZwX6}uvN?)H+hm)Km2f{gY^ z3To+z&KCl&_YUNvikSW@*J;Le6DYe8hxu|=?=;U(oW0|F^`(ROgXkb$+ z#-9|mHS2WHkD>I}tVj~eZV9itZ>=JurGwJgiS>+D-CrB*JN2=-)mw``L!dM=HN#EY zZ`AMFxXu)Ed2u6*b0X|Ug+0Mq*Av3C`)};#l{L;4eAIUtH54yNd&AjX9C@yAcYHHQ zrx9&)aG#wU*rckDKl5`ers?Ysso81L!!VTty8TTA)g0LwdV*hE;p*x!vGz@)_fNb= ziLuh}CRSxscn!rraHf{ivqD$Xy*_t-Vwhc>hdb&fCz`YOkq| zA9*`|bDx<9m;*;Ny-p<9i8n;Q%>kAz*q>IMxumsVYiN+bsyMatjqhNbP`;V9h{k34 zFrp86Rr!7>1&7uOzeHDk5b3IOrg^6tz^rlCE)f9Gg6Hdh0DbwIKS;QayU+fClXI>=$<|#{&!9x!F`z}sMQRx zUp$=&&h}tUknR@h~lZwd2VafBGrn!(f;)R>dIKtoVZdSk?#QiUVhbRvp|Oal>8f*k{js+lXAl{ z#1mY?nn?jV6&G%nRcC#jXi;ns{Whnk9a$9dk{2&+T z%R>ZBPT2u+(EK$Xz=R*Nc7km1gP1w$C4Oc+5g`MKs6?P3o$_YPZ~SD|688>2kfkQD z;X);GZEu!{x;lLV(jR&3oE3u@nz{sVL9 zK$%3ZRBbg4HN&I;%hjvqly2^WvQO@Nl|MO^V>+JOwhqhb?W>j6<p#+g9eD;9XtHbGaTh^n-DsRhO*s>C8U_vl?%Omd|$%BEuTe3T4w!!95bUy0y=_ z$eT&7h!5~!3Nt009c4R9`midc*Pl3Oln*jweY$0Mwxj1dUo2I}ub^e!NbcL(J=i+6Re5H6BlTBH% zJRtph3emd?x6kd|0sU1@DA$$GgZUKWYl7DcyB?2p4EU30$;u9Apd-+3@`7Q;)@iNV zu2-aQo$u}9tz792zB$^n(ir}`GQWI3R&A4nfkaCR{rn`rck%n28&bwVN5;Xa@__lJ zYgdf{g(4jyN4Jo5GSQjEp?1w=&YlQb$I~D1-Iuvf0x3McnA8h;tX?4e4 z^IK`rdRu?izEdVNRwfi^j>{afiMeoi^GN^X;YQ2iSJ_87aMtZ!iiC*2SjGmrB z4E$U$HNRn;znx6fNhps}i&yWx=~?iM0e#!0wdZ3JqVMdyhE_{vw49G_JF=c{upB*K zc#5fK;%jmivu%BcYxmn;+RpW%9lFulvFwZQL>s33$4p4vga)mOK3^#gG$8SubH3iC zrJy(sI|;lHzB8RA_QHwwtB)%O%cQY{(dzayt{+O79UAcqNknJ;P^Quk9KMm&@i-V= z7%wWe=gPxo(ito6{_cvKg#Ics)GTwBVlXtKHbcn;6#EasKo0WJI=;#9` z*SF<8W{|w&;HVfGt2Nr%>JuA0%wsReM%mc&dvLgLb|n>-<;K7ITVE=Gl#4gW0lDASLT zo@iE@7P?a z=c9|OHChJ<7U#E%z8Q;yp-A<1gt?3gre8yczo@4Bf!!LW$B(=!hp=I^!vyIfptLFA zH%Fwn#p9>$qKX!~y4+E|e4}mMfg{A#-ZjFDpeUKnhd9CT8mc*EaLyy>Zeun>1nS<93HzS@U+og#`)$K_p%AGiC7TlQVJs9VwyN>!@U zGEx`1=5%jgp=tWN;(~?1e!9?p`qz+$ITUECkMw8-vf3Ov`R=I5;1_$pe@7_a6rEq# zpQ7+c`CJppMo^5J0Y6-16yK*b3Yty7%-I6XmZRq`@BDl%I>9)TQ6u;$>VorI-<|J= zzp(FDQl?H=Z`|Q{y`x^W!MjXFt@EoQFiK7FvcZYe$`+}E2cMX-cZZf@*)5|o3noQ3 zsF{~+uaV@PL{ z)AKDElR|V z2LO98T;|`iI>0Izu7I0kfaVDJX~uaN4Cdo+FK(y{C!i<9AMfGFo?!TCr4w*GY3Jp5 z)7#DQze0wZp=mP*;4mRe41=+PhK{e?Flm4z1TGA@(GP*2CU7IdmeSDyp&@Vz2#mvl z=|tEhBS!2SA}V0un3%#5{0NA!L{I={LXR{=LqVeOBt*=dM2P$+;Qu+UgTc6t0|D>3 zVaar$Zyw-BB%Fc+cd+Mr*;ja-0tPc=fx$Tck}PDwn`fbbq}g%OTB#&eiC`TD!902Y zlKu!tqTn)+NsXOeZvZU95?H{~e+d-|;u6PB2$G%rPsBhe3d|^29CBGA&H(&G9i>Wo z7bIwvj))UjJ*5+X?%gBu8jj>vIU-50*HumcS;u4{j_g&%k-*GdI5Sl1100y64uZ^p zR2ZBNiWq*DUEwAeu`d|$-$n4zglxYdLI9^?;k1zS&@0~a;a~y!iC{3kzw+bMK8oZ7 z)=HZUmIJ2_-619v7%zmmRU3#dLthJNL+=y|3`ox zsEdbFKm~Z={mkMMAq;j19##L{pAUhzkw^WBa|ODEF0kv5E^64CdVK(t=v_3N^#3)e zAsY0%^G84)s%0GxqzOLOVgpDpa9W6A!+^|9)G!uHPmQ6?Kjr2%VB=1JjVtuW7VbG82PC47UTvOHpp}U@ zyzQq^a0V#AS{z!QfJZl@;5<-plb1zJlELU4z&(-r%ivHFB#LcLAj7UC(EzmxM{khg zJ;4SK@SUIys>%MPQkDF#N`Epv(0l(Vud(}}@7pPO-_+4?RwyFzXb`1&@<#{ojy`&5 zV{pt^_Ax_>-ONS+gV9IUrg1DR7td1Wk^>wuM~rw3$f&{(mdWn=07>(_V7#>P7?f;Bmru1 zCcv){(2X-;{x2aM4`%|V;*L5+EDmIu`i|6ifVJcIR}zlseEY{OtFHIQJ8sv5M*c{HPSl{Xdc}igia#6?q}Zkp^pFoIk-z?O|1woL_D7?Bj1gdu zJ4yz6_&w(!l6sjKumSr3$`XltD?B|W=z-D%u+yOQam3u>kp>MwmI!Bu2$4jPxVwBr zumB`re?SSgl-Bz}_c`Z%j`&x&-?MQ^wAHb&31KjNeAuNoa`7Zw1mHp2 zk}ocO4SK*i?~PwYya*<%xO4)z_dD>%!Mw)cBIOUhN4`~0mGI|?JXgJ?nQfXHkGPPN#6{Hix z&(E9)6rriw+LxAD=#d;G&}x~mTe*7U$0xDZDwH+GiAOYzGVfOOsM9~2oGo*k9SbZI zMFrRhpGkBxWgBkcLmSCa;Ra+5I)SkKuCQ9z690u{f^%$c$ z)+5It6b8YV@MnfZgy<+#z9lUC`#i38+g{=jqZS(X8ut|Mi|oG_^^%l}OZg(Vx~jg?I6&DnX## z70<%^c=zwx^S?8LA7`%|@AeeJ<|5?^KDQ|Hv4oVEA=R#9=@5?a{T8NrPH1M>POL#q zsMWioK}K)6lAvq$8lRoDnS;+kB4JZaLroB;PKFPta7|63SVN=$MXb8pF4WT71a&f8 z-LZuS7c$~{QG^*fWkhSAg7c_lvdu|Z2p4oI{0s$O5G+(s_-VDT?cne(H3kUr9VEyv zU$q`^N0Q=;wMoSi9N-pJvJYLdT#bsq=GpUxd+#-|$Ra!cE1Q`0YTiMsoWXPvX8-9B z9lgtb<9)A|4LBU@lbLTCRFlhDT1SaYtI@|;O!^`+8@(h_D}tNw$!ROC1aKAK2hmvVTH7OvBx3oB-#DkevrUn{{oG%DCLVQ zyE~NDO-h*x0hb+iyK<>nnKTD-qZ$*hKbzk!vEQ{iCckPF(JFQHna&&6?Jz(+D$~Y3 zs3fyHUnMiQC8g?)K~f)ec}OupT^N&Y9@OFP6%tyxT*88&euwk;3L}-2IrR)5<+Q$L zVjUf_I4iy;_+0e(oB49th>6)-VLwNvsx$N{ep$ApTnbwXli#f^%o`xyIMfl~Jtz+- zeUm<^>Tn)suw0+YC+Su3f~0@($_V|q+bVCX%hl@w#CLF(gY$!T3fl9R^;)#(=YpLz zRe!38ink3HF4!|P#W=k&yc`kn$%t(7Fy?-o29h@Axc<1JGdFL5=O8y^ZAYN{mm>+@ z4kCSQo4GN6vEd*?-u{p4*tV~+B?&P%_c>3}7$);JvEQ&r2EQ;?+XE3ny8{9mvBsbm zb_eu+C5yDRZC%WLuN-?d^s`1p)A}DAaGKCe>b`U2f8I@@HFd~O@=m!lU$qPDFxVM_LS3b&J$D;tjFZ?_iKo(CrA${vgl zZ%Z5B-hM)Yj5}^GGd0`)E9`>(@a_j$DUT_YI&q#sHvf$NDjm(og@!d!bYm4}8_znz z>8GVOq6h4xSD7Z*mZM(>IJ(Je2$Qv1b8*~2^5rZF>aaJyyis3;6d zOdRUQgzv4j+`Y%YG12{Z*V3`>A%&<>uG0wq`7*8Cy|(aqA^i?+tCcmrvacVb z%5>`FlFFE@8$Gs`p0;&m6x_90of*ltdOwcT4Y|!j-_znay6epW(0rb&-JT%jU#;cv z{FL4WpD55k{UrI#TCPIHHUFXh@E6Zt2DVQXp&Jd}5tM}2kP{SboDZuP&Xq)Se$s0U zEnF4Zrrz@n(yXOCdfQ7fNI8uNvuO)cIZ%54E=u5tQGZCd&WoTap89~h0T@czE|yP4 zZW7`p4_9J~FUB53;&UP5o2?!oh#skaeTHMA$TLi3%a_HOsXV$(a($-uX-B)gQ-qT6 z5)kBkzBXXUGTes1h|*9Ab(9t{xw>T-`5^M(ydn8?YkZiY(h;J%*sRm?0^QD*(X|`o z(zN8sMORFp@|E@0a#9#8yOT5Pn2^^bBV+r+S4RX#d^nf*&G(n8M7o)`k|lmI+-u3` z=A5BvdK>07{9{}>JM3;PD(`%Ka+w#oFKMAyvOootneu`m7Z3RsO$q)gcO%!1x{XwpSS5mxOTx#&3d12Bm5rwEX7ZcYbY@}{vxq{k zKd-zs5<_uCbq1?VlYIYz%gtrQSA??2K~liGr#|wRxZMv9g)N4yck)DEtu$CV>hMllO?Wx($be^q*#3mf0Hc7)H1?6F(jApi&=FaKZ z;RtrM2Q+SU9r#b|6DL@ew;AqWQ$-~m%M~r;2e{x+%Hr%}Ic4j%h$PG8y3kEwu*A#h5RlH+BB zUI)s@(as>Uy7La1E&Ze4S+tKenZuH!K9nZW1bV2P3t6eZo#j1eANe`4M)7;mXw_gV zO>9nT?DORKz6B9FPrBc{-Dr&L#6!tFR7aZe zST-`{HHwa`zKn3hn#<%$aU`K1=PtPV@_t*F_Osy7FY7m^XkC7H4#o+j`AuHD`Hep9 z2-mP+EZ=*+Z!#jOTR35e_SW|n`_J+jsxP>83iUa-w+1$N4Ud7ck!$e-D+uOW$%ARp z9Q=xd6imX#>>DYo2Fosmrp+^3>Xx#|R|@*J&-2*KSbN22LhpB3;HU^FcIwzu+XtED zvMN`~ray_C!>LhA{J83M*U4*U(9BfCEBr8anaZGM8gfV)%xHOkHA|nP`QvoiSzyJID#_#>{A}Y%8fw0L#Hk2#qQxag?|KTH@(n&= z!C-%JVK7egiKuAvKwv|gq;Bsk=wk)eV>-;fip9qSv-d(Gi=hEZC{PTNSO+4Zn2OJ# z$C%C7dh{q}57r#Zgo&PyUqnC~t-3N>9$67@1^=vrbHnZ*kPg)7_UcSKEy{}Kc{XRn0{3TdWn2A}S?^B9vt6-2N zSHQEbv7j5gUU$^Ufb>-s*J(Azo)%$LHu(`OOguJtVvUb2L?2imRF9!a(z^*$tP zOnmG9HhL)36P#*>8e` zRTPoFOvN^oCMsZ2uJcUvd8BJeSJH!lf!?JkcMb7ULC?pLT*d0Mv-JeFskWUd#W^2_xcUiDxwN^D3wIEEFR6=cA1`KNM2Se2yt3JxNI`! zLO4%8c}I@fA_mC;jFI0mLP_aJ41DkysJ=SkX6gNYn`SFux`AYVtMzti$b@@i%(r!k z@Yv6EEh1kWLVQc!Zx>Wh#8$lzKWvF@*Lkhc=W1bNA=MPiiXUXbclY>r=s|o4>&7E2 zU4}U-m6{8g*S_M~vPDvJH0{U_+w>E?r54Qjk}57G?HRz$wqM3_=F|Z_;ERJ zW(;-oteF3hA#`_o-rezWr@{PznH~1F8})!Gw#c7X&GDWCdbV>5_FOlMGAeD7Hs)!D z%rz$MF8IA#b}=5>v2UCTofn>aH?|}F+oYA%{Wm#sL?koe(_oJqvc(9&VZ=kWb>1Lb z&%@q3%DKNT0%Pxv{HI<0B zUXr?!X)*zP7W-1}7tQ)z!zUkQo{xN}n|V)YZIGa`cEt#}@B6saH+{a-$4+8hlXtAJ zynp+X_jbaM`6`_9HG0;fFTHj(>R+u_ z*rnz#eV)uxBT%FBy=Bj#60mwW=gERPyw0UO5C1V_T4S9gj7f;kaa(_3qe? zSgyNFIIn*z(j!g7zPrgh$L+Yi=)IWGZd(`M@V$aTu|{<_w`$*4Iy472vvms)0nBgMZ7moKWNm;|vJ}i<^a&!-- zoeLTz3e1jsoV?w@u@!rBdvFb}bGp`(khJU%kIgwrsYzrfilbAHeVj~fPN8#?LCPtM zKebWQbtWiT#*=c9u_-6?YLYmA(WM=qMVbFU3w5l*2RPPd;F z_s08E!7G9+5Aoj!D%Hzsz)E=Q^~UCMk`LbE9b%l>Iq}VgCPDMO!5$J#p0A@G^Bz5w zqzX1q;@&A@KS?n~-N4`HaXP?Dc~2m7^9JMQTAlrecA?>xyg#J_g2xT2X*{ zxWLSkJ8cN3Va6josnGgy=X{n*7S@Tt%55se?{TqGln?Un_m94@F1S1PnZVL_j##$i zR-W813D?TI`8ArP?{f{3FVHNpUzl)4ipJeq?U%G3sU=k+y^F=>%YH|o zz+7&9O$_N4W8R?raPa}YQ2Fk!SxNJn zTcg0bz2~kuoTpmU70N*U<2H9VnW6huR*9eA5$Wa63v|G?Ab6Nf3GCn)|B^eiod7?eh(gcU(xt1I zC+fG=G|Uq<`t!+`AG0q77=&cABn~YG;)CFfz(*fA{eR!XfFmEcBt)8mB<&{{4CegE zTu56Tj-bN&_X!QK^@TIa;DdVyGNs%DL*Se^&xodgAtYRk4V~`V&t4}0obVdhEXFgUVqR!^ zsA*n8(*NzJ_EWHOUm?t#N`nKiQ$)7C0)U!9^gdI zw!}pdV*s8JT%alnP66phYB;Y4fQ9yhZxUQ*3O%oSS`Hq{NR<$H5^z#ZL;#rhvpSlk z1nL9e%uqQ~XjJqj1|pIH04+JonBsmPme5S@2KsX3~5y}KD{O6W1n1z$$|E_81l7Tk^ z#o+3N#L&WBw?vhBPyz=*C+7_940Zzg(VQG%Ff!Pd0)rK(qyuDvPn7?C>x990|0&Un zQyZgv8VqNJ+I!|j?Tm8D0qu#(cRB~G1jDJIvVI4Hv)6DsA*g^}u;ow(&b(%vv2g^P zLr&H(8b7TcydX|~==u}A(KAKK`w##NAt#;w76RrPI3<_?nNT<*RD>ZoK&(GGiQoW= zLr)?a(S#cE)CyIAqy+H8{-5U2p5}&{_bUJs@e&u%1l3Ru<1o-lB=D3##RO7Z{GW?U z+oZ5UzYbiV2@=VEf; zSvZ^?Iz6)*0=E;vo>_pa|I(Qp1@R{~WWdjGI5VXF@68LmJDyR;B%Y|DcQGa?sdogJ zR3rIcHKh@73^9Nv)?b_u%m8ELNmuwHL83hKgdhcMBTuduKQu9!{m-g05>5-{7(f&B z#Sr0K%mOe)omlZlfy7V=ngEQV;AD{cAsWOfg}{s`%s(iA9yC}`3xU6);1?hzdo&3C z?1Vt{3mjk&eR7^Bk`ksJz!%d3 z)KDtO^~{Uu8P}kx|3a3h=>7{qt4^<{v-gAkML;1Qd=UPLJ;_ZR2j<3FLZcS|%{Vw6 zloQ9Ia!nnag*51LcD*RBVx+I*;H*$5deH`pYY>WR{!9Sf{2-H-iv1V&K@&&NMDR@E ymFsA^3>5z~9*n=gfd)8$=6JX;c-V=Di^0_Zt^~L|qMQVHkO1evCG!ETVE+f9k*k;h diff --git a/xpatch/src/main/java/com/storm/wind/xpatch/task/BuildAndSignApkTask.java b/xpatch/src/main/java/com/storm/wind/xpatch/task/BuildAndSignApkTask.java index 82880af..e4f93c7 100644 --- a/xpatch/src/main/java/com/storm/wind/xpatch/task/BuildAndSignApkTask.java +++ b/xpatch/src/main/java/com/storm/wind/xpatch/task/BuildAndSignApkTask.java @@ -128,6 +128,12 @@ public class BuildAndSignApkTask implements Runnable { commandList.add("pass:" + keyStorePassword); commandList.add("--out"); commandList.add(signedApkPath); + commandList.add("--v1-signing-enabled"); + commandList.add("true"); + commandList.add("--v2-signing-enabled"); // v2签名不兼容android 6 + commandList.add("false"); + commandList.add("--v3-signing-enabled"); // v3签名不兼容android 6 + commandList.add("false"); commandList.add(apkPath); int size = commandList.size(); diff --git a/xpatch/src/main/java/com/storm/wind/xpatch/util/FileUtils.java b/xpatch/src/main/java/com/storm/wind/xpatch/util/FileUtils.java index e6e61c6..bd24526 100644 --- a/xpatch/src/main/java/com/storm/wind/xpatch/util/FileUtils.java +++ b/xpatch/src/main/java/com/storm/wind/xpatch/util/FileUtils.java @@ -49,7 +49,13 @@ public class FileUtils { ZipFile zip = null; try { - zip = new ZipFile(zipFile, Charset.forName("gbk"));//防止中文目录,乱码 + try { + // api level 24 才有此方法 + zip = new ZipFile(zipFile, Charset.forName("gbk"));//防止中文目录,乱码 + } catch (NoSuchMethodError e) { + // api < 24 + zip = new ZipFile(zipFile); + } for (Enumeration entries = zip.entries(); entries.hasMoreElements(); ) { ZipEntry entry = (ZipEntry) entries.nextElement(); String zipEntryName = entry.getName();