diff --git a/TCPViewer.xcodeproj/project.pbxproj b/TCPViewer.xcodeproj/project.pbxproj
index eabc5c3..ceb284a 100644
--- a/TCPViewer.xcodeproj/project.pbxproj
+++ b/TCPViewer.xcodeproj/project.pbxproj
@@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
BA4D26BD2F9A2F770070BE17 /* PcapPlusPlusCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BA4D26B42F9A2F760070BE17 /* PcapPlusPlusCore.framework */; };
+ BA9F00012FBF00000070BE17 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = BA9F00032FBF00000070BE17 /* ZIPFoundation */; };
BA5E00012FA800000070BE17 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = BA5E00032FA800000070BE17 /* Sentry */; };
BAPM00012F9D00000070BE17 /* PcapPlusPlusCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BA4D26B42F9A2F760070BE17 /* PcapPlusPlusCore.framework */; };
BAPM00022F9D00000070BE17 /* HexFiend.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BA7A10022F9B00000070BE17 /* HexFiend.framework */; };
@@ -183,6 +184,7 @@
BAPM00022F9D00000070BE17 /* HexFiend.framework in Frameworks */,
D0C6D1832B761CD2FBBD11A7 /* Sparkle in Frameworks */,
BA5E00012FA800000070BE17 /* Sentry in Frameworks */,
+ BA9F00012FBF00000070BE17 /* ZIPFoundation in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -350,6 +352,7 @@
packageProductDependencies = (
24AFD2DE681080973988FA47 /* Sparkle */,
BA5E00032FA800000070BE17 /* Sentry */,
+ BA9F00032FBF00000070BE17 /* ZIPFoundation */,
);
productName = TCPViewer;
productReference = BAAE975D2F9B815F00C52A7C /* TCP Viewer.app */;
@@ -438,6 +441,7 @@
packageReferences = (
47F2E4C3668133A54F097208 /* XCRemoteSwiftPackageReference "Sparkle" */,
BA5E00022FA800000070BE17 /* XCRemoteSwiftPackageReference "sentry-cocoa" */,
+ BA9F00022FBF00000070BE17 /* XCRemoteSwiftPackageReference "ZIPFoundation" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = BA4D26862F9A2F490070BE17 /* Products */;
@@ -1295,6 +1299,14 @@
minimumVersion = 9.14.0;
};
};
+ BA9F00022FBF00000070BE17 /* XCRemoteSwiftPackageReference "ZIPFoundation" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/weichsel/ZIPFoundation.git";
+ requirement = {
+ kind = exactVersion;
+ version = 0.9.20;
+ };
+ };
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@@ -1308,6 +1320,11 @@
package = BA5E00022FA800000070BE17 /* XCRemoteSwiftPackageReference "sentry-cocoa" */;
productName = Sentry;
};
+ BA9F00032FBF00000070BE17 /* ZIPFoundation */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = BA9F00022FBF00000070BE17 /* XCRemoteSwiftPackageReference "ZIPFoundation" */;
+ productName = ZIPFoundation;
+ };
/* End XCSwiftPackageProductDependency section */
};
rootObject = BA4D267D2F9A2F490070BE17 /* Project object */;
diff --git a/TCPViewer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/TCPViewer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index ae78199..da47895 100644
--- a/TCPViewer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/TCPViewer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -1,5 +1,5 @@
{
- "originHash" : "45ccdc06172994e37b559028dd59b7094c4ab11c0d183fbfa94841c2b22d6b94",
+ "originHash" : "12417a22fbefd317a8960932e9f6094065b0c451031d01adb99a8c5a252832f8",
"pins" : [
{
"identity" : "sentry-cocoa",
@@ -18,6 +18,15 @@
"revision" : "066e75a8b3e99962685d6a90cdd5293ebffd9261",
"version" : "2.9.1"
}
+ },
+ {
+ "identity" : "zipfoundation",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/weichsel/ZIPFoundation.git",
+ "state" : {
+ "revision" : "22787ffb59de99e5dc1fbfe80b19c97a904ad48d",
+ "version" : "0.9.20"
+ }
}
],
"version" : 3
diff --git a/TCPViewer/App/AppDelegate.swift b/TCPViewer/App/AppDelegate.swift
index ebe491d..a287198 100644
--- a/TCPViewer/App/AppDelegate.swift
+++ b/TCPViewer/App/AppDelegate.swift
@@ -151,6 +151,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
return
}
+ let sessionURLs = supportedURLs.filter(TCPViewerCaptureFileImportPolicy.isSessionFileURL)
+ guard sessionURLs.isEmpty || (sessionURLs.count == 1 && supportedURLs.count == 1) else {
+ presentInvalidSessionOpenSelectionAlert()
+ completion?(false)
+ return
+ }
+
do {
let windowController = try frontmostOrNewTCPViewerWindowController()
focusWindowController(windowController)
@@ -163,6 +170,14 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
}
+ private func presentInvalidSessionOpenSelectionAlert() {
+ let alert = NSAlert()
+ alert.alertStyle = .warning
+ alert.messageText = "Open One Session"
+ alert.informativeText = "TCPViewer sessions replace the current document and cannot be merged with other capture files."
+ alert.runModal()
+ }
+
private func frontmostOrNewTCPViewerWindowController() throws -> TCPViewerWindowController {
if let controller = frontmostTCPViewerWindowController() {
return controller
diff --git a/TCPViewer/App/Document.swift b/TCPViewer/App/Document.swift
index eddb1ee..9575a2e 100644
--- a/TCPViewer/App/Document.swift
+++ b/TCPViewer/App/Document.swift
@@ -41,9 +41,13 @@ class Document: NSDocument {
tcpviewerWindowController?.rootViewController.exportSession(format: .pcapng)
}
+ @IBAction func exportSessionToFile(_ sender: Any?) {
+ tcpviewerWindowController?.rootViewController.exportTCPViewSession()
+ }
+
override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
switch menuItem.action {
- case #selector(exportSessionAsPcap(_:)), #selector(exportSessionAsPcapng(_:)):
+ case #selector(exportSessionAsPcap(_:)), #selector(exportSessionAsPcapng(_:)), #selector(exportSessionToFile(_:)):
return canExportSession
default:
return super.validateMenuItem(menuItem)
diff --git a/TCPViewer/Base.lproj/Main.storyboard b/TCPViewer/Base.lproj/Main.storyboard
index 760cb11..7b32f28 100644
--- a/TCPViewer/Base.lproj/Main.storyboard
+++ b/TCPViewer/Base.lproj/Main.storyboard
@@ -88,6 +88,11 @@
+