17 Commits

Author SHA1 Message Date
relikd
e0ccba1af8 chore: bump version 2025-11-06 02:00:34 +01:00
relikd
21c21ec059 feat: quit preview gracefully if Info.plist not found 2025-11-06 01:57:33 +01:00
relikd
cfb6b17bc7 feat: not-hidden combination 2025-11-06 01:25:35 +01:00
relikd
2d16cb666b feat: show xcarchive developer notes 2025-11-06 01:06:53 +01:00
relikd
1a6d98a4b2 chore: upgrade to recommended settings 2025-11-06 00:33:49 +01:00
relikd
85c1ae95c1 feat: hide empty entitlements 2025-11-06 00:20:33 +01:00
relikd
fd13f13a3c ref: parentDir() 2025-11-06 00:15:11 +01:00
relikd
8b916829d1 fix: preview of xcarchive for non app-like bundles 2025-11-06 00:10:10 +01:00
relikd
05f30ee755 feat: hide ATS if none are present 2025-11-05 23:53:50 +01:00
relikd
5166a67e48 ref: extract TransportSecurity into own class 2025-11-05 23:30:37 +01:00
relikd
d1aae4cc15 ref: introduce CLASS_VISIBLE 2025-11-05 23:29:37 +01:00
relikd
f38c1f802f feat: make appPlist optional (again) 2025-11-05 18:36:45 +01:00
relikd
af9c398571 feat: support for macOS xcarchive 2025-11-05 18:18:08 +01:00
relikd
36e30a1fdf chore: remove semicolons 2025-11-05 18:02:39 +01:00
relikd
fb8fa41dd0 ref: URL utils class 2025-11-05 17:54:29 +01:00
relikd
33cec015ab ref: static TransportSecurity strings 2025-11-05 02:06:02 +01:00
relikd
6dec6530c5 fix: check for empty entitlements dict 2025-11-05 02:04:28 +01:00
27 changed files with 342 additions and 207 deletions

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>

View File

@@ -25,7 +25,7 @@ public class CarReader {
return NSImage(cgImage: bestImage.image, size: bestImage.size)
}
}
return nil;
return nil
}
@@ -44,7 +44,7 @@ public class CarReader {
os_log(.debug, log: log, "[asset-car] available keys: %{public}@", catalog.allImageNames() ?? [])
return nil
}
return imageName;
return imageName
}
/// If exact name does not exist in catalog, search for a name that shares the same prefix.

View File

@@ -5,6 +5,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.3.0] 2025-11-06
Added:
- Show macOS apps in `.xcarchive`
- Show `.xcarchive` developer notes
Fixed:
- Cancel preview (and allow other plugins to run) if there is no `Info.plist` in `.xcarchive`
Changed:
- Hide Transport Security and Entitlements if they are empty
## [1.2.0] 2025-11-04
Added:
- Customizable HTML template
@@ -25,6 +37,7 @@ Added:
Initial release
[1.3.0]: https://github.com/relikd/QLAppBundle/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/relikd/QLAppBundle/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/relikd/QLAppBundle/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/relikd/QLAppBundle/compare/9b0761318c85090d1ef22f12d3eab67a9a194882...v1.0.0

View File

@@ -8,6 +8,6 @@ ENABLE_HARDENED_RUNTIME = YES
SWIFT_VERSION = 5.0
MACOSX_DEPLOYMENT_TARGET = 10.15
MARKETING_VERSION = 1.2.0
MARKETING_VERSION = 1.3.0
PRODUCT_NAME = QLAppBundle
PRODUCT_BUNDLE_IDENTIFIER = de.relikd.QLAppBundle

View File

