├── .github └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── NSBezierPath+IOS7RoundedRect ├── NSBezierPath+IOS7RoundedRect.h └── NSBezierPath+IOS7RoundedRect.m ├── ProvisionQL.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── ProvisionQL ├── GeneratePreviewForURL.m ├── GenerateThumbnailForURL.m ├── Resources │ ├── blankIcon.png │ ├── defaultIcon.png │ ├── mobileprovision_Icon.png │ └── template.html ├── Scripts │ └── autoupdate-revision.sh ├── Shared.h ├── Shared.m └── Supporting-files │ ├── Info.plist │ └── main.c ├── README.md └── Screenshots ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png └── README.md /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: macOS-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Build project 17 | run: set -o pipefail && xcodebuild -project ProvisionQL.xcodeproj -scheme ProvisionQL CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS noise 2 | .DS_Store 3 | ._* 4 | *~ 5 | 6 | # Other CSM 7 | .hg 8 | .svn 9 | CVS 10 | 11 | # Xcode settings 12 | xcuserdata/ 13 | 14 | # Xcode noise 15 | *.log 16 | *~.nib 17 | *.moved-aside 18 | *.xccheckout 19 | *.xcscmblueprint 20 | 21 | # Build generated 22 | [Bb]uild/ 23 | DerivedData/ 24 | 25 | # Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ProvisionQL 2 | 3 | ## Version 1.6.4 4 | 5 | * Adds error handling to entitlements parsing ([#47](https://github.com/ealeksandrov/ProvisionQL/pull/47)) 6 | 7 | ## Version 1.6.3 8 | 9 | * Improves app extensions (`.appex`) support ([#45](https://github.com/ealeksandrov/ProvisionQL/pull/45)) 10 | 11 | ## Version 1.6.2 12 | 13 | * Adds XML escaping for file name ([#36](https://github.com/ealeksandrov/ProvisionQL/issues/36)) 14 | * Improves relative date intervals formatting ([#35](https://github.com/ealeksandrov/ProvisionQL/issues/35)) 15 | * Moves entitlements higher in preview ([#31](https://github.com/ealeksandrov/ProvisionQL/issues/31)) 16 | 17 | ## Version 1.6.1 18 | 19 | * Adds code signing, fixes macOS Catalina compatibility ([#30](https://github.com/ealeksandrov/ProvisionQL/issues/30)) 20 | 21 | ## Version 1.6.0 22 | 23 | * Adds dark mode support ([#29](https://github.com/ealeksandrov/ProvisionQL/pull/29)) 24 | 25 | ## Version 1.5.0 26 | 27 | * Fixes missing icons for iPad-only apps ([#8](https://github.com/ealeksandrov/ProvisionQL/issues/22)) 28 | * Improves icons parsing and extraction 29 | 30 | ## Version 1.4.1 31 | 32 | * Fixes Quick Look timeout in some cases ([#8](https://github.com/ealeksandrov/ProvisionQL/issues/8)) 33 | 34 | ## Version 1.4.0 35 | 36 | * Adds parsing code signing entitlements from the application binary ([#16](https://github.com/ealeksandrov/ProvisionQL/pull/16) and [#3](https://github.com/ealeksandrov/ProvisionQL/issues/3)) 37 | * Adds xcarchives support (`.xcarchive`) ([#10](https://github.com/ealeksandrov/ProvisionQL/issues/10)) 38 | * Removes Xcode devices data and related formatting ([#9](https://github.com/ealeksandrov/ProvisionQL/issues/9)) 39 | * Removes application-bundle (`.app`) support ([#14](https://github.com/ealeksandrov/ProvisionQL/issues/14)) 40 | * Fixes expiration status calculation ([#17](https://github.com/ealeksandrov/ProvisionQL/issues/17)) 41 | * Fixes icons "IconFlavor" for apps thumbnails ([#2](https://github.com/ealeksandrov/ProvisionQL/issues/2)) 42 | * Fixes wrong thumnails and previews for bundles with multiple plugin executables 43 | * Improves app preview layout 44 | * Improves App Transport Security section formatting 45 | 46 | ## Version 1.3.0 47 | * Adds NSAppTransportSecurity, DTSDKName, and MinimumOSVersion ([#7](https://github.com/ealeksandrov/ProvisionQL/pull/7) 48 | 49 | ## Version 1.2.0 50 | * Adds support for app extensions (`.appex`) 51 | 52 | ## Version 1.1.0 53 | * Adds support for new Xcode 6 projects ([#6](https://github.com/ealeksandrov/ProvisionQL/pull/6) and [#5](https://github.com/ealeksandrov/ProvisionQL/issues/5)) 54 | 55 | ## Version 1.0.0 56 | * Initial release 57 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013-2023 Evgeny Aleksandrov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /NSBezierPath+IOS7RoundedRect/NSBezierPath+IOS7RoundedRect.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSBezierPath+IOS7RoundedRect.h 3 | // 4 | // Created by Matej Dunik on 11/12/13. 5 | // Copyright (c) 2013 PixelCut. All rights reserved except as below: 6 | // This code is provided as-is, without warranty of any kind. You may use it in your projects as you wish. 7 | // 8 | 9 | #import 10 | 11 | @interface NSBezierPath (IOS7RoundedRect) 12 | 13 | + (NSBezierPath *)bezierPathWithIOS7RoundedRect:(NSRect)rect cornerRadius:(CGFloat)radius; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /NSBezierPath+IOS7RoundedRect/NSBezierPath+IOS7RoundedRect.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSBezierPath+IOS7RoundedRect.m 3 | // 4 | // Created by Matej Dunik on 11/12/13. 5 | // Copyright (c) 2013 PixelCut. All rights reserved except as below: 6 | // This code is provided as-is, without warranty of any kind. You may use it in your projects as you wish. 7 | // 8 | 9 | #import "NSBezierPath+IOS7RoundedRect.h" 10 | 11 | @implementation NSBezierPath (IOS7RoundedRect) 12 | 13 | #define TOP_LEFT(X, Y) NSMakePoint(rect.origin.x + X * limitedRadius, rect.origin.y + Y * limitedRadius) 14 | #define TOP_RIGHT(X, Y) NSMakePoint(rect.origin.x + rect.size.width - X * limitedRadius, rect.origin.y + Y * limitedRadius) 15 | #define BOTTOM_RIGHT(X, Y) NSMakePoint(rect.origin.x + rect.size.width - X * limitedRadius, rect.origin.y + rect.size.height - Y * limitedRadius) 16 | #define BOTTOM_LEFT(X, Y) NSMakePoint(rect.origin.x + X * limitedRadius, rect.origin.y + rect.size.height - Y * limitedRadius) 17 | 18 | 19 | + (NSBezierPath *)bezierPathWithIOS7RoundedRect:(NSRect)rect cornerRadius:(CGFloat)radius { 20 | NSBezierPath *path = NSBezierPath.bezierPath; 21 | CGFloat limit = MIN(rect.size.width, rect.size.height) / 2 / 1.52866483; 22 | CGFloat limitedRadius = MIN(radius, limit); 23 | 24 | [path moveToPoint: TOP_LEFT(1.52866483, 0.00000000)]; 25 | [path lineToPoint: TOP_RIGHT(1.52866471, 0.00000000)]; 26 | [path curveToPoint: TOP_RIGHT(0.66993427, 0.06549600) controlPoint1: TOP_RIGHT(1.08849323, 0.00000000) controlPoint2: TOP_RIGHT(0.86840689, 0.00000000)]; 27 | [path lineToPoint: TOP_RIGHT(0.63149399, 0.07491100)]; 28 | [path curveToPoint: TOP_RIGHT(0.07491176, 0.63149399) controlPoint1: TOP_RIGHT(0.37282392, 0.16905899) controlPoint2: TOP_RIGHT(0.16906013, 0.37282401)]; 29 | [path curveToPoint: TOP_RIGHT(0.00000000, 1.52866483) controlPoint1: TOP_RIGHT(0.00000000, 0.86840701) controlPoint2: TOP_RIGHT(0.00000000, 1.08849299)]; 30 | [path lineToPoint: BOTTOM_RIGHT(0.00000000, 1.52866471)]; 31 | [path curveToPoint: BOTTOM_RIGHT(0.06549569, 0.66993493) controlPoint1: BOTTOM_RIGHT(0.00000000, 1.08849323) controlPoint2: BOTTOM_RIGHT(0.00000000, 0.86840689)]; 32 | [path lineToPoint: BOTTOM_RIGHT(0.07491111, 0.63149399)]; 33 | [path curveToPoint: BOTTOM_RIGHT(0.63149399, 0.07491111) controlPoint1: BOTTOM_RIGHT(0.16905883, 0.37282392) controlPoint2: BOTTOM_RIGHT(0.37282392, 0.16905883)]; 34 | [path curveToPoint: BOTTOM_RIGHT(1.52866471, 0.00000000) controlPoint1: BOTTOM_RIGHT(0.86840689, 0.00000000) controlPoint2: BOTTOM_RIGHT(1.08849323, 0.00000000)]; 35 | [path lineToPoint: BOTTOM_LEFT(1.52866483, 0.00000000)]; 36 | [path curveToPoint: BOTTOM_LEFT(0.66993397, 0.06549569) controlPoint1: BOTTOM_LEFT(1.08849299, 0.00000000) controlPoint2: BOTTOM_LEFT(0.86840701, 0.00000000)]; 37 | [path lineToPoint: BOTTOM_LEFT(0.63149399, 0.07491111)]; 38 | [path curveToPoint: BOTTOM_LEFT(0.07491100, 0.63149399) controlPoint1: BOTTOM_LEFT(0.37282401, 0.16905883) controlPoint2: BOTTOM_LEFT(0.16906001, 0.37282392)]; 39 | [path curveToPoint: BOTTOM_LEFT(0.00000000, 1.52866471) controlPoint1: BOTTOM_LEFT(0.00000000, 0.86840689) controlPoint2: BOTTOM_LEFT(0.00000000, 1.08849323)]; 40 | [path lineToPoint: TOP_LEFT(0.00000000, 1.52866483)]; 41 | [path curveToPoint: TOP_LEFT(0.06549600, 0.66993397) controlPoint1: TOP_LEFT(0.00000000, 1.08849299) controlPoint2: TOP_LEFT(0.00000000, 0.86840701)]; 42 | [path lineToPoint: TOP_LEFT(0.07491100, 0.63149399)]; 43 | [path curveToPoint: TOP_LEFT(0.63149399, 0.07491100) controlPoint1: TOP_LEFT(0.16906001, 0.37282401) controlPoint2: TOP_LEFT(0.37282401, 0.16906001)]; 44 | [path curveToPoint: TOP_LEFT(1.52866483, 0.00000000) controlPoint1: TOP_LEFT(0.86840701, 0.00000000) controlPoint2: TOP_LEFT(1.08849299, 0.00000000)]; 45 | [path closePath]; 46 | return path; 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /ProvisionQL.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 553C6D321879E457002237FC /* blankIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 553C6D311879E457002237FC /* blankIcon.png */; }; 11 | 55424C611870D4AA002F5408 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 55424C601870D4AA002F5408 /* AppKit.framework */; }; 12 | 55424C631870D90E002F5408 /* defaultIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 55424C621870D90E002F5408 /* defaultIcon.png */; }; 13 | 55424C671870DB2A002F5408 /* NSBezierPath+IOS7RoundedRect.h in Headers */ = {isa = PBXBuildFile; fileRef = 55424C651870DB2A002F5408 /* NSBezierPath+IOS7RoundedRect.h */; }; 14 | 55424C681870DB2A002F5408 /* NSBezierPath+IOS7RoundedRect.m in Sources */ = {isa = PBXBuildFile; fileRef = 55424C661870DB2A002F5408 /* NSBezierPath+IOS7RoundedRect.m */; }; 15 | 555E9515186E2D67001D406A /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = 555E9512186E2D67001D406A /* main.c */; }; 16 | 555E951C186E2DC0001D406A /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 555E9519186E2DC0001D406A /* template.html */; }; 17 | 557C842218731FB7008A2A0C /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 557C842118731FB7008A2A0C /* WebKit.framework */; }; 18 | 557C842618732599008A2A0C /* Shared.m in Sources */ = {isa = PBXBuildFile; fileRef = 557C842418732599008A2A0C /* Shared.m */; }; 19 | 557C842818733828008A2A0C /* Shared.h in Headers */ = {isa = PBXBuildFile; fileRef = 557C842718733828008A2A0C /* Shared.h */; }; 20 | 55DB7281186E193500CAFEE7 /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 55DB7280186E193500CAFEE7 /* QuickLook.framework */; }; 21 | 55DB7283186E193500CAFEE7 /* ApplicationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 55DB7282186E193500CAFEE7 /* ApplicationServices.framework */; }; 22 | 55DB7285186E193500CAFEE7 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 55DB7284186E193500CAFEE7 /* CoreServices.framework */; }; 23 | 55DB7287186E193500CAFEE7 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 55DB7286186E193500CAFEE7 /* CoreFoundation.framework */; }; 24 | 55DB728F186E193500CAFEE7 /* GenerateThumbnailForURL.m in Sources */ = {isa = PBXBuildFile; fileRef = 55DB728E186E193500CAFEE7 /* GenerateThumbnailForURL.m */; }; 25 | 55DB7291186E193500CAFEE7 /* GeneratePreviewForURL.m in Sources */ = {isa = PBXBuildFile; fileRef = 55DB7290186E193500CAFEE7 /* GeneratePreviewForURL.m */; }; 26 | 55DB729B186E195500CAFEE7 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 55DB729A186E195500CAFEE7 /* Security.framework */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | 553C6D311879E457002237FC /* blankIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = blankIcon.png; sourceTree = ""; }; 31 | 55424C601870D4AA002F5408 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 32 | 55424C621870D90E002F5408 /* defaultIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = defaultIcon.png; sourceTree = ""; }; 33 | 55424C651870DB2A002F5408 /* NSBezierPath+IOS7RoundedRect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBezierPath+IOS7RoundedRect.h"; sourceTree = ""; }; 34 | 55424C661870DB2A002F5408 /* NSBezierPath+IOS7RoundedRect.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBezierPath+IOS7RoundedRect.m"; sourceTree = ""; }; 35 | 55457C11203C4A9E00ED02E5 /* LICENSE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = ""; }; 36 | 55457C12203C4A9E00ED02E5 /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; 37 | 55457C13203C4A9E00ED02E5 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 38 | 555E9512186E2D67001D406A /* main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = main.c; sourceTree = ""; }; 39 | 555E9513186E2D67001D406A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | 555E9519186E2DC0001D406A /* template.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = template.html; sourceTree = ""; }; 41 | 557C842118731FB7008A2A0C /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; 42 | 557C842418732599008A2A0C /* Shared.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Shared.m; sourceTree = ""; }; 43 | 557C842718733828008A2A0C /* Shared.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Shared.h; sourceTree = ""; }; 44 | 55DB727D186E193500CAFEE7 /* ProvisionQL.qlgenerator */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ProvisionQL.qlgenerator; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | 55DB7280186E193500CAFEE7 /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = System/Library/Frameworks/QuickLook.framework; sourceTree = SDKROOT; }; 46 | 55DB7282186E193500CAFEE7 /* ApplicationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ApplicationServices.framework; path = System/Library/Frameworks/ApplicationServices.framework; sourceTree = SDKROOT; }; 47 | 55DB7284186E193500CAFEE7 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; 48 | 55DB7286186E193500CAFEE7 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; 49 | 55DB728E186E193500CAFEE7 /* GenerateThumbnailForURL.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GenerateThumbnailForURL.m; sourceTree = ""; }; 50 | 55DB7290186E193500CAFEE7 /* GeneratePreviewForURL.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GeneratePreviewForURL.m; sourceTree = ""; }; 51 | 55DB729A186E195500CAFEE7 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 52 | AE61B2AE236C969C003749A1 /* autoupdate-revision.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "autoupdate-revision.sh"; sourceTree = ""; }; 53 | /* End PBXFileReference section */ 54 | 55 | /* Begin PBXFrameworksBuildPhase section */ 56 | 55DB7278186E193500CAFEE7 /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | 557C842218731FB7008A2A0C /* WebKit.framework in Frameworks */, 61 | 55424C611870D4AA002F5408 /* AppKit.framework in Frameworks */, 62 | 55DB729B186E195500CAFEE7 /* Security.framework in Frameworks */, 63 | 55DB7287186E193500CAFEE7 /* CoreFoundation.framework in Frameworks */, 64 | 55DB7281186E193500CAFEE7 /* QuickLook.framework in Frameworks */, 65 | 55DB7285186E193500CAFEE7 /* CoreServices.framework in Frameworks */, 66 | 55DB7283186E193500CAFEE7 /* ApplicationServices.framework in Frameworks */, 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | /* End PBXFrameworksBuildPhase section */ 71 | 72 | /* Begin PBXGroup section */ 73 | 55424C641870DB2A002F5408 /* NSBezierPath+IOS7RoundedRect */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | 55424C651870DB2A002F5408 /* NSBezierPath+IOS7RoundedRect.h */, 77 | 55424C661870DB2A002F5408 /* NSBezierPath+IOS7RoundedRect.m */, 78 | ); 79 | path = "NSBezierPath+IOS7RoundedRect"; 80 | sourceTree = ""; 81 | }; 82 | 55457C10203C4A7500ED02E5 /* Metadata */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 55457C12203C4A9E00ED02E5 /* CHANGELOG.md */, 86 | 55457C13203C4A9E00ED02E5 /* README.md */, 87 | 55457C11203C4A9E00ED02E5 /* LICENSE.md */, 88 | ); 89 | name = Metadata; 90 | sourceTree = ""; 91 | }; 92 | 555E9511186E2D67001D406A /* Supporting-files */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 555E9512186E2D67001D406A /* main.c */, 96 | 555E9513186E2D67001D406A /* Info.plist */, 97 | ); 98 | path = "Supporting-files"; 99 | sourceTree = ""; 100 | }; 101 | 555E9518186E2DC0001D406A /* Resources */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 553C6D311879E457002237FC /* blankIcon.png */, 105 | 55424C621870D90E002F5408 /* defaultIcon.png */, 106 | 555E9519186E2DC0001D406A /* template.html */, 107 | ); 108 | path = Resources; 109 | sourceTree = ""; 110 | }; 111 | 555E951A186E2DC0001D406A /* Scripts */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | AE61B2AE236C969C003749A1 /* autoupdate-revision.sh */, 115 | ); 116 | path = Scripts; 117 | sourceTree = ""; 118 | }; 119 | 55DB7272186E193500CAFEE7 = { 120 | isa = PBXGroup; 121 | children = ( 122 | 55457C10203C4A7500ED02E5 /* Metadata */, 123 | 55424C641870DB2A002F5408 /* NSBezierPath+IOS7RoundedRect */, 124 | 55DB7288186E193500CAFEE7 /* ProvisionQL */, 125 | 55DB727F186E193500CAFEE7 /* Frameworks */, 126 | 55DB727E186E193500CAFEE7 /* Products */, 127 | ); 128 | sourceTree = ""; 129 | }; 130 | 55DB727E186E193500CAFEE7 /* Products */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 55DB727D186E193500CAFEE7 /* ProvisionQL.qlgenerator */, 134 | ); 135 | name = Products; 136 | sourceTree = ""; 137 | }; 138 | 55DB727F186E193500CAFEE7 /* Frameworks */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | 557C842118731FB7008A2A0C /* WebKit.framework */, 142 | 55424C601870D4AA002F5408 /* AppKit.framework */, 143 | 55DB729A186E195500CAFEE7 /* Security.framework */, 144 | 55DB7280186E193500CAFEE7 /* QuickLook.framework */, 145 | 55DB7282186E193500CAFEE7 /* ApplicationServices.framework */, 146 | 55DB7284186E193500CAFEE7 /* CoreServices.framework */, 147 | 55DB7286186E193500CAFEE7 /* CoreFoundation.framework */, 148 | ); 149 | name = Frameworks; 150 | sourceTree = ""; 151 | }; 152 | 55DB7288186E193500CAFEE7 /* ProvisionQL */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | 555E9511186E2D67001D406A /* Supporting-files */, 156 | 555E951A186E2DC0001D406A /* Scripts */, 157 | 555E9518186E2DC0001D406A /* Resources */, 158 | 557C842718733828008A2A0C /* Shared.h */, 159 | 557C842418732599008A2A0C /* Shared.m */, 160 | 55DB728E186E193500CAFEE7 /* GenerateThumbnailForURL.m */, 161 | 55DB7290186E193500CAFEE7 /* GeneratePreviewForURL.m */, 162 | ); 163 | path = ProvisionQL; 164 | sourceTree = ""; 165 | }; 166 | /* End PBXGroup section */ 167 | 168 | /* Begin PBXHeadersBuildPhase section */ 169 | 55DB7279186E193500CAFEE7 /* Headers */ = { 170 | isa = PBXHeadersBuildPhase; 171 | buildActionMask = 2147483647; 172 | files = ( 173 | 55424C671870DB2A002F5408 /* NSBezierPath+IOS7RoundedRect.h in Headers */, 174 | 557C842818733828008A2A0C /* Shared.h in Headers */, 175 | ); 176 | runOnlyForDeploymentPostprocessing = 0; 177 | }; 178 | /* End PBXHeadersBuildPhase section */ 179 | 180 | /* Begin PBXNativeTarget section */ 181 | 55DB727C186E193500CAFEE7 /* ProvisionQL */ = { 182 | isa = PBXNativeTarget; 183 | buildConfigurationList = 55DB7297186E193500CAFEE7 /* Build configuration list for PBXNativeTarget "ProvisionQL" */; 184 | buildPhases = ( 185 | 55DB7277186E193500CAFEE7 /* Sources */, 186 | 55DB7278186E193500CAFEE7 /* Frameworks */, 187 | 55DB7279186E193500CAFEE7 /* Headers */, 188 | 55DB727A186E193500CAFEE7 /* Resources */, 189 | 55DB727B186E193500CAFEE7 /* Rez */, 190 | 554C6385186E258000D9EEDE /* Run Script */, 191 | ); 192 | buildRules = ( 193 | ); 194 | dependencies = ( 195 | ); 196 | name = ProvisionQL; 197 | productName = ProvisionQL; 198 | productReference = 55DB727D186E193500CAFEE7 /* ProvisionQL.qlgenerator */; 199 | productType = "com.apple.product-type.bundle"; 200 | }; 201 | /* End PBXNativeTarget section */ 202 | 203 | /* Begin PBXProject section */ 204 | 55DB7273186E193500CAFEE7 /* Project object */ = { 205 | isa = PBXProject; 206 | attributes = { 207 | LastUpgradeCheck = 1000; 208 | ORGANIZATIONNAME = "Evgeny Aleksandrov"; 209 | TargetAttributes = { 210 | 55DB727C186E193500CAFEE7 = { 211 | DevelopmentTeam = 5567X9EQ9Q; 212 | ProvisioningStyle = Manual; 213 | }; 214 | }; 215 | }; 216 | buildConfigurationList = 55DB7276186E193500CAFEE7 /* Build configuration list for PBXProject "ProvisionQL" */; 217 | compatibilityVersion = "Xcode 3.2"; 218 | developmentRegion = en; 219 | hasScannedForEncodings = 0; 220 | knownRegions = ( 221 | en, 222 | Base, 223 | ); 224 | mainGroup = 55DB7272186E193500CAFEE7; 225 | productRefGroup = 55DB727E186E193500CAFEE7 /* Products */; 226 | projectDirPath = ""; 227 | projectRoot = ""; 228 | targets = ( 229 | 55DB727C186E193500CAFEE7 /* ProvisionQL */, 230 | ); 231 | }; 232 | /* End PBXProject section */ 233 | 234 | /* Begin PBXResourcesBuildPhase section */ 235 | 55DB727A186E193500CAFEE7 /* Resources */ = { 236 | isa = PBXResourcesBuildPhase; 237 | buildActionMask = 2147483647; 238 | files = ( 239 | 553C6D321879E457002237FC /* blankIcon.png in Resources */, 240 | 55424C631870D90E002F5408 /* defaultIcon.png in Resources */, 241 | 555E951C186E2DC0001D406A /* template.html in Resources */, 242 | ); 243 | runOnlyForDeploymentPostprocessing = 0; 244 | }; 245 | /* End PBXResourcesBuildPhase section */ 246 | 247 | /* Begin PBXRezBuildPhase section */ 248 | 55DB727B186E193500CAFEE7 /* Rez */ = { 249 | isa = PBXRezBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | ); 253 | runOnlyForDeploymentPostprocessing = 0; 254 | }; 255 | /* End PBXRezBuildPhase section */ 256 | 257 | /* Begin PBXShellScriptBuildPhase section */ 258 | 554C6385186E258000D9EEDE /* Run Script */ = { 259 | isa = PBXShellScriptBuildPhase; 260 | alwaysOutOfDate = 1; 261 | buildActionMask = 12; 262 | files = ( 263 | ); 264 | inputPaths = ( 265 | ); 266 | name = "Run Script"; 267 | outputPaths = ( 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | shellPath = /bin/sh; 271 | shellScript = "sh ProvisionQL/Scripts/autoupdate-revision.sh\n"; 272 | }; 273 | /* End PBXShellScriptBuildPhase section */ 274 | 275 | /* Begin PBXSourcesBuildPhase section */ 276 | 55DB7277186E193500CAFEE7 /* Sources */ = { 277 | isa = PBXSourcesBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | 55424C681870DB2A002F5408 /* NSBezierPath+IOS7RoundedRect.m in Sources */, 281 | 555E9515186E2D67001D406A /* main.c in Sources */, 282 | 557C842618732599008A2A0C /* Shared.m in Sources */, 283 | 55DB728F186E193500CAFEE7 /* GenerateThumbnailForURL.m in Sources */, 284 | 55DB7291186E193500CAFEE7 /* GeneratePreviewForURL.m in Sources */, 285 | ); 286 | runOnlyForDeploymentPostprocessing = 0; 287 | }; 288 | /* End PBXSourcesBuildPhase section */ 289 | 290 | /* Begin XCBuildConfiguration section */ 291 | 55DB7295186E193500CAFEE7 /* Debug */ = { 292 | isa = XCBuildConfiguration; 293 | buildSettings = { 294 | ALWAYS_SEARCH_USER_PATHS = NO; 295 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 296 | CLANG_CXX_LIBRARY = "libc++"; 297 | CLANG_ENABLE_OBJC_ARC = YES; 298 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 299 | CLANG_WARN_BOOL_CONVERSION = YES; 300 | CLANG_WARN_COMMA = YES; 301 | CLANG_WARN_CONSTANT_CONVERSION = YES; 302 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 303 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 304 | CLANG_WARN_EMPTY_BODY = YES; 305 | CLANG_WARN_ENUM_CONVERSION = YES; 306 | CLANG_WARN_INFINITE_RECURSION = YES; 307 | CLANG_WARN_INT_CONVERSION = YES; 308 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 309 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 310 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 311 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 312 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 313 | CLANG_WARN_STRICT_PROTOTYPES = YES; 314 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 315 | CLANG_WARN_UNREACHABLE_CODE = YES; 316 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 317 | COPY_PHASE_STRIP = NO; 318 | ENABLE_STRICT_OBJC_MSGSEND = YES; 319 | ENABLE_TESTABILITY = YES; 320 | GCC_C_LANGUAGE_STANDARD = gnu99; 321 | GCC_DYNAMIC_NO_PIC = NO; 322 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 323 | GCC_NO_COMMON_BLOCKS = YES; 324 | GCC_OPTIMIZATION_LEVEL = 0; 325 | GCC_PREPROCESSOR_DEFINITIONS = ( 326 | "DEBUG=1", 327 | "$(inherited)", 328 | ); 329 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 330 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 331 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 332 | GCC_WARN_UNDECLARED_SELECTOR = YES; 333 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 334 | GCC_WARN_UNUSED_FUNCTION = YES; 335 | GCC_WARN_UNUSED_VARIABLE = YES; 336 | MACOSX_DEPLOYMENT_TARGET = 10.13; 337 | ONLY_ACTIVE_ARCH = YES; 338 | SDKROOT = macosx; 339 | }; 340 | name = Debug; 341 | }; 342 | 55DB7296186E193500CAFEE7 /* Release */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | ALWAYS_SEARCH_USER_PATHS = NO; 346 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 347 | CLANG_CXX_LIBRARY = "libc++"; 348 | CLANG_ENABLE_OBJC_ARC = YES; 349 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 350 | CLANG_WARN_BOOL_CONVERSION = YES; 351 | CLANG_WARN_COMMA = YES; 352 | CLANG_WARN_CONSTANT_CONVERSION = YES; 353 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 354 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 355 | CLANG_WARN_EMPTY_BODY = YES; 356 | CLANG_WARN_ENUM_CONVERSION = YES; 357 | CLANG_WARN_INFINITE_RECURSION = YES; 358 | CLANG_WARN_INT_CONVERSION = YES; 359 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 360 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 361 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 362 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 363 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 364 | CLANG_WARN_STRICT_PROTOTYPES = YES; 365 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 366 | CLANG_WARN_UNREACHABLE_CODE = YES; 367 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 368 | COPY_PHASE_STRIP = YES; 369 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 370 | ENABLE_NS_ASSERTIONS = NO; 371 | ENABLE_STRICT_OBJC_MSGSEND = YES; 372 | GCC_C_LANGUAGE_STANDARD = gnu99; 373 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 374 | GCC_NO_COMMON_BLOCKS = YES; 375 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 376 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 377 | GCC_WARN_UNDECLARED_SELECTOR = YES; 378 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 379 | GCC_WARN_UNUSED_FUNCTION = YES; 380 | GCC_WARN_UNUSED_VARIABLE = YES; 381 | MACOSX_DEPLOYMENT_TARGET = 10.13; 382 | SDKROOT = macosx; 383 | }; 384 | name = Release; 385 | }; 386 | 55DB7298186E193500CAFEE7 /* Debug */ = { 387 | isa = XCBuildConfiguration; 388 | buildSettings = { 389 | CODE_SIGN_IDENTITY = "Developer ID Application"; 390 | CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; 391 | CODE_SIGN_STYLE = Manual; 392 | COMBINE_HIDPI_IMAGES = YES; 393 | CURRENT_PROJECT_VERSION = 1; 394 | DEVELOPMENT_TEAM = 5567X9EQ9Q; 395 | ENABLE_HARDENED_RUNTIME = YES; 396 | INFOPLIST_FILE = "ProvisionQL/Supporting-files/Info.plist"; 397 | INSTALL_PATH = /Library/QuickLook; 398 | MARKETING_VERSION = 1.6.4; 399 | OTHER_CODE_SIGN_FLAGS = "--timestamp"; 400 | PRODUCT_BUNDLE_IDENTIFIER = com.ealeksandrov.ProvisionQL; 401 | PRODUCT_NAME = "$(TARGET_NAME)"; 402 | WRAPPER_EXTENSION = qlgenerator; 403 | }; 404 | name = Debug; 405 | }; 406 | 55DB7299186E193500CAFEE7 /* Release */ = { 407 | isa = XCBuildConfiguration; 408 | buildSettings = { 409 | CODE_SIGN_IDENTITY = "Developer ID Application"; 410 | CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; 411 | CODE_SIGN_STYLE = Manual; 412 | COMBINE_HIDPI_IMAGES = YES; 413 | CURRENT_PROJECT_VERSION = 1; 414 | DEVELOPMENT_TEAM = 5567X9EQ9Q; 415 | ENABLE_HARDENED_RUNTIME = YES; 416 | INFOPLIST_FILE = "ProvisionQL/Supporting-files/Info.plist"; 417 | INSTALL_PATH = /Library/QuickLook; 418 | MARKETING_VERSION = 1.6.4; 419 | OTHER_CODE_SIGN_FLAGS = "--timestamp"; 420 | PRODUCT_BUNDLE_IDENTIFIER = com.ealeksandrov.ProvisionQL; 421 | PRODUCT_NAME = "$(TARGET_NAME)"; 422 | WRAPPER_EXTENSION = qlgenerator; 423 | }; 424 | name = Release; 425 | }; 426 | /* End XCBuildConfiguration section */ 427 | 428 | /* Begin XCConfigurationList section */ 429 | 55DB7276186E193500CAFEE7 /* Build configuration list for PBXProject "ProvisionQL" */ = { 430 | isa = XCConfigurationList; 431 | buildConfigurations = ( 432 | 55DB7295186E193500CAFEE7 /* Debug */, 433 | 55DB7296186E193500CAFEE7 /* Release */, 434 | ); 435 | defaultConfigurationIsVisible = 0; 436 | defaultConfigurationName = Release; 437 | }; 438 | 55DB7297186E193500CAFEE7 /* Build configuration list for PBXNativeTarget "ProvisionQL" */ = { 439 | isa = XCConfigurationList; 440 | buildConfigurations = ( 441 | 55DB7298186E193500CAFEE7 /* Debug */, 442 | 55DB7299186E193500CAFEE7 /* Release */, 443 | ); 444 | defaultConfigurationIsVisible = 0; 445 | defaultConfigurationName = Release; 446 | }; 447 | /* End XCConfigurationList section */ 448 | }; 449 | rootObject = 55DB7273186E193500CAFEE7 /* Project object */; 450 | } 451 | -------------------------------------------------------------------------------- /ProvisionQL.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ProvisionQL.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ProvisionQL/GeneratePreviewForURL.m: -------------------------------------------------------------------------------- 1 | #import "Shared.h" 2 | 3 | OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options); 4 | void CancelPreviewGeneration(void *thisInterface, QLPreviewRequestRef preview); 5 | 6 | /* ----------------------------------------------------------------------------- 7 | Generate a preview for file 8 | 9 | This function's job is to create preview for designated file 10 | ----------------------------------------------------------------------------- */ 11 | 12 | void displayKeyAndValue(NSUInteger level, NSString *key, id value, NSMutableString *output) { 13 | int indent = (int)(level * 4); 14 | 15 | if ([value isKindOfClass:[NSDictionary class]]) { 16 | if (key) { 17 | [output appendFormat:@"%*s%@ = {\n", indent, "", key]; 18 | } else if (level != 0) { 19 | [output appendFormat:@"%*s{\n", indent, ""]; 20 | } 21 | NSDictionary *dictionary = (NSDictionary *)value; 22 | NSArray *keys = [[dictionary allKeys] sortedArrayUsingSelector:@selector(compare:)]; 23 | for (NSString *subKey in keys) { 24 | NSUInteger subLevel = (key == nil && level == 0) ? 0 : level + 1; 25 | displayKeyAndValue(subLevel, subKey, [dictionary valueForKey:subKey], output); 26 | } 27 | if (level != 0) { 28 | [output appendFormat:@"%*s}\n", indent, ""]; 29 | } 30 | } else if ([value isKindOfClass:[NSArray class]]) { 31 | [output appendFormat:@"%*s%@ = (\n", indent, "", key]; 32 | NSArray *array = (NSArray *)value; 33 | for (id value in array) { 34 | displayKeyAndValue(level + 1, nil, value, output); 35 | } 36 | [output appendFormat:@"%*s)\n", indent, ""]; 37 | } else if ([value isKindOfClass:[NSData class]]) { 38 | NSData *data = (NSData *)value; 39 | if (key) { 40 | [output appendFormat:@"%*s%@ = %zd bytes of data\n", indent, "", key, [data length]]; 41 | } else { 42 | [output appendFormat:@"%*s%zd bytes of data\n", indent, "", [data length]]; 43 | } 44 | } else { 45 | if (key) { 46 | [output appendFormat:@"%*s%@ = %@\n", indent, "", key, value]; 47 | } else { 48 | [output appendFormat:@"%*s%@\n", indent, "", value]; 49 | } 50 | } 51 | } 52 | 53 | NSString *expirationStringForDateInCalendar(NSDate *date, NSCalendar *calendar) { 54 | NSString *result = nil; 55 | 56 | if (date) { 57 | NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init]; 58 | formatter.unitsStyle = NSDateComponentsFormatterUnitsStyleFull; 59 | formatter.maximumUnitCount = 1; 60 | 61 | NSDateComponents *dateComponents = [calendar components:(NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute) 62 | fromDate:[NSDate date] 63 | toDate:date 64 | options:0]; 65 | if ([date compare:[NSDate date]] == NSOrderedAscending) { 66 | if ([calendar isDate:date inSameDayAsDate:[NSDate date]]) { 67 | result = @"Expired today"; 68 | } else { 69 | NSDateComponents *reverseDateComponents = [calendar components:(NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute) 70 | fromDate:date 71 | toDate:[NSDate date] 72 | options:0]; 73 | result = [NSString stringWithFormat:@"Expired %@ ago", [formatter stringFromDateComponents:reverseDateComponents]]; 74 | } 75 | } else { 76 | if (dateComponents.day == 0) { 77 | result = @"Expires today"; 78 | } else if (dateComponents.day < 30) { 79 | result = [NSString stringWithFormat:@"Expires in %@", [formatter stringFromDateComponents:dateComponents]]; 80 | } else { 81 | result = [NSString stringWithFormat:@"Expires in %@", [formatter stringFromDateComponents:dateComponents]]; 82 | } 83 | } 84 | 85 | } 86 | 87 | return result; 88 | } 89 | 90 | NSString *formattedStringForCertificates(NSArray *value) { 91 | static NSString *const devCertSummaryKey = @"summary"; 92 | static NSString *const devCertInvalidityDateKey = @"invalidity"; 93 | 94 | NSMutableArray *certificateDetails = [NSMutableArray array]; 95 | NSArray *array = (NSArray *)value; 96 | for (NSData *data in array) { 97 | SecCertificateRef certificateRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)data); 98 | if (certificateRef) { 99 | CFStringRef summaryRef = SecCertificateCopySubjectSummary(certificateRef); 100 | NSString *summary = (NSString *)CFBridgingRelease(summaryRef); 101 | if (summary) { 102 | NSMutableDictionary *detailsDict = [NSMutableDictionary dictionaryWithObject:summary forKey:devCertSummaryKey]; 103 | 104 | CFErrorRef error; 105 | CFDictionaryRef valuesDict = SecCertificateCopyValues(certificateRef, (__bridge CFArrayRef)@[(__bridge id)kSecOIDInvalidityDate], &error); 106 | if (valuesDict) { 107 | CFDictionaryRef invalidityDateDictionaryRef = CFDictionaryGetValue(valuesDict, kSecOIDInvalidityDate); 108 | if (invalidityDateDictionaryRef) { 109 | CFTypeRef invalidityRef = CFDictionaryGetValue(invalidityDateDictionaryRef, kSecPropertyKeyValue); 110 | CFRetain(invalidityRef); 111 | 112 | // NOTE: the invalidity date type of kSecPropertyTypeDate is documented as a CFStringRef in the "Certificate, Key, and Trust Services Reference". 113 | // In reality, it's a __NSTaggedDate (presumably a tagged pointer representing an NSDate.) But to sure, we'll check: 114 | id invalidity = CFBridgingRelease(invalidityRef); 115 | if (invalidity) { 116 | if ([invalidity isKindOfClass:[NSDate class]]) { 117 | // use the date directly 118 | [detailsDict setObject:invalidity forKey:devCertInvalidityDateKey]; 119 | } else { 120 | // parse the date from a string 121 | NSString *string = [invalidity description]; 122 | NSDateFormatter *invalidityDateFormatter = [NSDateFormatter new]; 123 | [invalidityDateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss Z"]; 124 | NSDate *invalidityDate = [invalidityDateFormatter dateFromString:string]; 125 | if (invalidityDate) { 126 | [detailsDict setObject:invalidityDate forKey:devCertInvalidityDateKey]; 127 | } 128 | } 129 | } else { 130 | NSLog(@"No invalidity date in '%@' certificate, dictionary = %@", summary, invalidityDateDictionaryRef); 131 | } 132 | } else { 133 | NSLog(@"No invalidity values in '%@' certificate, dictionary = %@", summary, valuesDict); 134 | } 135 | 136 | CFRelease(valuesDict); 137 | } else { 138 | NSLog(@"Could not get values in '%@' certificate, error = %@", summary, error); 139 | } 140 | 141 | [certificateDetails addObject:detailsDict]; 142 | } else { 143 | NSLog(@"Could not get summary from certificate"); 144 | } 145 | 146 | CFRelease(certificateRef); 147 | } 148 | } 149 | 150 | NSMutableString *certificates = [NSMutableString string]; 151 | [certificates appendString:@"\n"]; 152 | 153 | NSArray *sortedCertificateDetails = [certificateDetails sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { 154 | return [((NSDictionary *)obj1)[devCertSummaryKey] compare:((NSDictionary *)obj2)[devCertSummaryKey]]; 155 | }]; 156 | 157 | for (NSDictionary *detailsDict in sortedCertificateDetails) { 158 | NSString *summary = detailsDict[devCertSummaryKey]; 159 | NSDate *invalidityDate = detailsDict[devCertInvalidityDateKey]; 160 | NSString *expiration = expirationStringForDateInCalendar(invalidityDate, [NSCalendar currentCalendar]); 161 | if (! expiration) { 162 | expiration = @"No invalidity date in certificate"; 163 | } 164 | [certificates appendFormat:@"\n", summary, expiration]; 165 | } 166 | [certificates appendString:@"
%@%@
\n"]; 167 | 168 | return [certificates copy]; 169 | } 170 | 171 | NSDictionary *formattedDevicesData(NSArray *value) { 172 | 173 | NSArray *array = (NSArray *)value; 174 | NSArray *sortedArray = [array sortedArrayUsingSelector:@selector(compare:)]; 175 | 176 | NSString *currentPrefix = nil; 177 | NSMutableString *devices = [NSMutableString string]; 178 | [devices appendString:@"\n"]; 179 | [devices appendString:@"\n"]; 180 | 181 | for (NSString *device in sortedArray) { 182 | // compute the prefix for the first column of the table 183 | NSString *displayPrefix = @""; 184 | NSString *devicePrefix = [device substringToIndex:1]; 185 | if (! [currentPrefix isEqualToString:devicePrefix]) { 186 | currentPrefix = devicePrefix; 187 | displayPrefix = [NSString stringWithFormat:@"%@ ➞ ", devicePrefix]; 188 | } 189 | 190 | [devices appendFormat:@"\n", displayPrefix, device]; 191 | } 192 | [devices appendString:@"
UDID
%@%@
\n"]; 193 | 194 | return @{@"ProvisionedDevicesFormatted" : [devices copy], @"ProvisionedDevicesCount" : [NSString stringWithFormat:@"%zd Device%s", [array count], ([array count] == 1 ? "" : "s")]}; 195 | } 196 | 197 | NSString *formattedDictionaryWithReplacements(NSDictionary *dictionary, NSDictionary *replacements, int level) { 198 | 199 | NSMutableString *string = [NSMutableString string]; 200 | 201 | for (NSString *key in dictionary) { 202 | NSString *localizedKey = replacements[key] ?: key; 203 | NSObject *object = dictionary[key]; 204 | 205 | for (int idx = 0; idx < level; idx++) { 206 | if (level == 1) { 207 | [string appendString:@"- "]; 208 | } else { 209 | [string appendString:@"  "]; 210 | } 211 | } 212 | 213 | if ([object isKindOfClass:[NSDictionary class]]) { 214 | object = formattedDictionaryWithReplacements((NSDictionary *)object, replacements, level + 1); 215 | [string appendFormat:@"%@:
%@
", localizedKey, object]; 216 | } 217 | else if ([object isKindOfClass:[NSNumber class]]) { 218 | object = [(NSNumber *)object boolValue] ? @"YES" : @"NO"; 219 | [string appendFormat:@"%@: %@
", localizedKey, object]; 220 | } 221 | else { 222 | [string appendFormat:@"%@: %@
", localizedKey, object]; 223 | } 224 | } 225 | 226 | return string; 227 | } 228 | 229 | NSString *escapedXML(NSString *stringToEscape) { 230 | stringToEscape = [stringToEscape stringByReplacingOccurrencesOfString:@"&" withString:@"&"]; 231 | NSDictionary *htmlEntityReplacement = @{ 232 | @"\"": @""", 233 | @"'": @"'", 234 | @"<": @"<", 235 | @">": @">", 236 | }; 237 | for (NSString *key in [htmlEntityReplacement allKeys]) { 238 | NSString *replacement = [htmlEntityReplacement objectForKey:key]; 239 | stringToEscape = [stringToEscape stringByReplacingOccurrencesOfString:key withString:replacement]; 240 | } 241 | return stringToEscape; 242 | } 243 | 244 | NSData *codesignEntitlementsDataFromApp(NSData *infoPlistData, NSString *basePath) { 245 | // read the CFBundleExecutable and extract it 246 | NSDictionary *appPropertyList = [NSPropertyListSerialization propertyListWithData:infoPlistData options:0 format:NULL error:NULL]; 247 | NSString *bundleExecutable = [appPropertyList objectForKey:@"CFBundleExecutable"]; 248 | 249 | NSString *binaryPath = [basePath stringByAppendingPathComponent:bundleExecutable]; 250 | // get entitlements: codesign -d --entitlements - --xml 251 | NSTask *codesignTask = [NSTask new]; 252 | [codesignTask setLaunchPath:@"/usr/bin/codesign"]; 253 | [codesignTask setStandardOutput:[NSPipe pipe]]; 254 | [codesignTask setStandardError:[NSPipe pipe]]; 255 | if (@available(macOS 11, *)) { 256 | [codesignTask setArguments:@[@"-d", binaryPath, @"--entitlements", @"-", @"--xml"]]; 257 | } else { 258 | [codesignTask setArguments:@[@"-d", binaryPath, @"--entitlements", @":-"]]; 259 | } 260 | [codesignTask launch]; 261 | 262 | NSData *outputData = [[[codesignTask standardOutput] fileHandleForReading] readDataToEndOfFile]; 263 | NSData *errorData = [[[codesignTask standardError] fileHandleForReading] readDataToEndOfFile]; 264 | [codesignTask waitUntilExit]; 265 | 266 | if (outputData.length == 0) { 267 | return errorData; 268 | } 269 | 270 | return outputData; 271 | } 272 | 273 | NSString *iconAsBase64(NSImage *appIcon) { 274 | if (!appIcon) { 275 | NSURL *iconURL = [[NSBundle bundleWithIdentifier:kPluginBundleId] URLForResource:@"defaultIcon" withExtension:@"png"]; 276 | appIcon = [[NSImage alloc] initWithContentsOfURL:iconURL]; 277 | } 278 | appIcon = roundCorners(appIcon); 279 | NSData *imageData = [appIcon TIFFRepresentation]; 280 | NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:imageData]; 281 | imageData = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}]; 282 | return [imageData base64EncodedStringWithOptions:0]; 283 | } 284 | 285 | OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options) { 286 | @autoreleasepool { 287 | // create temp directory 288 | NSFileManager *fileManager = [NSFileManager defaultManager]; 289 | NSString *tempDirFolder = [NSTemporaryDirectory() stringByAppendingPathComponent:kPluginBundleId]; 290 | NSString *currentTempDirFolder = [tempDirFolder stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]]; 291 | [fileManager createDirectoryAtPath:currentTempDirFolder withIntermediateDirectories:YES attributes:nil error:nil]; 292 | 293 | NSURL *URL = (__bridge NSURL *)url; 294 | NSString *dataType = (__bridge NSString *)contentTypeUTI; 295 | NSData *provisionData = nil; 296 | NSData *appPlist = nil; 297 | NSData *codesignEntitlementsData = nil; 298 | NSImage *appIcon = nil; 299 | 300 | if ([dataType isEqualToString:kDataType_ipa]) { 301 | provisionData = unzipFile(URL, @"Payload/*.app/embedded.mobileprovision"); 302 | appPlist = unzipFile(URL, @"Payload/*.app/Info.plist"); 303 | 304 | // read codesigning entitlements from application binary (extract it first) 305 | NSDictionary *appPropertyList = [NSPropertyListSerialization propertyListWithData:appPlist options:0 format:NULL error:NULL]; 306 | NSString *bundleExecutable = [appPropertyList objectForKey:@"CFBundleExecutable"]; 307 | 308 | unzipFileToDir(URL, currentTempDirFolder, [@"Payload/*.app/" stringByAppendingPathComponent:bundleExecutable]); 309 | 310 | codesignEntitlementsData = codesignEntitlementsDataFromApp(appPlist, currentTempDirFolder); 311 | 312 | [fileManager removeItemAtPath:tempDirFolder error:nil]; 313 | } else if ([dataType isEqualToString:kDataType_xcode_archive]) { 314 | // get the embedded plist for the iOS app 315 | NSURL *appsDir = [URL URLByAppendingPathComponent:@"Products/Applications/"]; 316 | if (appsDir != nil) { 317 | NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:appsDir.path error:nil]; 318 | if (dirFiles.count > 0) { 319 | NSURL *appURL = [appsDir URLByAppendingPathComponent:dirFiles[0] isDirectory:YES]; 320 | 321 | provisionData = [NSData dataWithContentsOfURL:[appURL URLByAppendingPathComponent:@"embedded.mobileprovision"]]; 322 | appPlist = [NSData dataWithContentsOfURL:[appURL URLByAppendingPathComponent:@"Info.plist"]]; 323 | 324 | // read codesigning entitlements from application binary 325 | codesignEntitlementsData = codesignEntitlementsDataFromApp(appPlist, appURL.path); 326 | } 327 | } 328 | } else if ([dataType isEqualToString:kDataType_app_extension]) { 329 | // get embedded plist and provisioning 330 | provisionData = [NSData dataWithContentsOfURL:[URL URLByAppendingPathComponent:@"embedded.mobileprovision"]]; 331 | appPlist = [NSData dataWithContentsOfURL:[URL URLByAppendingPathComponent:@"Info.plist"]]; 332 | // read codesigning entitlements from application binary 333 | codesignEntitlementsData = codesignEntitlementsDataFromApp(appPlist, URL.path); 334 | } else { 335 | // use provisioning directly 336 | provisionData = [NSData dataWithContentsOfURL:URL]; 337 | } 338 | 339 | NSMutableDictionary *synthesizedInfo = [NSMutableDictionary dictionary]; 340 | NSURL *htmlURL = [[NSBundle bundleWithIdentifier:kPluginBundleId] URLForResource:@"template" withExtension:@"html"]; 341 | NSMutableString *html = [NSMutableString stringWithContentsOfURL:htmlURL encoding:NSUTF8StringEncoding error:NULL]; 342 | NSDateFormatter *dateFormatter = [NSDateFormatter new]; 343 | [dateFormatter setDateStyle:NSDateFormatterMediumStyle]; 344 | [dateFormatter setTimeStyle:NSDateFormatterMediumStyle]; 345 | NSCalendar *calendar = [NSCalendar currentCalendar]; 346 | id value = nil; 347 | NSString *synthesizedValue = nil; 348 | 349 | if ([dataType isEqualToString:kDataType_ipa]) { 350 | [synthesizedInfo setObject:@"App info" forKey:@"AppInfoTitle"]; 351 | } else if ([dataType isEqualToString:kDataType_app_extension]) { 352 | [synthesizedInfo setObject:@"App extension info" forKey:@"AppInfoTitle"]; 353 | } else if ([dataType isEqualToString:kDataType_xcode_archive]) { 354 | [synthesizedInfo setObject:@"Archive info" forKey:@"AppInfoTitle"]; 355 | } 356 | 357 | if (!provisionData) { 358 | NSLog(@"No provisionData for %@", URL); 359 | 360 | if (appPlist != nil) { 361 | [synthesizedInfo setObject:@"hiddenDiv" forKey:@"ProvisionInfo"]; 362 | } else { 363 | return noErr; 364 | } 365 | } else { 366 | [synthesizedInfo setObject:@"" forKey:@"ProvisionInfo"]; 367 | } 368 | 369 | // MARK: App Info 370 | 371 | if (appPlist != nil) { 372 | NSDictionary *appPropertyList = [NSPropertyListSerialization propertyListWithData:appPlist options:0 format:NULL error:NULL]; 373 | 374 | NSString *iconName = mainIconNameForApp(appPropertyList); 375 | appIcon = imageFromApp(URL, dataType, iconName); 376 | [synthesizedInfo setObject:iconAsBase64(appIcon) forKey:@"AppIcon"]; 377 | 378 | NSString *bundleName = [appPropertyList objectForKey:@"CFBundleDisplayName"]; 379 | if (!bundleName) { 380 | bundleName = [appPropertyList objectForKey:@"CFBundleName"]; 381 | } 382 | [synthesizedInfo setObject:bundleName ?: @"" forKey:@"CFBundleName"]; 383 | [synthesizedInfo setObject:[appPropertyList objectForKey:@"CFBundleIdentifier"] ?: @"" forKey:@"CFBundleIdentifier"]; 384 | [synthesizedInfo setObject:[appPropertyList objectForKey:@"CFBundleShortVersionString"] ?: @"" forKey:@"CFBundleShortVersionString"]; 385 | [synthesizedInfo setObject:[appPropertyList objectForKey:@"CFBundleVersion"] ?: @"" forKey:@"CFBundleVersion"]; 386 | 387 | NSString *extensionType = [[appPropertyList objectForKey:@"NSExtension"] objectForKey:@"NSExtensionPointIdentifier"]; 388 | if(extensionType != nil) { 389 | [synthesizedInfo setObject:@"" forKey:@"ExtensionInfo"]; 390 | [synthesizedInfo setObject:extensionType forKey:@"NSExtensionPointIdentifier"]; 391 | } else { 392 | [synthesizedInfo setObject:@"hiddenDiv" forKey:@"ExtensionInfo"]; 393 | } 394 | 395 | NSString *sdkName = [appPropertyList objectForKey:@"DTSDKName"] ?: @""; 396 | [synthesizedInfo setObject:sdkName forKey:@"DTSDKName"]; 397 | 398 | NSString *minimumOSVersion = [appPropertyList objectForKey:@"MinimumOSVersion"] ?: @""; 399 | [synthesizedInfo setObject:minimumOSVersion forKey:@"MinimumOSVersion"]; 400 | 401 | NSDictionary *appTransportSecurity = [appPropertyList objectForKey:@"NSAppTransportSecurity"]; 402 | NSString *appTransportSecurityFormatted = @"No exceptions"; 403 | if ([appTransportSecurity isKindOfClass:[NSDictionary class]]) { 404 | NSDictionary *localizedKeys = @{ 405 | @"NSAllowsArbitraryLoads": @"Allows Arbitrary Loads", 406 | @"NSAllowsArbitraryLoadsForMedia": @"Allows Arbitrary Loads for Media", 407 | @"NSAllowsArbitraryLoadsInWebContent": @"Allows Arbitrary Loads in Web Content", 408 | @"NSAllowsLocalNetworking": @"Allows Local Networking", 409 | @"NSExceptionDomains": @"Exception Domains", 410 | 411 | @"NSIncludesSubdomains": @"Includes Subdomains", 412 | @"NSRequiresCertificateTransparency": @"Requires Certificate Transparency", 413 | 414 | @"NSExceptionAllowsInsecureHTTPLoads": @"Allows Insecure HTTP Loads", 415 | @"NSExceptionMinimumTLSVersion": @"Minimum TLS Version", 416 | @"NSExceptionRequiresForwardSecrecy": @"Requires Forward Secrecy", 417 | 418 | @"NSThirdPartyExceptionAllowsInsecureHTTPLoads": @"Allows Insecure HTTP Loads", 419 | @"NSThirdPartyExceptionMinimumTLSVersion": @"Minimum TLS Version", 420 | @"NSThirdPartyExceptionRequiresForwardSecrecy": @"Requires Forward Secrecy" 421 | }; 422 | 423 | NSString *formattedDictionaryString = formattedDictionaryWithReplacements(appTransportSecurity, localizedKeys, 0); 424 | appTransportSecurityFormatted = [NSString stringWithFormat:@"
%@
", formattedDictionaryString]; 425 | } else { 426 | double sdkNumber = [[sdkName stringByTrimmingCharactersInSet:[NSCharacterSet letterCharacterSet]] doubleValue]; 427 | if (sdkNumber < 9.0) { 428 | appTransportSecurityFormatted = @"Not applicable before iOS 9.0"; 429 | } 430 | } 431 | 432 | [synthesizedInfo setObject:appTransportSecurityFormatted forKey:@"AppTransportSecurityFormatted"]; 433 | 434 | NSMutableArray *platforms = [NSMutableArray array]; 435 | for (NSNumber *number in [appPropertyList objectForKey:@"UIDeviceFamily"]) { 436 | switch ([number intValue]) { 437 | case 1: 438 | [platforms addObject:@"iPhone"]; 439 | break; 440 | case 2: 441 | [platforms addObject:@"iPad"]; 442 | break; 443 | case 3: 444 | [platforms addObject:@"TV"]; 445 | break; 446 | case 4: 447 | [platforms addObject:@"Watch"]; 448 | break; 449 | default: 450 | break; 451 | } 452 | } 453 | [synthesizedInfo setObject:[platforms componentsJoinedByString:@", "] forKey:@"UIDeviceFamily"]; 454 | [synthesizedInfo setObject:@"" forKey:@"AppInfo"]; 455 | [synthesizedInfo setObject:@"hiddenDiv" forKey:@"ProvisionAsSubheader"]; 456 | } else { 457 | [synthesizedInfo setObject:@"hiddenDiv" forKey:@"AppInfo"]; 458 | [synthesizedInfo setObject:@"" forKey:@"ProvisionAsSubheader"]; 459 | } 460 | 461 | // MARK: Provisioning 462 | 463 | CMSDecoderRef decoder = NULL; 464 | CMSDecoderCreate(&decoder); 465 | CMSDecoderUpdateMessage(decoder, provisionData.bytes, provisionData.length); 466 | CMSDecoderFinalizeMessage(decoder); 467 | CFDataRef dataRef = NULL; 468 | CMSDecoderCopyContent(decoder, &dataRef); 469 | NSData *data = (NSData *)CFBridgingRelease(dataRef); 470 | CFRelease(decoder); 471 | 472 | if ((!data && !appPlist) || QLPreviewRequestIsCancelled(preview)) { 473 | return noErr; 474 | } 475 | 476 | if (data) { 477 | // use all keys and values in the property list to generate replacement tokens and values 478 | NSDictionary *propertyList = [NSPropertyListSerialization propertyListWithData:data options:0 format:NULL error:NULL]; 479 | for (NSString *key in [propertyList allKeys]) { 480 | NSString *replacementValue = [[propertyList valueForKey:key] description]; 481 | NSString *replacementToken = [NSString stringWithFormat:@"__%@__", key]; 482 | [html replaceOccurrencesOfString:replacementToken withString:replacementValue options:0 range:NSMakeRange(0, [html length])]; 483 | } 484 | 485 | // synthesize other replacement tokens and values 486 | value = [propertyList objectForKey:@"CreationDate"]; 487 | if ([value isKindOfClass:[NSDate class]]) { 488 | NSDate *date = (NSDate *)value; 489 | synthesizedValue = [dateFormatter stringFromDate:date]; 490 | [synthesizedInfo setObject:synthesizedValue forKey:@"CreationDateFormatted"]; 491 | 492 | NSDateComponents *dateComponents = [calendar components:(NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute) 493 | fromDate:date 494 | toDate:[NSDate date] 495 | options:0]; 496 | if ([calendar isDate:date inSameDayAsDate:[NSDate date]]) { 497 | synthesizedValue = @"Created today"; 498 | } else { 499 | NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init]; 500 | formatter.unitsStyle = NSDateComponentsFormatterUnitsStyleFull; 501 | formatter.maximumUnitCount = 1; 502 | 503 | synthesizedValue = [NSString stringWithFormat:@"Created %@ ago", [formatter stringFromDateComponents:dateComponents]]; 504 | } 505 | [synthesizedInfo setObject:synthesizedValue forKey:@"CreationSummary"]; 506 | } 507 | 508 | value = [propertyList objectForKey:@"ExpirationDate"]; 509 | if ([value isKindOfClass:[NSDate class]]) { 510 | NSDate *date = (NSDate *)value; 511 | synthesizedValue = [dateFormatter stringFromDate:date]; 512 | [synthesizedInfo setObject:synthesizedValue forKey:@"ExpirationDateFormatted"]; 513 | 514 | synthesizedValue = expirationStringForDateInCalendar(date, calendar); 515 | [synthesizedInfo setObject:synthesizedValue forKey:@"ExpirationSummary"]; 516 | 517 | int expStatus = expirationStatus(date, calendar); 518 | if (expStatus == 0) { 519 | synthesizedValue = @"expired"; 520 | } else if (expStatus == 1) { 521 | synthesizedValue = @"expiring"; 522 | } else { 523 | synthesizedValue = @"valid"; 524 | } 525 | [synthesizedInfo setObject:synthesizedValue forKey:@"ExpStatus"]; 526 | } 527 | 528 | value = [propertyList objectForKey:@"TeamIdentifier"]; 529 | if ([value isKindOfClass:[NSArray class]]) { 530 | NSArray *array = (NSArray *)value; 531 | synthesizedValue = [array componentsJoinedByString:@", "]; 532 | [synthesizedInfo setObject:synthesizedValue forKey:@"TeamIds"]; 533 | } 534 | 535 | BOOL showEntitlementsWarning = false; 536 | if (codesignEntitlementsData != nil) { 537 | // read the entitlements directly from the codesign output 538 | NSDictionary *entitlementsPropertyList = [NSPropertyListSerialization propertyListWithData:codesignEntitlementsData options:0 format:NULL error:NULL]; 539 | if (entitlementsPropertyList != nil) { 540 | NSMutableString *dictionaryFormatted = [NSMutableString string]; 541 | displayKeyAndValue(0, nil, entitlementsPropertyList, dictionaryFormatted); 542 | synthesizedValue = [NSString stringWithFormat:@"
%@
", dictionaryFormatted]; 543 | } else { 544 | NSString *outputString = [[NSString alloc] initWithData:codesignEntitlementsData encoding:NSUTF8StringEncoding]; 545 | NSString *errorOutput; 546 | if ([outputString hasPrefix:@"Executable="]) { 547 | // remove first line with long temporary path to the executable 548 | NSArray *allLines = [outputString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; 549 | errorOutput = [[allLines subarrayWithRange:NSMakeRange(1, allLines.count - 1)] componentsJoinedByString:@"
"]; 550 | } else { 551 | errorOutput = outputString; 552 | } 553 | showEntitlementsWarning = true; 554 | synthesizedValue = errorOutput; 555 | } 556 | [synthesizedInfo setObject:synthesizedValue forKey:@"EntitlementsFormatted"]; 557 | } else { 558 | // read the entitlements from the provisioning profile instead 559 | value = [propertyList objectForKey:@"Entitlements"]; 560 | if ([value isKindOfClass:[NSDictionary class]]) { 561 | NSDictionary *dictionary = (NSDictionary *)value; 562 | NSMutableString *dictionaryFormatted = [NSMutableString string]; 563 | displayKeyAndValue(0, nil, dictionary, dictionaryFormatted); 564 | synthesizedValue = [NSString stringWithFormat:@"
%@
", dictionaryFormatted]; 565 | 566 | [synthesizedInfo setObject:synthesizedValue forKey:@"EntitlementsFormatted"]; 567 | } else { 568 | [synthesizedInfo setObject:@"No Entitlements" forKey:@"EntitlementsFormatted"]; 569 | } 570 | } 571 | if (showEntitlementsWarning) { 572 | [synthesizedInfo setObject:@"" forKey:@"EntitlementsWarning"]; 573 | } else { 574 | [synthesizedInfo setObject:@"hiddenDiv" forKey:@"EntitlementsWarning"]; 575 | } 576 | 577 | value = [propertyList objectForKey:@"DeveloperCertificates"]; 578 | if ([value isKindOfClass:[NSArray class]]) { 579 | [synthesizedInfo setObject:formattedStringForCertificates(value) forKey:@"DeveloperCertificatesFormatted"]; 580 | } else { 581 | [synthesizedInfo setObject:@"No Developer Certificates" forKey:@"DeveloperCertificatesFormatted"]; 582 | } 583 | 584 | value = [propertyList objectForKey:@"ProvisionedDevices"]; 585 | if ([value isKindOfClass:[NSArray class]]) { 586 | [synthesizedInfo addEntriesFromDictionary:formattedDevicesData(value)]; 587 | } else { 588 | [synthesizedInfo setObject:@"No Devices" forKey:@"ProvisionedDevicesFormatted"]; 589 | [synthesizedInfo setObject:@"Distribution Profile" forKey:@"ProvisionedDevicesCount"]; 590 | } 591 | 592 | { 593 | NSString *profileString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 594 | profileString = escapedXML(profileString); 595 | synthesizedValue = [NSString stringWithFormat:@"
%@
", profileString]; 596 | [synthesizedInfo setObject:synthesizedValue forKey:@"RawData"]; 597 | } 598 | 599 | // older provisioning files don't include some key/value pairs 600 | value = [propertyList objectForKey:@"TeamName"]; 601 | if (! value) { 602 | [synthesizedInfo setObject:@"Team name not available" forKey:@"TeamName"]; 603 | } 604 | value = [propertyList objectForKey:@"TeamIdentifier"]; 605 | if (! value) { 606 | [synthesizedInfo setObject:@"Team ID not available" forKey:@"TeamIds"]; 607 | } 608 | value = [propertyList objectForKey:@"AppIDName"]; 609 | if (! value) { 610 | [synthesizedInfo setObject:@"App name not available" forKey:@"AppIDName"]; 611 | } 612 | 613 | // determine the profile type 614 | BOOL getTaskAllow = NO; 615 | value = [propertyList objectForKey:@"Entitlements"]; 616 | if ([value isKindOfClass:[NSDictionary class]]) { 617 | NSDictionary *dictionary = (NSDictionary *)value; 618 | getTaskAllow = [[dictionary valueForKey:@"get-task-allow"] boolValue]; 619 | } 620 | 621 | BOOL hasDevices = NO; 622 | value = [propertyList objectForKey:@"ProvisionedDevices"]; 623 | if ([value isKindOfClass:[NSArray class]]) { 624 | hasDevices = YES; 625 | } 626 | 627 | BOOL isEnterprise = [[propertyList objectForKey:@"ProvisionsAllDevices"] boolValue]; 628 | 629 | if ([dataType isEqualToString:kDataType_osx_provision]) { 630 | [synthesizedInfo setObject:@"mac" forKey:@"Platform"]; 631 | 632 | [synthesizedInfo setObject:@"Mac" forKey:@"ProfilePlatform"]; 633 | if (hasDevices) { 634 | [synthesizedInfo setObject:@"Development" forKey:@"ProfileType"]; 635 | } else { 636 | [synthesizedInfo setObject:@"Distribution (App Store)" forKey:@"ProfileType"]; 637 | } 638 | } else { 639 | [synthesizedInfo setObject:@"ios" forKey:@"Platform"]; 640 | 641 | [synthesizedInfo setObject:@"iOS" forKey:@"ProfilePlatform"]; 642 | if (hasDevices) { 643 | if (getTaskAllow) { 644 | [synthesizedInfo setObject:@"Development" forKey:@"ProfileType"]; 645 | } else { 646 | [synthesizedInfo setObject:@"Distribution (Ad Hoc)" forKey:@"ProfileType"]; 647 | } 648 | } else { 649 | if (isEnterprise) { 650 | [synthesizedInfo setObject:@"Enterprise" forKey:@"ProfileType"]; 651 | } else { 652 | [synthesizedInfo setObject:@"Distribution (App Store)" forKey:@"ProfileType"]; 653 | } 654 | } 655 | } 656 | } 657 | 658 | // MARK: File Info 659 | 660 | [synthesizedInfo setObject:escapedXML([URL lastPathComponent]) forKey:@"FileName"]; 661 | 662 | if ([[URL pathExtension] isEqualToString:@"app"] || [[URL pathExtension] isEqualToString:@"appex"]) { 663 | // get the "file" information using the application package folder 664 | NSString *folderPath = [URL path]; 665 | 666 | NSDictionary *folderAttributes = [fileManager attributesOfItemAtPath:folderPath error:NULL]; 667 | if (folderAttributes) { 668 | NSDate *folderModificationDate = [folderAttributes fileModificationDate]; 669 | 670 | unsigned long long folderSize = 0; 671 | NSArray *filesArray = [fileManager subpathsOfDirectoryAtPath:folderPath error:nil]; 672 | for (NSString *fileName in filesArray) { 673 | NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:[folderPath stringByAppendingPathComponent:fileName] error:NULL]; 674 | if (fileAttributes) 675 | folderSize += [fileAttributes fileSize]; 676 | } 677 | 678 | synthesizedValue = [NSString stringWithFormat:@"%@, Modified %@", 679 | [NSByteCountFormatter stringFromByteCount:folderSize countStyle:NSByteCountFormatterCountStyleFile], 680 | [dateFormatter stringFromDate:folderModificationDate]]; 681 | [synthesizedInfo setObject:synthesizedValue forKey:@"FileInfo"]; 682 | } else { 683 | [synthesizedInfo setObject:@"" forKey:@"FileInfo"]; 684 | } 685 | } else { 686 | NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:[URL path] error:NULL]; 687 | if (fileAttributes) { 688 | NSDate *fileModificationDate = [fileAttributes fileModificationDate]; 689 | unsigned long long fileSize = [fileAttributes fileSize]; 690 | 691 | synthesizedValue = [NSString stringWithFormat:@"%@, Modified %@", 692 | [NSByteCountFormatter stringFromByteCount:fileSize countStyle:NSByteCountFormatterCountStyleFile], 693 | [dateFormatter stringFromDate:fileModificationDate]]; 694 | [synthesizedInfo setObject:synthesizedValue forKey:@"FileInfo"]; 695 | } 696 | } 697 | 698 | // MARK: Footer 699 | 700 | #ifdef DEBUG 701 | [synthesizedInfo setObject:@"(debug)" forKey:@"DEBUG"]; 702 | #else 703 | [synthesizedInfo setObject:@"" forKey:@"DEBUG"]; 704 | #endif 705 | 706 | synthesizedValue = [[NSBundle bundleWithIdentifier:kPluginBundleId] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; 707 | [synthesizedInfo setObject:synthesizedValue ?: @"" forKey:@"BundleShortVersionString"]; 708 | 709 | synthesizedValue = [[NSBundle bundleWithIdentifier:kPluginBundleId] objectForInfoDictionaryKey:@"CFBundleVersion"]; 710 | [synthesizedInfo setObject:synthesizedValue ?: @"" forKey:@"BundleVersion"]; 711 | 712 | for (NSString *key in [synthesizedInfo allKeys]) { 713 | NSString *replacementValue = [synthesizedInfo objectForKey:key]; 714 | NSString *replacementToken = [NSString stringWithFormat:@"__%@__", key]; 715 | [html replaceOccurrencesOfString:replacementToken withString:replacementValue options:0 range:NSMakeRange(0, [html length])]; 716 | } 717 | 718 | NSDictionary *properties = @{ // properties for the HTML data 719 | (__bridge NSString *)kQLPreviewPropertyTextEncodingNameKey : @"UTF-8", 720 | (__bridge NSString *)kQLPreviewPropertyMIMETypeKey : @"text/html" }; 721 | 722 | QLPreviewRequestSetDataRepresentation(preview, (__bridge CFDataRef)[html dataUsingEncoding:NSUTF8StringEncoding], kUTTypeHTML, (__bridge CFDictionaryRef)properties); 723 | } 724 | 725 | return noErr; 726 | } 727 | 728 | void CancelThumbnailGeneration(void *thisInterface, QLThumbnailRequestRef thumbnail) { 729 | // Implement only if supported 730 | } 731 | -------------------------------------------------------------------------------- /ProvisionQL/GenerateThumbnailForURL.m: -------------------------------------------------------------------------------- 1 | #import "Shared.h" 2 | 3 | //Layout constants 4 | #define BADGE_MARGIN 10.0 5 | #define MIN_BADGE_WIDTH 40.0 6 | #define BADGE_HEIGHT 75.0 7 | #define BADGE_MARGIN_X 60.0 8 | #define BADGE_MARGIN_Y 80.0 9 | 10 | //Drawing constants 11 | #define BADGE_BG_COLOR [NSColor lightGrayColor] 12 | #define BADGE_VALID_COLOR [NSColor colorWithCalibratedRed:(0/255.0) green:(98/255.0) blue:(25/255.0) alpha:1] 13 | #define BADGE_EXPIRING_COLOR [NSColor colorWithCalibratedRed:(146/255.0) green:(95/255.0) blue:(28/255.0) alpha:1] 14 | #define BADGE_EXPIRED_COLOR [NSColor colorWithCalibratedRed:(141/255.0) green:(0/255.0) blue:(7/255.0) alpha:1] 15 | #define BADGE_FONT [NSFont boldSystemFontOfSize:64] 16 | 17 | 18 | OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize); 19 | void CancelThumbnailGeneration(void *thisInterface, QLThumbnailRequestRef thumbnail); 20 | 21 | /* ----------------------------------------------------------------------------- 22 | Generate a thumbnail for file 23 | 24 | This function's job is to create thumbnail for designated file as fast as possible 25 | ----------------------------------------------------------------------------- */ 26 | 27 | OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize) { 28 | @autoreleasepool { 29 | NSURL *URL = (__bridge NSURL *)url; 30 | NSString *dataType = (__bridge NSString *)contentTypeUTI; 31 | NSData *appPlist = nil; 32 | NSImage *appIcon = nil; 33 | 34 | if ([dataType isEqualToString:kDataType_xcode_archive]) { 35 | // get the embedded plist for the iOS app 36 | NSURL *appsDir = [URL URLByAppendingPathComponent:@"Products/Applications/"]; 37 | if (appsDir != nil) { 38 | NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:appsDir.path error:nil]; 39 | if (dirFiles.count > 0) { 40 | appPlist = [NSData dataWithContentsOfURL:[appsDir URLByAppendingPathComponent:[NSString stringWithFormat:@"%@/Info.plist", dirFiles[0]]]]; 41 | } 42 | } 43 | } else if([dataType isEqualToString:kDataType_ipa]) { 44 | appPlist = unzipFile(URL, @"Payload/*.app/Info.plist"); 45 | } 46 | 47 | if (QLThumbnailRequestIsCancelled(thumbnail)) { 48 | return noErr; 49 | } 50 | 51 | // MARK: .ipa & .xarchive 52 | 53 | if ([dataType isEqualToString:kDataType_ipa] || [dataType isEqualToString:kDataType_xcode_archive]) { 54 | NSDictionary *appPropertyList = [NSPropertyListSerialization propertyListWithData:appPlist options:0 format:NULL error:NULL]; 55 | NSString *iconName = mainIconNameForApp(appPropertyList); 56 | appIcon = imageFromApp(URL, dataType, iconName); 57 | 58 | if (QLThumbnailRequestIsCancelled(thumbnail)) { 59 | return noErr; 60 | } 61 | 62 | if (!appIcon) { 63 | NSURL *iconURL = [[NSBundle bundleWithIdentifier:kPluginBundleId] URLForResource:@"defaultIcon" withExtension:@"png"]; 64 | appIcon = [[NSImage alloc] initWithContentsOfURL:iconURL]; 65 | } 66 | static const NSString *IconFlavor; 67 | if (@available(macOS 10.15, *)) { 68 | IconFlavor = @"icon"; 69 | } else { 70 | IconFlavor = @"IconFlavor"; 71 | } 72 | NSDictionary *propertiesDict = nil; 73 | if ([dataType isEqualToString:kDataType_xcode_archive]) { 74 | // 0: Plain transparent, 1: Shadow, 2: Book, 3: Movie, 4: Address, 5: Image, 75 | // 6: Gloss, 7: Slide, 8: Square, 9: Border, 11: Calendar, 12: Pattern 76 | propertiesDict = @{IconFlavor : @(12)}; 77 | } else { 78 | propertiesDict = @{IconFlavor : @(0)}; 79 | } 80 | // image-only icons can be drawn efficiently. 81 | appIcon = roundCorners(appIcon); 82 | // if downscale, then this should respect retina resolution 83 | // if (appIcon.size.width > maxSize.width && appIcon.size.height > maxSize.height) { 84 | // [appIcon setSize:maxSize]; 85 | // } 86 | QLThumbnailRequestSetImageWithData(thumbnail, (__bridge CFDataRef)[appIcon TIFFRepresentation], (__bridge CFDictionaryRef)propertiesDict); 87 | return noErr; 88 | } 89 | 90 | // MARK: .provisioning 91 | 92 | // use provisioning directly 93 | NSData *provisionData = [NSData dataWithContentsOfURL:URL]; 94 | if (!provisionData) { 95 | NSLog(@"No provisionData for %@", URL); 96 | return noErr; 97 | } 98 | 99 | NSDictionary *optionsDict = (__bridge NSDictionary *)options; 100 | BOOL iconMode = ([optionsDict objectForKey:(NSString *)kQLThumbnailOptionIconModeKey]) ? YES : NO; 101 | NSUInteger devicesCount = 0; 102 | int expStatus = 0; 103 | 104 | if (iconMode) { 105 | NSURL *iconURL = [[NSBundle bundleWithIdentifier:kPluginBundleId] URLForResource:@"blankIcon" withExtension:@"png"]; 106 | appIcon = [[NSImage alloc] initWithContentsOfURL:iconURL]; 107 | } else { 108 | appIcon = [[NSWorkspace sharedWorkspace] iconForFileType:dataType]; 109 | [appIcon setSize:NSMakeSize(512,512)]; 110 | } 111 | 112 | CMSDecoderRef decoder = NULL; 113 | CMSDecoderCreate(&decoder); 114 | CMSDecoderUpdateMessage(decoder, provisionData.bytes, provisionData.length); 115 | CMSDecoderFinalizeMessage(decoder); 116 | CFDataRef dataRef = NULL; 117 | CMSDecoderCopyContent(decoder, &dataRef); 118 | NSData *data = (NSData *)CFBridgingRelease(dataRef); 119 | CFRelease(decoder); 120 | 121 | if (!data || QLThumbnailRequestIsCancelled(thumbnail)) { 122 | return noErr; 123 | } 124 | 125 | NSDictionary *propertyList = [NSPropertyListSerialization propertyListWithData:data options:0 format:NULL error:NULL]; 126 | id value = [propertyList objectForKey:@"ProvisionedDevices"]; 127 | if ([value isKindOfClass:[NSArray class]]) { 128 | devicesCount = [value count]; 129 | } 130 | 131 | value = [propertyList objectForKey:@"ExpirationDate"]; 132 | if ([value isKindOfClass:[NSDate class]]) { 133 | expStatus = expirationStatus(value, [NSCalendar currentCalendar]); 134 | } 135 | 136 | if (QLThumbnailRequestIsCancelled(thumbnail)) { 137 | return noErr; 138 | } 139 | 140 | NSSize canvasSize = appIcon.size; 141 | NSRect renderRect = NSMakeRect(0.0, 0.0, appIcon.size.width, appIcon.size.height); 142 | 143 | CGContextRef _context = QLThumbnailRequestCreateContext(thumbnail, canvasSize, false, NULL); 144 | if (_context) { 145 | NSGraphicsContext *_graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:(void *)_context flipped:NO]; 146 | 147 | [NSGraphicsContext setCurrentContext:_graphicsContext]; 148 | 149 | [appIcon drawInRect:renderRect]; 150 | 151 | NSString *badge = [NSString stringWithFormat:@"%lu",(unsigned long)devicesCount]; 152 | NSColor *outlineColor; 153 | 154 | if (expStatus == 2) { 155 | outlineColor = BADGE_VALID_COLOR; 156 | } else if (expStatus == 1) { 157 | outlineColor = BADGE_EXPIRING_COLOR; 158 | } else { 159 | outlineColor = BADGE_EXPIRED_COLOR; 160 | } 161 | 162 | NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; 163 | paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail; 164 | paragraphStyle.alignment = NSTextAlignmentCenter; 165 | 166 | NSDictionary *attrDict = @{NSFontAttributeName : BADGE_FONT, NSForegroundColorAttributeName : outlineColor, NSParagraphStyleAttributeName: paragraphStyle}; 167 | 168 | NSSize badgeNumSize = [badge sizeWithAttributes:attrDict]; 169 | int badgeWidth = badgeNumSize.width + BADGE_MARGIN * 2; 170 | badgeWidth = MAX(badgeWidth, MIN_BADGE_WIDTH); 171 | 172 | int badgeX = renderRect.origin.x + BADGE_MARGIN_X; 173 | int badgeY = renderRect.origin.y + renderRect.size.height - BADGE_HEIGHT - BADGE_MARGIN_Y; 174 | if (!iconMode) { 175 | badgeX += 75; 176 | badgeY -= 10; 177 | } 178 | int badgeNumX = badgeX + BADGE_MARGIN; 179 | NSRect badgeRect = NSMakeRect(badgeX, badgeY, badgeWidth, BADGE_HEIGHT); 180 | 181 | NSBezierPath *badgePath = [NSBezierPath bezierPathWithRoundedRect:badgeRect xRadius:10 yRadius:10]; 182 | [badgePath setLineWidth:8.0]; 183 | [BADGE_BG_COLOR set]; 184 | [badgePath fill]; 185 | [outlineColor set]; 186 | [badgePath stroke]; 187 | 188 | [badge drawAtPoint:NSMakePoint(badgeNumX,badgeY) withAttributes:attrDict]; 189 | 190 | QLThumbnailRequestFlushContext(thumbnail, _context); 191 | CFRelease(_context); 192 | } 193 | } 194 | 195 | return noErr; 196 | } 197 | 198 | void CancelPreviewGeneration(void *thisInterface, QLPreviewRequestRef preview) { 199 | // Implement only if supported 200 | } 201 | -------------------------------------------------------------------------------- /ProvisionQL/Resources/blankIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ealeksandrov/ProvisionQL/9ff31049592b27743e9205bf75751d319afc3e53/ProvisionQL/Resources/blankIcon.png -------------------------------------------------------------------------------- /ProvisionQL/Resources/defaultIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ealeksandrov/ProvisionQL/9ff31049592b27743e9205bf75751d319afc3e53/ProvisionQL/Resources/defaultIcon.png -------------------------------------------------------------------------------- /ProvisionQL/Resources/mobileprovision_Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ealeksandrov/ProvisionQL/9ff31049592b27743e9205bf75751d319afc3e53/ProvisionQL/Resources/mobileprovision_Icon.png -------------------------------------------------------------------------------- /ProvisionQL/Resources/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 138 | 139 | 140 |
141 |

__AppInfoTitle__

142 |
App icon
143 |
144 | Name: __CFBundleName__
145 | Version: __CFBundleShortVersionString__ (__CFBundleVersion__)
146 | BundleId: __CFBundleIdentifier__
147 |
148 | Extension type: __NSExtensionPointIdentifier__
149 |
150 | DeviceFamily: __UIDeviceFamily__
151 | SDK: __DTSDKName__
152 | Minimum OS Version: __MinimumOSVersion__
153 |
154 |
155 |

App Transport Security

156 | __AppTransportSecurityFormatted__ 157 |
158 | 159 |
160 |
161 |

Provisioning

162 | Profile name: __Name__
163 |
164 |
165 |

__Name__

166 |
167 | 168 | Profile UUID: __UUID__
169 | Profile Type: __ProfilePlatform__ __ProfileType__
170 | Team: __TeamName__ (__TeamIds__)
171 | Creation date: __CreationDateFormatted__ (__CreationSummary__)
172 | Expiration Date: __ExpirationDateFormatted__ (__ExpirationSummary__)
173 | 174 |

Entitlements

175 |
176 | Entitlements extraction failed. 177 |
178 | __EntitlementsFormatted__ 179 | 180 |

Developer Certificates

181 | __DeveloperCertificatesFormatted__ 182 | 183 |

Devices (__ProvisionedDevicesCount__)

184 | __ProvisionedDevicesFormatted__ 185 | 186 |
187 |
188 |

File info

189 | __FileName__
190 | __FileInfo__
191 |
192 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /ProvisionQL/Scripts/autoupdate-revision.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # autoupdate-revision.sh 3 | 4 | git=`sh /etc/profile; which git` 5 | branch=`${git} rev-parse --abbrev-ref HEAD` 6 | commits_count=`${git} rev-list ${branch} | wc -l | tr -d ' '` 7 | 8 | filepath="${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}" 9 | 10 | echo "Updating ${filepath}" 11 | echo "Current version build ${commits_count}" 12 | /usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${commits_count}" "${filepath}" 13 | -------------------------------------------------------------------------------- /ProvisionQL/Shared.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #import 6 | #import 7 | #import 8 | 9 | #import 10 | 11 | static NSString * const kPluginBundleId = @"com.ealeksandrov.ProvisionQL"; 12 | static NSString * const kDataType_ipa = @"com.apple.itunes.ipa"; 13 | static NSString * const kDataType_ios_provision = @"com.apple.mobileprovision"; 14 | static NSString * const kDataType_ios_provision_old = @"com.apple.iphone.mobileprovision"; 15 | static NSString * const kDataType_osx_provision = @"com.apple.provisionprofile"; 16 | static NSString * const kDataType_xcode_archive = @"com.apple.xcode.archive"; 17 | static NSString * const kDataType_app_extension = @"com.apple.application-and-system-extension"; 18 | 19 | NSData *unzipFile(NSURL *url, NSString *filePath); 20 | void unzipFileToDir(NSURL *url, NSString *filePath, NSString *targetDir); 21 | 22 | NSImage *roundCorners(NSImage *image); 23 | NSImage *imageFromApp(NSURL *URL, NSString *dataType, NSString *fileName); 24 | NSString *mainIconNameForApp(NSDictionary *appPropertyList); 25 | int expirationStatus(NSDate *date, NSCalendar *calendar); 26 | -------------------------------------------------------------------------------- /ProvisionQL/Shared.m: -------------------------------------------------------------------------------- 1 | #import "Shared.h" 2 | 3 | NSData *unzipFile(NSURL *url, NSString *filePath) { 4 | NSTask *task = [NSTask new]; 5 | [task setLaunchPath:@"/usr/bin/unzip"]; 6 | [task setStandardOutput:[NSPipe pipe]]; 7 | [task setArguments:@[@"-p", [url path], filePath]]; // @"-x", @"*/*/*/*" 8 | [task launch]; 9 | 10 | NSData *pipeData = [[[task standardOutput] fileHandleForReading] readDataToEndOfFile]; 11 | [task waitUntilExit]; 12 | if (pipeData.length == 0) { 13 | return nil; 14 | } 15 | return pipeData; 16 | } 17 | 18 | void unzipFileToDir(NSURL *url, NSString *targetDir, NSString *filePath) { 19 | NSTask *task = [NSTask new]; 20 | [task setLaunchPath:@"/usr/bin/unzip"]; 21 | [task setArguments:@[@"-u", @"-j", @"-d", targetDir, [url path], filePath]]; // @"-x", @"*/*/*/*" 22 | [task launch]; 23 | [task waitUntilExit]; 24 | } 25 | 26 | NSImage *roundCorners(NSImage *image) { 27 | NSImage *existingImage = image; 28 | NSSize existingSize = [existingImage size]; 29 | NSImage *composedImage = [[NSImage alloc] initWithSize:existingSize]; 30 | 31 | [composedImage lockFocus]; 32 | [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh]; 33 | 34 | NSRect imageFrame = NSRectFromCGRect(CGRectMake(0, 0, existingSize.width, existingSize.height)); 35 | NSBezierPath *clipPath = [NSBezierPath bezierPathWithIOS7RoundedRect:imageFrame cornerRadius:existingSize.width * 0.225]; 36 | [clipPath setWindingRule:NSWindingRuleEvenOdd]; 37 | [clipPath addClip]; 38 | 39 | [image drawAtPoint:NSZeroPoint fromRect:NSMakeRect(0, 0, existingSize.width, existingSize.height) operation:NSCompositingOperationSourceOver fraction:1]; 40 | 41 | [composedImage unlockFocus]; 42 | 43 | return composedImage; 44 | } 45 | 46 | int expirationStatus(NSDate *date, NSCalendar *calendar) { 47 | int result = 0; 48 | 49 | if (date) { 50 | NSDateComponents *dateComponents = [calendar components:NSCalendarUnitDay fromDate:[NSDate date] toDate:date options:0]; 51 | if ([date compare:[NSDate date]] == NSOrderedAscending) { 52 | // expired 53 | result = 0; 54 | } else if (dateComponents.day < 30) { 55 | // expiring 56 | result = 1; 57 | } else { 58 | // valid 59 | result = 2; 60 | } 61 | } 62 | 63 | return result; 64 | } 65 | 66 | NSImage *imageFromApp(NSURL *URL, NSString *dataType, NSString *fileName) { 67 | NSImage *appIcon = nil; 68 | 69 | if ([dataType isEqualToString:kDataType_xcode_archive]) { 70 | // get the embedded icon for the iOS app 71 | NSURL *appsDir = [URL URLByAppendingPathComponent:@"Products/Applications/"]; 72 | if (!appsDir) { 73 | return nil; 74 | } 75 | 76 | NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:appsDir.path error:nil]; 77 | NSString *appName = dirFiles.firstObject; 78 | if (!appName) { 79 | return nil; 80 | } 81 | 82 | NSURL *appURL = [appsDir URLByAppendingPathComponent:appName]; 83 | NSArray *appContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:appURL.path error:nil]; 84 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF contains %@", fileName]; 85 | NSString *appIconFullName = [appContents filteredArrayUsingPredicate:predicate].lastObject; 86 | if (!appIconFullName) { 87 | return nil; 88 | } 89 | 90 | NSURL *appIconFullURL = [appURL URLByAppendingPathComponent:appIconFullName]; 91 | appIcon = [[NSImage alloc] initWithContentsOfURL:appIconFullURL]; 92 | } else if([dataType isEqualToString:kDataType_ipa]) { 93 | NSData *data = unzipFile(URL, @"iTunesArtwork"); 94 | if (!data && fileName.length > 0) { 95 | data = unzipFile(URL, [NSString stringWithFormat:@"Payload/*.app/%@*", fileName]); 96 | } 97 | if (data != nil) { 98 | appIcon = [[NSImage alloc] initWithData:data]; 99 | } 100 | } 101 | 102 | return appIcon; 103 | } 104 | 105 | NSArray *iconsListForDictionary(NSDictionary *iconsDict) { 106 | if ([iconsDict isKindOfClass:[NSDictionary class]]) { 107 | id primaryIconDict = [iconsDict objectForKey:@"CFBundlePrimaryIcon"]; 108 | if ([primaryIconDict isKindOfClass:[NSDictionary class]]) { 109 | id tempIcons = [primaryIconDict objectForKey:@"CFBundleIconFiles"]; 110 | if ([tempIcons isKindOfClass:[NSArray class]]) { 111 | return tempIcons; 112 | } 113 | } 114 | } 115 | 116 | return nil; 117 | } 118 | 119 | NSString *mainIconNameForApp(NSDictionary *appPropertyList) { 120 | NSArray *icons; 121 | NSString *iconName; 122 | 123 | //Check for CFBundleIcons (since 5.0) 124 | icons = iconsListForDictionary([appPropertyList objectForKey:@"CFBundleIcons"]); 125 | if (!icons) { 126 | icons = iconsListForDictionary([appPropertyList objectForKey:@"CFBundleIcons~ipad"]); 127 | } 128 | 129 | if (!icons) { 130 | //Check for CFBundleIconFiles (since 3.2) 131 | id tempIcons = [appPropertyList objectForKey:@"CFBundleIconFiles"]; 132 | if ([tempIcons isKindOfClass:[NSArray class]]) { 133 | icons = tempIcons; 134 | } 135 | } 136 | 137 | if (icons) { 138 | //Search some patterns for primary app icon (120x120) 139 | NSArray *matches = @[@"120",@"60"]; 140 | 141 | for (NSString *match in matches) { 142 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF contains[c] %@",match]; 143 | NSArray *results = [icons filteredArrayUsingPredicate:predicate]; 144 | if ([results count]) { 145 | iconName = [results firstObject]; 146 | break; 147 | } 148 | } 149 | 150 | //If no one matches any pattern, just take last item 151 | if (!iconName) { 152 | iconName = [icons lastObject]; 153 | } 154 | } else { 155 | //Check for CFBundleIconFile (legacy, before 3.2) 156 | NSString *legacyIcon = [appPropertyList objectForKey:@"CFBundleIconFile"]; 157 | if ([legacyIcon length]) { 158 | iconName = legacyIcon; 159 | } 160 | } 161 | 162 | return iconName; 163 | } 164 | -------------------------------------------------------------------------------- /ProvisionQL/Supporting-files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeRole 11 | QLGenerator 12 | LSItemContentTypes 13 | 14 | com.apple.itunes.ipa 15 | com.apple.iphone.mobileprovision 16 | com.apple.mobileprovision 17 | com.apple.provisionprofile 18 | com.apple.application-and-system-extension 19 | com.apple.xcode.archive 20 | 21 | 22 | 23 | CFBundleExecutable 24 | ${EXECUTABLE_NAME} 25 | CFBundleIdentifier 26 | $(PRODUCT_BUNDLE_IDENTIFIER) 27 | CFBundleInfoDictionaryVersion 28 | 6.0 29 | CFBundleName 30 | ${PRODUCT_NAME} 31 | CFBundleShortVersionString 32 | $(MARKETING_VERSION) 33 | CFBundleVersion 34 | $(CURRENT_PROJECT_VERSION) 35 | CFPlugInDynamicRegisterFunction 36 | 37 | CFPlugInDynamicRegistration 38 | NO 39 | CFPlugInFactories 40 | 41 | 6BE4A2DB-04D7-404F-9CEA-7AD00B98F179 42 | QuickLookGeneratorPluginFactory 43 | 44 | CFPlugInTypes 45 | 46 | 5E2D9680-5022-40FA-B806-43349622E5B9 47 | 48 | 6BE4A2DB-04D7-404F-9CEA-7AD00B98F179 49 | 50 | 51 | CFPlugInUnloadFunction 52 | 53 | NSHumanReadableCopyright 54 | Copyright © 2013-2018 Evgeny Aleksandrov. All rights reserved. 55 | QLNeedsToBeRunInMainThread 56 | 57 | QLPreviewHeight 58 | 600 59 | QLPreviewWidth 60 | 640 61 | QLSupportsConcurrentRequests 62 | 63 | QLThumbnailMinimumSize 64 | 16 65 | 66 | 67 | -------------------------------------------------------------------------------- /ProvisionQL/Supporting-files/main.c: -------------------------------------------------------------------------------- 1 | //============================================================================== 2 | // 3 | // DO NO MODIFY THE CONTENT OF THIS FILE 4 | // 5 | // This file contains the generic CFPlug-in code necessary for your generator 6 | // To complete your generator implement the function in GenerateThumbnailForURL/GeneratePreviewForURL.c 7 | // 8 | //============================================================================== 9 | 10 | 11 | 12 | 13 | 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | // ----------------------------------------------------------------------------- 21 | // constants 22 | // ----------------------------------------------------------------------------- 23 | 24 | // Don't modify this line 25 | #define PLUGIN_ID "6BE4A2DB-04D7-404F-9CEA-7AD00B98F179" 26 | 27 | // 28 | // Below is the generic glue code for all plug-ins. 29 | // 30 | // You should not have to modify this code aside from changing 31 | // names if you decide to change the names defined in the Info.plist 32 | // 33 | 34 | 35 | // ----------------------------------------------------------------------------- 36 | // typedefs 37 | // ----------------------------------------------------------------------------- 38 | 39 | // The thumbnail generation function to be implemented in GenerateThumbnailForURL.c 40 | OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize); 41 | void CancelThumbnailGeneration(void* thisInterface, QLThumbnailRequestRef thumbnail); 42 | 43 | // The preview generation function to be implemented in GeneratePreviewForURL.c 44 | OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options); 45 | void CancelPreviewGeneration(void *thisInterface, QLPreviewRequestRef preview); 46 | 47 | // The layout for an instance of QuickLookGeneratorPlugIn 48 | typedef struct __QuickLookGeneratorPluginType 49 | { 50 | void *conduitInterface; 51 | CFUUIDRef factoryID; 52 | UInt32 refCount; 53 | } QuickLookGeneratorPluginType; 54 | 55 | // ----------------------------------------------------------------------------- 56 | // prototypes 57 | // ----------------------------------------------------------------------------- 58 | // Forward declaration for the IUnknown implementation. 59 | // 60 | 61 | QuickLookGeneratorPluginType *AllocQuickLookGeneratorPluginType(CFUUIDRef inFactoryID); 62 | void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInstance); 63 | HRESULT QuickLookGeneratorQueryInterface(void *thisInstance,REFIID iid,LPVOID *ppv); 64 | void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator,CFUUIDRef typeID); 65 | ULONG QuickLookGeneratorPluginAddRef(void *thisInstance); 66 | ULONG QuickLookGeneratorPluginRelease(void *thisInstance); 67 | 68 | // ----------------------------------------------------------------------------- 69 | // myInterfaceFtbl definition 70 | // ----------------------------------------------------------------------------- 71 | // The QLGeneratorInterfaceStruct function table. 72 | // 73 | static QLGeneratorInterfaceStruct myInterfaceFtbl = { 74 | NULL, 75 | QuickLookGeneratorQueryInterface, 76 | QuickLookGeneratorPluginAddRef, 77 | QuickLookGeneratorPluginRelease, 78 | NULL, 79 | NULL, 80 | NULL, 81 | NULL 82 | }; 83 | 84 | 85 | // ----------------------------------------------------------------------------- 86 | // AllocQuickLookGeneratorPluginType 87 | // ----------------------------------------------------------------------------- 88 | // Utility function that allocates a new instance. 89 | // You can do some initial setup for the generator here if you wish 90 | // like allocating globals etc... 91 | // 92 | QuickLookGeneratorPluginType *AllocQuickLookGeneratorPluginType(CFUUIDRef inFactoryID) 93 | { 94 | QuickLookGeneratorPluginType *theNewInstance; 95 | 96 | theNewInstance = (QuickLookGeneratorPluginType *)malloc(sizeof(QuickLookGeneratorPluginType)); 97 | memset(theNewInstance,0,sizeof(QuickLookGeneratorPluginType)); 98 | 99 | /* Point to the function table Malloc enough to store the stuff and copy the filler from myInterfaceFtbl over */ 100 | theNewInstance->conduitInterface = malloc(sizeof(QLGeneratorInterfaceStruct)); 101 | memcpy(theNewInstance->conduitInterface,&myInterfaceFtbl,sizeof(QLGeneratorInterfaceStruct)); 102 | 103 | /* Retain and keep an open instance refcount for each factory. */ 104 | theNewInstance->factoryID = CFRetain(inFactoryID); 105 | CFPlugInAddInstanceForFactory(inFactoryID); 106 | 107 | /* This function returns the IUnknown interface so set the refCount to one. */ 108 | theNewInstance->refCount = 1; 109 | return theNewInstance; 110 | } 111 | 112 | // ----------------------------------------------------------------------------- 113 | // DeallocQuickLookGeneratorPluginType 114 | // ----------------------------------------------------------------------------- 115 | // Utility function that deallocates the instance when 116 | // the refCount goes to zero. 117 | // In the current implementation generator interfaces are never deallocated 118 | // but implement this as this might change in the future 119 | // 120 | void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInstance) 121 | { 122 | CFUUIDRef theFactoryID; 123 | 124 | theFactoryID = thisInstance->factoryID; 125 | /* Free the conduitInterface table up */ 126 | free(thisInstance->conduitInterface); 127 | 128 | /* Free the instance structure */ 129 | free(thisInstance); 130 | if (theFactoryID){ 131 | CFPlugInRemoveInstanceForFactory(theFactoryID); 132 | CFRelease(theFactoryID); 133 | } 134 | } 135 | 136 | // ----------------------------------------------------------------------------- 137 | // QuickLookGeneratorQueryInterface 138 | // ----------------------------------------------------------------------------- 139 | // Implementation of the IUnknown QueryInterface function. 140 | // 141 | HRESULT QuickLookGeneratorQueryInterface(void *thisInstance,REFIID iid,LPVOID *ppv) 142 | { 143 | CFUUIDRef interfaceID; 144 | 145 | interfaceID = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault,iid); 146 | 147 | if (CFEqual(interfaceID,kQLGeneratorCallbacksInterfaceID)){ 148 | /* If the Right interface was requested, bump the ref count, 149 | * set the ppv parameter equal to the instance, and 150 | * return good status. 151 | */ 152 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->GenerateThumbnailForURL = GenerateThumbnailForURL; 153 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->CancelThumbnailGeneration = CancelThumbnailGeneration; 154 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->GeneratePreviewForURL = GeneratePreviewForURL; 155 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->CancelPreviewGeneration = CancelPreviewGeneration; 156 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType*)thisInstance)->conduitInterface)->AddRef(thisInstance); 157 | *ppv = thisInstance; 158 | CFRelease(interfaceID); 159 | return S_OK; 160 | }else{ 161 | /* Requested interface unknown, bail with error. */ 162 | *ppv = NULL; 163 | CFRelease(interfaceID); 164 | return E_NOINTERFACE; 165 | } 166 | } 167 | 168 | // ----------------------------------------------------------------------------- 169 | // QuickLookGeneratorPluginAddRef 170 | // ----------------------------------------------------------------------------- 171 | // Implementation of reference counting for this type. Whenever an interface 172 | // is requested, bump the refCount for the instance. NOTE: returning the 173 | // refcount is a convention but is not required so don't rely on it. 174 | // 175 | ULONG QuickLookGeneratorPluginAddRef(void *thisInstance) 176 | { 177 | ((QuickLookGeneratorPluginType *)thisInstance )->refCount += 1; 178 | return ((QuickLookGeneratorPluginType*) thisInstance)->refCount; 179 | } 180 | 181 | // ----------------------------------------------------------------------------- 182 | // QuickLookGeneratorPluginRelease 183 | // ----------------------------------------------------------------------------- 184 | // When an interface is released, decrement the refCount. 185 | // If the refCount goes to zero, deallocate the instance. 186 | // 187 | ULONG QuickLookGeneratorPluginRelease(void *thisInstance) 188 | { 189 | ((QuickLookGeneratorPluginType*)thisInstance)->refCount -= 1; 190 | if (((QuickLookGeneratorPluginType*)thisInstance)->refCount == 0){ 191 | DeallocQuickLookGeneratorPluginType((QuickLookGeneratorPluginType*)thisInstance ); 192 | return 0; 193 | }else{ 194 | return ((QuickLookGeneratorPluginType*) thisInstance )->refCount; 195 | } 196 | } 197 | 198 | // ----------------------------------------------------------------------------- 199 | // QuickLookGeneratorPluginFactory 200 | // ----------------------------------------------------------------------------- 201 | void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator,CFUUIDRef typeID) 202 | { 203 | QuickLookGeneratorPluginType *result; 204 | CFUUIDRef uuid; 205 | 206 | /* If correct type is being requested, allocate an 207 | * instance of kQLGeneratorTypeID and return the IUnknown interface. 208 | */ 209 | if (CFEqual(typeID,kQLGeneratorTypeID)){ 210 | uuid = CFUUIDCreateFromString(kCFAllocatorDefault,CFSTR(PLUGIN_ID)); 211 | result = AllocQuickLookGeneratorPluginType(uuid); 212 | CFRelease(uuid); 213 | return result; 214 | } 215 | /* If the requested type is incorrect, return NULL. */ 216 | return NULL; 217 | } 218 | 219 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ProvisionQL - Quick Look for ipa & provision 2 | 3 | [![Build Status](https://github.com/ealeksandrov/ProvisionQL/workflows/build/badge.svg?branch=master)](https://github.com/ealeksandrov/ProvisionQL/actions) 4 | [![Latest Release](https://img.shields.io/github/release/ealeksandrov/ProvisionQL.svg)](https://github.com/ealeksandrov/ProvisionQL/releases/latest) 5 | [![License](https://img.shields.io/github/license/ealeksandrov/ProvisionQL.svg)](LICENSE.md) 6 | ![Platform](https://img.shields.io/badge/platform-macos-lightgrey.svg) 7 | 8 | ![Thumbnails example](https://raw.github.com/ealeksandrov/ProvisionQL/master/Screenshots/1.png) 9 | 10 | Inspired by a number of existing alternatives, the goal of this project is to provide clean, reliable, current and open source Quick Look plugin for iOS & macOS developers. 11 | 12 | Thumbnails will show app icon for `.ipa`/ `.xcarchive` or expiring status and device count for `.mobileprovision`. Quick Look preview will give a lot of information, including devices UUIDs, certificates, entitlements and much more. 13 | 14 | ![Valid AdHoc provision](https://raw.github.com/ealeksandrov/ProvisionQL/master/Screenshots/2.png) 15 | 16 | Supported file types: 17 | 18 | * `.ipa` - iOS packaged application 19 | * `.xcarchive` - Xcode archive 20 | * `.appex` - iOS/OSX application extension 21 | * `.mobileprovision` - iOS provisioning profile 22 | * `.provisionprofile` - OSX provisioning profile 23 | 24 | [More screenshots](https://github.com/ealeksandrov/ProvisionQL/blob/master/Screenshots/README.md) 25 | 26 | ### Acknowledgments 27 | 28 | Initially based on [Provisioning by Craig Hockenberry](https://github.com/chockenberry/Provisioning). 29 | 30 | ### Tutorials based on this example: 31 | 32 | * English - [aleksandrov.ws](https://aleksandrov.ws/2014/02/25/osx-quick-look-plugin-development/) 33 | * Russian - [habrahabr.ru](https://habrahabr.ru/post/208552/) 34 | 35 | ## Installation 36 | 37 | ### Homebrew Cask 38 | 39 | [Homebrew cask](https://caskroom.github.io) is the easiest way to install binary applications and Quick Look plugins. 40 | 41 | If you have [homebrew](http://brew.sh/) installed - just run one line and you are ready: 42 | 43 | ```sh 44 | brew install --cask provisionql 45 | ``` 46 | 47 | ### Manual 48 | 49 | * download archive with latest version from the [Releases](https://github.com/ealeksandrov/ProvisionQL/releases/latest) page; 50 | * move `ProvisionQL.qlgenerator` to `~/Library/QuickLook/`(current user) or `/Library/QuickLook/`(all users); 51 | * run `qlmanage -r` to refresh Quick Look plugins list. 52 | 53 | ## Author 54 | 55 | Created and maintained by Evgeny Aleksandrov ([@ealeksandrov](https://twitter.com/ealeksandrov)). 56 | 57 | ## License 58 | 59 | `ProvisionQL` is available under the MIT license. See the [LICENSE.md](LICENSE.md) file for more info. 60 | -------------------------------------------------------------------------------- /Screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ealeksandrov/ProvisionQL/9ff31049592b27743e9205bf75751d319afc3e53/Screenshots/1.png -------------------------------------------------------------------------------- /Screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ealeksandrov/ProvisionQL/9ff31049592b27743e9205bf75751d319afc3e53/Screenshots/2.png -------------------------------------------------------------------------------- /Screenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ealeksandrov/ProvisionQL/9ff31049592b27743e9205bf75751d319afc3e53/Screenshots/3.png -------------------------------------------------------------------------------- /Screenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ealeksandrov/ProvisionQL/9ff31049592b27743e9205bf75751d319afc3e53/Screenshots/4.png -------------------------------------------------------------------------------- /Screenshots/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ealeksandrov/ProvisionQL/9ff31049592b27743e9205bf75751d319afc3e53/Screenshots/5.png -------------------------------------------------------------------------------- /Screenshots/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ealeksandrov/ProvisionQL/9ff31049592b27743e9205bf75751d319afc3e53/Screenshots/6.png -------------------------------------------------------------------------------- /Screenshots/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ealeksandrov/ProvisionQL/9ff31049592b27743e9205bf75751d319afc3e53/Screenshots/7.png -------------------------------------------------------------------------------- /Screenshots/README.md: -------------------------------------------------------------------------------- 1 | # ProvisionQL screenshots 2 | 3 | ## Thumbnails 4 | 5 | ![Thumbnails example](https://raw.github.com/ealeksandrov/ProvisionQL/master/Screenshots/1.png) 6 | 7 | ## Provision 8 | 9 | ### Valid AdHoc provision 10 | 11 | ![Valid AdHoc provision](https://raw.github.com/ealeksandrov/ProvisionQL/master/Screenshots/2.png) 12 | 13 | ### Valid AppStore provision 14 | 15 | ![Valid AppStore provision](https://raw.github.com/ealeksandrov/ProvisionQL/master/Screenshots/3.png) 16 | 17 | ### Expired AppStore provision 18 | 19 | ![Expired AppStore provision](https://raw.github.com/ealeksandrov/ProvisionQL/master/Screenshots/4.png) 20 | 21 | ## Apps 22 | 23 | ### Xcode archive 24 | 25 | ![Xcode archive](https://raw.github.com/ealeksandrov/ProvisionQL/master/Screenshots/5.png) 26 | 27 | ### AdHoc ipa 28 | 29 | ![App from App Store](https://raw.github.com/ealeksandrov/ProvisionQL/master/Screenshots/6.png) 30 | 31 | ### App Store ipa 32 | 33 | ![App from App Store](https://raw.github.com/ealeksandrov/ProvisionQL/master/Screenshots/7.png) 34 | --------------------------------------------------------------------------------