Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c60d292b27 | |||
| 4452349d49 | |||
| d726caa79d |
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// ReceiptDateFormatterTests.swift
|
||||
// AppReceiptValidator
|
||||
//
|
||||
// Created by Hannes Oud on 09.10.20.
|
||||
// Copyright © 2020 IdeasOnCanvas GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
import AppReceiptValidator
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
|
||||
final class ReceiptDateFormatterTests: XCTestCase {
|
||||
|
||||
func testDateFormatting() throws {
|
||||
let dateStrings = [
|
||||
"2020-01-01T12:00:00Z",
|
||||
"2020-01-01T12:00:00.123Z",
|
||||
"2020-01-01T12:00:00.999Z",
|
||||
"2020-01-01T12:00:01Z"
|
||||
]
|
||||
for dateString in dateStrings {
|
||||
let parsed = try XCTUnwrap(AppReceiptValidator.ReceiptDateFormatter.date(from: dateString))
|
||||
XCTAssertEqual(AppReceiptValidator.ReceiptDateFormatter.string(from: parsed), dateString)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
D114544621A6BDE6001BEC61 /* DeviceIdentifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D114544521A6BDE6001BEC61 /* DeviceIdentifierTests.swift */; };
|
||||
D114544721A6BDE6001BEC61 /* DeviceIdentifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D114544521A6BDE6001BEC61 /* DeviceIdentifierTests.swift */; };
|
||||
D11B81CF2530687D00E19863 /* ReceiptDateFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D11B81CE2530687D00E19863 /* ReceiptDateFormatterTests.swift */; };
|
||||
D11B81D02530687D00E19863 /* ReceiptDateFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D11B81CE2530687D00E19863 /* ReceiptDateFormatterTests.swift */; };
|
||||
D1239FFF1F6A7B5000D0421E /* AppleIncRootCertificate.cer in Resources */ = {isa = PBXBuildFile; fileRef = D19095C41F601DEA0095729B /* AppleIncRootCertificate.cer */; };
|
||||
D123A0001F6A7CCF00D0421E /* AppleIncRootCertificate.cer in Resources */ = {isa = PBXBuildFile; fileRef = D19095C41F601DEA0095729B /* AppleIncRootCertificate.cer */; };
|
||||
D13E5B7D20331B9B001880F0 /* DropAcceptingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D13E5B7C20331B9B001880F0 /* DropAcceptingTextView.swift */; };
|
||||
@@ -278,6 +280,7 @@
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
D114544521A6BDE6001BEC61 /* DeviceIdentifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceIdentifierTests.swift; sourceTree = "<group>"; };
|
||||
D11B81CE2530687D00E19863 /* ReceiptDateFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiptDateFormatterTests.swift; sourceTree = "<group>"; };
|
||||
D13E5B7C20331B9B001880F0 /* DropAcceptingTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropAcceptingTextView.swift; sourceTree = "<group>"; };
|
||||
D14FA72E1F6143C400545540 /* Date+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Convenience.swift"; sourceTree = "<group>"; };
|
||||
D14FA7311F61472400545540 /* mac_mindnode_rebought_receipt */ = {isa = PBXFileReference; lastKnownFileType = file; path = mac_mindnode_rebought_receipt; sourceTree = "<group>"; };
|
||||
@@ -606,6 +609,7 @@
|
||||
D1D6F5411F5D8A3800E86FE1 /* AppReceiptValidationTests.swift */,
|
||||
D1AA845A1F6ABB31007F2558 /* AppReceiptPropertyValidationTests.swift */,
|
||||
D150A0ED1F669A880026ED04 /* AppReceiptValidationInAppPurchaseTests.swift */,
|
||||
D11B81CE2530687D00E19863 /* ReceiptDateFormatterTests.swift */,
|
||||
D114544521A6BDE6001BEC61 /* DeviceIdentifierTests.swift */,
|
||||
D1D6F5481F5D9B1100E86FE1 /* Tools */,
|
||||
D1D6F5431F5D8DBC00E86FE1 /* Test Assets */,
|
||||
@@ -1403,6 +1407,7 @@
|
||||
D19095CD1F601E960095729B /* AppReceiptValidationTests.swift in Sources */,
|
||||
D1AA845D1F6ABB59007F2558 /* AppReceiptPropertyValidationTests.swift in Sources */,
|
||||
D150A0EF1F669A880026ED04 /* AppReceiptValidationInAppPurchaseTests.swift in Sources */,
|
||||
D11B81D02530687D00E19863 /* ReceiptDateFormatterTests.swift in Sources */,
|
||||
D114544721A6BDE6001BEC61 /* DeviceIdentifierTests.swift in Sources */,
|
||||
D150A0F01F67E0990026ED04 /* Date+Convenience.swift in Sources */,
|
||||
);
|
||||
@@ -1416,6 +1421,7 @@
|
||||
D19095CE1F601E980095729B /* AppReceiptValidationTests.swift in Sources */,
|
||||
D1AA845C1F6ABB59007F2558 /* AppReceiptPropertyValidationTests.swift in Sources */,
|
||||
D150A0EE1F669A880026ED04 /* AppReceiptValidationInAppPurchaseTests.swift in Sources */,
|
||||
D11B81CF2530687D00E19863 /* ReceiptDateFormatterTests.swift in Sources */,
|
||||
D114544621A6BDE6001BEC61 /* DeviceIdentifierTests.swift in Sources */,
|
||||
D150A0F11F67E0990026ED04 /* Date+Convenience.swift in Sources */,
|
||||
);
|
||||
|
||||
@@ -88,16 +88,6 @@ public struct AppReceiptValidator {
|
||||
let receiptContainer = try self.extractPKCS7Container(data: receiptData)
|
||||
return try parseReceipt(pkcs7: receiptContainer, parseUnofficialParts: true)
|
||||
}
|
||||
|
||||
/// Uses receipt-conform representation of dates like "2017-01-01T12:00:00Z"
|
||||
public static let asn1DateFormatter: DateFormatter = {
|
||||
// Date formatter code from https://www.objc.io/issues/17-security/receipt-validation/#parsing-the-receipt
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
dateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
|
||||
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
return dateFormatter
|
||||
}()
|
||||
}
|
||||
|
||||
// MARK: - Full Validation
|
||||
@@ -351,6 +341,59 @@ private extension AppReceiptValidator {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ReceiptDateFormatter
|
||||
|
||||
extension AppReceiptValidator {
|
||||
|
||||
/// Static formatting methods to use for string encoded date values in receipts
|
||||
public enum ReceiptDateFormatter {
|
||||
|
||||
/// Uses receipt-conform representation of dates like "2017-01-01T12:00:00Z",
|
||||
/// as a fallback, dates like "2017-01-01T12:00:00.123Z" are also parsed.
|
||||
public static func date(from string: String) -> Date? {
|
||||
return self.asn1DateFormatter.date(from: string) // expected
|
||||
?? self.fallbackDateFormatterWithMS.date(from: string) // try again with milliseconds
|
||||
}
|
||||
|
||||
/// Returns receipt-conform string representation of dates like "2017-01-01T12:00:00Z",
|
||||
/// but if the date has sub-second fractions a millisecond representation like "2017-01-01T12:00:00.123Z" is returned.
|
||||
public static func string(from date: Date) -> String {
|
||||
if floor(date.timeIntervalSince1970) == date.timeIntervalSince1970 {
|
||||
// Integer seconds granularity is what we expect
|
||||
return self.asn1DateFormatter.string(from: date)
|
||||
} else {
|
||||
// millis seconds granularity is what we expect
|
||||
return self.fallbackDateFormatterWithMS.string(from: date)
|
||||
}
|
||||
}
|
||||
|
||||
/// Uses receipt-conform representation of dates like "2017-01-01T12:00:00Z"
|
||||
static let asn1DateFormatter: DateFormatter = {
|
||||
// Date formatter code from https://www.objc.io/issues/17-security/receipt-validation/#parsing-the-receipt
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
dateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
|
||||
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
return dateFormatter
|
||||
}()
|
||||
|
||||
/// Uses receipt-conform representation of dates like "2017-01-01T12:00:00.123Z"
|
||||
///
|
||||
/// This is not the officially intended format, but added after hearing reports about new format adding ms https://twitter.com/depth42/status/1314179654811607041
|
||||
private static let fallbackDateFormatterWithMS: DateFormatter = {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
dateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS'Z'"
|
||||
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
return dateFormatter
|
||||
}()
|
||||
}
|
||||
|
||||
/// Uses receipt-conform representation of dates like "2017-01-01T12:00:00Z"
|
||||
@available(*, deprecated, message: "Use AppReceiptValidator.ReceiptDateFormatter.string(from:) or AppReceiptValidator.ReceiptDateFormatter.date(from:) instead, to cover unexpected date formats")
|
||||
public static let asn1DateFormatter: DateFormatter = ReceiptDateFormatter.asn1DateFormatter
|
||||
}
|
||||
|
||||
// MARK: - Result
|
||||
|
||||
extension AppReceiptValidator {
|
||||
|
||||
@@ -153,7 +153,7 @@ extension ASN1Object {
|
||||
var dateValue: Date? {
|
||||
guard let string = self.stringValue else { return nil }
|
||||
|
||||
return AppReceiptValidator.asn1DateFormatter.date(from: string)
|
||||
return AppReceiptValidator.ReceiptDateFormatter.date(from: string)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -294,7 +294,7 @@ private struct StringFormatter {
|
||||
func format(_ date: Date?) -> String {
|
||||
guard let date = date else { return fallback }
|
||||
|
||||
return quoted(AppReceiptValidator.asn1DateFormatter.string(from: date))
|
||||
return quoted(AppReceiptValidator.ReceiptDateFormatter.string(from: date))
|
||||
}
|
||||
|
||||
func format(_ string: String?) -> String {
|
||||
@@ -318,7 +318,7 @@ private func parseBase64(string: String) -> Data? {
|
||||
|
||||
// Parses a string of type "2017-01-01T12:00:00Z"
|
||||
private func parseDate(string: String) -> Date? {
|
||||
guard let date = AppReceiptValidator.asn1DateFormatter.date(from: string) else {
|
||||
guard let date = AppReceiptValidator.ReceiptDateFormatter.date(from: string) else {
|
||||
assertionFailure("Date could not be parsed from string '\(string)', make sure it has a correct format, example `2017-01-01T12:00:00Z`")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public enum KnownUnofficialReceiptAttribute: Int32 {
|
||||
var parsingType: ParsingType {
|
||||
switch self {
|
||||
case .date1, .date2, .date3:
|
||||
return .string
|
||||
return .date
|
||||
case .provisioningType, .ageRating, .clientName:
|
||||
return .string
|
||||
}
|
||||
@@ -115,7 +115,7 @@ extension UnofficialReceipt.Entry.Value: CustomStringConvertible {
|
||||
case .string(let value):
|
||||
return "\"\(value)\""
|
||||
case .date(let date):
|
||||
return AppReceiptValidator.asn1DateFormatter.string(from: date)
|
||||
return AppReceiptValidator.ReceiptDateFormatter.string(from: date)
|
||||
case .bytes(let bytes):
|
||||
if bytes.count == 2 && bytes.first == 12 && bytes.dropFirst().first == 0 {
|
||||
return "2 bytes (12, 0)"
|
||||
|
||||
Reference in New Issue
Block a user