From d8bd1ef533eee1e94b3dddce1f9f8952abcf9023 Mon Sep 17 00:00:00 2001 From: Assam Boudjelthia Date: Tue, 29 Sep 2020 12:02:16 +0300 Subject: Android: rename Android's package name for Qt 6 Rename Android package name org.qtproject.qt5.android to org.qtproject.qt.android to avoid inconsistency with Qt 6 name. Also, we include the major version number in the jar target. Task-number: QTBUG-86969 Change-Id: Ibeafcd164cc48100710fc134cb9c2f953ec33426 Reviewed-by: Alex Blasche --- src/android/android.pro | 6 +- .../qtproject/qt/android/purchasing/Base64.java | 571 +++++++++++++++++++++ .../android/purchasing/Base64DecoderException.java | 33 ++ .../qt/android/purchasing/QtInAppPurchase.java | 472 +++++++++++++++++ .../qtproject/qt/android/purchasing/Security.java | 131 +++++ .../qt/android/purchasing/qt_attribution.json | 29 ++ .../qtproject/qt5/android/purchasing/Base64.java | 571 --------------------- .../android/purchasing/Base64DecoderException.java | 33 -- .../qt5/android/purchasing/QtInAppPurchase.java | 472 ----------------- .../qtproject/qt5/android/purchasing/Security.java | 131 ----- .../qt5/android/purchasing/qt_attribution.json | 29 -- .../android/qandroidinapppurchasebackend.cpp | 2 +- .../inapppurchase/android/qandroidjni.cpp | 2 +- src/purchasing/purchasing.pro | 2 +- 14 files changed, 1242 insertions(+), 1242 deletions(-) create mode 100644 src/android/src/org/qtproject/qt/android/purchasing/Base64.java create mode 100644 src/android/src/org/qtproject/qt/android/purchasing/Base64DecoderException.java create mode 100644 src/android/src/org/qtproject/qt/android/purchasing/QtInAppPurchase.java create mode 100644 src/android/src/org/qtproject/qt/android/purchasing/Security.java create mode 100644 src/android/src/org/qtproject/qt/android/purchasing/qt_attribution.json delete mode 100644 src/android/src/org/qtproject/qt5/android/purchasing/Base64.java delete mode 100644 src/android/src/org/qtproject/qt5/android/purchasing/Base64DecoderException.java delete mode 100644 src/android/src/org/qtproject/qt5/android/purchasing/QtInAppPurchase.java delete mode 100644 src/android/src/org/qtproject/qt5/android/purchasing/Security.java delete mode 100644 src/android/src/org/qtproject/qt5/android/purchasing/qt_attribution.json diff --git a/src/android/android.pro b/src/android/android.pro index a21ac2d..50f1d6c 100644 --- a/src/android/android.pro +++ b/src/android/android.pro @@ -1,11 +1,11 @@ -TARGET = QtPurchasing +TARGET = Qt$${QT_MAJOR_VERSION}AndroidPurchasing CONFIG += java load(sdk) DESTDIR = $$[QT_INSTALL_PREFIX/get]/jar -PATHPREFIX = $$PWD/src/org/qtproject/qt5/android/purchasing/ +PATHPREFIX = $$PWD/src/org/qtproject/qt/android/purchasing/ !build_pass { isEmpty(SDK_ROOT): SDK_ROOT = $$(ANDROID_SDK_ROOT) @@ -56,5 +56,5 @@ INSTALLS += target OTHER_FILES += \ $$JAVASOURCES \ $$PWD/src/com/android/vending/billing/qt_attribution.json \ - $$PWD/src/org/qtproject/qt5/android/purchasing/qt_attribution.json \ + $$PWD/src/org/qtproject/qt/android/purchasing/qt_attribution.json \ $$PWD/src/LICENSE-APACHE-2.0.txt diff --git a/src/android/src/org/qtproject/qt/android/purchasing/Base64.java b/src/android/src/org/qtproject/qt/android/purchasing/Base64.java new file mode 100644 index 0000000..f4c9379 --- /dev/null +++ b/src/android/src/org/qtproject/qt/android/purchasing/Base64.java @@ -0,0 +1,571 @@ +// Portions copyright 2002, Google, Inc. +// Portions copyright (c) 2015 The Qt Company 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 org.qtproject.qt.android.purchasing; + +// This code was converted from code at http://iharder.sourceforge.net/base64/ +// Lots of extraneous features were removed. +/* The original code said: + *

+ * I am placing this code in the Public Domain. Do with it as you will. + * This software comes with no guarantees or warranties but with + * plenty of well-wishing instead! + * Please visit + * http://iharder.net/xmlizable + * periodically to check for updates or to contribute improvements. + *

