Commit 3c983af1 authored by George Nachman's avatar George Nachman

Implement importing signed script archives. Add a new file extension of...

Implement importing signed script archives. Add a new file extension of .itermscript. The icon doesn't show up for some reason.
parent 6ceb5623
......@@ -945,13 +945,13 @@ DQ
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="4hk-FZ-gFt"/>
<menuItem title="Import" identifier="Import Script" id="hZE-iO-QIR">
<menuItem title="Import" identifier="Import Script" id="hZE-iO-QIR">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="importScript:" target="201" id="eOl-HZ-58e"/>
</connections>
</menuItem>
<menuItem title="Export" identifier="Export Script" id="dlj-rW-u7D">
<menuItem title="Export" identifier="Export Script" id="dlj-rW-u7D">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="exportScript:" target="201" id="foG-lG-KnC"/>
......
......@@ -70,6 +70,10 @@ NSString *const SIGArchiveHeaderMagicString = @"signed-archive";
readError = [SIGError errorWithCode:SIGErrorCodeIORead
detail:@"Short read"];
}
if (!data && !readError) {
readError = [SIGError errorWithCode:SIGErrorCodeIORead
detail:@"Uncaught read failure"];
}
if (error) {
*error = readError;
}
......
......@@ -159,6 +159,12 @@
[chunks addObject:chunk];
offset = chunk.payloadOffset + chunk.payloadLength;
}
if (offset > length) {
if (errorOut) {
*errorOut = [SIGError errorWithCode:SIGErrorCodeInputFileMalformed];
}
return NO;
}
_chunks = [chunks copy];
return YES;
}
......
......@@ -8,20 +8,29 @@
#import <Foundation/Foundation.h>
#import "SIGArchiveReader.h"
NS_ASSUME_NONNULL_BEGIN
@interface SIGArchiveVerifier : NSObject
@property (nonatomic, readonly) NSURL *url;
@property (nonatomic, readonly) BOOL verified;
@property (nonatomic, readonly) SIGArchiveReader *reader;
- (instancetype)initWithURL:(NSURL *)url NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (BOOL)smellsLikeSignedArchive:(out NSError **)error;
- (void)verifyWithCompletion:(void (^)(BOOL ok, NSError * _Nullable error))completion;
- (void)verifyAndWritePayloadToURL:(NSURL *)url
completion:(void (^)(BOOL ok, NSError * _Nullable error))completion;
// Must have called verifyWithCompletion: before.
- (BOOL)copyPayloadToURL:(NSURL *)url
error:(out NSError **)errorOut;
@end
NS_ASSUME_NONNULL_END
......@@ -23,6 +23,7 @@ static NSInteger SIGArchiveVerifiedLowestSupportedVersion = 1;
@implementation SIGArchiveVerifier {
SIGArchiveReader *_reader;
NSError *_readerLoadError;
SIGTrust *_trust;
NSInputStream *_payloadInputStream;
SIGCertificate *_certificate;
......@@ -39,6 +40,27 @@ static NSInteger SIGArchiveVerifiedLowestSupportedVersion = 1;
return self;
}
- (BOOL)smellsLikeSignedArchive:(out NSError **)error {
if (!self.reader) {
return NO;
}
NSString *header = [self.reader header:error];
if (!header) {
return NO;
}
const BOOL ok = [header isEqualToString:SIGArchiveHeaderMagicString];
if (error) {
if (ok) {
*error = nil;
} else {
*error = [SIGError errorWithCode:SIGErrorCodeMalformedHeader];
}
}
return ok;
}
- (void)verifyWithCompletion:(void (^)(BOOL, NSError *))completion {
assert(!_called);
_called = YES;
......@@ -57,6 +79,7 @@ static NSInteger SIGArchiveVerifiedLowestSupportedVersion = 1;
NSError *internalError = nil;
const BOOL verified = [self verify:&internalError];
self->_verified = verified;
completion(verified, internalError);
}];
}
......@@ -76,11 +99,14 @@ static NSInteger SIGArchiveVerifiedLowestSupportedVersion = 1;
}];
}
#pragma mark - Private
- (BOOL)copyPayloadToURL:(NSURL *)url
error:(out NSError **)errorOut {
if (!_verified) {
if (errorOut) {
*errorOut = [SIGError errorWithCode:SIGErrorCodeConsistency detail:@"Application error: archive not verified"];
}
return NO;
}
NSError *error = nil;
NSInputStream *readStream = [_reader payloadInputStream:&error];
if (!readStream || error) {
......@@ -132,6 +158,8 @@ static NSInteger SIGArchiveVerifiedLowestSupportedVersion = 1;
return YES;
}
#pragma mark - Private
- (NSDictionary<NSString *, NSString *> *)metadataDictionaryFromString:(NSString *)metadata {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
NSArray<NSString *> *rows = [metadata componentsSeparatedByString:@"\n"];
......@@ -192,28 +220,19 @@ static NSInteger SIGArchiveVerifiedLowestSupportedVersion = 1;
- (BOOL)prepareToVerify:(out NSError **)error {
assert(!_prepared);
_prepared = YES;
_reader = [[SIGArchiveReader alloc] initWithURL:_url];
if (!_reader) {
if (!self.reader) {
if (error) {
*error = [SIGError errorWithCode:SIGErrorCodeUnknown
detail:@"Could not create archive reader"];
*error = _readerLoadError;
}
return NO;
}
if (![_reader load:error]) {
return NO;
}
NSString *header = [_reader header:error];
if (!header) {
return NO;
}
if (![header isEqualToString:SIGArchiveHeaderMagicString]) {
if (error) {
*error = [SIGError errorWithCode:SIGErrorCodeMalformedHeader];
}
if (![self smellsLikeSignedArchive:error]) {
return NO;
}
......@@ -283,4 +302,24 @@ static NSInteger SIGArchiveVerifiedLowestSupportedVersion = 1;
error:error];
}
- (SIGArchiveReader *)reader {
if (!_reader && !_readerLoadError) {
[self createReader];
}
return _reader;
}
- (void)createReader {
_reader = [[SIGArchiveReader alloc] initWithURL:_url];
if (!_reader) {
_readerLoadError = [SIGError errorWithCode:SIGErrorCodeUnknown
detail:@"Could not create archive reader"];
return;
}
NSError *error;
[_reader load:&error];
_readerLoadError = error;
}
@end
......@@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly, nullable) NSData *data;
@property (nonatomic, readonly, nullable) SIGKey *publicKey;
@property (nonatomic, readonly, nullable) NSString *longDescription;
@property (nonatomic, readonly, nullable) NSString *name;
@property (nonatomic, readonly, nullable) NSData *serialNumber;
- (nullable instancetype)initWithSecCertificate:(SecCertificateRef)secCertificate NS_DESIGNATED_INITIALIZER;
......
......@@ -61,6 +61,17 @@
return (__bridge_transfer NSData *)SecCertificateCopyData(_secCertificate);
}
- (NSString *)name {
CFStringRef value;
const OSStatus status = SecCertificateCopyCommonName(_secCertificate,
&value);
NSString *string = (__bridge_transfer NSString *)value;
if (string == NULL || status != noErr) {
return nil;
}
return string;
}
- (NSString *)longDescription {
CFErrorRef error = NULL;
NSString *value = (__bridge_transfer NSString *)SecCertificateCopyLongDescription(NULL,
......
......@@ -16,7 +16,7 @@ NSString *const SIGErrorDomain = @"com.iterm2.sig";
NSString *localizedDescription = [self localizedDescriptionFcode:code];
return [SIGError errorWithDomain:SIGErrorDomain
code:code
userInfo:@{ NSLocalizedDescriptionKey: localizedDescription }];
userInfo:@{ NSLocalizedDescriptionKey: [localizedDescription stringByAppendingString:@"."] }];
}
+ (instancetype)errorWithCode:(SIGErrorCode)code detail:(NSString *)detail {
......@@ -90,7 +90,7 @@ NSString *const SIGErrorDomain = @"com.iterm2.sig";
case SIGErrorCodeTrustFailed:
return @"Trust chain verification failed";
case SIGErrorCodeSignatureDoesNotMatchPayload:
return @"Signature does not match payload. Do not use the file";
return @"Signature does not match payload";
}
return @"Unknown error";
}
......
......@@ -72,7 +72,7 @@
CFErrorRef secError = NULL;
const BOOL trusted = SecTrustEvaluateWithError(self->_secTrust,
&secError);
if (error) {
if (secError) {
*error = [SIGError errorWrapping:(__bridge NSError *)secError
code:SIGErrorCodeTrust
detail:@"Failed to evaluate certificate chain"];
......
......@@ -1466,6 +1466,9 @@
A61ABBC31AE5F54A004656C2 /* NSMutableDictionary+Profile.h in Headers */ = {isa = PBXBuildFile; fileRef = A61ABBC01AE5F54A004656C2 /* NSMutableDictionary+Profile.h */; };
A61B66CA18D4D855009AC9D5 /* NSView+iTerm.h in Headers */ = {isa = PBXBuildFile; fileRef = A61B66C818D4D855009AC9D5 /* NSView+iTerm.h */; };
A61B66CF18D51EAC009AC9D5 /* iTermInstantReplayWindowController.h in Headers */ = {isa = PBXBuildFile; fileRef = A61B66CD18D51EAC009AC9D5 /* iTermInstantReplayWindowController.h */; };
A61BB46421CA2D5A0027F47D /* iTerm2Script.icns in Resources */ = {isa = PBXBuildFile; fileRef = A61BB46321CA2D5A0027F47D /* iTerm2Script.icns */; };
A61BB46521CA2D5A0027F47D /* iTerm2Script.icns in Resources */ = {isa = PBXBuildFile; fileRef = A61BB46321CA2D5A0027F47D /* iTerm2Script.icns */; };
A61BB46621CA2D5A0027F47D /* iTerm2Script.icns in Resources */ = {isa = PBXBuildFile; fileRef = A61BB46321CA2D5A0027F47D /* iTerm2Script.icns */; };
A61D16FC1AAFD5530013FCCA /* iTermBackgroundColorRun.h in Headers */ = {isa = PBXBuildFile; fileRef = A61D16FA1AAFD5530013FCCA /* iTermBackgroundColorRun.h */; };
A61D16FD1AAFD5530013FCCA /* iTermBackgroundColorRun.h in Headers */ = {isa = PBXBuildFile; fileRef = A61D16FA1AAFD5530013FCCA /* iTermBackgroundColorRun.h */; };
A61ED2A520E99DCD0035BECD /* iTermStatusBarClockComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = A61ED2A320E99DCD0035BECD /* iTermStatusBarClockComponent.h */; };
......@@ -4313,6 +4316,7 @@
A61B66C918D4D855009AC9D5 /* NSView+iTerm.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.objc; path = "NSView+iTerm.m"; sourceTree = "<group>"; tabWidth = 4; };
A61B66CD18D51EAC009AC9D5 /* iTermInstantReplayWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = iTermInstantReplayWindowController.h; sourceTree = "<group>"; tabWidth = 4; };
A61B66CE18D51EAC009AC9D5 /* iTermInstantReplayWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.objc; path = iTermInstantReplayWindowController.m; sourceTree = "<group>"; tabWidth = 4; };
A61BB46321CA2D5A0027F47D /* iTerm2Script.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = iTerm2Script.icns; path = images/iTerm2Script.icns; sourceTree = "<group>"; };
A61CEAA51C72EA4C00939E97 /* iTermWeakReferenceTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = iTermWeakReferenceTest.m; sourceTree = "<group>"; };
A61D16FA1AAFD5530013FCCA /* iTermBackgroundColorRun.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iTermBackgroundColorRun.h; sourceTree = "<group>"; };
A61D16FB1AAFD5530013FCCA /* iTermBackgroundColorRun.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = iTermBackgroundColorRun.m; sourceTree = "<group>"; };
......@@ -6142,6 +6146,7 @@
1D03D42719144C3B0049EB8F /* Other Assets */ = {
isa = PBXGroup;
children = (
A61BB46321CA2D5A0027F47D /* iTerm2Script.icns */,
A6FE8D14209541BC00B5C648 /* rsa_pub.pem */,
A60C0350208964FA00FE2F1F /* it2_api_wrapper.sh */,
A6ECA5921D7535E700D19511 /* image_decoder.sb */,
......@@ -9933,6 +9938,7 @@
A67D0D671A2EE12A003A8B35 /* iTermPasswordManager.xib in Resources */,
A67F61E0214395D50093940A /* graphic_yarn.png in Resources */,
A65FF3C520F18552008BD0AE /* iTermMiniSearchFieldViewController.xib in Resources */,
A61BB46621CA2D5A0027F47D /* iTerm2Script.icns in Resources */,
1D6ED9B519AEA20D005A7799 /* bell.png in Resources */,
A6E7AE9A214394DD00DE9F3B /* graphic_code.png in Resources */,
A65B72AB1B3285A300F947A7 /* DisableTips.png in Resources */,
......@@ -10407,6 +10413,7 @@
1D44D0821CC7F5A600BE5630 /* PTYTextViewTest-golden-travis-testMinimumContrastAndCursorBoost.png in Resources */,
1D44CFC61CC7F5A600BE5630 /* PTYTextViewTest-golden-nonretina-testOversizeGlyphs.png in Resources */,
53FF9819209247E2008688D7 /* iTermScriptTemplatePickerWindowController.xib in Resources */,
A61BB46521CA2D5A0027F47D /* iTerm2Script.icns in Resources */,
A62DBBA320F47CFA008B63D1 /* GoBack@2x.png in Resources */,
1D44D0721CC7F5A600BE5630 /* PTYTextViewTest-golden-travis-testDontUseNonAsciiFont.png in Resources */,
1D44D0521CC7F5A600BE5630 /* PTYTextViewTest-golden-travis-testBackgroundImageWithTransparency.png in Resources */,
......@@ -10682,6 +10689,7 @@
A67D0D4A1A2EE12A003A8B35 /* configPanel.strings in Resources */,
A66F52D32106F6F100571168 /* gitdown@2x.png in Resources */,
A67D0D531A2EE12A003A8B35 /* GlobalSearch.xib in Resources */,
A61BB46421CA2D5A0027F47D /* iTerm2Script.icns in Resources */,
A67D0D8F1A2EE12A003A8B35 /* TmuxDashboard.xib in Resources */,
A66DB8331C8E4C2E00233E88 /* ChangeProfile@2x.png in Resources */,
A62DBBA220F47CFA008B63D1 /* GoBack@2x.png in Resources */,
......@@ -83,6 +83,24 @@
<string>public.unix-executable</string>
</array>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>itermscript</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>iTerm2Script.icns</string>
<key>CFBundleTypeName</key>
<string>iTerm2 Script</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.data</string>
</array>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
</array>
<key>NSAppleEventsUsageDescription</key>
<string>An application in iTerm2 wants to use AppleScript.</string>
......
......@@ -65,6 +65,24 @@
<string>public.unix-executable</string>
</array>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>itermscript</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>iTerm2Script.icns</string>
<key>CFBundleTypeName</key>
<string>iTerm2 Script</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.data</string>
</array>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
</array>
<key>NSCalendarsUsageDescription</key>
<string>An application in iTerm2 wants to use Calendar data.</string>
......
......@@ -75,6 +75,24 @@
<key>CFBundleTypeRole</key>
<string>Editor</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>itermscript</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>iTerm2Script.icns</string>
<key>CFBundleTypeName</key>
<string>iTerm2 Script</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.data</string>
</array>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Shell</string>
......
......@@ -75,6 +75,24 @@
<key>CFBundleTypeRole</key>
<string>Editor</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>itermscript</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>iTerm2Script.icns</string>
<key>CFBundleTypeName</key>
<string>iTerm2 Script</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.data</string>
</array>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Shell</string>
......
......@@ -75,6 +75,24 @@
<key>CFBundleTypeRole</key>
<string>Editor</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>itermscript</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>iTerm2Script.icns</string>
<key>CFBundleTypeName</key>
<string>iTerm2 Script</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.data</string>
</array>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Shell</string>
......
......@@ -55,7 +55,7 @@
#import "iTermModifierRemapper.h"
#import "iTermPreferences.h"
#import "iTermPythonRuntimeDownloader.h"
#import "iTermRemotePreferences.h"
#import "iTermScriptImporter.h"
#import "iTermAdvancedSettingsModel.h"
#import "iTermOpenQuicklyWindowController.h"
#import "iTermOrphanServerAdopter.h"
......@@ -72,6 +72,7 @@
#import "iTermQuickLookController.h"
#import "iTermRemotePreferences.h"
#import "iTermRestorableSession.h"
#import "iTermRemotePreferences.h"
#import "iTermScriptsMenuController.h"
#import "iTermSystemVersion.h"
#import "iTermTipController.h"
......@@ -545,6 +546,23 @@ static BOOL hasBecomeActive = NO;
*/
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename {
DLog(@"application:%@ openFile:%@", theApplication, filename);
if ([[filename pathExtension] isEqualToString:@"itermscript"]) {
[iTermScriptImporter importScriptFromURL:[NSURL fileURLWithPath:filename]
userInitiated:NO
completion:^(NSString * _Nullable errorMessage) {
if (errorMessage) {
NSAlert *alert = [[NSAlert alloc] init];
alert.messageText = @"Script Not Installed";
alert.informativeText = errorMessage;
[alert runModal];
} else {
NSAlert *alert = [[NSAlert alloc] init];
alert.messageText = @"Script Imported Successfully";
[alert runModal];
}
}];
return YES;
}
if ([filename hasSuffix:@".itermcolors"]) {
DLog(@"Importing color presets from %@", filename);
if ([iTermColorPresets importColorPresetFromFile:filename]) {
......
......@@ -34,14 +34,13 @@
[_signButton setAction:@selector(didToggleSignButton:)];
_signButton.translatesAutoresizingMaskIntoConstraints = NO;
_signButton.buttonType = NSSwitchButton;
_signButton.title = @"Code-sign exported script";
_signButton.title = @"Code-sign exported script using identity: ";
[_signButton sizeToFit];
[self addSubview:_signButton];
_identityButton = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(22, 0, 400, 22) pullsDown:NO];
_identityButton.translatesAutoresizingMaskIntoConstraints = NO;
_identityButton.enabled = NO;
_identityButton.title = @"Signing Identity";
[self addSubview:_identityButton];
_identities = [SIGIdentity allSigningIdentities];
[_identities enumerateObjectsUsingBlock:^(SIGIdentity * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
......
......@@ -88,7 +88,7 @@
sourceURLs = @[ [relativeURL URLByAppendingPathComponent:@"setup.py"],
[relativeURL URLByAppendingPathComponent:name] ];
NSString *extension = signingIdentity ? @"its" : @"zip";
NSString *extension = signingIdentity ? @"itermscript" : @"zip";
NSURL *zipURL = [self urlForNewZipFileInFolder:destinationFolder name:name extension:extension];
[iTermCommandRunner zipURLs:sourceURLs
arguments:@[ @"-r" ]
......
......@@ -14,6 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
// url is path to zip file
+ (void)importScriptFromURL:(NSURL *)url
userInitiated:(BOOL)userInitiated
completion:(void (^)(NSString * _Nullable errorMessage))completion;
@end
......
......@@ -10,13 +10,18 @@
#import "iTermBuildingScriptWindowController.h"
#import "iTermCommandRunner.h"
#import "iTermScriptArchive.h"
#import "iTermWarning.h"
#import "NSFileManager+iTerm.h"
#import "NSWorkspace+iTerm.h"
#import "SIGArchiveVerifier.h"
#import "SIGCertificate.h"
static BOOL sInstallingScript;
@implementation iTermScriptImporter
+ (void)importScriptFromURL:(NSURL *)url
+ (void)importScriptFromURL:(NSURL *)downloadedURL
userInitiated:(BOOL)userInitiated
completion:(void (^)(NSString *errorMessage))completion {
if (sInstallingScript) {
completion(@"Another import is in progress. Please try again after it completes.");
......@@ -24,27 +29,120 @@ static BOOL sInstallingScript;
}
sInstallingScript = YES;
NSString *tempDir = [[NSFileManager defaultManager] temporaryDirectory];
iTermBuildingScriptWindowController *pleaseWait = [iTermBuildingScriptWindowController newPleaseWaitWindowController];
[pleaseWait.window makeKeyAndOrderFront:nil];
[iTermCommandRunner unzipURL:url
withArguments:@[ @"-q" ]
destination:tempDir
completion:^(BOOL ok) {
if (!ok) {
[pleaseWait.window close];
completion(@"Could not unzip archive");
sInstallingScript = NO;
return;
}
[self didUnzipSuccessfullyTo:tempDir withCompletion:^(NSString *errorMessage) {
[self eraseTempDir:tempDir];
[pleaseWait.window close];
sInstallingScript = NO;
completion(errorMessage);
[self verifyAndUnwrapArchive:downloadedURL requireSignature:!userInitiated completion:^(NSURL *url, NSString *errorMessage) {
if (errorMessage) {
completion(errorMessage);
sInstallingScript = NO;
return;
}
iTermBuildingScriptWindowController *pleaseWait = [iTermBuildingScriptWindowController newPleaseWaitWindowController];
[pleaseWait.window makeKeyAndOrderFront:nil];
NSString *tempDir = [[NSFileManager defaultManager] temporaryDirectory];
[iTermCommandRunner unzipURL:url
withArguments:@[ @"-q" ]
destination:tempDir
completion:^(BOOL ok) {
if (!ok) {
[pleaseWait.window close];
completion(@"Could not unzip archive");
sInstallingScript = NO;
return;
}
[self didUnzipSuccessfullyTo:tempDir withCompletion:^(NSString *errorMessage) {
[self eraseTempDir:tempDir];
[pleaseWait.window close];
sInstallingScript = NO;
completion(errorMessage);
}];
}];
}];
}];
}
+ (void)verifyAndUnwrapArchive:(NSURL *)url
requireSignature:(BOOL)requireSignature
completion:(void (^)(NSURL *url, NSString *))completion {
SIGArchiveVerifier *verifier = [[SIGArchiveVerifier alloc] initWithURL:url];
if ([[url pathExtension] isEqualToString:@"itermscript"]) {
if (![verifier smellsLikeSignedArchive:NULL]) {
completion(nil, @"This script archive is corrupt and cannot be installed.");
return;
}
NSURL *zipURL = [NSURL fileURLWithPath:[[NSWorkspace sharedWorkspace] temporaryFileNameWithPrefix:@"script" suffix:@".zip"]];
[verifier verifyWithCompletion:^(BOOL ok, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self verifierDidComplete:verifier
withSuccess:ok
payloadURL:zipURL
requireSignature:requireSignature
error:error
completion:completion];
});
}];
return;
}
if (requireSignature) {
completion(nil, @"This is not a valid iTerm2 script archive.");
return;
}
completion(url, nil);
}
+ (void)verifierDidComplete:(SIGArchiveVerifier *)verifier
withSuccess:(BOOL)ok
payloadURL:(NSURL *)zipURL
requireSignature:(BOOL)requireSignature
error:(NSError *)error
completion:(void (^)(NSURL *url, NSString *))completion {
if (!ok) {
completion(nil, error.localizedDescription ?: @"Unknown error");
return;
}
if (requireSignature) {
[self confirmInstallationOfVerifiedArchive:verifier.reader
completion:^(BOOL ok) {
if (!ok) {
completion(nil, @"Installation canceled by user request.");
return;
}
[self copyPayloadFromVerifier:verifier
toURL:zipURL
completion:completion];
}];
return;
}
[self copyPayloadFromVerifier:verifier
toURL:zipURL
completion:completion];
}
+ (void)copyPayloadFromVerifier:(SIGArchiveVerifier *)verifier
toURL:(NSURL *)zipURL
completion:(void (^)(NSURL *, NSString *))completion {
NSError *innerError = nil;
const BOOL ok = [verifier copyPayloadToURL:zipURL error:&innerError];
if (!ok) {
completion(nil, innerError.localizedDescription ?: @"Unknown error");
return;
}
completion(zipURL, nil);
}
+ (void)confirmInstallationOfVerifiedArchive:(SIGArchiveReader *)reader
completion:(void (^)(BOOL ok))completion {
SIGCertificate *cert = [[SIGCertificate alloc] initWithData:[reader signingCertificate:nil]];
NSString *body = [NSString stringWithFormat:@"The signature of ”%@” has been verified. The author is:\n\n%@\n\nWould you like to install it?", reader.url.lastPathComponent, ((cert.name ?: cert.longDescription) ?: @"Unknown")];
iTermWarningSelection selection = [iTermWarning showWarningWithTitle:body
actions:@[ @"OK", @"Cancel" ]
accessory:nil
identifier:nil
silenceable:kiTermWarningTypePersistent
heading:@"Confirm Installation"
window:nil];
completion(selection == kiTermWarningSelection0);
}
+ (void)didUnzipSuccessfullyTo:(NSString *)tempDir
......
......@@ -213,7 +213,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)chooseAndImportScript {
NSOpenPanel *panel = [[NSOpenPanel alloc] init];
panel.allowedFileTypes = @[ @"zip" ];
panel.allowedFileTypes = @[ @"zip", @"itermscript" ];
if ([panel runModal] == NSModalResponseOK) {
NSURL *url = panel.URL;
dispatch_async(dispatch_get_main_queue(), ^{
......@@ -224,6 +224,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)importFromURL:(NSURL *)url {
[iTermScriptImporter importScriptFromURL:url
userInitiated:YES
completion:^(NSString * _Nullable errorMessage) {
// Mojave deadlocks if you do this without the dispatch_async
dispatch_async(dispatch_get_main_queue(), ^{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment