Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 60f1b27ec4 | |||
| 6f15dda97a | |||
| f7c3e26500 | |||
| 61794a63e3 | |||
| 137a767294 | |||
| 11b8faf96c | |||
| ee521f9429 | |||
| bf7010545b | |||
| 6a1b919093 | |||
| e8146f7122 | |||
| 2f54c5b042 | |||
| f0ba73b45e | |||
| 0ff100054d | |||
| 15e3bb5471 | |||
| c42f8d9ac9 | |||
| 0efe3dd839 | |||
| cb4690217b | |||
| 8266af7edb | |||
| 51d190ece0 | |||
| 26385ee11b | |||
| a96f16969d | |||
| 1ba0b31095 | |||
| 5121e901f5 | |||
| d31ab05fe2 | |||
| c4769bc42a | |||
| 6605544598 | |||
| 3bb92db368 | |||
| 97b0adff0a | |||
| bf875864ea | |||
| c536866906 | |||
| ede64d2e3b | |||
| 112a5a9e4f | |||
| 7d8cb7bd06 | |||
| da06a099ce | |||
| 95337f7544 | |||
| 7ee443a4af | |||
| ae082be181 | |||
| bcb0ee2c27 | |||
| f8dae53736 | |||
| 5c90e58864 | |||
| df47710990 | |||
| 9b03d4b850 | |||
| 06b43589d6 | |||
| 1722f04928 | |||
| 16ef7de4bf | |||
| 7521a419d1 | |||
| 5f0ad3de3f | |||
| 40eeef07a5 | |||
| 11563b5094 | |||
| 35815fcd7c | |||
| 17c598d414 | |||
| cff64329b3 |
+7
-7
@@ -1,18 +1,18 @@
|
||||
language: objective-c
|
||||
osx_image: xcode9.2
|
||||
osx_image: xcode10.2
|
||||
|
||||
env:
|
||||
global:
|
||||
- LC_CTYPE=en_US.UTF-8
|
||||
- LANG=en_US.UTF-8
|
||||
- PROJECT=AppReceiptValidator/AppReceiptValidator.xcodeproj
|
||||
|
||||
matrix:
|
||||
- DESTINATION="OS=11.2,name=iPhone X" SCHEME="AppReceiptValidator Demo iOS"
|
||||
- LC_CTYPE=en_US.UTF-8
|
||||
- LANG=en_US.UTF-8
|
||||
- PROJECT=AppReceiptValidator/AppReceiptValidator.xcodeproj
|
||||
matrix:
|
||||
- DESTINATION="OS=12.2,name=iPhone X" SCHEME="AppReceiptValidator Demo iOS"
|
||||
- DESTINATION="arch=x86_64" SCHEME="AppReceiptValidator Demo macOS"
|
||||
|
||||
script:
|
||||
- set -o pipefail
|
||||
- xcodebuild -version
|
||||
- xcodebuild -showsdks
|
||||
- echo "PROJECT $PROJECT, SCHEME $SCHEME, DESTINATION $DESTINATION"
|
||||
- xcodebuild -project "$PROJECT" -scheme "$SCHEME" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES test | xcpretty;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
sources:
|
||||
- AppReceiptValidator
|
||||
- Sourcery/Protocols
|
||||
templates:
|
||||
- Sourcery/Templates
|
||||
output:
|
||||
Sourcery/Generated
|
||||
@@ -4,7 +4,7 @@ disabled_rules:
|
||||
- nesting
|
||||
- todo
|
||||
excluded:
|
||||
- Sourcery/Generated
|
||||
- excluded_dir_example
|
||||
file_length:
|
||||
warning: 500
|
||||
large_tuple:
|
||||
|
||||
@@ -13,7 +13,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,8 @@
|
||||
import AppReceiptValidator
|
||||
import UIKit
|
||||
|
||||
|
||||
// MARK: - ViewController
|
||||
|
||||
/// Displays two textfields. One to paste a receipt into as base64 string, the other displaying the parsed receipt.
|
||||
/// A device identifier for validation is not supported, have a look at the mac demo instead.
|
||||
class ViewController: UIViewController, UITextViewDelegate {
|
||||
|
||||
@IBOutlet private var inputTextView: UITextView!
|
||||
@@ -24,8 +23,8 @@ class ViewController: UIViewController, UITextViewDelegate {
|
||||
self.inputTextView.delegate = self
|
||||
self.inputTextView.text = ""
|
||||
self.outputTextView.text = "Parsed Receipt will be shown here"
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(triggerAutoPaste), name: .UIApplicationWillEnterForeground, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(triggerAutoPaste), name: .UIPasteboardChanged, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(triggerAutoPaste), name: UIApplication.willEnterForegroundNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(triggerAutoPaste), name: UIPasteboard.changedNotification, object: nil)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
|
||||
+1
-6
@@ -1,10 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict/>
|
||||
</plist>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@@ -686,7 +685,7 @@
|
||||
<scene sceneID="R2V-B0-nI4">
|
||||
<objects>
|
||||
<windowController id="B8D-0N-5wS" sceneMemberID="viewController">
|
||||
<window key="window" title="AppReceiptValidator Receipt Parser" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="IQv-IB-iLA">
|
||||
<window key="window" title="AppReceiptValidator Receipt Parser" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="IQv-IB-iLA">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
|
||||
@@ -712,78 +711,93 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<scrollView horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ynK-dw-fFc">
|
||||
<rect key="frame" x="0.0" y="0.0" width="316" height="270"/>
|
||||
<clipView key="contentView" id="yEL-yO-YvB">
|
||||
<rect key="frame" x="1" y="1" width="314" height="268"/>
|
||||
<rect key="frame" x="0.0" y="22" width="316" height="248"/>
|
||||
<clipView key="contentView" drawsBackground="NO" id="yEL-yO-YvB">
|
||||
<rect key="frame" x="1" y="1" width="314" height="246"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView toolTip="Paste Base64 here" importsGraphics="NO" richText="NO" verticallyResizable="YES" usesFontPanel="YES" findStyle="panel" allowsCharacterPickerTouchBarItem="NO" allowsUndo="YES" usesRuler="YES" allowsNonContiguousLayout="YES" textCompletion="NO" id="0xW-mT-lME" customClass="DropAcceptingTextView" customModule="AppReceiptValidator_Demo_macOS" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="314" height="268"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="314" height="246"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<size key="minSize" width="314" height="268"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<size key="minSize" width="314" height="246"/>
|
||||
<size key="maxSize" width="629" height="10000000"/>
|
||||
<color key="insertionPointColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="TaD-v9-pOg">
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="TaD-v9-pOg">
|
||||
<rect key="frame" x="-100" y="-100" width="87" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="hkR-e4-WtN">
|
||||
<rect key="frame" x="299" y="1" width="16" height="268"/>
|
||||
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="hkR-e4-WtN">
|
||||
<rect key="frame" x="299" y="1" width="16" height="246"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<scrollView toolTip="Parsed Receipt will be shown here" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6mt-ay-mAL">
|
||||
<rect key="frame" x="316" y="0.0" width="315" height="270"/>
|
||||
<clipView key="contentView" id="YwZ-F9-Cvh">
|
||||
<clipView key="contentView" drawsBackground="NO" id="YwZ-F9-Cvh">
|
||||
<rect key="frame" x="1" y="1" width="313" height="268"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView importsGraphics="NO" richText="NO" verticallyResizable="YES" usesFontPanel="YES" findStyle="panel" allowsCharacterPickerTouchBarItem="NO" allowsUndo="YES" usesRuler="YES" allowsNonContiguousLayout="YES" textCompletion="NO" id="GHT-gS-G1g" customClass="TextView" customModule="AppReceiptValidator_Demo_macOS" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="313" height="268"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<size key="minSize" width="313" height="268"/>
|
||||
<size key="maxSize" width="463" height="10000000"/>
|
||||
<color key="insertionPointColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="insertionPointColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
</textView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="xmd-vm-w1P">
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="xmd-vm-w1P">
|
||||
<rect key="frame" x="-100" y="-100" width="87" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="uk9-Sg-RUp">
|
||||
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="uk9-Sg-RUp">
|
||||
<rect key="frame" x="298" y="1" width="16" height="268"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="yvX-By-nNw">
|
||||
<rect key="frame" x="0.0" y="0.0" width="316" height="22"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Optional DeviceIdentifier for Validation (MAC Address, UUID, Base64)" drawsBackground="YES" id="Z2r-sB-vIS">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<action selector="identifierDidChange:" target="XfG-lQ-9wD" id="ccM-mH-NPQ"/>
|
||||
</connections>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="6mt-ay-mAL" firstAttribute="leading" secondItem="yvX-By-nNw" secondAttribute="trailing" id="231-cE-39Q"/>
|
||||
<constraint firstItem="6mt-ay-mAL" firstAttribute="top" secondItem="m2S-Jp-Qdl" secondAttribute="top" id="CdY-lv-wee"/>
|
||||
<constraint firstItem="ynK-dw-fFc" firstAttribute="leading" secondItem="m2S-Jp-Qdl" secondAttribute="leading" id="Cvz-FD-lzg"/>
|
||||
<constraint firstItem="yvX-By-nNw" firstAttribute="top" secondItem="ynK-dw-fFc" secondAttribute="bottom" id="KSy-yc-TCD"/>
|
||||
<constraint firstItem="ynK-dw-fFc" firstAttribute="top" secondItem="m2S-Jp-Qdl" secondAttribute="top" id="Rno-M6-Pjq"/>
|
||||
<constraint firstItem="6mt-ay-mAL" firstAttribute="width" secondItem="m2S-Jp-Qdl" secondAttribute="width" multiplier="1:2" id="SWu-Yn-pVc"/>
|
||||
<constraint firstAttribute="bottom" secondItem="yvX-By-nNw" secondAttribute="bottom" id="WOQ-2e-jWj"/>
|
||||
<constraint firstAttribute="trailing" secondItem="6mt-ay-mAL" secondAttribute="trailing" id="Yun-Rd-wsm"/>
|
||||
<constraint firstItem="ynK-dw-fFc" firstAttribute="top" secondItem="m2S-Jp-Qdl" secondAttribute="top" id="gIn-Ps-HEe"/>
|
||||
<constraint firstAttribute="bottom" secondItem="6mt-ay-mAL" secondAttribute="bottom" id="lKc-RV-ybx"/>
|
||||
<constraint firstAttribute="bottom" secondItem="ynK-dw-fFc" secondAttribute="bottom" id="qAW-TV-hHz"/>
|
||||
<constraint firstItem="yvX-By-nNw" firstAttribute="leading" secondItem="m2S-Jp-Qdl" secondAttribute="leading" id="s2r-QB-ZQb"/>
|
||||
<constraint firstItem="ynK-dw-fFc" firstAttribute="width" secondItem="m2S-Jp-Qdl" secondAttribute="width" multiplier="1:2" id="wsI-iB-DvJ"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="dropReceivingView" destination="0xW-mT-lME" id="cuR-H1-BKN"/>
|
||||
<outlet property="identifierTextField" destination="yvX-By-nNw" id="pW5-lJ-8Cy"/>
|
||||
<outlet property="inputTextView" destination="0xW-mT-lME" id="8Y7-yb-63r"/>
|
||||
<outlet property="outputTextView" destination="GHT-gS-G1g" id="cVN-4m-knJ"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="76" y="726"/>
|
||||
<point key="canvasLocation" x="75.5" y="726"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
|
||||
@@ -32,7 +32,7 @@ final class DropAcceptingTextView: NSTextView {
|
||||
}
|
||||
|
||||
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
|
||||
if let fileURL = sender.fileURLs.first {
|
||||
if let fileURL = sender.fileURLs.first {
|
||||
self.handleDroppedFile?(fileURL)
|
||||
return true
|
||||
}
|
||||
@@ -43,7 +43,7 @@ final class DropAcceptingTextView: NSTextView {
|
||||
fileprivate extension NSDraggingInfo {
|
||||
|
||||
var fileURLs: [URL] {
|
||||
let asStrings = self.draggingPasteboard().propertyList(forType: makeFileNameType()) as? [String] ?? []
|
||||
let asStrings = self.draggingPasteboard.propertyList(forType: makeFileNameType()) as? [String] ?? []
|
||||
return asStrings.map { URL(fileURLWithPath: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
import AppReceiptValidator
|
||||
import Cocoa
|
||||
|
||||
/// Displays two textfields. One to paste a receipt into as base64 string, the other displaying the parsed receipt.
|
||||
/// A device identifier can be added in a third field, which is then used to validate the receipt.
|
||||
class ViewController: NSViewController, NSTextViewDelegate, NSTextFieldDelegate {
|
||||
|
||||
// MARK: - ViewController
|
||||
|
||||
class ViewController: NSViewController, NSTextViewDelegate {
|
||||
|
||||
private var textFieldObserver: Any?
|
||||
@IBOutlet private var inputTextView: NSTextView!
|
||||
@IBOutlet private var identifierTextField: NSTextField!
|
||||
@IBOutlet private var outputTextView: NSTextView!
|
||||
@IBOutlet private var dropReceivingView: DropAcceptingTextView!
|
||||
|
||||
@@ -28,12 +29,24 @@ class ViewController: NSViewController, NSTextViewDelegate {
|
||||
self.dropReceivingView.handleDroppedFile = { [unowned self] url in
|
||||
self.update(url: url)
|
||||
}
|
||||
self.textFieldObserver = NotificationCenter.default.addObserver(forName: NSTextField.textDidChangeNotification, object: self.identifierTextField, queue: .main) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.identifierDidChange(self.identifierTextField)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSTextViewDelegate
|
||||
|
||||
func textDidChange(_ notification: Notification) {
|
||||
let string = inputTextView.string
|
||||
let string = self.inputTextView.string
|
||||
self.update(base64String: string)
|
||||
}
|
||||
|
||||
// MARK: - Identifier Textfield
|
||||
|
||||
@IBAction func identifierDidChange(_ sender: NSTextField) {
|
||||
let string = self.inputTextView.string
|
||||
self.update(base64String: string)
|
||||
}
|
||||
|
||||
@@ -57,7 +70,8 @@ private extension ViewController {
|
||||
}
|
||||
do {
|
||||
let result = try AppReceiptValidator().parseUnofficialReceipt(origin: .data(data))
|
||||
self.render(string: "\(result.receipt)\n\(result.unofficialReceipt)")
|
||||
let validationResult = self.validateReceiptIfNecessary(data: data, macAddress: self.identifierTextField.stringValue) ?? "<Receipt not Validated, No Identifier provided, Supported: UUID, base64, MAC-Address>"
|
||||
self.render(string: "\(validationResult)\n\n✅ Receipt Parsed\n\(result.receipt)\n\(result.unofficialReceipt)")
|
||||
} catch {
|
||||
self.render(string: "\(error)")
|
||||
}
|
||||
@@ -79,6 +93,26 @@ private extension ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
func validateReceiptIfNecessary(data: Data, macAddress: String?) -> String? {
|
||||
guard let macAddress = macAddress, macAddress.isEmpty == false else { return nil }
|
||||
|
||||
let sanitized = macAddress.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let deviceIdentifier = AppReceiptValidator.Parameters.DeviceIdentifier(macAddress: sanitized) ??
|
||||
(UUID(uuidString: sanitized).flatMap { AppReceiptValidator.Parameters.DeviceIdentifier(uuid: $0) }) ??
|
||||
AppReceiptValidator.Parameters.DeviceIdentifier(base64Encoded: sanitized)
|
||||
guard let identifier = deviceIdentifier else { return "<Receipt not Validated\nIdentifier not parseable>" }
|
||||
|
||||
let parameters = AppReceiptValidator.Parameters(receiptOrigin: .data(data), shouldValidateSignaturePresence: true, signatureValidation: .shouldValidate(rootCertificateOrigin: .cerFileBundledWithAppReceiptValidator), shouldValidateHash: true, deviceIdentifier: identifier, propertyValidations: [])
|
||||
let result = AppReceiptValidator().validateReceipt(parameters: parameters)
|
||||
|
||||
switch result {
|
||||
case .success:
|
||||
return "✅ Receipt Validated\nSignature and Hash Check successful"
|
||||
case .error(let error, _, _):
|
||||
return "❌ Receipt Invalid\n\(error)"
|
||||
}
|
||||
}
|
||||
|
||||
func render(string: String) {
|
||||
self.outputTextView.string = string
|
||||
}
|
||||
|
||||
+17
-17
@@ -22,7 +22,6 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
} catch {
|
||||
XCTFail("Unexpectedly failed parsing a receipt \(error)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func testNonMindNodeReceiptParsingWithMultipleInAppPurchases() {
|
||||
@@ -55,7 +54,7 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
expirationDate: nil,
|
||||
inAppPurchaseReceipts: [
|
||||
InAppPurchaseReceipt(
|
||||
quantity: nil,
|
||||
quantity: 1,
|
||||
productIdentifier: "consumable",
|
||||
transactionIdentifier: "1000000166865231",
|
||||
originalTransactionIdentifier: "1000000166865231",
|
||||
@@ -63,10 +62,10 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-07T20:37:55Z"),
|
||||
subscriptionExpirationDate: nil,
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: nil
|
||||
webOrderLineItemId: 0
|
||||
),
|
||||
InAppPurchaseReceipt(
|
||||
quantity: nil,
|
||||
quantity: 1,
|
||||
productIdentifier: "monthly",
|
||||
transactionIdentifier: "1000000166965150",
|
||||
originalTransactionIdentifier: "1000000166965150",
|
||||
@@ -74,10 +73,10 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-10T06:49:33Z"),
|
||||
subscriptionExpirationDate: Date.demoDate(string: "2015-08-10T06:54:32Z"),
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: nil
|
||||
webOrderLineItemId: 1000000030274153
|
||||
),
|
||||
InAppPurchaseReceipt( // restores
|
||||
quantity: nil,
|
||||
InAppPurchaseReceipt(
|
||||
quantity: 1,
|
||||
productIdentifier: "monthly",
|
||||
transactionIdentifier: "1000000166965327",
|
||||
originalTransactionIdentifier: "1000000166965150",
|
||||
@@ -85,10 +84,10 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-10T06:53:18Z"),
|
||||
subscriptionExpirationDate: Date.demoDate(string: "2015-08-10T06:59:32Z"),
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: nil
|
||||
webOrderLineItemId: 1000000030274154
|
||||
),
|
||||
InAppPurchaseReceipt(
|
||||
quantity: nil,
|
||||
quantity: 1,
|
||||
productIdentifier: "monthly",
|
||||
transactionIdentifier: "1000000166965895",
|
||||
originalTransactionIdentifier: "1000000166965150",
|
||||
@@ -96,10 +95,10 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-10T06:57:34Z"),
|
||||
subscriptionExpirationDate: Date.demoDate(string: "2015-08-10T07:04:32Z"),
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: nil
|
||||
webOrderLineItemId: 1000000030274165
|
||||
),
|
||||
InAppPurchaseReceipt(
|
||||
quantity: nil,
|
||||
quantity: 1,
|
||||
productIdentifier: "monthly",
|
||||
transactionIdentifier: "1000000166967152",
|
||||
originalTransactionIdentifier: "1000000166965150",
|
||||
@@ -107,10 +106,10 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-10T07:02:33Z"),
|
||||
subscriptionExpirationDate: Date.demoDate(string: "2015-08-10T07:09:32Z"),
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: nil
|
||||
webOrderLineItemId: 1000000030274192
|
||||
),
|
||||
InAppPurchaseReceipt(
|
||||
quantity: nil,
|
||||
quantity: 1,
|
||||
productIdentifier: "monthly",
|
||||
transactionIdentifier: "1000000166967484",
|
||||
originalTransactionIdentifier: "1000000166965150",
|
||||
@@ -118,10 +117,10 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-10T07:08:30Z"),
|
||||
subscriptionExpirationDate: Date.demoDate(string: "2015-08-10T07:14:32Z"),
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: nil
|
||||
webOrderLineItemId: 1000000030274219
|
||||
),
|
||||
InAppPurchaseReceipt(
|
||||
quantity: nil,
|
||||
quantity: 1,
|
||||
productIdentifier: "monthly",
|
||||
transactionIdentifier: "1000000166967782",
|
||||
originalTransactionIdentifier: "1000000166965150",
|
||||
@@ -129,8 +128,9 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-10T07:12:34Z"),
|
||||
subscriptionExpirationDate: Date.demoDate(string: "2015-08-10T07:19:32Z"),
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: nil
|
||||
webOrderLineItemId: 1000000030274249
|
||||
)
|
||||
])
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ class AppReceiptValidationTests: XCTestCase {
|
||||
_ = try receiptValidator.parseReceipt(origin: .data(data))
|
||||
XCTFail("Unexpectedly succeeded in parsing a non-receipt")
|
||||
} catch {
|
||||
guard let e = error as? AppReceiptValidator.Error, e == AppReceiptValidator.Error.emptyReceiptContents else {
|
||||
guard error as? AppReceiptValidator.Error == AppReceiptValidator.Error.emptyReceiptContents else {
|
||||
XCTFail("Unexpected error, expeced .emptyReceiptContents, got \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// DeviceIdentifierTests.swift
|
||||
// AppReceiptValidator
|
||||
//
|
||||
// Created by Hannes Oud on 22.11.18.
|
||||
// Copyright © 2018 IdeasOnCanvas GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
@testable import AppReceiptValidator
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
final class DeviceIdentifierTests: XCTestCase {
|
||||
|
||||
func testBase64Initializer() {
|
||||
let deviceIdentifier = AppReceiptValidator.Parameters.DeviceIdentifier(base64Encoded: "bEAItZRe")
|
||||
XCTAssertNotNil(deviceIdentifier)
|
||||
}
|
||||
|
||||
func testMacAddressInitializer() {
|
||||
XCTAssertNotNil(AppReceiptValidator.Parameters.DeviceIdentifier(macAddress: "00:0d:3f:cd:02:5f"))
|
||||
}
|
||||
|
||||
func testMacAddressInitializerOtherSeparator() {
|
||||
XCTAssertNotNil(AppReceiptValidator.Parameters.DeviceIdentifier(macAddress: "00-0d-3f-cd-02-5f", separator: "-"))
|
||||
}
|
||||
|
||||
func testUUIDInitializer() {
|
||||
XCTAssertNotNil(AppReceiptValidator.Parameters.DeviceIdentifier(uuid: UUID()))
|
||||
}
|
||||
|
||||
func testCurrent() {
|
||||
let deviceIdentifierData = AppReceiptValidator.Parameters.DeviceIdentifier.currentDevice.getData()
|
||||
XCTAssertNotNil(deviceIdentifierData)
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
func testMacAddressRetrieval() {
|
||||
guard let (data, string) = AppReceiptValidator.Parameters.DeviceIdentifier.getPrimaryNetworkMACAddress() else {
|
||||
XCTFail("Failed to get device mac address")
|
||||
return
|
||||
}
|
||||
guard let deviceIdentifierData = AppReceiptValidator.Parameters.DeviceIdentifier.currentDevice.getData() else {
|
||||
XCTFail("Failed to get device mac address")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(data, deviceIdentifierData)
|
||||
guard let deviceIdentifierFromString = AppReceiptValidator.Parameters.DeviceIdentifier(macAddress: string) else {
|
||||
XCTFail("Failed to get device identifier from mac address string")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(deviceIdentifierFromString.getData(), deviceIdentifierData)
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -47,8 +47,8 @@ extension KnownOrUnknown: RawRepresentable {
|
||||
|
||||
extension KnownOrUnknown: Hashable {
|
||||
|
||||
public var hashValue: Int {
|
||||
return self.rawValue.hashValue
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(self.rawValue.hashValue)
|
||||
}
|
||||
|
||||
public static func == (lhs: KnownOrUnknown<Known>, rhs: KnownOrUnknown<Known>) -> Bool {
|
||||
|
||||
@@ -7,13 +7,11 @@
|
||||
objects = {
|
||||
|
||||
/* 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 */; };
|
||||
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 */; };
|
||||
D14FA7261F61350F00545540 /* AutoEquatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14FA71F1F6134CF00545540 /* AutoEquatable.swift */; };
|
||||
D14FA7271F61351000545540 /* AutoEquatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14FA71F1F6134CF00545540 /* AutoEquatable.swift */; };
|
||||
D14FA7291F61351400545540 /* AutoEquatable.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14FA71E1F6134CF00545540 /* AutoEquatable.generated.swift */; };
|
||||
D14FA72A1F61351500545540 /* AutoEquatable.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14FA71E1F6134CF00545540 /* AutoEquatable.generated.swift */; };
|
||||
D14FA7321F61476700545540 /* mac_mindnode_rebought_receipt in Resources */ = {isa = PBXBuildFile; fileRef = D14FA7311F61472400545540 /* mac_mindnode_rebought_receipt */; };
|
||||
D14FA7331F61476800545540 /* mac_mindnode_rebought_receipt in Resources */ = {isa = PBXBuildFile; fileRef = D14FA7311F61472400545540 /* mac_mindnode_rebought_receipt */; };
|
||||
D14FA7381F6181C700545540 /* OpenSSLWrappers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14FA7371F6181C700545540 /* OpenSSLWrappers.swift */; };
|
||||
@@ -279,10 +277,8 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
D114544521A6BDE6001BEC61 /* DeviceIdentifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceIdentifierTests.swift; sourceTree = "<group>"; };
|
||||
D13E5B7C20331B9B001880F0 /* DropAcceptingTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropAcceptingTextView.swift; sourceTree = "<group>"; };
|
||||
D14FA71E1F6134CF00545540 /* AutoEquatable.generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoEquatable.generated.swift; sourceTree = "<group>"; };
|
||||
D14FA71F1F6134CF00545540 /* AutoEquatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoEquatable.swift; sourceTree = "<group>"; };
|
||||
D14FA7221F6134CF00545540 /* AutoEquatable.stencil */ = {isa = PBXFileReference; lastKnownFileType = text; path = AutoEquatable.stencil; 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>"; };
|
||||
D14FA7371F6181C700545540 /* OpenSSLWrappers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSSLWrappers.swift; sourceTree = "<group>"; };
|
||||
@@ -542,40 +538,6 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
D14FA71B1F6134CF00545540 /* Sourcery */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D14FA7231F6134EC00545540 /* Protocols */,
|
||||
D14FA71D1F6134CF00545540 /* Generated */,
|
||||
D14FA7201F6134CF00545540 /* Templates */,
|
||||
);
|
||||
path = Sourcery;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D14FA71D1F6134CF00545540 /* Generated */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D14FA71E1F6134CF00545540 /* AutoEquatable.generated.swift */,
|
||||
);
|
||||
path = Generated;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D14FA7201F6134CF00545540 /* Templates */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D14FA7221F6134CF00545540 /* AutoEquatable.stencil */,
|
||||
);
|
||||
path = Templates;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D14FA7231F6134EC00545540 /* Protocols */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D14FA71F1F6134CF00545540 /* AutoEquatable.swift */,
|
||||
);
|
||||
path = Protocols;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D14FA7341F614A9C00545540 /* OpenSSL */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -644,6 +606,7 @@
|
||||
D1D6F5411F5D8A3800E86FE1 /* AppReceiptValidationTests.swift */,
|
||||
D1AA845A1F6ABB31007F2558 /* AppReceiptPropertyValidationTests.swift */,
|
||||
D150A0ED1F669A880026ED04 /* AppReceiptValidationInAppPurchaseTests.swift */,
|
||||
D114544521A6BDE6001BEC61 /* DeviceIdentifierTests.swift */,
|
||||
D1D6F5481F5D9B1100E86FE1 /* Tools */,
|
||||
D1D6F5431F5D8DBC00E86FE1 /* Test Assets */,
|
||||
);
|
||||
@@ -851,7 +814,6 @@
|
||||
D1D6F4921F5D67E600E86FE1 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D14FA71B1F6134CF00545540 /* Sourcery */,
|
||||
D1D6F4FC1F5D696800E86FE1 /* AppReceiptValidator */,
|
||||
D1D6F4E51F5D691400E86FE1 /* AppReceiptValidator Demo iOS */,
|
||||
D19095821F6000A40095729B /* AppReceiptValidator Demo macOS */,
|
||||
@@ -1135,6 +1097,7 @@
|
||||
D190957D1F6000A40095729B /* Sources */,
|
||||
D190957E1F6000A40095729B /* Frameworks */,
|
||||
D190957F1F6000A40095729B /* Resources */,
|
||||
D1DEE48D20EBAEE800F95036 /* Swiftlint */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -1190,7 +1153,6 @@
|
||||
D1D6F4B11F5D684C00E86FE1 /* Frameworks */,
|
||||
D1D6F4B21F5D684C00E86FE1 /* Headers */,
|
||||
D1D6F4B31F5D684C00E86FE1 /* Resources */,
|
||||
D1D6F52D1F5D872200E86FE1 /* Swiftlint */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -1209,7 +1171,6 @@
|
||||
D1D6F4BE1F5D687400E86FE1 /* Frameworks */,
|
||||
D1D6F4BF1F5D687400E86FE1 /* Headers */,
|
||||
D1D6F4C01F5D687400E86FE1 /* Resources */,
|
||||
D1D6F52E1F5D872B00E86FE1 /* Swiftlint */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -1228,6 +1189,7 @@
|
||||
D1D6F4E11F5D691400E86FE1 /* Frameworks */,
|
||||
D1D6F4E21F5D691400E86FE1 /* Resources */,
|
||||
D1D6F5071F5D696800E86FE1 /* Embed Frameworks */,
|
||||
D1DEE48C20EBAEC800F95036 /* Swiftlint */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -1246,36 +1208,42 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0900;
|
||||
LastUpgradeCheck = 0900;
|
||||
LastUpgradeCheck = 0930;
|
||||
ORGANIZATIONNAME = "IdeasOnCanvas GmbH";
|
||||
TargetAttributes = {
|
||||
D19095801F6000A40095729B = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.Sandbox = {
|
||||
enabled = 0;
|
||||
};
|
||||
};
|
||||
};
|
||||
D19095911F6000A40095729B = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Automatic;
|
||||
TestTargetID = D19095801F6000A40095729B;
|
||||
};
|
||||
D19095A81F6001800095729B = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Automatic;
|
||||
TestTargetID = D1D6F4E31F5D691400E86FE1;
|
||||
};
|
||||
D1D6F4B41F5D684C00E86FE1 = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
LastSwiftMigration = 0900;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
D1D6F4C11F5D687400E86FE1 = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
LastSwiftMigration = 0900;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
D1D6F4E31F5D691400E86FE1 = {
|
||||
CreatedOnToolsVersion = 9.0;
|
||||
LastSwiftMigration = 0900;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
@@ -1376,33 +1344,43 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
D1D6F52D1F5D872200E86FE1 /* Swiftlint */ = {
|
||||
D1DEE48C20EBAEC800F95036 /* Swiftlint */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"$(SRCROOT)",
|
||||
);
|
||||
name = Swiftlint;
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi";
|
||||
shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
|
||||
};
|
||||
D1D6F52E1F5D872B00E86FE1 /* Swiftlint */ = {
|
||||
D1DEE48D20EBAEE800F95036 /* Swiftlint */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"$(SRCROOT)",
|
||||
);
|
||||
name = Swiftlint;
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi";
|
||||
shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
@@ -1425,6 +1403,7 @@
|
||||
D19095CD1F601E960095729B /* AppReceiptValidationTests.swift in Sources */,
|
||||
D1AA845D1F6ABB59007F2558 /* AppReceiptPropertyValidationTests.swift in Sources */,
|
||||
D150A0EF1F669A880026ED04 /* AppReceiptValidationInAppPurchaseTests.swift in Sources */,
|
||||
D114544721A6BDE6001BEC61 /* DeviceIdentifierTests.swift in Sources */,
|
||||
D150A0F01F67E0990026ED04 /* Date+Convenience.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1437,6 +1416,7 @@
|
||||
D19095CE1F601E980095729B /* AppReceiptValidationTests.swift in Sources */,
|
||||
D1AA845C1F6ABB59007F2558 /* AppReceiptPropertyValidationTests.swift in Sources */,
|
||||
D150A0EE1F669A880026ED04 /* AppReceiptValidationInAppPurchaseTests.swift in Sources */,
|
||||
D114544621A6BDE6001BEC61 /* DeviceIdentifierTests.swift in Sources */,
|
||||
D150A0F11F67E0990026ED04 /* Date+Convenience.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1449,8 +1429,6 @@
|
||||
D1D6F53F1F5D89D000E86FE1 /* AppReceiptValidator.swift in Sources */,
|
||||
D1FE34401F604F540029576B /* AppReceiptValidator+Parameters.swift in Sources */,
|
||||
D19095C31F6019FC0095729B /* DeviceIdentifier+installedDeviceIdentifier_iOS.swift in Sources */,
|
||||
D14FA7271F61351000545540 /* AutoEquatable.swift in Sources */,
|
||||
D14FA7291F61351400545540 /* AutoEquatable.generated.swift in Sources */,
|
||||
D1DFC5DA20037B8400C7B99B /* KnownOrUnknown.swift in Sources */,
|
||||
D1FE343D1F604F020029576B /* Receipt.swift in Sources */,
|
||||
D14FA73B1F618B0100545540 /* ASN1Helpers.swift in Sources */,
|
||||
@@ -1468,11 +1446,9 @@
|
||||
D1FE34411F604F540029576B /* AppReceiptValidator+Parameters.swift in Sources */,
|
||||
D19095C01F60158B0095729B /* DeviceIdentifier+installedDeviceIdentifier_macOS.swift in Sources */,
|
||||
D1FE343E1F604F020029576B /* Receipt.swift in Sources */,
|
||||
D14FA72A1F61351500545540 /* AutoEquatable.generated.swift in Sources */,
|
||||
D1DFC5DB20037B8400C7B99B /* KnownOrUnknown.swift in Sources */,
|
||||
D14FA73C1F618B0100545540 /* ASN1Helpers.swift in Sources */,
|
||||
D14FA7391F6181D000545540 /* OpenSSLWrappers.swift in Sources */,
|
||||
D14FA7261F61350F00545540 /* AutoEquatable.swift in Sources */,
|
||||
D19095BD1F6004D10095729B /* pkcs7_union_accessors.c in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1543,17 +1519,17 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "AppReceiptValidator Demo macOS/AppReceiptValidator_Demo_macOS.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = W6L39UYL6Z;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = "AppReceiptValidator Demo macOS/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ideasoncanvas.AppReceiptValidator-Demo-macOS";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = macosx;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -1561,17 +1537,17 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "AppReceiptValidator Demo macOS/AppReceiptValidator_Demo_macOS.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = W6L39UYL6Z;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = "AppReceiptValidator Demo macOS/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ideasoncanvas.AppReceiptValidator-Demo-macOS";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = macosx;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -1579,19 +1555,17 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = W6L39UYL6Z;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = "AppReceiptValidator Tests macOS/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ideasoncanvas.AppReceiptValidator-Demo-macOSTests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AppReceiptValidator Demo macOS.app/Contents/MacOS/AppReceiptValidator Demo macOS";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -1599,19 +1573,17 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = W6L39UYL6Z;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = "AppReceiptValidator Tests macOS/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ideasoncanvas.AppReceiptValidator-Demo-macOSTests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AppReceiptValidator Demo macOS.app/Contents/MacOS/AppReceiptValidator Demo macOS";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -1619,14 +1591,13 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = W6L39UYL6Z;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = "AppReceiptValidator Tests iOS/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ideasoncanvas.AppReceiptValidator-iOSTests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AppReceiptValidator Demo iOS.app/AppReceiptValidator Demo iOS";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -1634,14 +1605,13 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = W6L39UYL6Z;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = "AppReceiptValidator Tests iOS/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ideasoncanvas.AppReceiptValidator-iOSTests";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AppReceiptValidator Demo iOS.app/AppReceiptValidator Demo iOS";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -1659,6 +1629,7 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -1666,6 +1637,7 @@
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
@@ -1674,7 +1646,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -1718,6 +1690,7 @@
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
@@ -1725,6 +1698,7 @@
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
@@ -1733,7 +1707,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
@@ -1759,12 +1733,13 @@
|
||||
D1D6F4BB1F5D684C00E86FE1 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = W6L39UYL6Z;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
@@ -1779,7 +1754,9 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ideasoncanvas.AppReceiptValidator;
|
||||
PRODUCT_NAME = AppReceiptValidator;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
@@ -1789,12 +1766,13 @@
|
||||
D1D6F4BC1F5D684C00E86FE1 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = W6L39UYL6Z;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
@@ -1809,6 +1787,8 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ideasoncanvas.AppReceiptValidator;
|
||||
PRODUCT_NAME = AppReceiptValidator;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
@@ -1818,12 +1798,13 @@
|
||||
D1D6F4C81F5D687400E86FE1 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = W6L39UYL6Z;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
@@ -1840,6 +1821,7 @@
|
||||
PRODUCT_NAME = AppReceiptValidator;
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
@@ -1848,12 +1830,13 @@
|
||||
D1D6F4C91F5D687400E86FE1 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = W6L39UYL6Z;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
@@ -1870,6 +1853,7 @@
|
||||
PRODUCT_NAME = AppReceiptValidator;
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
};
|
||||
@@ -1881,13 +1865,13 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = W6L39UYL6Z;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = "AppReceiptValidator Demo iOS/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ideasoncanvas.AppReceiptValidator.Demo-iOS";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -1898,12 +1882,12 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = W6L39UYL6Z;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = "AppReceiptValidator Demo iOS/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ideasoncanvas.AppReceiptValidator.Demo-iOS";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0900"
|
||||
LastUpgradeVersion = "0930"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -40,11 +40,12 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
skipped = "NO"
|
||||
parallelizable = "YES"
|
||||
testExecutionOrdering = "random">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D19095A81F6001800095729B"
|
||||
@@ -70,7 +71,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0900"
|
||||
LastUpgradeVersion = "0930"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -40,11 +40,12 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
skipped = "NO"
|
||||
parallelizable = "YES"
|
||||
testExecutionOrdering = "random">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D19095911F6000A40095729B"
|
||||
@@ -70,7 +71,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
+22
-3
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0900"
|
||||
LastUpgradeVersion = "0930"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -26,10 +26,30 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES"
|
||||
testExecutionOrdering = "random">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D19095A81F6001800095729B"
|
||||
BuildableName = "AppReceiptValidator Tests iOS.xctest"
|
||||
BlueprintName = "AppReceiptValidator Tests iOS"
|
||||
ReferencedContainer = "container:AppReceiptValidator.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D1D6F4B41F5D684C00E86FE1"
|
||||
BuildableName = "AppReceiptValidator.framework"
|
||||
BlueprintName = "AppReceiptValidator iOS"
|
||||
ReferencedContainer = "container:AppReceiptValidator.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
@@ -37,7 +57,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
+7
-21
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0900"
|
||||
LastUpgradeVersion = "0930"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -20,36 +20,23 @@
|
||||
ReferencedContainer = "container:AppReceiptValidator.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D1D6F5321F5D894900E86FE1"
|
||||
BuildableName = "AppReceiptValidator_macOSTests.xctest"
|
||||
BlueprintName = "AppReceiptValidator_macOSTests"
|
||||
ReferencedContainer = "container:AppReceiptValidator.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
skipped = "NO"
|
||||
parallelizable = "YES"
|
||||
testExecutionOrdering = "random">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D1D6F5321F5D894900E86FE1"
|
||||
BuildableName = "AppReceiptValidator_macOSTests.xctest"
|
||||
BlueprintName = "AppReceiptValidator_macOSTests"
|
||||
BlueprintIdentifier = "D19095911F6000A40095729B"
|
||||
BuildableName = "AppReceiptValidator Tests macOS.xctest"
|
||||
BlueprintName = "AppReceiptValidator Tests macOS"
|
||||
ReferencedContainer = "container:AppReceiptValidator.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
@@ -70,7 +57,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -12,7 +12,7 @@ public extension AppReceiptValidator {
|
||||
|
||||
/// Describes how to validate a receipt, and how/where to obtain the dependencies (receipt, deviceIdentifier, apple root certificate)
|
||||
/// Use .default to initialize the standard parameters. By default, no `propertyValidations` are active.
|
||||
public struct Parameters {
|
||||
struct Parameters {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
@@ -62,11 +62,14 @@ extension AppReceiptValidator.Parameters {
|
||||
|
||||
case installedInMainBundle
|
||||
case data(Data)
|
||||
case dynamic(() -> Data?)
|
||||
|
||||
public func loadData() -> Data? {
|
||||
switch self {
|
||||
case .data(let data):
|
||||
return data
|
||||
case .dynamic(let maker):
|
||||
return maker()
|
||||
case .installedInMainBundle:
|
||||
guard let receiptUrl = Bundle.main.appStoreReceiptURL else { return nil }
|
||||
guard (try? receiptUrl.checkResourceIsReachable()) ?? false else { return nil }
|
||||
@@ -83,9 +86,9 @@ public extension AppReceiptValidator.Parameters {
|
||||
|
||||
/// Used for calculating/validating the SHA1-Hash part of a receipt.
|
||||
///
|
||||
/// - currentDevice: Obtains it from the system location: MAC Adress on macOS, deviceIdentifierForVendor on iOS
|
||||
/// - currentDevice: Obtains it from the system location: MAC Address on macOS, deviceIdentifierForVendor on iOS
|
||||
/// - data: Specific Data to use
|
||||
public enum DeviceIdentifier {
|
||||
enum DeviceIdentifier {
|
||||
|
||||
case currentDevice
|
||||
case data(Data)
|
||||
@@ -99,6 +102,20 @@ public extension AppReceiptValidator.Parameters {
|
||||
self = .data(uuid.data)
|
||||
}
|
||||
|
||||
/// Returns .data by parsing a MAC Address String
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - macAddress: A MAC Address of the form "00:0d:3f:cd:02:5f"
|
||||
/// - separator: Defaults to `":"`
|
||||
///
|
||||
/// - Note: on macOS the MAC Addresses can be read from terminal command `ifconfig` looking for ther `ether` entry of `5e`.
|
||||
public init?(macAddress: String, separator: String = ":") {
|
||||
let bytes = macAddress.components(separatedBy: separator).compactMap { UInt8($0, radix: 16) }
|
||||
guard bytes.count == 6 else { return nil }
|
||||
|
||||
self = .data(Data(bytes))
|
||||
}
|
||||
|
||||
public func getData() -> Data? {
|
||||
switch self {
|
||||
case .data(let data):
|
||||
|
||||
@@ -117,15 +117,17 @@ private extension AppReceiptValidator {
|
||||
var sha1Context = SHA_CTX()
|
||||
|
||||
SHA1_Init(&sha1Context)
|
||||
|
||||
deviceIdentifierData.withUnsafeBytes { pointer -> Void in
|
||||
SHA1_Update(&sha1Context, pointer, deviceIdentifierData.count)
|
||||
deviceIdentifierData.withUnsafeBytes { poi -> Void in
|
||||
print(poi)
|
||||
}
|
||||
receiptOpaqueValueData.withUnsafeBytes { pointer -> Void in
|
||||
SHA1_Update(&sha1Context, pointer, receiptOpaqueValueData.count)
|
||||
_ = deviceIdentifierData.withUnsafeBytes { pointer -> Void in
|
||||
SHA1_Update(&sha1Context, pointer.baseAddress, deviceIdentifierData.count)
|
||||
}
|
||||
receiptBundleIdData.withUnsafeBytes { pointer -> Void in
|
||||
SHA1_Update(&sha1Context, pointer, receiptBundleIdData.count)
|
||||
_ = receiptOpaqueValueData.withUnsafeBytes { pointer -> Void in
|
||||
SHA1_Update(&sha1Context, pointer.baseAddress, receiptOpaqueValueData.count)
|
||||
}
|
||||
_ = receiptBundleIdData.withUnsafeBytes { pointer -> Void in
|
||||
SHA1_Update(&sha1Context, pointer.baseAddress, receiptBundleIdData.count)
|
||||
}
|
||||
SHA1_Final(&computedHash, &sha1Context)
|
||||
|
||||
@@ -243,28 +245,30 @@ private extension AppReceiptValidator {
|
||||
return (receipt: receipt, unofficialReceipt: unofficialReceipt)
|
||||
}
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
private func parseInAppPurchaseReceipt(pointer: UnsafePointer<UInt8>, length: Int) throws -> InAppPurchaseReceipt {
|
||||
var inAppPurchaseReceipt = InAppPurchaseReceipt()
|
||||
try self.parseASN1Set(pointer: pointer, length: length) { attributeType, value in
|
||||
guard let attribute = KnownInAppPurchaseAttribute(rawValue: attributeType) else { return }
|
||||
guard let value = value.unwrapped else { return } // always unwrap set members
|
||||
|
||||
switch attribute {
|
||||
case .quantity:
|
||||
inAppPurchaseReceipt.quantity = value.intValue
|
||||
case .productIdentifier:
|
||||
inAppPurchaseReceipt.productIdentifier = value.unwrappedStringValue
|
||||
inAppPurchaseReceipt.productIdentifier = value.stringValue
|
||||
case .transactionIdentifier:
|
||||
inAppPurchaseReceipt.transactionIdentifier = value.unwrappedStringValue
|
||||
inAppPurchaseReceipt.transactionIdentifier = value.stringValue
|
||||
case .originalTransactionIdentifier:
|
||||
inAppPurchaseReceipt.originalTransactionIdentifier = value.unwrappedStringValue
|
||||
inAppPurchaseReceipt.originalTransactionIdentifier = value.stringValue
|
||||
case .purchaseDate:
|
||||
inAppPurchaseReceipt.purchaseDate = value.unwrappedDateValue
|
||||
inAppPurchaseReceipt.purchaseDate = value.dateValue
|
||||
case .originalPurchaseDate:
|
||||
inAppPurchaseReceipt.originalPurchaseDate = value.unwrappedDateValue
|
||||
inAppPurchaseReceipt.originalPurchaseDate = value.dateValue
|
||||
case .subscriptionExpirationDate:
|
||||
inAppPurchaseReceipt.subscriptionExpirationDate = value.unwrappedDateValue
|
||||
inAppPurchaseReceipt.subscriptionExpirationDate = value.dateValue
|
||||
case .cancellationDate:
|
||||
inAppPurchaseReceipt.cancellationDate = value.unwrappedDateValue
|
||||
inAppPurchaseReceipt.cancellationDate = value.dateValue
|
||||
case .webOrderLineItemId:
|
||||
inAppPurchaseReceipt.webOrderLineItemId = value.intValue
|
||||
}
|
||||
@@ -336,7 +340,6 @@ private extension AppReceiptValidator {
|
||||
case expirationDate = 21
|
||||
}
|
||||
|
||||
|
||||
/// See Receipt.swift for details and a link to Apple reference
|
||||
enum KnownInAppPurchaseAttribute: Int32 {
|
||||
case quantity = 1701
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ extension AppReceiptValidator.Parameters.DeviceIdentifier {
|
||||
/// Original implementation https://gist.github.com/mminer/82975d3781e2f42fc644d7fbfbf4f905
|
||||
///
|
||||
/// - Returns: The MAC Address as Data and String representation
|
||||
private static func getPrimaryNetworkMACAddress() -> (data: Data, addressString: String)? {
|
||||
static func getPrimaryNetworkMACAddress() -> (data: Data, addressString: String)? {
|
||||
let matching = IOServiceMatching("IOEthernetInterface") as NSMutableDictionary
|
||||
matching[kIOPropertyMatchKey] = ["IOPrimaryInterface": true]
|
||||
var servicesIterator: io_iterator_t = 0
|
||||
@@ -53,6 +53,6 @@ extension AppReceiptValidator.Parameters.DeviceIdentifier {
|
||||
.map { String(format: "%02x", $0) }
|
||||
.joined(separator: ":")
|
||||
|
||||
return (data: Data(bytes: address), addressString: addressString)
|
||||
return (data: Data(address), addressString: addressString)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
import AppReceiptValidator.OpenSSL
|
||||
import Foundation
|
||||
|
||||
// A resource for ASN1 can be https://www.oss.com/asn1/resources/reference/asn1-reference-card.html
|
||||
|
||||
/// An ASN1 Sequence Object. Of interest are the attributeType and the valueObject.
|
||||
/// The attributeType determines how to interpret the valueObject.
|
||||
///
|
||||
@@ -116,7 +118,11 @@ extension ASN1Object {
|
||||
|
||||
extension ASN1Object {
|
||||
|
||||
/// Unwraps an OCTET_STRING, which is a binary container, that can contain another ASN1Object.
|
||||
/// This is the case when we are looking at an entry in an ASN1Set
|
||||
var unwrapped: ASN1Object? {
|
||||
guard self.type == V_ASN1_OCTET_STRING else { return nil }
|
||||
|
||||
guard let endPointer = valuePointer?.advanced(by: length) else { return nil }
|
||||
|
||||
var innerPointer = valuePointer
|
||||
@@ -155,7 +161,11 @@ extension ASN1Object {
|
||||
|
||||
extension ASN1Object {
|
||||
|
||||
var intValue: Int? {
|
||||
var unwrappedIntValue: Int64? {
|
||||
return self.unwrapped?.intValue
|
||||
}
|
||||
|
||||
var intValue: Int64? {
|
||||
guard self.type == V_ASN1_INTEGER else { return nil }
|
||||
|
||||
var pointer = self.valuePointer
|
||||
@@ -163,11 +173,11 @@ extension ASN1Object {
|
||||
defer {
|
||||
ASN1_INTEGER_free(integer)
|
||||
}
|
||||
let result = ASN1_INTEGER_get(integer)
|
||||
let result = Int64(ASN1_INTEGER_get(integer))
|
||||
return result
|
||||
}
|
||||
|
||||
func intValue(byAdvancingPointer pointer: inout UnsafePointer<UInt8>?, length: Int? = nil) -> Int? {
|
||||
func intValue(byAdvancingPointer pointer: inout UnsafePointer<UInt8>?, length: Int? = nil) -> Int64? {
|
||||
let length = length ?? self.length
|
||||
pointer = pointer?.advanced(by: length)
|
||||
guard let intValue = self.intValue else { return nil }
|
||||
|
||||
@@ -14,8 +14,8 @@ final class BIOWrapper {
|
||||
let bio = BIO_new(BIO_s_mem())
|
||||
|
||||
init(data: Data) {
|
||||
data.withUnsafeBytes { pointer -> Void in
|
||||
BIO_write(self.bio, pointer, Int32(data.count))
|
||||
_ = data.withUnsafeBytes { pointer in
|
||||
BIO_write(self.bio, pointer.baseAddress, Int32(data.count))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import Foundation
|
||||
/// Receipts are made up of a number of fields. This represents all fields that are available locally when parsing a receipt file in ASN.1 form.
|
||||
///
|
||||
/// See [Apple Reference](https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html)
|
||||
public struct Receipt {
|
||||
public struct Receipt: Equatable {
|
||||
|
||||
/// The app’s bundle identifier. This corresponds to the value of `CFBundleIdentifier` in the Info.plist file.
|
||||
/// Use this value to validate if the receipt was indeed generated for your app. ASN.1 Field Type 2.
|
||||
@@ -55,6 +55,7 @@ public struct Receipt {
|
||||
/// The in-app purchase receipt for a non-consumable product, auto-renewable subscription, non-renewing subscription, or free subscription remains in the receipt indefinitely.
|
||||
public internal(set) var inAppPurchaseReceipts: [InAppPurchaseReceipt] = []
|
||||
|
||||
/// For documentation of parameters, have a look at the documented properties of `Receipt`
|
||||
public init(bundleIdentifier: String?, bundleIdData: Data?, appVersion: String?, opaqueValue: Data?, sha1Hash: Data?, originalAppVersion: String?, receiptCreationDate: Date?, expirationDate: Date?, inAppPurchaseReceipts: [InAppPurchaseReceipt]) {
|
||||
self.bundleIdentifier = bundleIdentifier
|
||||
self.bundleIdData = bundleIdData
|
||||
@@ -67,17 +68,36 @@ public struct Receipt {
|
||||
self.inAppPurchaseReceipts = inAppPurchaseReceipts
|
||||
}
|
||||
|
||||
/// Convenience initializer with stringified parameters for `Date` and `Data` type parameters.
|
||||
/// When logging a `Receipt`'s (`CustomStringConvertible`) description, you can use that as swift source code calling this initializer,
|
||||
/// which can simplify creating tests.
|
||||
///
|
||||
/// For documentation of parameters, have a look at the documented properties of `Receipt`.
|
||||
///
|
||||
/// - Note: Dates must be formatted as "2017-01-01T12:00:00Z", otherwise will be assigned `nil`.
|
||||
/// Data must be formatted as Base64, otherwise will be assigned `nil`.
|
||||
/// Correct formatting will be asserted in DEBUG builds.
|
||||
public init(bundleIdentifier: String, bundleIdData: String, appVersion: String, opaqueValue: String, sha1Hash: String, originalAppVersion: String, receiptCreationDate: String, expirationDate: String?, inAppPurchaseReceipts: [InAppPurchaseReceipt]) {
|
||||
self.init(bundleIdentifier: bundleIdentifier,
|
||||
bundleIdData: parseBase64(string: bundleIdData),
|
||||
appVersion: appVersion,
|
||||
opaqueValue: parseBase64(string: opaqueValue),
|
||||
sha1Hash: parseBase64(string: sha1Hash),
|
||||
originalAppVersion: originalAppVersion,
|
||||
receiptCreationDate: parseDate(string: receiptCreationDate),
|
||||
expirationDate: expirationDate.flatMap { parseDate(string: $0) },
|
||||
inAppPurchaseReceipts: inAppPurchaseReceipts)
|
||||
}
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
// MARK: - Equatable
|
||||
|
||||
extension Receipt: AutoEquatable {}
|
||||
|
||||
// MARK: - CustomStringConvertible
|
||||
|
||||
extension Receipt: CustomStringConvertible, CustomDebugStringConvertible {
|
||||
|
||||
// This description is carefully matched to match the stringy convenience initializer of `Receipt`
|
||||
|
||||
public var description: String {
|
||||
let formatter = StringFormatter()
|
||||
let props: [(String, String)] = [
|
||||
@@ -105,7 +125,7 @@ extension Receipt: CustomStringConvertible, CustomDebugStringConvertible {
|
||||
///
|
||||
/// Documentation was obtained from: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html
|
||||
///
|
||||
/// The following fields are part of JSON communication but not part of the parsed version (matched Sept 2017):
|
||||
/// The following fields are part of JSON communication but not part of the parsed version (matched May 2019):
|
||||
/// - Subscription Expiration Intent
|
||||
/// - Subscription Retry Flag
|
||||
/// - Subscription Trial Period
|
||||
@@ -115,11 +135,11 @@ extension Receipt: CustomStringConvertible, CustomDebugStringConvertible {
|
||||
/// - Subscription Auto Renew Status
|
||||
/// - Subscription Auto Renew Preference
|
||||
/// - Subscription Price Consent Status
|
||||
public struct InAppPurchaseReceipt {
|
||||
public struct InAppPurchaseReceipt: Equatable {
|
||||
|
||||
/// The number of items purchased. ASN.1 Field Type 1701.
|
||||
/// This value corresponds to the quantity property of the `SKPayment` object stored in the transaction’s payment property.
|
||||
public internal(set) var quantity: Int?
|
||||
public internal(set) var quantity: Int64?
|
||||
|
||||
/// The product identifier of the item that was purchased. ASN.1 Field Type 1702.
|
||||
/// This value corresponds to the `productIdentifier` property of the `SKPayment` object stored in the transaction’s `payment` property.
|
||||
@@ -171,10 +191,10 @@ public struct InAppPurchaseReceipt {
|
||||
|
||||
/// The primary key for identifying subscription purchases. ASN.1 Field Type 1711.
|
||||
/// This value is a unique ID that identifies purchase events across devices, including subscription renewal purchase events.
|
||||
public internal(set) var webOrderLineItemId: Int?
|
||||
public internal(set) var webOrderLineItemId: Int64?
|
||||
|
||||
/// For documentation see InAppPurchaseReceipt itself.
|
||||
public init(quantity: Int?, productIdentifier: String?, transactionIdentifier: String?, originalTransactionIdentifier: String?, purchaseDate: Date?, originalPurchaseDate: Date?, subscriptionExpirationDate: Date?, cancellationDate: Date?, webOrderLineItemId: Int?) {
|
||||
/// For documentation of parameters, have a look at the documented properties of `InAppPurchaseReceipt`.
|
||||
public init(quantity: Int64?, productIdentifier: String?, transactionIdentifier: String?, originalTransactionIdentifier: String?, purchaseDate: Date?, originalPurchaseDate: Date?, subscriptionExpirationDate: Date?, cancellationDate: Date?, webOrderLineItemId: Int64?) {
|
||||
self.quantity = quantity
|
||||
self.productIdentifier = productIdentifier
|
||||
self.transactionIdentifier = transactionIdentifier
|
||||
@@ -186,17 +206,36 @@ public struct InAppPurchaseReceipt {
|
||||
self.webOrderLineItemId = webOrderLineItemId
|
||||
}
|
||||
|
||||
/// Convenience initializer with stringified parameters for `Date` and `Data` type parameters.
|
||||
/// When logging a `Receipt`'s (`CustomStringConvertible`) description, you can use that as swift source code calling this initializer,
|
||||
/// which can simplify creating tests.
|
||||
///
|
||||
/// For documentation of parameters, have a look at the documented properties of `InAppPurchaseReceipt`.
|
||||
///
|
||||
/// - Note: Dates must be formatted as "2017-01-01T12:00:00Z", otherwise will be assigned `nil`.
|
||||
/// Data must be formatted as Base64, otherwise will be assigned `nil`.
|
||||
/// Correct formatting will be asserted in DEBUG builds.
|
||||
public init(quantity: Int64?, productIdentifier: String, transactionIdentifier: String, originalTransactionIdentifier: String, purchaseDate: String, originalPurchaseDate: String, subscriptionExpirationDate: String?, cancellationDate: String?, webOrderLineItemId: Int64?) {
|
||||
self.init(quantity: quantity,
|
||||
productIdentifier: productIdentifier,
|
||||
transactionIdentifier: transactionIdentifier,
|
||||
originalTransactionIdentifier: originalTransactionIdentifier,
|
||||
purchaseDate: parseDate(string: purchaseDate),
|
||||
originalPurchaseDate: parseDate(string: originalPurchaseDate),
|
||||
subscriptionExpirationDate: subscriptionExpirationDate.flatMap { parseDate(string: $0) },
|
||||
cancellationDate: cancellationDate.flatMap { parseDate(string: $0) },
|
||||
webOrderLineItemId: webOrderLineItemId)
|
||||
}
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
// MARK: - Equatable
|
||||
|
||||
extension InAppPurchaseReceipt: AutoEquatable {}
|
||||
|
||||
// MARK: - CustomStringConvertible
|
||||
|
||||
extension InAppPurchaseReceipt: CustomStringConvertible, CustomDebugStringConvertible {
|
||||
|
||||
// This description is carefully matched to match the stringy convenience initializer of `InAppPurchaseReceipt`
|
||||
|
||||
public var description: String {
|
||||
let formatter = StringFormatter()
|
||||
let props: [(String, String)] = [
|
||||
@@ -236,7 +275,7 @@ private struct StringFormatter {
|
||||
return pairs.map { self.format(key: $0, value: $1) }.joined(separator: ",\n")
|
||||
}
|
||||
|
||||
func format(_ int: Int?) -> String {
|
||||
func format(_ int: Int64?) -> String {
|
||||
guard let int = int else { return fallback }
|
||||
|
||||
return "\(int)"
|
||||
@@ -249,16 +288,40 @@ private struct StringFormatter {
|
||||
func format(_ data: Data?) -> String {
|
||||
guard let data = data else { return fallback }
|
||||
|
||||
return data.base64EncodedString()
|
||||
return quoted(data.base64EncodedString())
|
||||
}
|
||||
|
||||
func format(_ date: Date?) -> String {
|
||||
guard let date = date else { return fallback }
|
||||
|
||||
return AppReceiptValidator.asn1DateFormatter.string(from: date)
|
||||
return quoted(AppReceiptValidator.asn1DateFormatter.string(from: date))
|
||||
}
|
||||
|
||||
func format(_ string: String?) -> String {
|
||||
return string ?? fallback
|
||||
return string.map(quoted) ?? fallback
|
||||
}
|
||||
|
||||
/// Surrounds a string with quotes "…"
|
||||
func quoted(_ string: String) -> String {
|
||||
return "\"" + string + "\""
|
||||
}
|
||||
}
|
||||
|
||||
private func parseBase64(string: String) -> Data? {
|
||||
guard let data = Data(base64Encoded: string) else {
|
||||
assertionFailure("Data could not be parsed from string '\(string)', make sure it is non-empty nad base64 encoded.")
|
||||
return nil
|
||||
}
|
||||
|
||||
return 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 {
|
||||
assertionFailure("Date could not be parsed from string '\(string)', make sure it has a correct format, example `2017-01-01T12:00:00Z`")
|
||||
return nil
|
||||
}
|
||||
|
||||
return date
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
// Generated using Sourcery 0.10.0 — https://github.com/krzysztofzablocki/Sourcery
|
||||
// DO NOT EDIT
|
||||
|
||||
// swiftlint:disable file_length
|
||||
private func compareOptionals<T>(lhs: T?, rhs: T?, compare: (_ lhs: T, _ rhs: T) -> Bool) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (lValue?, rValue?):
|
||||
return compare(lValue, rValue)
|
||||
case (nil, nil):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private func compareArrays<T>(lhs: [T], rhs: [T], compare: (_ lhs: T, _ rhs: T) -> Bool) -> Bool {
|
||||
guard lhs.count == rhs.count else { return false }
|
||||
for (idx, lhsItem) in lhs.enumerated() {
|
||||
guard compare(lhsItem, rhs[idx]) else { return false }
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// MARK: - AutoEquatable for classes, protocols, structs
|
||||
// MARK: - InAppPurchaseReceipt AutoEquatable
|
||||
extension InAppPurchaseReceipt: Equatable {}
|
||||
public func == (lhs: InAppPurchaseReceipt, rhs: InAppPurchaseReceipt) -> Bool {
|
||||
guard compareOptionals(lhs: lhs.quantity, rhs: rhs.quantity, compare: ==) else { return false }
|
||||
guard compareOptionals(lhs: lhs.productIdentifier, rhs: rhs.productIdentifier, compare: ==) else { return false }
|
||||
guard compareOptionals(lhs: lhs.transactionIdentifier, rhs: rhs.transactionIdentifier, compare: ==) else { return false }
|
||||
guard compareOptionals(lhs: lhs.originalTransactionIdentifier, rhs: rhs.originalTransactionIdentifier, compare: ==) else { return false }
|
||||
guard compareOptionals(lhs: lhs.purchaseDate, rhs: rhs.purchaseDate, compare: ==) else { return false }
|
||||
guard compareOptionals(lhs: lhs.originalPurchaseDate, rhs: rhs.originalPurchaseDate, compare: ==) else { return false }
|
||||
guard compareOptionals(lhs: lhs.subscriptionExpirationDate, rhs: rhs.subscriptionExpirationDate, compare: ==) else { return false }
|
||||
guard compareOptionals(lhs: lhs.cancellationDate, rhs: rhs.cancellationDate, compare: ==) else { return false }
|
||||
guard compareOptionals(lhs: lhs.webOrderLineItemId, rhs: rhs.webOrderLineItemId, compare: ==) else { return false }
|
||||
return true
|
||||
}
|
||||
// MARK: - Receipt AutoEquatable
|
||||
extension Receipt: Equatable {}
|
||||
public func == (lhs: Receipt, rhs: Receipt) -> Bool {
|
||||
guard compareOptionals(lhs: lhs.bundleIdentifier, rhs: rhs.bundleIdentifier, compare: ==) else { return false }
|
||||
guard compareOptionals(lhs: lhs.bundleIdData, rhs: rhs.bundleIdData, compare: ==) else { return false }
|
||||
guard compareOptionals(lhs: lhs.appVersion, rhs: rhs.appVersion, compare: ==) else { return false }
|
||||
guard compareOptionals(lhs: lhs.opaqueValue, rhs: rhs.opaqueValue, compare: ==) else { return false }
|
||||
guard compareOptionals(lhs: lhs.sha1Hash, rhs: rhs.sha1Hash, compare: ==) else { return false }
|
||||
guard compareOptionals(lhs: lhs.originalAppVersion, rhs: rhs.originalAppVersion, compare: ==) else { return false }
|
||||
guard compareOptionals(lhs: lhs.receiptCreationDate, rhs: rhs.receiptCreationDate, compare: ==) else { return false }
|
||||
guard compareOptionals(lhs: lhs.expirationDate, rhs: rhs.expirationDate, compare: ==) else { return false }
|
||||
guard lhs.inAppPurchaseReceipts == rhs.inAppPurchaseReceipts else { return false }
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: - AutoEquatable for Enums
|
||||
@@ -1,8 +0,0 @@
|
||||
//
|
||||
// AutoEquatable.swift
|
||||
//
|
||||
//
|
||||
// Created by Hannes Oud on 07.09.17.
|
||||
//
|
||||
|
||||
protocol AutoEquatable {}
|
||||
@@ -1,62 +0,0 @@
|
||||
// swiftlint:disable file_length
|
||||
fileprivate func compareOptionals<T>(lhs: T?, rhs: T?, compare: (_ lhs: T, _ rhs: T) -> Bool) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (lValue?, rValue?):
|
||||
return compare(lValue, rValue)
|
||||
case (nil, nil):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func compareArrays<T>(lhs: [T], rhs: [T], compare: (_ lhs: T, _ rhs: T) -> Bool) -> Bool {
|
||||
guard lhs.count == rhs.count else { return false }
|
||||
for (idx, lhsItem) in lhs.enumerated() {
|
||||
guard compare(lhsItem, rhs[idx]) else { return false }
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
{% macro compareVariables variables %}
|
||||
{% for variable in variables where variable.readAccess != "private" and variable.readAccess != "fileprivate" %}{% if not variable.annotations.skipEquality %}guard {% if not variable.isOptional %}{% if not variable.annotations.arrayEquality %}lhs.{{ variable.name }} == rhs.{{ variable.name }}{% else %}compareArrays(lhs: lhs.{{ variable.name }}, rhs: rhs.{{ variable.name }}, compare: ==){% endif %}{% else %}compareOptionals(lhs: lhs.{{ variable.name }}, rhs: rhs.{{ variable.name }}, compare: ==){% endif %} else { return false }{% endif %}
|
||||
{% endfor %}
|
||||
{% endmacro %}
|
||||
|
||||
// MARK: - AutoEquatable for classes, protocols, structs
|
||||
{% for type in types.implementing.AutoEquatable|!enum %}
|
||||
// MARK: - {{ type.name }} AutoEquatable
|
||||
{% if not type.kind == "protocol" %}extension {{ type.name }}: Equatable {}{% endif %}
|
||||
{% if type.supertype.based.Equatable or type.supertype.implements.AutoEquatable %}THIS WONT COMPILE, WE DONT SUPPORT INHERITANCE for AutoEquatable{% endif %}
|
||||
{{ type.accessLevel }} func == (lhs: {{ type.name }}, rhs: {{ type.name }}) -> Bool {
|
||||
{% if not type.kind == "protocol" %}
|
||||
{% call compareVariables type.storedVariables %}
|
||||
{% else %}
|
||||
{% call compareVariables type.allVariables %}
|
||||
{% endif %}
|
||||
return true
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
// MARK: - AutoEquatable for Enums
|
||||
{% for type in types.implementing.AutoEquatable|enum %}
|
||||
// MARK: - {{ type.name }} AutoEquatable
|
||||
extension {{ type.name }}: Equatable {}
|
||||
{{ type.accessLevel }} func == (lhs: {{ type.name }}, rhs: {{ type.name }}) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
{% for case in type.cases %}
|
||||
{% if case.hasAssociatedValue %}case (.{{ case.name }}(let lhs), .{{ case.name }}(let rhs)):{% else %}case (.{{ case.name }}, .{{ case.name }}):{% endif %}
|
||||
{% ifnot case.hasAssociatedValue %}return true{% else %}
|
||||
{% if case.associatedValues.count == 1 %}
|
||||
return lhs == rhs
|
||||
{% else %}
|
||||
{% for associated in case.associatedValues %}if lhs.{{ associated.externalName }} != rhs.{{ associated.externalName }} { return false }
|
||||
{% endfor %}return true
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if type.cases.count > 1 %}default: return false{% endif %}
|
||||
}
|
||||
}
|
||||
{% endfor %}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://github.com/Carthage/Carthage)
|
||||

|
||||

|
||||

|
||||
[](LICENSE)
|
||||
[](https://travis-ci.org/IdeasOnCanvas/AppReceiptValidator)
|
||||
[](https://twitter.com/hannesoid)
|
||||
@@ -73,7 +73,7 @@ Receipt(
|
||||
)
|
||||
```
|
||||
|
||||
**Receipt** is *Equatable*, thanks [Sourcery](https://github.com/krzysztofzablocki/Sourcery), so you can do comparisons in Unit Tests.
|
||||
**Receipt** is *Equatable*, so you can do comparisons in Unit Tests.
|
||||
There are also some opt-in unofficial attributes, but this is experimental and should not be used in production.
|
||||
|
||||
### Validating a receipt's signature and hash
|
||||
@@ -150,19 +150,20 @@ If you have no receipt (happens in development builds) or your receipt is invali
|
||||
|
||||
### AppReceiptValidator Uses OpenSSL
|
||||
|
||||
OpenSSL is used for pkcs7 container parsing and signature validation, and also for parsing the ASN1 payload of the pkcs7, which contains the receipts attributes.
|
||||
OpenSSL is used for PKCS#7 container parsing and signature validation, and also for parsing the ASN1 payload of the PKCS#7, which contains the receipts attributes.
|
||||
|
||||
### Other Options
|
||||
|
||||
##### Alternatives to PKCS7 of OpenSSL
|
||||
##### Alternatives to PKCS#7 of OpenSSL
|
||||
|
||||
- `Security.framework` - `CMSDecoder` for PKCS7 interaction *only available on macOS*
|
||||
- `BoringSSL` instead of OpenSSL, Pod, *only available on iOS (?)*
|
||||
- `Security.framework` - `CMSDecoder` for PKCS#7 interaction *only available on macOS*, [AppStoreReceiptChecker](https://github.com/delicious-monster/AppStoreReceiptChecker) uses this.
|
||||
- `BoringSSL` instead of OpenSSL, seems included as frameworks in modern iOS and macOS, but not officially supported?
|
||||
|
||||
##### Alternatives to ASN1 of OpenSSL
|
||||
|
||||
- [decoding-asn1-der-sequences-in-swift](http://nspasteboard.com/2016/10/23/decoding-asn1-der-sequences-in-swift/) implemented [here](https://gist.github.com/Jugale/2daaec0715d4f6d7347534d42bfa7110)
|
||||
- [Asn1Parser.swift](https://github.com/TakeScoop/SwiftyRSA/blob/03250be7319d8c54159234e5258ead395ea4de4c/SwiftyRSA/Asn1Parser.swift)
|
||||
- [AppStoreReceiptChecker](https://github.com/delicious-monster/AppStoreReceiptChecker)
|
||||
|
||||
##### Validation Server to Server
|
||||
An app can send its receipt file to a backend from where Apples receipt API can be called. See Resources.
|
||||
@@ -187,6 +188,7 @@ Advantages doing it locally:
|
||||
- [nsomar about Module Maps 1](http://nsomar.com/project-and-private-headers-in-a-swift-and-objective-c-framework/)
|
||||
- [nsomar about Module Maps 2](http://nsomar.com/modular-framework-creating-and-using-them/)
|
||||
- [SwiftyStoreKit](https://github.com/bizz84/SwiftyStoreKit)
|
||||
- [AppStoreReceiptChecker](https://github.com/delicious-monster/AppStoreReceiptChecker) - macOS, uses CMSDecoder and a Swift ASN1 Implementation
|
||||
|
||||
## Updating Apple Root Certificate
|
||||
For convenience, AppReceiptValidator contains a copy of apples root certificate to validate the signature against. If uncomfortable with this, you can specify your own by changing the parameters like this:
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 299 KiB After Width: | Height: | Size: 463 KiB |
Reference in New Issue
Block a user