+ * + * @author Robert Harder + * @author rharder@usa.net + * @version 1.3 + */ + +/** + * Base64 converter class. This code is not a complete MIME encoder; + * it simply converts binary data to base64 data and back. + * + *

Note {@link CharBase64} is a GWT-compatible implementation of this + * class. + */ +public class Base64 { + /** Specify encoding (value is {@code true}). */ + public final static boolean ENCODE = true; + + /** Specify decoding (value is {@code false}). */ + public final static boolean DECODE = false; + + /** The equals sign (=) as a byte. */ + private final static byte EQUALS_SIGN = (byte) '='; + + /** The new line character (\n) as a byte. */ + private final static byte NEW_LINE = (byte) '\n'; + + /** + * The 64 valid Base64 values. + */ + private final static byte[] ALPHABET = + {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', + (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', + (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', + (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', + (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', + (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', + (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', + (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', + (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', + (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', + (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', + (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', + (byte) '9', (byte) '+', (byte) '/'}; + + /** + * The 64 valid web safe Base64 values. + */ + private final static byte[] WEBSAFE_ALPHABET = + {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', + (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', + (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', + (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', + (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', + (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', + (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', + (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', + (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', + (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', + (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', + (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', + (byte) '9', (byte) '-', (byte) '_'}; + + /** + * Translates a Base64 value to either its 6-bit reconstruction value + * or a negative number indicating some other meaning. + **/ + private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 + 62, // Plus sign at decimal 43 + -9, -9, -9, // Decimal 44 - 46 + 63, // Slash at decimal 47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' + -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' + -9, -9, -9, -9, -9 // Decimal 123 - 127 + /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + + /** The web safe decodabet */ + private final static byte[] WEBSAFE_DECODABET = + {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44 + 62, // Dash '-' sign at decimal 45 + -9, -9, // Decimal 46-47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' + -9, -9, -9, -9, // Decimal 91-94 + 63, // Underscore '_' at decimal 95 + -9, // Decimal 96 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' + -9, -9, -9, -9, -9 // Decimal 123 - 127 + /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + + // Indicates white space in encoding + private final static byte WHITE_SPACE_ENC = -5; + // Indicates equals sign in encoding + private final static byte EQUALS_SIGN_ENC = -1; + + /** Defeats instantiation. */ + private Base64() { + } + + /* ******** E N C O D I N G M E T H O D S ******** */ + + /** + * Encodes up to three bytes of the array source + * and writes the resulting four Base64 bytes to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accommodate srcOffset + 3 for + * the source array or destOffset + 4 for + * the destination array. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param numSigBytes the number of significant bytes in your array + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @param alphabet is the encoding alphabet + * @return the destination array + * @since 1.3 + */ + private static byte[] encode3to4(byte[] source, int srcOffset, + int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) { + // 1 2 3 + // 01234567890123456789012345678901 Bit position + // --------000000001111111122222222 Array position from threeBytes + // --------| || || || | Six bit groups to index alphabet + // >>18 >>12 >> 6 >> 0 Right shift necessary + // 0x3f 0x3f 0x3f Additional AND + + // Create buffer with zero-padding if there are only one or two + // significant bytes passed in the array. + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte to an int. + int inBuff = + (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) + | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) + | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); + + switch (numSigBytes) { + case 3: + destination[destOffset] = alphabet[(inBuff >>> 18)]; + destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = alphabet[(inBuff) & 0x3f]; + return destination; + case 2: + destination[destOffset] = alphabet[(inBuff >>> 18)]; + destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + case 1: + destination[destOffset] = alphabet[(inBuff >>> 18)]; + destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = EQUALS_SIGN; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + default: + return destination; + } // end switch + } // end encode3to4 + + /** + * Encodes a byte array into Base64 notation. + * Equivalent to calling + * {@code encodeBytes(source, 0, source.length)} + * + * @param source The data to convert + * @since 1.4 + */ + public static String encode(byte[] source) { + return encode(source, 0, source.length, ALPHABET, true); + } + + /** + * Encodes a byte array into web safe Base64 notation. + * + * @param source The data to convert + * @param doPadding is {@code true} to pad result with '=' chars + * if it does not fall on 3 byte boundaries + */ + public static String encodeWebSafe(byte[] source, boolean doPadding) { + return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding); + } + + /** + * Encodes a byte array into Base64 notation. + * + * @param source the data to convert + * @param off offset in array where conversion should begin + * @param len length of data to convert + * @param alphabet the encoding alphabet + * @param doPadding is {@code true} to pad result with '=' chars + * if it does not fall on 3 byte boundaries + * @since 1.4 + */ + public static String encode(byte[] source, int off, int len, byte[] alphabet, + boolean doPadding) { + byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE); + int outLen = outBuff.length; + + // If doPadding is false, set length to truncate '=' + // padding characters + while (doPadding == false && outLen > 0) { + if (outBuff[outLen - 1] != '=') { + break; + } + outLen -= 1; + } + + return new String(outBuff, 0, outLen); + } + + /** + * Encodes a byte array into Base64 notation. + * + * @param source the data to convert + * @param off offset in array where conversion should begin + * @param len length of data to convert + * @param alphabet is the encoding alphabet + * @param maxLineLength maximum length of one line. + * @return the BASE64-encoded byte array + */ + public static byte[] encode(byte[] source, int off, int len, byte[] alphabet, + int maxLineLength) { + int lenDiv3 = (len + 2) / 3; // ceil(len / 3) + int len43 = lenDiv3 * 4; + byte[] outBuff = new byte[len43 // Main 4:3 + + (len43 / maxLineLength)]; // New lines + + int d = 0; + int e = 0; + int len2 = len - 2; + int lineLength = 0; + for (; d < len2; d += 3, e += 4) { + + // The following block of code is the same as + // encode3to4( source, d + off, 3, outBuff, e, alphabet ); + // but inlined for faster encoding (~20% improvement) + int inBuff = + ((source[d + off] << 24) >>> 8) + | ((source[d + 1 + off] << 24) >>> 16) + | ((source[d + 2 + off] << 24) >>> 24); + outBuff[e] = alphabet[(inBuff >>> 18)]; + outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f]; + outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f]; + outBuff[e + 3] = alphabet[(inBuff) & 0x3f]; + + lineLength += 4; + if (lineLength == maxLineLength) { + outBuff[e + 4] = NEW_LINE; + e++; + lineLength = 0; + } // end if: end of line + } // end for: each piece of array + + if (d < len) { + encode3to4(source, d + off, len - d, outBuff, e, alphabet); + + lineLength += 4; + if (lineLength == maxLineLength) { + // Add a last newline + outBuff[e + 4] = NEW_LINE; + e++; + } + e += 4; + } + + assert (e == outBuff.length); + return outBuff; + } + + + /* ******** D E C O D I N G M E T H O D S ******** */ + + + /** + * Decodes four bytes from array source + * and writes the resulting bytes (up to three of them) + * to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accommodate srcOffset + 4 for + * the source array or destOffset + 3 for + * the destination array. + * This method returns the actual number of bytes that + * were converted from the Base64 encoding. + * + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @param decodabet the decodabet for decoding Base64 content + * @return the number of decoded bytes converted + * @since 1.3 + */ + private static int decode4to3(byte[] source, int srcOffset, + byte[] destination, int destOffset, byte[] decodabet) { + // Example: Dk== + if (source[srcOffset + 2] == EQUALS_SIGN) { + int outBuff = + ((decodabet[source[srcOffset]] << 24) >>> 6) + | ((decodabet[source[srcOffset + 1]] << 24) >>> 12); + + destination[destOffset] = (byte) (outBuff >>> 16); + return 1; + } else if (source[srcOffset + 3] == EQUALS_SIGN) { + // Example: DkL= + int outBuff = + ((decodabet[source[srcOffset]] << 24) >>> 6) + | ((decodabet[source[srcOffset + 1]] << 24) >>> 12) + | ((decodabet[source[srcOffset + 2]] << 24) >>> 18); + + destination[destOffset] = (byte) (outBuff >>> 16); + destination[destOffset + 1] = (byte) (outBuff >>> 8); + return 2; + } else { + // Example: DkLE + int outBuff = + ((decodabet[source[srcOffset]] << 24) >>> 6) + | ((decodabet[source[srcOffset + 1]] << 24) >>> 12) + | ((decodabet[source[srcOffset + 2]] << 24) >>> 18) + | ((decodabet[source[srcOffset + 3]] << 24) >>> 24); + + destination[destOffset] = (byte) (outBuff >> 16); + destination[destOffset + 1] = (byte) (outBuff >> 8); + destination[destOffset + 2] = (byte) (outBuff); + return 3; + } + } // end decodeToBytes + + + /** + * Decodes data from Base64 notation. + * + * @param s the string to decode (decoded in default encoding) + * @return the decoded data + * @since 1.4 + */ + public static byte[] decode(String s) throws Base64DecoderException { + byte[] bytes = s.getBytes(); + return decode(bytes, 0, bytes.length); + } + + /** + * Decodes data from web safe Base64 notation. + * Web safe encoding uses '-' instead of '+', '_' instead of '/' + * + * @param s the string to decode (decoded in default encoding) + * @return the decoded data + */ + public static byte[] decodeWebSafe(String s) throws Base64DecoderException { + byte[] bytes = s.getBytes(); + return decodeWebSafe(bytes, 0, bytes.length); + } + + /** + * Decodes Base64 content in byte array format and returns + * the decoded byte array. + * + * @param source The Base64 encoded data + * @return decoded data + * @since 1.3 + * @throws Base64DecoderException + */ + public static byte[] decode(byte[] source) throws Base64DecoderException { + return decode(source, 0, source.length); + } + + /** + * Decodes web safe Base64 content in byte array format and returns + * the decoded data. + * Web safe encoding uses '-' instead of '+', '_' instead of '/' + * + * @param source the string to decode (decoded in default encoding) + * @return the decoded data + */ + public static byte[] decodeWebSafe(byte[] source) + throws Base64DecoderException { + return decodeWebSafe(source, 0, source.length); + } + + /** + * Decodes Base64 content in byte array format and returns + * the decoded byte array. + * + * @param source the Base64 encoded data + * @param off the offset of where to begin decoding + * @param len the length of characters to decode + * @return decoded data + * @since 1.3 + * @throws Base64DecoderException + */ + public static byte[] decode(byte[] source, int off, int len) + throws Base64DecoderException { + return decode(source, off, len, DECODABET); + } + + /** + * Decodes web safe Base64 content in byte array format and returns + * the decoded byte array. + * Web safe encoding uses '-' instead of '+', '_' instead of '/' + * + * @param source the Base64 encoded data + * @param off the offset of where to begin decoding + * @param len the length of characters to decode + * @return decoded data + */ + public static byte[] decodeWebSafe(byte[] source, int off, int len) + throws Base64DecoderException { + return decode(source, off, len, WEBSAFE_DECODABET); + } + + /** + * Decodes Base64 content using the supplied decodabet and returns + * the decoded byte array. + * + * @param source the Base64 encoded data + * @param off the offset of where to begin decoding + * @param len the length of characters to decode + * @param decodabet the decodabet for decoding Base64 content + * @return decoded data + */ + public static byte[] decode(byte[] source, int off, int len, byte[] decodabet) + throws Base64DecoderException { + int len34 = len * 3 / 4; + byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output + int outBuffPosn = 0; + + byte[] b4 = new byte[4]; + int b4Posn = 0; + int i = 0; + byte sbiCrop = 0; + byte sbiDecode = 0; + for (i = 0; i < len; i++) { + sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits + sbiDecode = decodabet[sbiCrop]; + + if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better + if (sbiDecode >= EQUALS_SIGN_ENC) { + // An equals sign (for padding) must not occur at position 0 or 1 + // and must be the last byte[s] in the encoded value + if (sbiCrop == EQUALS_SIGN) { + int bytesLeft = len - i; + byte lastByte = (byte) (source[len - 1 + off] & 0x7f); + if (b4Posn == 0 || b4Posn == 1) { + throw new Base64DecoderException( + "invalid padding byte '=' at byte offset " + i); + } else if ((b4Posn == 3 && bytesLeft > 2) + || (b4Posn == 4 && bytesLeft > 1)) { + throw new Base64DecoderException( + "padding byte '=' falsely signals end of encoded value " + + "at offset " + i); + } else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) { + throw new Base64DecoderException( + "encoded value has invalid trailing byte"); + } + break; + } + + b4[b4Posn++] = sbiCrop; + if (b4Posn == 4) { + outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); + b4Posn = 0; + } + } + } else { + throw new Base64DecoderException("Bad Base64 input character at " + i + + ": " + source[i + off] + "(decimal)"); + } + } + + // Because web safe encoding allows non padding base64 encodes, we + // need to pad the rest of the b4 buffer with equal signs when + // b4Posn != 0. There can be at most 2 equal signs at the end of + // four characters, so the b4 buffer must have two or three + // characters. This also catches the case where the input is + // padded with EQUALS_SIGN + if (b4Posn != 0) { + if (b4Posn == 1) { + throw new Base64DecoderException("single trailing character at offset " + + (len - 1)); + } + b4[b4Posn++] = EQUALS_SIGN; + outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); + } + + byte[] out = new byte[outBuffPosn]; + System.arraycopy(outBuff, 0, out, 0, outBuffPosn); + return out; + } +} diff --git a/src/android/src/org/qtproject/qt/android/purchasing/Base64DecoderException.java b/src/android/src/org/qtproject/qt/android/purchasing/Base64DecoderException.java new file mode 100644 index 0000000..f919c9f --- /dev/null +++ b/src/android/src/org/qtproject/qt/android/purchasing/Base64DecoderException.java @@ -0,0 +1,33 @@ +// Copyright 2002, Google, Inc. +// Copyright (c) 2015 The Qt Company 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 org.qtproject.qt.android.purchasing; + +/** + * Exception thrown when encountering an invalid Base64 input character. + * + * @author nelson + */ +public class Base64DecoderException extends Exception { + public Base64DecoderException() { + super(); + } + + public Base64DecoderException(String s) { + super(s); + } + + private static final long serialVersionUID = 1L; +} diff --git a/src/android/src/org/qtproject/qt/android/purchasing/QtInAppPurchase.java b/src/android/src/org/qtproject/qt/android/purchasing/QtInAppPurchase.java new file mode 100644 index 0000000..d642e92 --- /dev/null +++ b/src/android/src/org/qtproject/qt/android/purchasing/QtInAppPurchase.java @@ -0,0 +1,472 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Purchasing module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3-COMM$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +package org.qtproject.qt.android.purchasing; + +import java.util.ArrayList; +import java.util.HashSet; +import android.app.PendingIntent; +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentSender; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.os.RemoteException; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.util.Log; +import org.json.JSONObject; +import org.json.JSONException; + + +import com.android.vending.billing.IInAppBillingService; + +public class QtInAppPurchase +{ + private Context m_context = null; + private IInAppBillingService m_service = null; + private String m_publicKey = null; + private long m_nativePointer; + + public static final int RESULT_OK = 0; + public static final int RESULT_USER_CANCELED = 1; + public static final int RESULT_BILLING_UNAVAILABLE = 3; + public static final int RESULT_ITEM_UNAVAILABLE = 4; + public static final int RESULT_DEVELOPER_ERROR = 5; + public static final int RESULT_ERROR = 6; + public static final int RESULT_ITEM_ALREADY_OWNED = 7; + public static final int RESULT_ITEM_NOT_OWNED = 8; + public static final int RESULT_QTPURCHASING_ERROR = 9; // No match with any already defined response codes + public static final String TAG = "QtInAppPurchase"; + public static final String TYPE_INAPP = "inapp"; + public static final int IAP_VERSION = 3; + + // Should be in sync with QInAppTransaction::FailureReason + public static final int FAILUREREASON_NOFAILURE = 0; + public static final int FAILUREREASON_USERCANCELED = 1; + public static final int FAILUREREASON_ERROR = 2; + + private ServiceConnection m_serviceConnection = new ServiceConnection() + { + @Override + public void onServiceConnected(ComponentName name, IBinder service) + { + m_service = IInAppBillingService.Stub.asInterface(service); + try { + int response = m_service.isBillingSupported(3, m_context.getPackageName(), TYPE_INAPP); + if (response != RESULT_OK) { + Log.e(TAG, "In-app billing not supported"); + return; + } + } catch (RemoteException e) { + e.printStackTrace(); + } + + // Asynchronously populate list of purchased products + final Handler handler = new Handler(); + Thread thread = new Thread(new Runnable() + { + public void run() + { + queryPurchasedProducts(); + handler.post(new Runnable() + { + public void run() { purchasedProductsQueried(m_nativePointer); } + }); + } + }); + thread.start(); + } + + @Override + public void onServiceDisconnected(ComponentName name) + { + m_service = null; + } + }; + + public QtInAppPurchase(Context context, long nativePointer) + { + m_context = context; + m_nativePointer = nativePointer; + } + + public void initializeConnection() + { + + Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND"); + serviceIntent.setPackage("com.android.vending"); + try { + if (!m_context.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) { + m_context.bindService(serviceIntent, m_serviceConnection, Context.BIND_AUTO_CREATE); + } else { + Log.e(TAG, "No in-app billing service available."); + purchasedProductsQueried(m_nativePointer); + } + } catch (Exception e) { + Log.e(TAG, "Could not query InAppBillingService intent."); + purchasedProductsQueried(m_nativePointer); + } + } + + private int bundleResponseCode(Bundle bundle) + { + Object o = bundle.get("RESPONSE_CODE"); + if (o == null) { + // Works around known issue where the response code is not bundled. + return RESULT_OK; + } else if (o instanceof Integer) { + return ((Integer)o).intValue(); + } else if (o instanceof Long) { + return (int)((Long)o).longValue(); + } + + Log.e(TAG, "Unexpected result for response code: " + o); + return RESULT_QTPURCHASING_ERROR; + } + + private void queryPurchasedProducts() + { + if (m_service == null) { + Log.e(TAG, "queryPurchasedProducts: Service not initialized"); + return; + } + + String continuationToken = null; + try { + do { + Bundle ownedItems = m_service.getPurchases(IAP_VERSION, + m_context.getPackageName(), + TYPE_INAPP, + continuationToken); + int responseCode = bundleResponseCode(ownedItems); + if (responseCode != RESULT_OK) { + Log.e(TAG, "queryPurchasedProducts: Failed to query purchases products"); + return; + } + + ArrayList dataList = ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST"); + if (dataList == null) { + Log.e(TAG, "queryPurchasedProducts: No data list in bundle"); + return; + } + + ArrayList signatureList = ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE_LIST"); + if (signatureList == null) { + Log.e(TAG, "queryPurchasedProducts: No signature list in bundle"); + return; + } + + if (dataList.size() != signatureList.size()) { + Log.e(TAG, "queryPurchasedProducts: Mismatching sizes of lists in bundle"); + return; + } + + for (int i=0; i 0); + } catch (RemoteException e) { + e.printStackTrace(); + } + + } + + public void queryDetails(final String[] productIds) + { + if (m_service == null) { + Log.e(TAG, "queryDetails: Service not initialized"); + for (String productId : productIds) + queryFailed(m_nativePointer, productId); + return; + } + + // Asynchronously query details about product + Thread thread = new Thread(new Runnable() + { + public void run() + { + synchronized(m_service) { + HashSet failedProducts = new HashSet(); + + int index = 0; + while (index < productIds.length) { + ArrayList productIdList = new ArrayList(); + for (int i = index; i < Math.min(index + 20, productIds.length); ++i) { + productIdList.add(productIds[i]); + failedProducts.add(productIds[i]); // Assume guilt until innocence is proven + } + index += productIdList.size(); + + try { + Bundle productIdBundle = new Bundle(); + productIdBundle.putStringArrayList("ITEM_ID_LIST", productIdList); + + Bundle bundle = m_service.getSkuDetails(IAP_VERSION, + m_context.getPackageName(), + "inapp", + productIdBundle); + + int responseCode = bundleResponseCode(bundle); + if (responseCode != RESULT_OK) { + Log.e(TAG, "queryDetails: Couldn't retrieve sku details."); + continue; + } + + ArrayList detailsList = bundle.getStringArrayList("DETAILS_LIST"); + if (detailsList == null) { + Log.e(TAG, "queryDetails: No details list in response."); + continue; + } + + for (String details : detailsList) { + try { + JSONObject jo = new JSONObject(details); + String queriedProductId = jo.getString("productId"); + String queriedPrice = jo.getString("price"); + String queriedTitle = jo.getString("title"); + String queriedDescription = jo.getString("description"); + if (queriedProductId == null || queriedPrice == null || queriedTitle == null || queriedDescription == null) { + Log.e(TAG, "Data missing from product details."); + } else { + failedProducts.remove(queriedProductId); + registerProduct(m_nativePointer, + queriedProductId, + queriedPrice, + queriedTitle, + queriedDescription); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + for (String failedProduct : failedProducts) + queryFailed(m_nativePointer, failedProduct); + } + } + }); + thread.start(); + } + + public void handleActivityResult(int requestCode, int resultCode, Intent data, String expectedIdentifier) + { + if (data == null) { + purchaseFailed(requestCode, FAILUREREASON_ERROR, "Data missing from result"); + return; + } + + int responseCode = data.getIntExtra("RESPONSE_CODE", -1); + String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA"); + String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE"); + String purchaseToken = ""; + String orderId = ""; + long timestamp = 0; + + if (responseCode == RESULT_USER_CANCELED) { + purchaseFailed(requestCode, FAILUREREASON_USERCANCELED, ""); + return; + } else if (responseCode != RESULT_OK) { + String errorString; + switch (responseCode) { + case RESULT_BILLING_UNAVAILABLE: errorString = "Billing unavailable"; break; + case RESULT_ITEM_UNAVAILABLE: errorString = "Item unavailable"; break; + case RESULT_DEVELOPER_ERROR: errorString = "Developer error"; break; + case RESULT_ERROR: errorString = "Fatal error occurred"; break; + case RESULT_ITEM_ALREADY_OWNED: errorString = "Item already owned"; break; + default: errorString = "Unknown billing error " + responseCode; break; + }; + + purchaseFailed(requestCode, FAILUREREASON_ERROR, errorString); + return; + } + + try { + if (m_publicKey != null && !Security.verifyPurchase(m_publicKey, purchaseData, dataSignature)) { + purchaseFailed(requestCode, FAILUREREASON_ERROR, "Signature could not be verified"); + return; + } + + JSONObject jo = new JSONObject(purchaseData); + String sku = jo.getString("productId"); + if (!sku.equals(expectedIdentifier)) { + purchaseFailed(requestCode, FAILUREREASON_ERROR, "Unexpected identifier in result"); + return; + } + + int purchaseState = jo.getInt("purchaseState"); + if (purchaseState != 0) { + purchaseFailed(requestCode, FAILUREREASON_ERROR, "Unexpected purchase state in result"); + return; + } + + purchaseToken = jo.getString("purchaseToken"); + if (jo.has("orderId")) + orderId = jo.getString("orderId"); + if (jo.has("purchaseTime")) + timestamp = jo.getLong("purchaseTime"); + + } catch (Exception e) { + e.printStackTrace(); + purchaseFailed(requestCode, FAILUREREASON_ERROR, e.getMessage()); + } + + purchaseSucceeded(requestCode, dataSignature, purchaseData, purchaseToken, orderId, timestamp); + } + + public void setPublicKey(String publicKey) + { + m_publicKey = publicKey; + } + + public IntentSender createBuyIntentSender(String identifier, int requestCode) + { + if (m_service == null) { + Log.e(TAG, "Unable to create buy intent. No IAP service connection."); + return null; + } + + try { + Bundle purchaseBundle = m_service.getBuyIntent(3, + m_context.getPackageName(), + identifier, + TYPE_INAPP, + identifier); + int response = bundleResponseCode(purchaseBundle); + + if (response != RESULT_OK) { + Log.e(TAG, "Unable to create buy intent. Response code: " + response); + String errorString; + switch (response) { + case RESULT_BILLING_UNAVAILABLE: errorString = "Billing unavailable"; break; + case RESULT_ITEM_UNAVAILABLE: errorString = "Item unavailable"; break; + case RESULT_DEVELOPER_ERROR: errorString = "Developer error"; break; + case RESULT_ERROR: errorString = "Fatal error occurred"; break; + case RESULT_ITEM_ALREADY_OWNED: errorString = "Item already owned"; break; + default: errorString = "Unknown billing error " + response; break; + }; + + purchaseFailed(requestCode, FAILUREREASON_ERROR, errorString); + return null; + } + + PendingIntent pendingIntent = purchaseBundle.getParcelable("BUY_INTENT"); + return pendingIntent.getIntentSender(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public void consumePurchase(String purchaseToken) + { + if (m_service == null) { + Log.e(TAG, "consumePurchase: Unable to consume purchase. No IAP service connection."); + return; + } + + try { + int response = m_service.consumePurchase(3, m_context.getPackageName(), purchaseToken); + if (response != RESULT_OK) { + Log.e(TAG, "consumePurchase: Unable to consume purchase. Response code: " + response); + return; + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void purchaseFailed(int requestCode, int failureReason, String errorString) + { + purchaseFailed(m_nativePointer, requestCode, failureReason, errorString); + } + + private void purchaseSucceeded(int requestCode, + String signature, + String purchaseData, + String purchaseToken, + String orderId, + long timestamp) + { + purchaseSucceeded(m_nativePointer, requestCode, signature, purchaseData, purchaseToken, orderId, timestamp); + } + + private native static void queryFailed(long nativePointer, String productId); + private native static void purchasedProductsQueried(long nativePointer); + private native static void registerProduct(long nativePointer, + String productId, + String price, + String title, + String description); + private native static void purchaseFailed(long nativePointer, int requestCode, int failureReason, String errorString); + private native static void purchaseSucceeded(long nativePointer, + int requestCode, + String signature, + String data, + String purchaseToken, + String orderId, + long timestamp); + private native static void registerPurchased(long nativePointer, + String identifier, + String signature, + String data, + String purchaseToken, + String orderId, + long timestamp); +} diff --git a/src/android/src/org/qtproject/qt/android/purchasing/Security.java b/src/android/src/org/qtproject/qt/android/purchasing/Security.java new file mode 100644 index 0000000..5ce0ab6 --- /dev/null +++ b/src/android/src/org/qtproject/qt/android/purchasing/Security.java @@ -0,0 +1,131 @@ +/* Copyright (c) 2012 Google Inc. + * Copyright (c) 2015 The Qt Company 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 org.qtproject.qt.android.purchasing; + +import android.text.TextUtils; +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; + + +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; + +/** + * Security-related methods. For a secure implementation, all of this code + * should be implemented on a server that communicates with the + * application on the device. For the sake of simplicity and clarity of this + * example, this code is included here and is executed on the device. If you + * must verify the purchases on the phone, you should obfuscate this code to + * make it harder for an attacker to replace the code with stubs that treat all + * purchases as verified. + */ +public class Security { + private static final String TAG = "IABUtil/Security"; + + private static final String KEY_FACTORY_ALGORITHM = "RSA"; + private static final String SIGNATURE_ALGORITHM = "SHA1withRSA"; + + /** + * Verifies that the data was signed with the given signature, and returns + * the verified purchase. The data is in JSON format and signed + * with a private key. The data also contains the {@link PurchaseState} + * and product ID of the purchase. + * @param base64PublicKey the base64-encoded public key to use for verifying. + * @param signedData the signed JSON string (signed, not encrypted) + * @param signature the signature for the data, signed with the private key + */ + public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) { + if (signedData == null) { + Log.e(TAG, "data is null"); + return false; + } + + boolean verified = false; + if (!TextUtils.isEmpty(signature)) { + PublicKey key = Security.generatePublicKey(base64PublicKey); + verified = Security.verify(key, signedData, signature); + if (!verified) { + Log.w(TAG, "signature does not match data."); + return false; + } + } + return true; + } + + /** + * Generates a PublicKey instance from a string containing the + * Base64-encoded public key. + * + * @param encodedPublicKey Base64-encoded public key + * @throws IllegalArgumentException if encodedPublicKey is invalid + */ + public static PublicKey generatePublicKey(String encodedPublicKey) { + try { + byte[] decodedKey = Base64.decode(encodedPublicKey); + KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM); + return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey)); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } catch (InvalidKeySpecException e) { + Log.e(TAG, "Invalid key specification."); + throw new IllegalArgumentException(e); + } catch (Base64DecoderException e) { + Log.e(TAG, "Base64 decoding failed."); + throw new IllegalArgumentException(e); + } + } + + /** + * Verifies that the signature from the server matches the computed + * signature on the data. Returns true if the data is correctly signed. + * + * @param publicKey public key associated with the developer account + * @param signedData signed data from server + * @param signature server signature + * @return true if the data and signature match + */ + public static boolean verify(PublicKey publicKey, String signedData, String signature) { + Signature sig; + try { + sig = Signature.getInstance(SIGNATURE_ALGORITHM); + sig.initVerify(publicKey); + sig.update(signedData.getBytes()); + if (!sig.verify(Base64.decode(signature))) { + Log.e(TAG, "Signature verification failed."); + return false; + } + return true; + } catch (NoSuchAlgorithmException e) { + Log.e(TAG, "NoSuchAlgorithmException."); + } catch (InvalidKeyException e) { + Log.e(TAG, "Invalid key specification."); + } catch (SignatureException e) { + Log.e(TAG, "Signature exception."); + } catch (Base64DecoderException e) { + Log.e(TAG, "Base64 decoding failed."); + } + return false; + } +} diff --git a/src/android/src/org/qtproject/qt/android/purchasing/qt_attribution.json b/src/android/src/org/qtproject/qt/android/purchasing/qt_attribution.json new file mode 100644 index 0000000..a8e5c21 --- /dev/null +++ b/src/android/src/org/qtproject/qt/android/purchasing/qt_attribution.json @@ -0,0 +1,29 @@ +[ + { + "Id": "base64decoder", + "Name": "Base64 Decoder", + "QDocModule": "qtpurchasing", + "QtUsage": "Used on Android in verification of purchases if a public key is set.", + "Files": "Base64.java Base64DecoderException.java", + + "Description": "Converts between binary and base64.", + "License": "Apache License 2.0", + "LicenseId": "Apache-2.0", + "LicenseFile": "../../../../../LICENSE-APACHE-2.0.txt", + "Copyright": "Copyright 2002, Google, Inc." + }, + + { + "Id": "pkeyverify", + "Name": "Public Key Verification", + "QDocModule": "qtpurchasing", + "QtUsage": "Used on Android in verification of purchases if a public key is set.", + "Files": "Security.java", + + "Description": "Verifies a given signature on data.", + "License": "Apache License 2.0", + "LicenseId": "Apache-2.0", + "LicenseFile": "../../../../../LICENSE-APACHE-2.0.txt", + "Copyright": "Copyright (c) 2012 Google Inc." + } +] diff --git a/src/android/src/org/qtproject/qt5/android/purchasing/Base64.java b/src/android/src/org/qtproject/qt5/android/purchasing/Base64.java deleted file mode 100644 index 58f1982..0000000 --- a/src/android/src/org/qtproject/qt5/android/purchasing/Base64.java +++ /dev/null @@ -1,571 +0,0 @@ -// Portions copyright 2002, Google, Inc. -// Portions copyright (c) 2015 The Qt Company 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 org.qtproject.qt5.android.purchasing; - -// This code was converted from code at http://iharder.sourceforge.net/base64/ -// Lots of extraneous features were removed. -/* The original code said: - *

- * I am placing this code in the Public Domain. Do with it as you will. - * This software comes with no guarantees or warranties but with - * plenty of well-wishing instead! - * Please visit - * http://iharder.net/xmlizable - * periodically to check for updates or to contribute improvements. - *

- * - * @author Robert Harder - * @author rharder@usa.net - * @version 1.3 - */ - -/** - * Base64 converter class. This code is not a complete MIME encoder; - * it simply converts binary data to base64 data and back. - * - *

Note {@link CharBase64} is a GWT-compatible implementation of this - * class. - */ -public class Base64 { - /** Specify encoding (value is {@code true}). */ - public final static boolean ENCODE = true; - - /** Specify decoding (value is {@code false}). */ - public final static boolean DECODE = false; - - /** The equals sign (=) as a byte. */ - private final static byte EQUALS_SIGN = (byte) '='; - - /** The new line character (\n) as a byte. */ - private final static byte NEW_LINE = (byte) '\n'; - - /** - * The 64 valid Base64 values. - */ - private final static byte[] ALPHABET = - {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', - (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', - (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', - (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', - (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', - (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', - (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', - (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', - (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', - (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', - (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', - (byte) '9', (byte) '+', (byte) '/'}; - - /** - * The 64 valid web safe Base64 values. - */ - private final static byte[] WEBSAFE_ALPHABET = - {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', - (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', - (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', - (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', - (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', - (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', - (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', - (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', - (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', - (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', - (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', - (byte) '9', (byte) '-', (byte) '_'}; - - /** - * Translates a Base64 value to either its 6-bit reconstruction value - * or a negative number indicating some other meaning. - **/ - private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 - 62, // Plus sign at decimal 43 - -9, -9, -9, // Decimal 44 - 46 - 63, // Slash at decimal 47 - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' - -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ - }; - - /** The web safe decodabet */ - private final static byte[] WEBSAFE_DECODABET = - {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44 - 62, // Dash '-' sign at decimal 45 - -9, -9, // Decimal 46-47 - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' - -9, -9, -9, -9, // Decimal 91-94 - 63, // Underscore '_' at decimal 95 - -9, // Decimal 96 - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ - }; - - // Indicates white space in encoding - private final static byte WHITE_SPACE_ENC = -5; - // Indicates equals sign in encoding - private final static byte EQUALS_SIGN_ENC = -1; - - /** Defeats instantiation. */ - private Base64() { - } - - /* ******** E N C O D I N G M E T H O D S ******** */ - - /** - * Encodes up to three bytes of the array source - * and writes the resulting four Base64 bytes to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accommodate srcOffset + 3 for - * the source array or destOffset + 4 for - * the destination array. - * The actual number of significant bytes in your array is - * given by numSigBytes. - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param numSigBytes the number of significant bytes in your array - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @param alphabet is the encoding alphabet - * @return the destination array - * @since 1.3 - */ - private static byte[] encode3to4(byte[] source, int srcOffset, - int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) { - // 1 2 3 - // 01234567890123456789012345678901 Bit position - // --------000000001111111122222222 Array position from threeBytes - // --------| || || || | Six bit groups to index alphabet - // >>18 >>12 >> 6 >> 0 Right shift necessary - // 0x3f 0x3f 0x3f Additional AND - - // Create buffer with zero-padding if there are only one or two - // significant bytes passed in the array. - // We have to shift left 24 in order to flush out the 1's that appear - // when Java treats a value as negative that is cast from a byte to an int. - int inBuff = - (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) - | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) - | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); - - switch (numSigBytes) { - case 3: - destination[destOffset] = alphabet[(inBuff >>> 18)]; - destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f]; - destination[destOffset + 3] = alphabet[(inBuff) & 0x3f]; - return destination; - case 2: - destination[destOffset] = alphabet[(inBuff >>> 18)]; - destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f]; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - case 1: - destination[destOffset] = alphabet[(inBuff >>> 18)]; - destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = EQUALS_SIGN; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - default: - return destination; - } // end switch - } // end encode3to4 - - /** - * Encodes a byte array into Base64 notation. - * Equivalent to calling - * {@code encodeBytes(source, 0, source.length)} - * - * @param source The data to convert - * @since 1.4 - */ - public static String encode(byte[] source) { - return encode(source, 0, source.length, ALPHABET, true); - } - - /** - * Encodes a byte array into web safe Base64 notation. - * - * @param source The data to convert - * @param doPadding is {@code true} to pad result with '=' chars - * if it does not fall on 3 byte boundaries - */ - public static String encodeWebSafe(byte[] source, boolean doPadding) { - return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding); - } - - /** - * Encodes a byte array into Base64 notation. - * - * @param source the data to convert - * @param off offset in array where conversion should begin - * @param len length of data to convert - * @param alphabet the encoding alphabet - * @param doPadding is {@code true} to pad result with '=' chars - * if it does not fall on 3 byte boundaries - * @since 1.4 - */ - public static String encode(byte[] source, int off, int len, byte[] alphabet, - boolean doPadding) { - byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE); - int outLen = outBuff.length; - - // If doPadding is false, set length to truncate '=' - // padding characters - while (doPadding == false && outLen > 0) { - if (outBuff[outLen - 1] != '=') { - break; - } - outLen -= 1; - } - - return new String(outBuff, 0, outLen); - } - - /** - * Encodes a byte array into Base64 notation. - * - * @param source the data to convert - * @param off offset in array where conversion should begin - * @param len length of data to convert - * @param alphabet is the encoding alphabet - * @param maxLineLength maximum length of one line. - * @return the BASE64-encoded byte array - */ - public static byte[] encode(byte[] source, int off, int len, byte[] alphabet, - int maxLineLength) { - int lenDiv3 = (len + 2) / 3; // ceil(len / 3) - int len43 = lenDiv3 * 4; - byte[] outBuff = new byte[len43 // Main 4:3 - + (len43 / maxLineLength)]; // New lines - - int d = 0; - int e = 0; - int len2 = len - 2; - int lineLength = 0; - for (; d < len2; d += 3, e += 4) { - - // The following block of code is the same as - // encode3to4( source, d + off, 3, outBuff, e, alphabet ); - // but inlined for faster encoding (~20% improvement) - int inBuff = - ((source[d + off] << 24) >>> 8) - | ((source[d + 1 + off] << 24) >>> 16) - | ((source[d + 2 + off] << 24) >>> 24); - outBuff[e] = alphabet[(inBuff >>> 18)]; - outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f]; - outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f]; - outBuff[e + 3] = alphabet[(inBuff) & 0x3f]; - - lineLength += 4; - if (lineLength == maxLineLength) { - outBuff[e + 4] = NEW_LINE; - e++; - lineLength = 0; - } // end if: end of line - } // end for: each piece of array - - if (d < len) { - encode3to4(source, d + off, len - d, outBuff, e, alphabet); - - lineLength += 4; - if (lineLength == maxLineLength) { - // Add a last newline - outBuff[e + 4] = NEW_LINE; - e++; - } - e += 4; - } - - assert (e == outBuff.length); - return outBuff; - } - - - /* ******** D E C O D I N G M E T H O D S ******** */ - - - /** - * Decodes four bytes from array source - * and writes the resulting bytes (up to three of them) - * to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accommodate srcOffset + 4 for - * the source array or destOffset + 3 for - * the destination array. - * This method returns the actual number of bytes that - * were converted from the Base64 encoding. - * - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @param decodabet the decodabet for decoding Base64 content - * @return the number of decoded bytes converted - * @since 1.3 - */ - private static int decode4to3(byte[] source, int srcOffset, - byte[] destination, int destOffset, byte[] decodabet) { - // Example: Dk== - if (source[srcOffset + 2] == EQUALS_SIGN) { - int outBuff = - ((decodabet[source[srcOffset]] << 24) >>> 6) - | ((decodabet[source[srcOffset + 1]] << 24) >>> 12); - - destination[destOffset] = (byte) (outBuff >>> 16); - return 1; - } else if (source[srcOffset + 3] == EQUALS_SIGN) { - // Example: DkL= - int outBuff = - ((decodabet[source[srcOffset]] << 24) >>> 6) - | ((decodabet[source[srcOffset + 1]] << 24) >>> 12) - | ((decodabet[source[srcOffset + 2]] << 24) >>> 18); - - destination[destOffset] = (byte) (outBuff >>> 16); - destination[destOffset + 1] = (byte) (outBuff >>> 8); - return 2; - } else { - // Example: DkLE - int outBuff = - ((decodabet[source[srcOffset]] << 24) >>> 6) - | ((decodabet[source[srcOffset + 1]] << 24) >>> 12) - | ((decodabet[source[srcOffset + 2]] << 24) >>> 18) - | ((decodabet[source[srcOffset + 3]] << 24) >>> 24); - - destination[destOffset] = (byte) (outBuff >> 16); - destination[destOffset + 1] = (byte) (outBuff >> 8); - destination[destOffset + 2] = (byte) (outBuff); - return 3; - } - } // end decodeToBytes - - - /** - * Decodes data from Base64 notation. - * - * @param s the string to decode (decoded in default encoding) - * @return the decoded data - * @since 1.4 - */ - public static byte[] decode(String s) throws Base64DecoderException { - byte[] bytes = s.getBytes(); - return decode(bytes, 0, bytes.length); - } - - /** - * Decodes data from web safe Base64 notation. - * Web safe encoding uses '-' instead of '+', '_' instead of '/' - * - * @param s the string to decode (decoded in default encoding) - * @return the decoded data - */ - public static byte[] decodeWebSafe(String s) throws Base64DecoderException { - byte[] bytes = s.getBytes(); - return decodeWebSafe(bytes, 0, bytes.length); - } - - /** - * Decodes Base64 content in byte array format and returns - * the decoded byte array. - * - * @param source The Base64 encoded data - * @return decoded data - * @since 1.3 - * @throws Base64DecoderException - */ - public static byte[] decode(byte[] source) throws Base64DecoderException { - return decode(source, 0, source.length); - } - - /** - * Decodes web safe Base64 content in byte array format and returns - * the decoded data. - * Web safe encoding uses '-' instead of '+', '_' instead of '/' - * - * @param source the string to decode (decoded in default encoding) - * @return the decoded data - */ - public static byte[] decodeWebSafe(byte[] source) - throws Base64DecoderException { - return decodeWebSafe(source, 0, source.length); - } - - /** - * Decodes Base64 content in byte array format and returns - * the decoded byte array. - * - * @param source the Base64 encoded data - * @param off the offset of where to begin decoding - * @param len the length of characters to decode - * @return decoded data - * @since 1.3 - * @throws Base64DecoderException - */ - public static byte[] decode(byte[] source, int off, int len) - throws Base64DecoderException { - return decode(source, off, len, DECODABET); - } - - /** - * Decodes web safe Base64 content in byte array format and returns - * the decoded byte array. - * Web safe encoding uses '-' instead of '+', '_' instead of '/' - * - * @param source the Base64 encoded data - * @param off the offset of where to begin decoding - * @param len the length of characters to decode - * @return decoded data - */ - public static byte[] decodeWebSafe(byte[] source, int off, int len) - throws Base64DecoderException { - return decode(source, off, len, WEBSAFE_DECODABET); - } - - /** - * Decodes Base64 content using the supplied decodabet and returns - * the decoded byte array. - * - * @param source the Base64 encoded data - * @param off the offset of where to begin decoding - * @param len the length of characters to decode - * @param decodabet the decodabet for decoding Base64 content - * @return decoded data - */ - public static byte[] decode(byte[] source, int off, int len, byte[] decodabet) - throws Base64DecoderException { - int len34 = len * 3 / 4; - byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output - int outBuffPosn = 0; - - byte[] b4 = new byte[4]; - int b4Posn = 0; - int i = 0; - byte sbiCrop = 0; - byte sbiDecode = 0; - for (i = 0; i < len; i++) { - sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits - sbiDecode = decodabet[sbiCrop]; - - if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better - if (sbiDecode >= EQUALS_SIGN_ENC) { - // An equals sign (for padding) must not occur at position 0 or 1 - // and must be the last byte[s] in the encoded value - if (sbiCrop == EQUALS_SIGN) { - int bytesLeft = len - i; - byte lastByte = (byte) (source[len - 1 + off] & 0x7f); - if (b4Posn == 0 || b4Posn == 1) { - throw new Base64DecoderException( - "invalid padding byte '=' at byte offset " + i); - } else if ((b4Posn == 3 && bytesLeft > 2) - || (b4Posn == 4 && bytesLeft > 1)) { - throw new Base64DecoderException( - "padding byte '=' falsely signals end of encoded value " - + "at offset " + i); - } else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) { - throw new Base64DecoderException( - "encoded value has invalid trailing byte"); - } - break; - } - - b4[b4Posn++] = sbiCrop; - if (b4Posn == 4) { - outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); - b4Posn = 0; - } - } - } else { - throw new Base64DecoderException("Bad Base64 input character at " + i - + ": " + source[i + off] + "(decimal)"); - } - } - - // Because web safe encoding allows non padding base64 encodes, we - // need to pad the rest of the b4 buffer with equal signs when - // b4Posn != 0. There can be at most 2 equal signs at the end of - // four characters, so the b4 buffer must have two or three - // characters. This also catches the case where the input is - // padded with EQUALS_SIGN - if (b4Posn != 0) { - if (b4Posn == 1) { - throw new Base64DecoderException("single trailing character at offset " - + (len - 1)); - } - b4[b4Posn++] = EQUALS_SIGN; - outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); - } - - byte[] out = new byte[outBuffPosn]; - System.arraycopy(outBuff, 0, out, 0, outBuffPosn); - return out; - } -} diff --git a/src/android/src/org/qtproject/qt5/android/purchasing/Base64DecoderException.java b/src/android/src/org/qtproject/qt5/android/purchasing/Base64DecoderException.java deleted file mode 100644 index 0260bd9..0000000 --- a/src/android/src/org/qtproject/qt5/android/purchasing/Base64DecoderException.java +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2002, Google, Inc. -// Copyright (c) 2015 The Qt Company 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 org.qtproject.qt5.android.purchasing; - -/** - * Exception thrown when encountering an invalid Base64 input character. - * - * @author nelson - */ -public class Base64DecoderException extends Exception { - public Base64DecoderException() { - super(); - } - - public Base64DecoderException(String s) { - super(s); - } - - private static final long serialVersionUID = 1L; -} diff --git a/src/android/src/org/qtproject/qt5/android/purchasing/QtInAppPurchase.java b/src/android/src/org/qtproject/qt5/android/purchasing/QtInAppPurchase.java deleted file mode 100644 index 94bc202..0000000 --- a/src/android/src/org/qtproject/qt5/android/purchasing/QtInAppPurchase.java +++ /dev/null @@ -1,472 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 The Qt Company Ltd. -** Contact: http://www.qt.io/licensing/ -** -** This file is part of the Purchasing module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL3-COMM$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see http://www.qt.io/terms-conditions. For further -** information use the contact form at http://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPLv3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -package org.qtproject.qt5.android.purchasing; - -import java.util.ArrayList; -import java.util.HashSet; -import android.app.PendingIntent; -import android.app.Activity; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentSender; -import android.content.ServiceConnection; -import android.content.pm.PackageManager; -import android.os.RemoteException; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.util.Log; -import org.json.JSONObject; -import org.json.JSONException; - - -import com.android.vending.billing.IInAppBillingService; - -public class QtInAppPurchase -{ - private Context m_context = null; - private IInAppBillingService m_service = null; - private String m_publicKey = null; - private long m_nativePointer; - - public static final int RESULT_OK = 0; - public static final int RESULT_USER_CANCELED = 1; - public static final int RESULT_BILLING_UNAVAILABLE = 3; - public static final int RESULT_ITEM_UNAVAILABLE = 4; - public static final int RESULT_DEVELOPER_ERROR = 5; - public static final int RESULT_ERROR = 6; - public static final int RESULT_ITEM_ALREADY_OWNED = 7; - public static final int RESULT_ITEM_NOT_OWNED = 8; - public static final int RESULT_QTPURCHASING_ERROR = 9; // No match with any already defined response codes - public static final String TAG = "QtInAppPurchase"; - public static final String TYPE_INAPP = "inapp"; - public static final int IAP_VERSION = 3; - - // Should be in sync with QInAppTransaction::FailureReason - public static final int FAILUREREASON_NOFAILURE = 0; - public static final int FAILUREREASON_USERCANCELED = 1; - public static final int FAILUREREASON_ERROR = 2; - - private ServiceConnection m_serviceConnection = new ServiceConnection() - { - @Override - public void onServiceConnected(ComponentName name, IBinder service) - { - m_service = IInAppBillingService.Stub.asInterface(service); - try { - int response = m_service.isBillingSupported(3, m_context.getPackageName(), TYPE_INAPP); - if (response != RESULT_OK) { - Log.e(TAG, "In-app billing not supported"); - return; - } - } catch (RemoteException e) { - e.printStackTrace(); - } - - // Asynchronously populate list of purchased products - final Handler handler = new Handler(); - Thread thread = new Thread(new Runnable() - { - public void run() - { - queryPurchasedProducts(); - handler.post(new Runnable() - { - public void run() { purchasedProductsQueried(m_nativePointer); } - }); - } - }); - thread.start(); - } - - @Override - public void onServiceDisconnected(ComponentName name) - { - m_service = null; - } - }; - - public QtInAppPurchase(Context context, long nativePointer) - { - m_context = context; - m_nativePointer = nativePointer; - } - - public void initializeConnection() - { - - Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND"); - serviceIntent.setPackage("com.android.vending"); - try { - if (!m_context.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) { - m_context.bindService(serviceIntent, m_serviceConnection, Context.BIND_AUTO_CREATE); - } else { - Log.e(TAG, "No in-app billing service available."); - purchasedProductsQueried(m_nativePointer); - } - } catch (Exception e) { - Log.e(TAG, "Could not query InAppBillingService intent."); - purchasedProductsQueried(m_nativePointer); - } - } - - private int bundleResponseCode(Bundle bundle) - { - Object o = bundle.get("RESPONSE_CODE"); - if (o == null) { - // Works around known issue where the response code is not bundled. - return RESULT_OK; - } else if (o instanceof Integer) { - return ((Integer)o).intValue(); - } else if (o instanceof Long) { - return (int)((Long)o).longValue(); - } - - Log.e(TAG, "Unexpected result for response code: " + o); - return RESULT_QTPURCHASING_ERROR; - } - - private void queryPurchasedProducts() - { - if (m_service == null) { - Log.e(TAG, "queryPurchasedProducts: Service not initialized"); - return; - } - - String continuationToken = null; - try { - do { - Bundle ownedItems = m_service.getPurchases(IAP_VERSION, - m_context.getPackageName(), - TYPE_INAPP, - continuationToken); - int responseCode = bundleResponseCode(ownedItems); - if (responseCode != RESULT_OK) { - Log.e(TAG, "queryPurchasedProducts: Failed to query purchases products"); - return; - } - - ArrayList dataList = ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST"); - if (dataList == null) { - Log.e(TAG, "queryPurchasedProducts: No data list in bundle"); - return; - } - - ArrayList signatureList = ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE_LIST"); - if (signatureList == null) { - Log.e(TAG, "queryPurchasedProducts: No signature list in bundle"); - return; - } - - if (dataList.size() != signatureList.size()) { - Log.e(TAG, "queryPurchasedProducts: Mismatching sizes of lists in bundle"); - return; - } - - for (int i=0; i 0); - } catch (RemoteException e) { - e.printStackTrace(); - } - - } - - public void queryDetails(final String[] productIds) - { - if (m_service == null) { - Log.e(TAG, "queryDetails: Service not initialized"); - for (String productId : productIds) - queryFailed(m_nativePointer, productId); - return; - } - - // Asynchronously query details about product - Thread thread = new Thread(new Runnable() - { - public void run() - { - synchronized(m_service) { - HashSet failedProducts = new HashSet(); - - int index = 0; - while (index < productIds.length) { - ArrayList productIdList = new ArrayList(); - for (int i = index; i < Math.min(index + 20, productIds.length); ++i) { - productIdList.add(productIds[i]); - failedProducts.add(productIds[i]); // Assume guilt until innocence is proven - } - index += productIdList.size(); - - try { - Bundle productIdBundle = new Bundle(); - productIdBundle.putStringArrayList("ITEM_ID_LIST", productIdList); - - Bundle bundle = m_service.getSkuDetails(IAP_VERSION, - m_context.getPackageName(), - "inapp", - productIdBundle); - - int responseCode = bundleResponseCode(bundle); - if (responseCode != RESULT_OK) { - Log.e(TAG, "queryDetails: Couldn't retrieve sku details."); - continue; - } - - ArrayList detailsList = bundle.getStringArrayList("DETAILS_LIST"); - if (detailsList == null) { - Log.e(TAG, "queryDetails: No details list in response."); - continue; - } - - for (String details : detailsList) { - try { - JSONObject jo = new JSONObject(details); - String queriedProductId = jo.getString("productId"); - String queriedPrice = jo.getString("price"); - String queriedTitle = jo.getString("title"); - String queriedDescription = jo.getString("description"); - if (queriedProductId == null || queriedPrice == null || queriedTitle == null || queriedDescription == null) { - Log.e(TAG, "Data missing from product details."); - } else { - failedProducts.remove(queriedProductId); - registerProduct(m_nativePointer, - queriedProductId, - queriedPrice, - queriedTitle, - queriedDescription); - } - } catch (JSONException e) { - e.printStackTrace(); - } - } - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - for (String failedProduct : failedProducts) - queryFailed(m_nativePointer, failedProduct); - } - } - }); - thread.start(); - } - - public void handleActivityResult(int requestCode, int resultCode, Intent data, String expectedIdentifier) - { - if (data == null) { - purchaseFailed(requestCode, FAILUREREASON_ERROR, "Data missing from result"); - return; - } - - int responseCode = data.getIntExtra("RESPONSE_CODE", -1); - String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA"); - String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE"); - String purchaseToken = ""; - String orderId = ""; - long timestamp = 0; - - if (responseCode == RESULT_USER_CANCELED) { - purchaseFailed(requestCode, FAILUREREASON_USERCANCELED, ""); - return; - } else if (responseCode != RESULT_OK) { - String errorString; - switch (responseCode) { - case RESULT_BILLING_UNAVAILABLE: errorString = "Billing unavailable"; break; - case RESULT_ITEM_UNAVAILABLE: errorString = "Item unavailable"; break; - case RESULT_DEVELOPER_ERROR: errorString = "Developer error"; break; - case RESULT_ERROR: errorString = "Fatal error occurred"; break; - case RESULT_ITEM_ALREADY_OWNED: errorString = "Item already owned"; break; - default: errorString = "Unknown billing error " + responseCode; break; - }; - - purchaseFailed(requestCode, FAILUREREASON_ERROR, errorString); - return; - } - - try { - if (m_publicKey != null && !Security.verifyPurchase(m_publicKey, purchaseData, dataSignature)) { - purchaseFailed(requestCode, FAILUREREASON_ERROR, "Signature could not be verified"); - return; - } - - JSONObject jo = new JSONObject(purchaseData); - String sku = jo.getString("productId"); - if (!sku.equals(expectedIdentifier)) { - purchaseFailed(requestCode, FAILUREREASON_ERROR, "Unexpected identifier in result"); - return; - } - - int purchaseState = jo.getInt("purchaseState"); - if (purchaseState != 0) { - purchaseFailed(requestCode, FAILUREREASON_ERROR, "Unexpected purchase state in result"); - return; - } - - purchaseToken = jo.getString("purchaseToken"); - if (jo.has("orderId")) - orderId = jo.getString("orderId"); - if (jo.has("purchaseTime")) - timestamp = jo.getLong("purchaseTime"); - - } catch (Exception e) { - e.printStackTrace(); - purchaseFailed(requestCode, FAILUREREASON_ERROR, e.getMessage()); - } - - purchaseSucceeded(requestCode, dataSignature, purchaseData, purchaseToken, orderId, timestamp); - } - - public void setPublicKey(String publicKey) - { - m_publicKey = publicKey; - } - - public IntentSender createBuyIntentSender(String identifier, int requestCode) - { - if (m_service == null) { - Log.e(TAG, "Unable to create buy intent. No IAP service connection."); - return null; - } - - try { - Bundle purchaseBundle = m_service.getBuyIntent(3, - m_context.getPackageName(), - identifier, - TYPE_INAPP, - identifier); - int response = bundleResponseCode(purchaseBundle); - - if (response != RESULT_OK) { - Log.e(TAG, "Unable to create buy intent. Response code: " + response); - String errorString; - switch (response) { - case RESULT_BILLING_UNAVAILABLE: errorString = "Billing unavailable"; break; - case RESULT_ITEM_UNAVAILABLE: errorString = "Item unavailable"; break; - case RESULT_DEVELOPER_ERROR: errorString = "Developer error"; break; - case RESULT_ERROR: errorString = "Fatal error occurred"; break; - case RESULT_ITEM_ALREADY_OWNED: errorString = "Item already owned"; break; - default: errorString = "Unknown billing error " + response; break; - }; - - purchaseFailed(requestCode, FAILUREREASON_ERROR, errorString); - return null; - } - - PendingIntent pendingIntent = purchaseBundle.getParcelable("BUY_INTENT"); - return pendingIntent.getIntentSender(); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - public void consumePurchase(String purchaseToken) - { - if (m_service == null) { - Log.e(TAG, "consumePurchase: Unable to consume purchase. No IAP service connection."); - return; - } - - try { - int response = m_service.consumePurchase(3, m_context.getPackageName(), purchaseToken); - if (response != RESULT_OK) { - Log.e(TAG, "consumePurchase: Unable to consume purchase. Response code: " + response); - return; - } - - } catch (Exception e) { - e.printStackTrace(); - } - } - - private void purchaseFailed(int requestCode, int failureReason, String errorString) - { - purchaseFailed(m_nativePointer, requestCode, failureReason, errorString); - } - - private void purchaseSucceeded(int requestCode, - String signature, - String purchaseData, - String purchaseToken, - String orderId, - long timestamp) - { - purchaseSucceeded(m_nativePointer, requestCode, signature, purchaseData, purchaseToken, orderId, timestamp); - } - - private native static void queryFailed(long nativePointer, String productId); - private native static void purchasedProductsQueried(long nativePointer); - private native static void registerProduct(long nativePointer, - String productId, - String price, - String title, - String description); - private native static void purchaseFailed(long nativePointer, int requestCode, int failureReason, String errorString); - private native static void purchaseSucceeded(long nativePointer, - int requestCode, - String signature, - String data, - String purchaseToken, - String orderId, - long timestamp); - private native static void registerPurchased(long nativePointer, - String identifier, - String signature, - String data, - String purchaseToken, - String orderId, - long timestamp); -} diff --git a/src/android/src/org/qtproject/qt5/android/purchasing/Security.java b/src/android/src/org/qtproject/qt5/android/purchasing/Security.java deleted file mode 100644 index a48dda0..0000000 --- a/src/android/src/org/qtproject/qt5/android/purchasing/Security.java +++ /dev/null @@ -1,131 +0,0 @@ -/* Copyright (c) 2012 Google Inc. - * Copyright (c) 2015 The Qt Company 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 org.qtproject.qt5.android.purchasing; - -import android.text.TextUtils; -import android.util.Log; - -import org.json.JSONException; -import org.json.JSONObject; - - -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; - -/** - * Security-related methods. For a secure implementation, all of this code - * should be implemented on a server that communicates with the - * application on the device. For the sake of simplicity and clarity of this - * example, this code is included here and is executed on the device. If you - * must verify the purchases on the phone, you should obfuscate this code to - * make it harder for an attacker to replace the code with stubs that treat all - * purchases as verified. - */ -public class Security { - private static final String TAG = "IABUtil/Security"; - - private static final String KEY_FACTORY_ALGORITHM = "RSA"; - private static final String SIGNATURE_ALGORITHM = "SHA1withRSA"; - - /** - * Verifies that the data was signed with the given signature, and returns - * the verified purchase. The data is in JSON format and signed - * with a private key. The data also contains the {@link PurchaseState} - * and product ID of the purchase. - * @param base64PublicKey the base64-encoded public key to use for verifying. - * @param signedData the signed JSON string (signed, not encrypted) - * @param signature the signature for the data, signed with the private key - */ - public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) { - if (signedData == null) { - Log.e(TAG, "data is null"); - return false; - } - - boolean verified = false; - if (!TextUtils.isEmpty(signature)) { - PublicKey key = Security.generatePublicKey(base64PublicKey); - verified = Security.verify(key, signedData, signature); - if (!verified) { - Log.w(TAG, "signature does not match data."); - return false; - } - } - return true; - } - - /** - * Generates a PublicKey instance from a string containing the - * Base64-encoded public key. - * - * @param encodedPublicKey Base64-encoded public key - * @throws IllegalArgumentException if encodedPublicKey is invalid - */ - public static PublicKey generatePublicKey(String encodedPublicKey) { - try { - byte[] decodedKey = Base64.decode(encodedPublicKey); - KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM); - return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey)); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } catch (InvalidKeySpecException e) { - Log.e(TAG, "Invalid key specification."); - throw new IllegalArgumentException(e); - } catch (Base64DecoderException e) { - Log.e(TAG, "Base64 decoding failed."); - throw new IllegalArgumentException(e); - } - } - - /** - * Verifies that the signature from the server matches the computed - * signature on the data. Returns true if the data is correctly signed. - * - * @param publicKey public key associated with the developer account - * @param signedData signed data from server - * @param signature server signature - * @return true if the data and signature match - */ - public static boolean verify(PublicKey publicKey, String signedData, String signature) { - Signature sig; - try { - sig = Signature.getInstance(SIGNATURE_ALGORITHM); - sig.initVerify(publicKey); - sig.update(signedData.getBytes()); - if (!sig.verify(Base64.decode(signature))) { - Log.e(TAG, "Signature verification failed."); - return false; - } - return true; - } catch (NoSuchAlgorithmException e) { - Log.e(TAG, "NoSuchAlgorithmException."); - } catch (InvalidKeyException e) { - Log.e(TAG, "Invalid key specification."); - } catch (SignatureException e) { - Log.e(TAG, "Signature exception."); - } catch (Base64DecoderException e) { - Log.e(TAG, "Base64 decoding failed."); - } - return false; - } -} diff --git a/src/android/src/org/qtproject/qt5/android/purchasing/qt_attribution.json b/src/android/src/org/qtproject/qt5/android/purchasing/qt_attribution.json deleted file mode 100644 index a8e5c21..0000000 --- a/src/android/src/org/qtproject/qt5/android/purchasing/qt_attribution.json +++ /dev/null @@ -1,29 +0,0 @@ -[ - { - "Id": "base64decoder", - "Name": "Base64 Decoder", - "QDocModule": "qtpurchasing", - "QtUsage": "Used on Android in verification of purchases if a public key is set.", - "Files": "Base64.java Base64DecoderException.java", - - "Description": "Converts between binary and base64.", - "License": "Apache License 2.0", - "LicenseId": "Apache-2.0", - "LicenseFile": "../../../../../LICENSE-APACHE-2.0.txt", - "Copyright": "Copyright 2002, Google, Inc." - }, - - { - "Id": "pkeyverify", - "Name": "Public Key Verification", - "QDocModule": "qtpurchasing", - "QtUsage": "Used on Android in verification of purchases if a public key is set.", - "Files": "Security.java", - - "Description": "Verifies a given signature on data.", - "License": "Apache License 2.0", - "LicenseId": "Apache-2.0", - "LicenseFile": "../../../../../LICENSE-APACHE-2.0.txt", - "Copyright": "Copyright (c) 2012 Google Inc." - } -] diff --git a/src/purchasing/inapppurchase/android/qandroidinapppurchasebackend.cpp b/src/purchasing/inapppurchase/android/qandroidinapppurchasebackend.cpp index aab454c..9445b57 100644 --- a/src/purchasing/inapppurchase/android/qandroidinapppurchasebackend.cpp +++ b/src/purchasing/inapppurchase/android/qandroidinapppurchasebackend.cpp @@ -51,7 +51,7 @@ QAndroidInAppPurchaseBackend::QAndroidInAppPurchaseBackend(QObject *parent) qDebug("Creating backend"); #endif - m_javaObject = QAndroidJniObject("org/qtproject/qt5/android/purchasing/QtInAppPurchase", + m_javaObject = QAndroidJniObject("org/qtproject/qt/android/purchasing/QtInAppPurchase", "(Landroid/content/Context;J)V", QtAndroid::androidActivity().object(), reinterpret_cast(this)); diff --git a/src/purchasing/inapppurchase/android/qandroidjni.cpp b/src/purchasing/inapppurchase/android/qandroidjni.cpp index d1f4920..c202c1e 100644 --- a/src/purchasing/inapppurchase/android/qandroidjni.cpp +++ b/src/purchasing/inapppurchase/android/qandroidjni.cpp @@ -130,7 +130,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR; - jclass clazz = env->FindClass("org/qtproject/qt5/android/purchasing/QtInAppPurchase"); + jclass clazz = env->FindClass("org/qtproject/qt/android/purchasing/QtInAppPurchase"); if (!clazz) return JNI_ERR; diff --git a/src/purchasing/purchasing.pro b/src/purchasing/purchasing.pro index 1c3fc52..a2b3845 100644 --- a/src/purchasing/purchasing.pro +++ b/src/purchasing/purchasing.pro @@ -7,7 +7,7 @@ QMAKE_TARGET_DESCRIPTION = "Purchasing component for Qt." QMAKE_DOCS = $$PWD/doc/qtpurchasing.qdocconf ANDROID_BUNDLED_JAR_DEPENDENCIES = \ - jar/QtPurchasing.jar + jar/Qt$${QT_MAJOR_VERSION}AndroidPurchasing.jar HEADERS += \ $$PWD/qtpurchasingglobal.h -- cgit v1.2.3