@@ -9,13 +9,14 @@
/* Begin PBXBuildFile section */
5405CF5E2EA1199B00613856 /* MetaInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5405CF5D2EA1199B00613856 /* MetaInfo.swift */; };
5405CF652EA1376B00613856 /* Zip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5405CF642EA1376B00613856 /* Zip.swift */; };
5412DECE2EBC168600F9040D /* Preview+ArchiveInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5412DECD2EBC168600F9040D /* Preview+ArchiveInfo.swift */; };
5412DED02EBC283000F9040D /* RuntimeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5412DECF2EBC283000F9040D /* RuntimeError.swift */; };
543FE5742EB3BB5E0059F98B /* AppIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 543FE5732EB3BB5E0059F98B /* AppIcon.icns */; };
54442C232E378BAF008A870E /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54442C222E378BAF008A870E /* Quartz.framework */; };
54442C702E378BDD008A870E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54442C6A2E378BDD008A870E /* AppDelegate.swift */; };
54442C722E378BDD008A870E /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54442C6D2E378BDD008A870E /* MainMenu.xib */; };
54442C792E378BE0008A870E /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54442C742E378BE0008A870E /* PreviewViewController.swift */; };
54442C7B2E378BE0008A870E /* PreviewViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54442C762E378BE0008A870E /* PreviewViewController.xib */; };
544AF3692EB6AAC0006837F2 /* AssetCarReader.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 544AF3682EB6AAC0006837F2 /* AssetCarReader.xcconfig */; };
545459C42EA469E4002892E5 /* defaultIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 54D3A6F22EA4603B001EF4F6 /* defaultIcon.png */; };
545459C52EA469EA002892E5 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 54D3A6F32EA4603B001EF4F6 /* template.html */; };
54581FD12EB29A0B0043A0B3 /* QuickLookThumbnailing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54581FD02EB29A0B0043A0B3 /* QuickLookThumbnailing.framework */; };
@@ -37,13 +38,16 @@
547F52F72EB2CAC7002B6D5F /* Preview+Footer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52F62EB2CAC7002B6D5F /* Preview+Footer.swift */; };
547F52F92EB2CBAB002B6D5F /* Date+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 547F52F82EB2CBAB002B6D5F /* Date+Format.swift */; };
549E3B9F2EBA9D2500ADFF56 /* QLAppBundle Preview Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 54442C202E378BAF008A870E /* QLAppBundle Preview Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
549E3BA12EBAE7D300ADFF56 /* URL+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E3BA02EBAE7D300ADFF56 /* URL+File.swift */; };
549E3BA22EBAECD400ADFF56 /* URL+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E3BA02EBAE7D300ADFF56 /* URL+File.swift */; };
549E3BA42EBC021500ADFF56 /* Preview+TransportSecurity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549E3BA32EBC021500ADFF56 /* Preview+TransportSecurity.swift */; };
54AE5BFF2EB3DB1000B4CFC7 /* ThumbnailProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AE5BFD2EB3DB1000B4CFC7 /* ThumbnailProvider.swift */; };
54B6FFEE2EB6A847007397C0 /* AssetCarReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B6FFEC2EB6A847007397C0 /* AssetCarReader.swift */; };
54B6FFEF2EB6A8E0007397C0 /* CoreUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54D3A6F52EA4610B001EF4F6 /* CoreUI.framework */; };
54B6FFF02EB6AA0F007397C0 /* AssetCarReader.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54352E8A2EB6A79A0082F61D /* AssetCarReader.framework */; };
54B6FFF12EB6AA0F007397C0 /* AssetCarReader.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 54352E8A2EB6A79A0082F61D /* AssetCarReader.framework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
54B6FFF12EB6AA0F007397C0 /* AssetCarReader.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 54352E8A2EB6A79A0082F61D /* AssetCarReader.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
54B6FFF52EB6AA14007397C0 /* AssetCarReader.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54352E8A2EB6A79A0082F61D /* AssetCarReader.framework */; };
54B6FFF62EB6AA14007397C0 /* AssetCarReader.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 54352E8A2EB6A79A0082F61D /* AssetCarReader.framework */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
54B6FFF62EB6AA14007397C0 /* AssetCarReader.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 54352E8A2EB6A79A0082F61D /* AssetCarReader.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
54D3A6EC2EA31B52001EF4F6 /* AppCategories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D3A6EB2EA31B52001EF4F6 /* AppCategories.swift */; };
54D3A6EE2EA39CC6001EF4F6 /* AppIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D3A6ED2EA39CC6001EF4F6 /* AppIcon.swift */; };
54D3A6F02EA3F49F001EF4F6 /* NSBezierPath+RoundedRect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D3A6EF2EA3F49F001EF4F6 /* NSBezierPath+RoundedRect.swift */; };
@@ -122,6 +126,8 @@
/* Begin PBXFileReference section */
5405CF5D2EA1199B00613856 /* MetaInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaInfo.swift; sourceTree = "<group>"; };
5405CF642EA1376B00613856 /* Zip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Zip.swift; sourceTree = "<group>"; };
5412DECD2EBC168600F9040D /* Preview+ArchiveInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preview+ArchiveInfo.swift"; sourceTree = "<group>"; };
5412DECF2EBC283000F9040D /* RuntimeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeError.swift; sourceTree = "<group>"; };
54352E8A2EB6A79A0082F61D /* AssetCarReader.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AssetCarReader.framework; sourceTree = BUILT_PRODUCTS_DIR; };
543FE5732EB3BB5E0059F98B /* AppIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = AppIcon.icns; sourceTree = "<group>"; };
543FE5752EB3BC740059F98B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -130,11 +136,9 @@
54442C222E378BAF008A870E /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; };
54442C6A2E378BDD008A870E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
54442C6C2E378BDD008A870E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
54442C6E2E378BDD008A870E /* App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = "<group>"; };
54442C732E378BE0008A870E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
54442C742E378BE0008A870E /* PreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewViewController.swift; sourceTree = "<group>"; };
54442C752E378BE0008A870E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/PreviewViewController.xib; sourceTree = "<group>"; };
54442C772E378BE0008A870E /* QLPreview.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QLPreview.entitlements; sourceTree = "<group>"; };
544AF3682EB6AAC0006837F2 /* AssetCarReader.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AssetCarReader.xcconfig; sourceTree = "<group>"; };
54581FCF2EB29A0B0043A0B3 /* QLAppBundle Thumbnail Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "QLAppBundle Thumbnail Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
54581FD02EB29A0B0043A0B3 /* QuickLookThumbnailing.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLookThumbnailing.framework; path = System/Library/Frameworks/QuickLookThumbnailing.framework; sourceTree = SDKROOT; };
@@ -152,8 +156,9 @@
547F52FB2EB37F10002B6D5F /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
547F52FC2EB37F3A002B6D5F /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
549E3B9E2EBA8FDA00ADFF56 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
549E3BA02EBAE7D300ADFF56 /* URL+File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+File.swift"; sourceTree = "<group>"; };
549E3BA32EBC021500ADFF56 /* Preview+TransportSecurity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Preview+TransportSecurity.swift"; sourceTree = "<group>"; };
54AE5BFB2EB3DB1000B4CFC7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
54AE5BFC2EB3DB1000B4CFC7 /* QLThumbnail.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QLThumbnail.entitlements; sourceTree = "<group>"; };
54AE5BFD2EB3DB1000B4CFC7 /* ThumbnailProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbnailProvider.swift; sourceTree = "<group>"; };
54B6FFEC2EB6A847007397C0 /* AssetCarReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCarReader.swift; sourceTree = "<group>"; };
54D3A6EB2EA31B52001EF4F6 /* AppCategories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCategories.swift; sourceTree = "<group>"; };
@@ -210,18 +215,22 @@
isa = PBXGroup;
children = (
5405CF5D2EA1199B00613856 /* MetaInfo.swift */,
5412DECF2EBC283000F9040D /* RuntimeError.swift */,
54D3A6EB2EA31B52001EF4F6 /* AppCategories.swift */,
54D3A6ED2EA39CC6001EF4F6 /* AppIcon.swift */,
5469E11C2EA5930C00D46CE7 /* Entitlements.swift */,
547F52DC2EB2C15D002B6D5F /* ExpirationStatus.swift */,
547F52E62EB2C41C002B6D5F /* PreviewGenerator.swift */,
547F52EC2EB2C822002B6D5F /* Preview+AppInfo.swift */,
547F52EE2EB2C8E8002B6D5F /* Preview+Provisioning.swift */,
547F52F32EB2CA05002B6D5F /* Preview+Entitlements.swift */,
5412DECD2EBC168600F9040D /* Preview+ArchiveInfo.swift */,
547F52E32EB2C3D8002B6D5F /* Preview+iTunesPurchase.swift */,
549E3BA32EBC021500ADFF56 /* Preview+TransportSecurity.swift */,
547F52F32EB2CA05002B6D5F /* Preview+Entitlements.swift */,
547F52EE2EB2C8E8002B6D5F /* Preview+Provisioning.swift */,
547F52E92EB2C672002B6D5F /* Preview+FileInfo.swift */,
547F52F62EB2CAC7002B6D5F /* Preview+Footer.swift */,
5405CF642EA1376B00613856 /* Zip.swift */,
549E3BA02EBAE7D300ADFF56 /* URL+File.swift */,
54D3A6EF2EA3F49F001EF4F6 /* NSBezierPath+RoundedRect.swift */,
547F52F82EB2CBAB002B6D5F /* Date+Format.swift */,
);
@@ -272,7 +281,6 @@
isa = PBXGroup;
children = (
543FE5752EB3BC740059F98B /* Info.plist */,
54442C6E2E378BDD008A870E /* App.entitlements */,
54442C6A2E378BDD008A870E /* AppDelegate.swift */,
54442C6D2E378BDD008A870E /* MainMenu.xib */,
);
@@ -283,7 +291,6 @@
isa = PBXGroup;
children = (
54442C732E378BE0008A870E /* Info.plist */,
54442C772E378BE0008A870E /* QLPreview.entitlements */,
54442C742E378BE0008A870E /* PreviewViewController.swift */,
54442C762E378BE0008A870E /* PreviewViewController.xib */,
);
@@ -294,7 +301,6 @@
isa = PBXGroup;
children = (
54AE5BFB2EB3DB1000B4CFC7 /* Info.plist */,
54AE5BFC2EB3DB1000B4CFC7 /* QLThumbnail.entitlements */,
54AE5BFD2EB3DB1000B4CFC7 /* ThumbnailProvider.swift */,
);
path = QLThumbnail;
@@ -434,7 +440,7 @@
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1640;
LastUpgradeCheck = 1640;
LastUpgradeCheck = 2600;
TargetAttributes = {
54442BF32E378B71008A870E = {
CreatedOnToolsVersion = 16.4;
@@ -474,7 +480,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
544AF3692EB6AAC0006837F2 /* AssetCarReader.xcconfig in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -529,6 +534,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
549E3BA42EBC021500ADFF56 /* Preview+TransportSecurity.swift in Sources */,
5412DECE2EBC168600F9040D /* Preview+ArchiveInfo.swift in Sources */,
54D3A6F02EA3F49F001EF4F6 /* NSBezierPath+RoundedRect.swift in Sources */,
547F52E42EB2C3D8002B6D5F /* Preview+iTunesPurchase.swift in Sources */,
547F52F72EB2CAC7002B6D5F /* Preview+Footer.swift in Sources */,
@@ -540,11 +547,13 @@
547F52E82EB2C41C002B6D5F /* PreviewGenerator.swift in Sources */,
547F52EB2EB2C672002B6D5F /* Preview+FileInfo.swift in Sources */,
547F52ED2EB2C822002B6D5F /* Preview+AppInfo.swift in Sources */,
549E3BA12EBAE7D300ADFF56 /* URL+File.swift in Sources */,
547F52F42EB2CA05002B6D5F /* Preview+Entitlements.swift in Sources */,
5405CF5E2EA1199B00613856 /* MetaInfo.swift in Sources */,
547F52F92EB2CBAB002B6D5F /* Date+Format.swift in Sources */,
54D3A6EC2EA31B52001EF4F6 /* AppCategories.swift in Sources */,
5405CF652EA1376B00613856 /* Zip.swift in Sources */,
5412DED02EBC283000F9040D /* RuntimeError.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -556,6 +565,7 @@
547899722EB38F3D00F96B80 /* MetaInfo.swift in Sources */,
547899732EB38F3D00F96B80 /* NSBezierPath+RoundedRect.swift in Sources */,
54AE5BFF2EB3DB1000B4CFC7 /* ThumbnailProvider.swift in Sources */,
549E3BA22EBAECD400ADFF56 /* URL+File.swift in Sources */,
547899752EB38F3D00F96B80 /* AppIcon.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -610,6 +620,7 @@
baseConfigurationReference = 544AF3682EB6AAC0006837F2 /* AssetCarReader.xcconfig */;
buildSettings = {
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DYLIB_INSTALL_NAME_BASE = "@rpath";
@@ -644,6 +655,7 @@
baseConfigurationReference = 544AF3682EB6AAC0006837F2 /* AssetCarReader.xcconfig */;
buildSettings = {
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_IDENTITY = "";
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DYLIB_INSTALL_NAME_BASE = "@rpath";
@@ -708,7 +720,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1655;
CURRENT_PROJECT_VERSION = 1791;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = UY657LKNHJ;
@@ -734,6 +746,7 @@
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
@@ -774,7 +787,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1655;
CURRENT_PROJECT_VERSION = 1791;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = UY657LKNHJ;
@@ -794,6 +807,7 @@
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_COMPILATION_MODE = wholemodule;
};
name = Release;
@@ -801,11 +815,12 @@
54442C022E378B71008A870E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = UY657LKNHJ;
ENABLE_APP_SANDBOX = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = App/Info.plist;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
@@ -826,11 +841,12 @@
54442C032E378B71008A870E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
COMBINE_HIDPI_IMAGES = YES;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = UY657LKNHJ;
ENABLE_APP_SANDBOX = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = App/Info.plist;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
@@ -850,10 +866,11 @@
54442C322E378BAF008A870E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = QLPreview/QLPreview.entitlements;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = UY657LKNHJ;
ENABLE_APP_SANDBOX = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = QLPreview/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "$(PRODUCT_NAME) (debug)";
@@ -875,10 +892,11 @@
54442C332E378BAF008A870E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = QLPreview/QLPreview.entitlements;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = UY657LKNHJ;
ENABLE_APP_SANDBOX = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = QLPreview/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "$(PRODUCT_NAME)";
@@ -900,10 +918,11 @@
54581FDB2EB29A0B0043A0B3 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = QLThumbnail/QLThumbnail.entitlements;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = UY657LKNHJ;
ENABLE_APP_SANDBOX = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = QLThumbnail/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "$(PRODUCT_NAME) (debug)";
@@ -925,10 +944,11 @@
54581FDC2EB29A0B0043A0B3 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = QLThumbnail/QLThumbnail.entitlements;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = UY657LKNHJ;
ENABLE_APP_SANDBOX = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = QLThumbnail/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "$(PRODUCT_NAME)";

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1640"
LastUpgradeVersion = "2600"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1640"
LastUpgradeVersion = "2600"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1640"
LastUpgradeVersion = "2600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -13,12 +13,9 @@ class PreviewViewController: NSViewController, QLPreviewingController {
/// Load resource file either from user documents dir (if exists) or app bundle (default).
func bundleFile(filename: String, ext: String) throws -> String {
if let appSupport = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let override = appSupport.appendingPathComponent(filename + "." + ext)
if FileManager.default.fileExists(atPath: override.path) {
return try String(contentsOfFile: override.path, encoding: .utf8)
}
// else: do NOT copy! Breaks on future updates
if let userFile = URL.UserModDir?.appendingPathComponent(filename + "." + ext, isDirectory: false), userFile.exists() {
return try String(contentsOf: userFile, encoding: .utf8)
// else: do NOT copy! Breaks on future updates
}
// else, load bundle file
let path = Bundle.main.url(forResource: filename, withExtension: ext)
@@ -27,7 +24,8 @@ class PreviewViewController: NSViewController, QLPreviewingController {
func preparePreviewOfFile(at url: URL) async throws {
let meta = MetaInfo(url)
let html = PreviewGenerator(meta).generate(
// throws an exception if appPlist not found. Thus allowing another QuickLook plugin to try
let html = try PreviewGenerator(meta).generate(
template: try bundleFile(filename: "template", ext: "html"),
css: try bundleFile(filename: "style", ext: "css"),
)

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>

View File

@@ -6,7 +6,7 @@ body {
line-height: 1.3;
}
.hiddenDiv {
.hidden, .not- {
display: none;
}

View File

@@ -5,8 +5,9 @@
<style>__CSS__</style>
</head>
<body>
<div class="app">
<h1>__QuickLookTitle__</h1>
<h1>__QuickLookTitle__</h1>
<div class="app __AppInfoHidden__">
<div class="floatLeft icon"><img alt="App icon" src="data:image/png;base64,__AppIcon__"/></div>
<div class="floatLeft info">
Name: <strong>__AppName__</strong><br />
@@ -22,6 +23,15 @@
<br class="clear" />
</div>
<div class="not-__AppInfoHidden__">
Could not find any Info.plist
</div>
<div class="__ArchiveHidden__">
<h2>Archive Notes</h2>
<pre>__ArchiveComment__</pre>
</div>
<div class="__iTunesHidden__">
<h2>iTunes Metadata</h2>
iTunesId: __iTunesId__<br />
@@ -34,12 +44,12 @@
Price: __iTunesPrice__<br />
</div>
<div>
<div class="__TransportSecurityHidden__">
<h2>App Transport Security</h2>
__AppTransportSecurity__
__TransportSecurityDict__
</div>
<div>
<div class="__EntitlementsHidden__">
<h2>Entitlements</h2>
<div class="warning __EntitlementsWarningHidden__">
<strong>Entitlements extraction failed.</strong>
@@ -55,14 +65,10 @@
Team: __ProvisionTeamName__ (__ProvisionTeamIds__)<br />
Creation date: __ProvisionCreateDate__<br />
Expiration Date: <strong><span class="__ProvisionExpireStatus__">__ProvisionExpireDate__</span></strong><br />
</div>
<div class="__ProvisionHidden__">
<h2>Developer Certificates</h2>
__ProvisionDevelopCertificates__
</div>
<div class="__ProvisionHidden__">
<h2>Devices (__ProvisionDeviceCount__)</h2>
__ProvisionDeviceIds__
</div>

View File

@@ -15,7 +15,7 @@ struct AppIcon {
/// Try multiple methods to extract image.
/// This method will always return an image even if none is found, in which case it returns the default image.
func extractImage(from appPlist: PlistDict) -> NSImage {
func extractImage(from appPlist: PlistDict?) -> NSImage {
// no need to unwrap the plist, and most .ipa should include the Artwork anyway
if meta.type == .IPA {
if let data = meta.zipFile!.unzipFile("iTunesArtwork") {
@@ -25,12 +25,13 @@ struct AppIcon {
}
// Extract image name from app plist
var plistImgNames = iconNamesFromPlist(appPlist)
var plistImgNames = (appPlist == nil) ? [] : iconNamesFromPlist(appPlist!)
os_log(.debug, log: log, "[icon] icon names in plist: %{public}@", plistImgNames)
// If no previous filename works (or empty), try default icon names
plistImgNames.append("Icon")
plistImgNames.append("icon")
plistImgNames.append("AppIcon")
// First, try if an image file with that name exists.
if let actualName = expandImageName(plistImgNames) {
@@ -54,7 +55,7 @@ struct AppIcon {
/// Extract an image from `Assets.car`
func imageFromAssetsCar(_ imageName: String) -> NSImage? {
guard let data = meta.readPayloadFile("Assets.car") else {
guard let data = meta.readPayloadFile("Assets.car", osxSubdir: "Resources") else {
return nil
}
return CarReader(data)?.imageFromAssetsCar(imageName)
@@ -89,7 +90,7 @@ extension AppIcon {
if let icon = appPlist["CFBundleIconFile"] as? String { // may be nil
return [icon]
}
return [] // [self sortedByResolution:icons];
return []
}
/// Given a filename, search Bundle or Filesystem for files that match. Select the filename with the highest resolution.
@@ -114,10 +115,9 @@ extension AppIcon {
}
case .Archive, .Extension:
let basePath = meta.effectiveUrl ?? meta.url
for iconPath in iconList {
let fileName = iconPath.components(separatedBy: "/").last!
let parentDir = basePath.appendingPathComponent(iconPath, isDirectory: false).deletingLastPathComponent().path
let parentDir = meta.effectiveUrl("Resources", iconPath).parentDir().path
guard let files = try? FileManager.default.contentsOfDirectory(atPath: parentDir) else {
continue
}
@@ -218,7 +218,6 @@ extension NSImage {
/// Convert image to PNG and encode with base64 to be embeded in html output.
func asBase64() -> String {
// appIcon = [self roundCorners:appIcon];
let imageData = tiffRepresentation!
let imageRep = NSBitmapImageRep(data: imageData)!
let imageDataPNG = imageRep.representation(using: .png, properties: [:])!

View File

@@ -67,6 +67,8 @@ struct Entitlements {
// MARK: - SecCode in-memory reader
// Same as system call:
// `codesign -d ./binary --entitlements - --xml` or: `codesign -d ./binary --entitlements :-`
/// use in-memory `SecCode` for entitlement extraction
private func getSecCodeEntitlements() -> PlistDict? {
let url = URL(fileURLWithPath: self.binaryPath)
@@ -84,13 +86,13 @@ struct Entitlements {
// if 'entitlements-dict' key exists, use that one
os_log(.debug, log: log, "[entitlements] read SecCode 'entitlements-dict' key")
if let plist = requirementInfo[kSecCodeInfoEntitlementsDict as String] as? PlistDict {
if let plist = requirementInfo[kSecCodeInfoEntitlementsDict as String] as? PlistDict, !plist.isEmpty {
return plist
}
// else, fallback to parse data from 'entitlements' key
os_log(.debug, log: log, "[entitlements] read SecCode 'entitlements' key")
guard let data = requirementInfo[kSecCodeInfoEntitlements as String] as? Data else {
guard let data = requirementInfo[kSecCodeInfoEntitlements as String] as? Data, !data.isEmpty else {
return nil
}
@@ -107,7 +109,10 @@ struct Entitlements {
os_log(.error, log: log, "[entitlements] unpack error for FADE7171 size %lu != %lu", data.count, size)
// but try anyway
}
return data.subdata(in: 8..<data.count).asPlistOrNil()
guard let rv = data.subdata(in: 8..<data.count).asPlistOrNil(), !rv.isEmpty else {
return nil
}
return rv
}

View File

@@ -16,46 +16,67 @@ enum FileType {
struct MetaInfo {
let UTI: String
let url: URL
let effectiveUrl: URL? // if set, will point to the app inside of an archive
private let effectiveUrl: URL // if set, will point to the app inside of an archive
let type: FileType
let zipFile: ZipFile? // only set for zipped file types
let isOSX = false // relict of the past when ProvisionQL also processed provision profiles
let isOSX: Bool
/// Use file url and UTI type to generate an info object to pass around.
init(_ url: URL) {
self.url = url
self.UTI = try! url.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier ?? "Unknown"
var isOSX = false
var effective: URL? = nil
var zipFile: ZipFile? = nil
switch self.UTI {
case "com.apple.itunes.ipa", "com.opa334.trollstore.tipa", "dyn.ah62d4rv4ge81k4puqe":
self.type = FileType.IPA;
zipFile = ZipFile(self.url.path);
self.type = FileType.IPA
zipFile = ZipFile(self.url.path)
case "com.apple.xcode.archive":
self.type = FileType.Archive;
effective = appPathForArchive(self.url);
self.type = FileType.Archive
let productsDir = url.appendingPathComponent("Products", isDirectory: true)
if productsDir.exists(), let bundleDir = recursiveSearchInfoPlist(productsDir) {
isOSX = bundleDir.appendingPathComponent("MacOS").exists() && bundleDir.lastPathComponent == "Contents"
effective = bundleDir
} else {
effective = productsDir // this is wrong but dont use `url` either because that will find the `Info.plist` of the archive itself
}
case "com.apple.application-and-system-extension":
self.type = FileType.Extension;
self.type = FileType.Extension
default:
os_log(.error, log: log, "Unsupported file type: %{public}@", self.UTI)
fatalError()
}
self.isOSX = isOSX
self.zipFile = zipFile
self.effectiveUrl = effective
self.effectiveUrl = effective ?? url
}
/// Evaluate path with `osxSubdir` and `filename`
func effectiveUrl(_ osxSubdir: String?, _ filename: String) -> URL {
switch self.type {
case .IPA:
return effectiveUrl
case .Archive, .Extension:
if isOSX, let osxSubdir {
return effectiveUrl
.appendingPathComponent(osxSubdir, isDirectory: true)
.appendingPathComponent(filename, isDirectory: false)
}
return effectiveUrl.appendingPathComponent(filename, isDirectory: false)
}
}
/// Load a file from bundle into memory. Either by file path or via unzip.
func readPayloadFile(_ filename: String) -> Data? {
switch (self.type) {
func readPayloadFile(_ filename: String, osxSubdir: String?) -> Data? {
switch self.type {
case .IPA:
return zipFile!.unzipFile("Payload/*.app/".appending(filename))
case .Archive:
return try? Data(contentsOf: effectiveUrl!.appendingPathComponent(filename))
case .Extension:
return try? Data(contentsOf: url.appendingPathComponent(filename))
case .Archive, .Extension:
return try? Data(contentsOf: self.effectiveUrl(osxSubdir, filename))
}
}
@@ -63,7 +84,7 @@ struct MetaInfo {
func readPlistApp() -> PlistDict? {
switch self.type {
case .IPA, .Archive, .Extension:
return self.readPayloadFile("Info.plist")?.asPlistOrNil()
return self.readPayloadFile("Info.plist", osxSubdir: nil)?.asPlistOrNil()
}
}
}
@@ -88,15 +109,24 @@ extension Data {
}
// MARK: - Meta data for QuickLook
// MARK: - helper methods
/// Search an archive for the .app or .ipa bundle.
private func appPathForArchive(_ url: URL) -> URL? {
let appsDir = url.appendingPathComponent("Products/Applications/")
if FileManager.default.fileExists(atPath: appsDir.path) {
if let x = try? FileManager.default.contentsOfDirectory(at: appsDir, includingPropertiesForKeys: nil), !x.isEmpty {
return x.first
/// breadth-first search for `Info.plist`
private func recursiveSearchInfoPlist(_ url: URL) -> URL? {
var queue: [URL] = [url]
while !queue.isEmpty {
let current = queue.removeLast()
if current.pathExtension == "framework" {
continue // do not evaluate bundled frameworks
}
if let subfiles = try? FileManager.default.contentsOfDirectory(at: current, includingPropertiesForKeys: []) {
for fname in subfiles {
if fname.lastPathComponent == "Info.plist" {
return fname.parentDir()
}
}
queue.append(contentsOf: subfiles)
}
}
return nil;
return nil
}

View File

@@ -1,64 +1,11 @@
import Foundation
/// Print recursive tree of key-value mappings.
private func recursiveDict(_ dictionary: [String: Any], withReplacements replacements: [String: String] = [:], _ level: Int = 0) -> String {
var output = ""
for (key, value) in dictionary {
let localizedKey = replacements[key] ?? key
for _ in 0..<level {
output += (level == 1) ? "- " : "&nbsp;&nbsp;"
}
if let subDict = value as? [String: Any] {
output += "\(localizedKey):<div class=\"list\">\n"
output += recursiveDict(subDict, withReplacements: replacements, level + 1)
output += "</div>\n"
} else if let number = value as? NSNumber {
output += "\(localizedKey): \(number.boolValue ? "YES" : "NO")<br />"
} else {
output += "\(localizedKey): \(value)<br />"
}
}
return output
}
extension PreviewGenerator {
/// @return List of ATS flags.
private func formattedAppTransportSecurity(_ appPlist: PlistDict) -> String {
if let value = appPlist["NSAppTransportSecurity"] as? PlistDict {
let localizedKeys = [
"NSAllowsArbitraryLoads": "Allows Arbitrary Loads",
"NSAllowsArbitraryLoadsForMedia": "Allows Arbitrary Loads for Media",
"NSAllowsArbitraryLoadsInWebContent": "Allows Arbitrary Loads in Web Content",
"NSAllowsLocalNetworking": "Allows Local Networking",
"NSExceptionDomains": "Exception Domains",
"NSIncludesSubdomains": "Includes Subdomains",
"NSRequiresCertificateTransparency": "Requires Certificate Transparency",
"NSExceptionAllowsInsecureHTTPLoads": "Allows Insecure HTTP Loads",
"NSExceptionMinimumTLSVersion": "Minimum TLS Version",
"NSExceptionRequiresForwardSecrecy": "Requires Forward Secrecy",
"NSThirdPartyExceptionAllowsInsecureHTTPLoads": "Allows Insecure HTTP Loads",
"NSThirdPartyExceptionMinimumTLSVersion": "Minimum TLS Version",
"NSThirdPartyExceptionRequiresForwardSecrecy": "Requires Forward Secrecy",
]
return "<div class=\"list\">\(recursiveDict(value, withReplacements: localizedKeys))</div>"
private func deviceFamilyList(_ appPlist: PlistDict, isOSX: Bool) -> String {
if isOSX {
return (appPlist["CFBundleSupportedPlatforms"] as? [String])?.joined(separator: ", ") ?? "macOS"
}
let sdkName = appPlist["DTSDKName"] as? String ?? "0"
let sdkNumber = Double(sdkName.trimmingCharacters(in: .letters)) ?? 0
if sdkNumber < 9.0 {
return "Not applicable before iOS 9.0"
}
return "No exceptions"
}
/// Process info stored in `Info.plist`
mutating func procAppInfo(_ appPlist: PlistDict) {
var platforms = (appPlist["UIDeviceFamily"] as? [Int])?.compactMap({
let platforms = (appPlist["UIDeviceFamily"] as? [Int])?.compactMap({
switch $0 {
case 1: return "iPhone"
case 2: return "iPad"
@@ -70,23 +17,33 @@ extension PreviewGenerator {
let minVersion = appPlist["MinimumOSVersion"] as? String ?? ""
if platforms?.isEmpty ?? true, minVersion.hasPrefix("1.") || minVersion.hasPrefix("2.") || minVersion.hasPrefix("3.") {
platforms = "iPhone"
return "iPhone"
}
return platforms ?? ""
}
/// Process info stored in `Info.plist`
mutating func procAppInfo(_ appPlist: PlistDict?, isOSX: Bool) {
guard let appPlist else {
self.apply(["AppInfoHidden": CLASS_HIDDEN])
return
}
let minVersion = appPlist[isOSX ? "LSMinimumSystemVersion" : "MinimumOSVersion"] as? String ?? ""
let extensionType = (appPlist["NSExtension"] as? PlistDict)?["NSExtensionPointIdentifier"] as? String
self.apply([
"AppInfoHidden": CLASS_VISIBLE,
"AppName": appPlist["CFBundleDisplayName"] as? String ?? appPlist["CFBundleName"] as? String ?? "",
"AppVersion": appPlist["CFBundleShortVersionString"] as? String ?? "",
"AppBuildVer": appPlist["CFBundleVersion"] as? String ?? "",
"AppId": appPlist["CFBundleIdentifier"] as? String ?? "",
"AppExtensionTypeHidden": extensionType != nil ? "" : CLASS_HIDDEN,
"AppExtensionTypeHidden": extensionType != nil ? CLASS_VISIBLE : CLASS_HIDDEN,
"AppExtensionType": extensionType ?? "",
"AppDeviceFamily": platforms ?? "",
"AppDeviceFamily": deviceFamilyList(appPlist, isOSX: isOSX),
"AppSDK": appPlist["DTSDKName"] as? String ?? "",
"AppMinOS": minVersion,
"AppTransportSecurity": formattedAppTransportSecurity(appPlist),
])
}
}

View File

@@ -0,0 +1,29 @@
import Foundation
extension MetaInfo {
/// Read `Info.plist` if type `.Archive`
func readPlistXCArchive() -> PlistDict? {
switch self.type {
case .Archive:
// not `readPayloadFile` because plist is in root dir
return try? Data(contentsOf: self.url.appendingPathComponent("Info.plist", isDirectory: false)).asPlistOrNil()
case .IPA, .Extension:
return nil
}
}
}
extension PreviewGenerator {
/// Process info of `.xcarchive` stored in root `Info.plist`
mutating func procArchiveInfo(_ archivePlist: PlistDict?) {
guard let archivePlist, let comment = archivePlist["Comment"] as? String else {
self.apply(["ArchiveHidden": CLASS_HIDDEN])
return
}
self.apply([
"ArchiveHidden": CLASS_VISIBLE,
"ArchiveComment": comment,
])
}
}

View File

@@ -16,20 +16,24 @@ extension PreviewGenerator {
}
try! meta.zipFile!.unzipFile("Payload/*.app/\(bundleExecutable)", toDir: tmpPath)
return Entitlements(forBinary: tmpPath + "/" + bundleExecutable)
case .Archive:
return Entitlements(forBinary: meta.effectiveUrl!.path + "/" + bundleExecutable)
case .Extension:
return Entitlements(forBinary: meta.url.path + "/" + bundleExecutable)
case .Archive, .Extension:
return Entitlements(forBinary: meta.effectiveUrl("MacOS", bundleExecutable).path)
}
}
/// Process compiled binary and provision plist to extract `Entitlements`
mutating func procEntitlements(_ meta: MetaInfo, _ appPlist: PlistDict, _ provisionPlist: PlistDict?) {
var entitlements = readEntitlements(meta, appPlist["CFBundleExecutable"] as? String)
mutating func procEntitlements(_ meta: MetaInfo, _ appPlist: PlistDict?, _ provisionPlist: PlistDict?) {
var entitlements = readEntitlements(meta, appPlist?["CFBundleExecutable"] as? String)
entitlements.applyFallbackIfNeeded(provisionPlist?["Entitlements"] as? PlistDict)
if entitlements.html == nil && !entitlements.hasError {
self.apply(["EntitlementsHidden" : CLASS_HIDDEN])
return
}
self.apply([
"EntitlementsWarningHidden": entitlements.hasError ? "" : CLASS_HIDDEN,
"EntitlementsHidden" : CLASS_VISIBLE,
"EntitlementsWarningHidden": entitlements.hasError ? CLASS_VISIBLE : CLASS_HIDDEN,
"EntitlementsDict": entitlements.html ?? "No Entitlements",
])
}

View File

@@ -1,27 +1,12 @@
import Foundation
extension PreviewGenerator {
/// Calculate file / folder size.
private func getFileSize(_ path: String) -> Int64 {
var isDir: ObjCBool = false
FileManager.default.fileExists(atPath: path, isDirectory: &isDir)
if !isDir.boolValue {
return try! FileManager.default.attributesOfItem(atPath: path)[.size] as! Int64
}
var fileSize: Int64 = 0
for child in try! FileManager.default.subpathsOfDirectory(atPath: path) {
fileSize += try! FileManager.default.attributesOfItem(atPath: path + "/" + child)[.size] as! Int64
}
return fileSize
}
/// Process meta information about the file itself. Like file size and last modification.
mutating func procFileInfo(_ url: URL) {
let attrs = try? FileManager.default.attributesOfItem(atPath: url.path)
self.apply([
"FileName": escapeXML(url.lastPathComponent),
"FileSize": ByteCountFormatter.string(fromByteCount: getFileSize(url.path), countStyle: .file),
"FileModified": (attrs?[.modificationDate] as? Date)?.mediumFormat() ?? "",
"FileSize": url.fileSizeHuman(),
"FileModified": url.modificationDate()?.mediumFormat() ?? "",
])
}
}
@@ -36,3 +21,32 @@ private func escapeXML(_ stringToEscape: String) -> String {
.replacingOccurrences(of: "<", with: "&lt;")
.replacingOccurrences(of: ">", with: "&gt;")
}
extension URL {
/// Last modification date of file (or folder)
@inlinable func modificationDate() -> Date? {
(try? FileManager.default.attributesOfItem(atPath: self.path))?[.modificationDate] as? Date
}
/// Calls `fileSize()`. Will convert `Int` to human readable `String`.
func fileSizeHuman() -> String {
ByteCountFormatter.string(fromByteCount: self.fileSize(), countStyle: .file)
}
// MARK: - private methods
/// Calculate file or folder size.
private func fileSize() -> Int64 {
var isDir: ObjCBool = false
FileManager.default.fileExists(atPath: self.path, isDirectory: &isDir)
if !isDir.boolValue {
return try! FileManager.default.attributesOfItem(atPath: self.path)[.size] as! Int64
}
var fileSize: Int64 = 0
for child in try! FileManager.default.subpathsOfDirectory(atPath: self.path) {
fileSize += try! FileManager.default.attributesOfItem(atPath: self.path + "/" + child)[.size] as! Int64
}
return fileSize
}
}

View File

@@ -7,7 +7,7 @@ private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Htm
extension MetaInfo {
/// Read `embedded.mobileprovision` file and decode with CMS decoder.
func readPlistProvision() -> PlistDict? {
guard let provisionData = self.readPayloadFile("embedded.mobileprovision") else {
guard let provisionData = self.readPayloadFile("embedded.mobileprovision", osxSubdir: nil) else {
os_log(.info, log: log, "No embedded.mobileprovision file for %{public}@", self.url.path)
return nil
}
@@ -48,7 +48,7 @@ extension PreviewGenerator {
os_log(.error, log: log, "No invalidity date in '%{public}@' certificate, dictionary = %{public}@", subject, innerDict)
return nil
}
return Date.parseAny(dateString);
return Date.parseAny(dateString)
}
/// Process list of all certificates. Return a two column table with subject and expiration date.
@@ -124,7 +124,7 @@ extension PreviewGenerator {
let certs = getCertificateList(provisionPlist)
self.apply([
"ProvisionHidden": "",
"ProvisionHidden": CLASS_VISIBLE,
"ProvisionProfileName": provisionPlist["Name"] as? String ?? "",
"ProvisionProfileId": provisionPlist["UUID"] as? String ?? "",
"ProvisionTeamName": provisionPlist["TeamName"] as? String ?? "<em>Team name not available</em>",

View File

@@ -0,0 +1,57 @@
import Foundation
private let TransportSecurityLocalizedKeys = [
"NSAllowsArbitraryLoads": "Allows Arbitrary Loads",
"NSAllowsArbitraryLoadsForMedia": "Allows Arbitrary Loads for Media",
"NSAllowsArbitraryLoadsInWebContent": "Allows Arbitrary Loads in Web Content",
"NSAllowsLocalNetworking": "Allows Local Networking",
"NSExceptionDomains": "Exception Domains",
"NSIncludesSubdomains": "Includes Subdomains",
"NSRequiresCertificateTransparency": "Requires Certificate Transparency",
"NSExceptionAllowsInsecureHTTPLoads": "Allows Insecure HTTP Loads",
"NSExceptionMinimumTLSVersion": "Minimum TLS Version",
"NSExceptionRequiresForwardSecrecy": "Requires Forward Secrecy",
"NSThirdPartyExceptionAllowsInsecureHTTPLoads": "Allows Insecure HTTP Loads",
"NSThirdPartyExceptionMinimumTLSVersion": "Minimum TLS Version",
"NSThirdPartyExceptionRequiresForwardSecrecy": "Requires Forward Secrecy",
]
/// Print recursive tree of key-value mappings.
private func recursiveTransportSecurity(_ dictionary: PlistDict, _ level: Int = 0) -> String {
var output = ""
for (key, value) in dictionary {
let localizedKey = TransportSecurityLocalizedKeys[key] ?? key
for _ in 0..<level {
output += (level == 1) ? "- " : "&nbsp;&nbsp;"
}
if let subDict = value as? [String: Any] {
output += "\(localizedKey):<div class=\"list\">\n"
output += recursiveTransportSecurity(subDict, level + 1)
output += "</div>\n"
} else if let number = value as? NSNumber {
output += "\(localizedKey): \(number.boolValue ? "YES" : "NO")<br />"
} else {
output += "\(localizedKey): \(value)<br />"
}
}
return output
}
extension PreviewGenerator {
/// Process ATS info in `Info.plist`
mutating func procTransportSecurity(_ appPlist: PlistDict?) {
guard let value = appPlist?["NSAppTransportSecurity"] as? PlistDict else {
self.apply(["TransportSecurityHidden": CLASS_HIDDEN])
return
}
self.apply([
"TransportSecurityHidden": CLASS_VISIBLE,
"TransportSecurityDict": "<div class=\"list\">\(recursiveTransportSecurity(value))</div>",
])
}
}

View File

@@ -56,7 +56,7 @@ extension PreviewGenerator {
name = appleId
}
self.apply([
"iTunesHidden": "",
"iTunesHidden": CLASS_VISIBLE,
"iTunesId": (itunesPlist["itemId"] as? Int)?.description ?? "",
"iTunesName": itunesPlist["itemName"] as? String ?? "",
"iTunesGenres": formattedGenres(itunesPlist),

View File

@@ -1,25 +1,27 @@
import Foundation
let CLASS_HIDDEN = "hiddenDiv"
let CLASS_HIDDEN = "hidden"
let CLASS_VISIBLE = ""
struct PreviewGenerator {
var data: [String: String] = [:] // used for TAG replacements
let meta: MetaInfo
init(_ meta: MetaInfo) {
init(_ meta: MetaInfo) throws {
self.meta = meta
guard let plistApp = meta.readPlistApp() else {
return
throw RuntimeError("Info.plist not found")
}
let plistItunes = meta.readPlistItunes()
let plistProvision = meta.readPlistProvision()
data["QuickLookTitle"] = stringForFileType(meta)
procAppInfo(plistApp)
procItunesMeta(plistItunes)
procProvision(plistProvision, isOSX: meta.isOSX)
procAppInfo(plistApp, isOSX: meta.isOSX)
procArchiveInfo(meta.readPlistXCArchive())
procItunesMeta(meta.readPlistItunes())
procTransportSecurity(plistApp)
procEntitlements(meta, plistApp, plistProvision)
procProvision(plistProvision, isOSX: meta.isOSX)
procFileInfo(meta.url)
procFooterInfo()
// App Icon (last, because the image uses a lot of memory)

14
src/RuntimeError.swift Normal file
View File

@@ -0,0 +1,14 @@
import Foundation
// used to quit QuickLook generation without returning a valid preview
struct RuntimeError: LocalizedError {
let description: String
init(_ description: String) {
self.description = description
}
var errorDescription: String? {
description
}
}

17
src/URL+File.swift Normal file
View File

@@ -0,0 +1,17 @@
import Foundation
extension URL {
/// Folder where user can mofifications to html template
static let UserModDir: URL? =
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
/// Returns `true` if file or folder exists.
@inlinable func exists() -> Bool {
FileManager.default.fileExists(atPath: self.path)
}
/// Returns URL by deleting last path component
@inlinable func parentDir() -> URL {
self.deletingLastPathComponent()
}
}

View File

@@ -224,9 +224,9 @@ private func listZip(_ path: String) -> [ZipEntry] {
}
guard let endRecord = findCentralDirectory(fp), endRecord.sizeOfCentralDirectory > 0 else {
return [];
return []
}
return listDirectoryEntries(fp, endRecord);
return listDirectoryEntries(fp, endRecord)
}
/// Find signature for central directory.