diff --git a/build.gradle b/build.gradle index d3825c09..5de38f76 100644 --- a/build.gradle +++ b/build.gradle @@ -24,8 +24,13 @@ java { withJavadocJar() } +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} + javadoc { source = sourceSets.main.allJava + options.encoding = 'UTF-8' } signing { diff --git a/src/main/java/com/apple/itunes/storekit/verification/SignedDataVerifier.java b/src/main/java/com/apple/itunes/storekit/verification/SignedDataVerifier.java index 33601d9f..480e0ccc 100644 --- a/src/main/java/com/apple/itunes/storekit/verification/SignedDataVerifier.java +++ b/src/main/java/com/apple/itunes/storekit/verification/SignedDataVerifier.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.security.PublicKey; import java.security.interfaces.ECPublicKey; import java.util.Base64; @@ -218,7 +219,7 @@ protected T decodeSignedObject(String signedObject } protected T parseJWTPayload(Class clazz, DecodedJWT jwt) throws VerificationException { - String payload = new String(Base64.getUrlDecoder().decode(jwt.getPayload())); + String payload = new String(Base64.getUrlDecoder().decode(jwt.getPayload()), StandardCharsets.UTF_8); try { return objectMapper.readValue(payload, clazz); } catch (JsonProcessingException e) { diff --git a/src/test/java/com/apple/itunes/storekit/verification/SignedDataVerifierTest.java b/src/test/java/com/apple/itunes/storekit/verification/SignedDataVerifierTest.java index de602c80..76fa69ee 100644 --- a/src/test/java/com/apple/itunes/storekit/verification/SignedDataVerifierTest.java +++ b/src/test/java/com/apple/itunes/storekit/verification/SignedDataVerifierTest.java @@ -2,16 +2,19 @@ package com.apple.itunes.storekit.verification; +import com.apple.itunes.storekit.model.AdvancedCommerceTransactionInfo; import com.apple.itunes.storekit.model.Environment; import com.apple.itunes.storekit.model.JWSRenewalInfoDecodedPayload; import com.apple.itunes.storekit.model.JWSTransactionDecodedPayload; import com.apple.itunes.storekit.model.NotificationTypeV2; import com.apple.itunes.storekit.model.ResponseBodyV2DecodedPayload; +import com.apple.itunes.storekit.util.SignedDataCreator; import com.apple.itunes.storekit.util.TestingUtility; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.io.IOException; +import java.security.NoSuchAlgorithmException; public class SignedDataVerifierTest { @@ -84,4 +87,22 @@ public void testMalformedJWTWithMalformedData() throws IOException { VerificationException exception = Assertions.assertThrows(VerificationException.class, () -> verifier.verifyAndDecodeNotification("a.b.c")); Assertions.assertEquals(VerificationStatus.VERIFICATION_FAILURE, exception.getStatus()); } + + /** + * The payload of a JWS is always UTF-8 encoded, so decoding it must not depend on the default charset of the JVM. + * The expected values below are written as escape sequences so that this test does not depend on the encoding + * used to compile it either. + */ + @Test + public void testNonAsciiDataDecodingIsIndependentOfTheDefaultCharset() throws VerificationException, IOException, NoSuchAlgorithmException { + String signedTransaction = SignedDataCreator.createSignedDataFromJson("models/signedTransactionWithNonAsciiData.json"); + + JWSTransactionDecodedPayload transaction = TestingUtility.getSignedPayloadVerifier().verifyAndDecodeTransaction(signedTransaction); + + AdvancedCommerceTransactionInfo advancedCommerceInfo = transaction.getAdvancedCommerceInfo(); + Assertions.assertEquals("Abonnement Caf\u00e9 \u2014 5,99 \u20ac par mois", advancedCommerceInfo.getDescriptors().getDescription()); + Assertions.assertEquals("Caf\u00e9 Premium", advancedCommerceInfo.getDescriptors().getDisplayName()); + Assertions.assertEquals("\u30d7\u30ec\u30df\u30a2\u30e0\u6a5f\u80fd", advancedCommerceInfo.getItems().get(0).getDescription()); + Assertions.assertEquals("\u30d7\u30ec\u30df\u30a2\u30e0", advancedCommerceInfo.getItems().get(0).getDisplayName()); + } } diff --git a/src/test/resources/models/signedTransactionWithNonAsciiData.json b/src/test/resources/models/signedTransactionWithNonAsciiData.json new file mode 100644 index 00000000..cfdb2c5e --- /dev/null +++ b/src/test/resources/models/signedTransactionWithNonAsciiData.json @@ -0,0 +1,23 @@ +{ + "transactionId":"23456", + "originalTransactionId":"12345", + "bundleId":"com.example", + "productId":"com.example.product", + "purchaseDate":1698148900000, + "signedDate":1698148900000, + "environment":"LocalTesting", + "advancedCommerceInfo": { + "descriptors": { + "description": "Abonnement Café — 5,99 € par mois", + "displayName": "Café Premium" + }, + "items": [ + { + "SKU": "com.example.sku.premium", + "description": "プレミアム機能", + "displayName": "プレミアム", + "price": 9990 + } + ] + } +}