diff --git a/extension/safari/README.md b/extension/safari/README.md index 730cf82..9ceafdc 100644 --- a/extension/safari/README.md +++ b/extension/safari/README.md @@ -20,3 +20,12 @@ sites. It should be running now! (?) Check the `fs/mnt` folder of the TabFS repo on your computer to see if it's mounted. + +### tips + +- To open Web inspector: Safari -> Develop menu -> Web Extension + Background Pages -> TabFS + +- You need to rebuild if you change background.js. This is pretty + annoying. + diff --git a/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift b/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift index fbe9538..75790fa 100644 --- a/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift +++ b/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift @@ -16,7 +16,7 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { guard let message = item.userInfo?["message"] as? [AnyHashable: Any] else { return } guard message["op"] as! String == "safari_did_connect" else { return } - + // The XPC service is a subprocess that lives outside the macOS App Sandbox. // (Safari extension native code, including this file, has to live in the sandbox.) // It can do forbidden things like spawn tabfs filesystem and set up WebSocket server. diff --git a/extension/safari/TabFS/TabFS.xcodeproj/project.pbxproj b/extension/safari/TabFS/TabFS.xcodeproj/project.pbxproj index d274516..5495f1d 100644 --- a/extension/safari/TabFS/TabFS.xcodeproj/project.pbxproj +++ b/extension/safari/TabFS/TabFS.xcodeproj/project.pbxproj @@ -9,10 +9,11 @@ /* Begin PBXBuildFile section */ F028D2B625D0B7370095C2D5 /* TabFSService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028D2B525D0B7370095C2D5 /* TabFSService.swift */; }; F028D2B825D0B7370095C2D5 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028D2B725D0B7370095C2D5 /* main.swift */; }; - F028D2BC25D0B7370095C2D5 /* TabFSService.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = F028D2B125D0B7370095C2D5 /* TabFSService.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; F028D2D725D0B8500095C2D5 /* TabFSServiceProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028D2B325D0B7370095C2D5 /* TabFSServiceProtocols.swift */; }; F028D2DE25D0B8590095C2D5 /* TabFSServiceProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028D2B325D0B7370095C2D5 /* TabFSServiceProtocols.swift */; }; F028D2ED25D106F10095C2D5 /* TabFSService.xpc in CopyFiles */ = {isa = PBXBuildFile; fileRef = F028D2B125D0B7370095C2D5 /* TabFSService.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + F028D30125D17B080095C2D5 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028D30025D17B080095C2D5 /* main.swift */; }; + F028D34525D17D6A0095C2D5 /* TabFSServer in CopyFiles */ = {isa = PBXBuildFile; fileRef = F028D2FE25D17B080095C2D5 /* TabFSServer */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; F04429F625C7507200D998A5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04429F525C7507200D998A5 /* AppDelegate.swift */; }; F04429F925C7507200D998A5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F04429F725C7507200D998A5 /* Main.storyboard */; }; F04429FB25C7507200D998A5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04429FA25C7507200D998A5 /* ViewController.swift */; }; @@ -27,12 +28,12 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - F028D2BA25D0B7370095C2D5 /* PBXContainerItemProxy */ = { + F028D33725D17D100095C2D5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = F04429E925C7507200D998A5 /* Project object */; proxyType = 1; - remoteGlobalIDString = F028D2B025D0B7370095C2D5; - remoteInfo = TabFSService; + remoteGlobalIDString = F028D2FD25D17B080095C2D5; + remoteInfo = TabFSServer; }; F0442A0525C7507400D998A5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; @@ -44,17 +45,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - F028D2BD25D0B7370095C2D5 /* Embed XPC Services */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices"; - dstSubfolderSpec = 16; - files = ( - F028D2BC25D0B7370095C2D5 /* TabFSService.xpc in Embed XPC Services */, - ); - name = "Embed XPC Services"; - runOnlyForDeploymentPostprocessing = 0; - }; F028D2E525D106BB0095C2D5 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -65,6 +55,25 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F028D2FC25D17B080095C2D5 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; + F028D30E25D17BD20095C2D5 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 7; + files = ( + F028D34525D17D6A0095C2D5 /* TabFSServer in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F0442A1425C7507400D998A5 /* Embed App Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -84,6 +93,8 @@ F028D2B525D0B7370095C2D5 /* TabFSService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabFSService.swift; sourceTree = ""; }; F028D2B725D0B7370095C2D5 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; F028D2B925D0B7370095C2D5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F028D2FE25D17B080095C2D5 /* TabFSServer */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = TabFSServer; sourceTree = BUILT_PRODUCTS_DIR; }; + F028D30025D17B080095C2D5 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; F04429F125C7507200D998A5 /* TabFS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TabFS.app; sourceTree = BUILT_PRODUCTS_DIR; }; F04429F425C7507200D998A5 /* TabFS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TabFS.entitlements; sourceTree = ""; }; F04429F525C7507200D998A5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -110,6 +121,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F028D2FB25D17B080095C2D5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; F04429EE25C7507200D998A5 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -139,12 +157,21 @@ path = TabFSService; sourceTree = ""; }; + F028D2FF25D17B080095C2D5 /* TabFSServer */ = { + isa = PBXGroup; + children = ( + F028D30025D17B080095C2D5 /* main.swift */, + ); + path = TabFSServer; + sourceTree = ""; + }; F04429E825C7507200D998A5 = { isa = PBXGroup; children = ( F04429F325C7507200D998A5 /* TabFS */, F0442A0A25C7507400D998A5 /* TabFS Extension */, F028D2B225D0B7370095C2D5 /* TabFSService */, + F028D2FF25D17B080095C2D5 /* TabFSServer */, F0442A0725C7507400D998A5 /* Frameworks */, F04429F225C7507200D998A5 /* Products */, ); @@ -156,6 +183,7 @@ F04429F125C7507200D998A5 /* TabFS.app */, F0442A0325C7507400D998A5 /* TabFS Extension.appex */, F028D2B125D0B7370095C2D5 /* TabFSService.xpc */, + F028D2FE25D17B080095C2D5 /* TabFSServer */, ); name = Products; sourceTree = ""; @@ -214,16 +242,35 @@ F028D2AD25D0B7370095C2D5 /* Sources */, F028D2AE25D0B7370095C2D5 /* Frameworks */, F028D2AF25D0B7370095C2D5 /* Resources */, + F028D30E25D17BD20095C2D5 /* CopyFiles */, ); buildRules = ( ); dependencies = ( + F028D33825D17D100095C2D5 /* PBXTargetDependency */, ); name = TabFSService; productName = TabFSService; productReference = F028D2B125D0B7370095C2D5 /* TabFSService.xpc */; productType = "com.apple.product-type.xpc-service"; }; + F028D2FD25D17B080095C2D5 /* TabFSServer */ = { + isa = PBXNativeTarget; + buildConfigurationList = F028D30425D17B090095C2D5 /* Build configuration list for PBXNativeTarget "TabFSServer" */; + buildPhases = ( + F028D2FA25D17B080095C2D5 /* Sources */, + F028D2FB25D17B080095C2D5 /* Frameworks */, + F028D2FC25D17B080095C2D5 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TabFSServer; + productName = TabFSServer; + productReference = F028D2FE25D17B080095C2D5 /* TabFSServer */; + productType = "com.apple.product-type.tool"; + }; F04429F025C7507200D998A5 /* TabFS */ = { isa = PBXNativeTarget; buildConfigurationList = F0442A1525C7507400D998A5 /* Build configuration list for PBXNativeTarget "TabFS" */; @@ -232,13 +279,11 @@ F04429EE25C7507200D998A5 /* Frameworks */, F04429EF25C7507200D998A5 /* Resources */, F0442A1425C7507400D998A5 /* Embed App Extensions */, - F028D2BD25D0B7370095C2D5 /* Embed XPC Services */, ); buildRules = ( ); dependencies = ( F0442A0625C7507400D998A5 /* PBXTargetDependency */, - F028D2BB25D0B7370095C2D5 /* PBXTargetDependency */, ); name = TabFS; productName = TabFS; @@ -275,6 +320,9 @@ F028D2B025D0B7370095C2D5 = { CreatedOnToolsVersion = 12.1; }; + F028D2FD25D17B080095C2D5 = { + CreatedOnToolsVersion = 12.1; + }; F04429F025C7507200D998A5 = { CreatedOnToolsVersion = 12.1; }; @@ -299,6 +347,7 @@ F04429F025C7507200D998A5 /* TabFS */, F0442A0225C7507400D998A5 /* TabFS Extension */, F028D2B025D0B7370095C2D5 /* TabFSService */, + F028D2FD25D17B080095C2D5 /* TabFSServer */, ); }; /* End PBXProject section */ @@ -344,6 +393,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F028D2FA25D17B080095C2D5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F028D30125D17B080095C2D5 /* main.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; F04429ED25C7507200D998A5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -365,10 +422,10 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - F028D2BB25D0B7370095C2D5 /* PBXTargetDependency */ = { + F028D33825D17D100095C2D5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = F028D2B025D0B7370095C2D5 /* TabFSService */; - targetProxy = F028D2BA25D0B7370095C2D5 /* PBXContainerItemProxy */; + target = F028D2FD25D17B080095C2D5 /* TabFSServer */; + targetProxy = F028D33725D17D100095C2D5 /* PBXContainerItemProxy */; }; F0442A0625C7507400D998A5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; @@ -421,6 +478,28 @@ }; name = Release; }; + F028D30225D17B080095C2D5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75YA78K5AM; + ENABLE_HARDENED_RUNTIME = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + F028D30325D17B080095C2D5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 75YA78K5AM; + ENABLE_HARDENED_RUNTIME = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; F0442A0F25C7507400D998A5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -628,6 +707,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F028D30425D17B090095C2D5 /* Build configuration list for PBXNativeTarget "TabFSServer" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F028D30225D17B080095C2D5 /* Debug */, + F028D30325D17B080095C2D5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; F04429EC25C7507200D998A5 /* Build configuration list for PBXProject "TabFS" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/extension/safari/TabFS/TabFS.xcodeproj/project.xcworkspace/xcuserdata/osnr.xcuserdatad/UserInterfaceState.xcuserstate b/extension/safari/TabFS/TabFS.xcodeproj/project.xcworkspace/xcuserdata/osnr.xcuserdatad/UserInterfaceState.xcuserstate index 7b7b1d3..db4d28a 100644 Binary files a/extension/safari/TabFS/TabFS.xcodeproj/project.xcworkspace/xcuserdata/osnr.xcuserdatad/UserInterfaceState.xcuserstate and b/extension/safari/TabFS/TabFS.xcodeproj/project.xcworkspace/xcuserdata/osnr.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/extension/safari/TabFS/TabFS.xcodeproj/xcuserdata/osnr.xcuserdatad/xcschemes/xcschememanagement.plist b/extension/safari/TabFS/TabFS.xcodeproj/xcuserdata/osnr.xcuserdatad/xcschemes/xcschememanagement.plist index 9296675..e89e3dc 100644 --- a/extension/safari/TabFS/TabFS.xcodeproj/xcuserdata/osnr.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/extension/safari/TabFS/TabFS.xcodeproj/xcuserdata/osnr.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,13 +7,18 @@ TabFS.xcscheme_^#shared#^_ orderHint - 0 + 2 - TabFSService.xcscheme_^#shared#^_ + TabFSServer.xcscheme_^#shared#^_ orderHint 1 + TabFSService.xcscheme_^#shared#^_ + + orderHint + 0 + diff --git a/extension/safari/TabFS/TabFSServer/main.swift b/extension/safari/TabFS/TabFSServer/main.swift new file mode 100644 index 0000000..245bfb1 --- /dev/null +++ b/extension/safari/TabFS/TabFSServer/main.swift @@ -0,0 +1,113 @@ +// +// main.swift +// TabFSServer +// +// Created by Omar Rizwan on 2/8/21. +// + +import Foundation +import Network +import os.log + +class TabFSServer { + + var fs: Process! + var fsInput: FileHandle! + var fsOutput: FileHandle! + func startFs() { + let fileURL = URL(fileURLWithPath: #filePath) + let repoURL = fileURL.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent() + + fs = Process() + fs.executableURL = repoURL.appendingPathComponent("fs").appendingPathComponent("tabfs") + fs.currentDirectoryURL = fs.executableURL?.deletingLastPathComponent() + + fs.arguments = [] + + let inputPipe = Pipe(), outputPipe = Pipe() + fs.standardInput = inputPipe + fs.standardOutput = outputPipe + + fsInput = inputPipe.fileHandleForWriting + fsOutput = outputPipe.fileHandleForReading + + try! fs.run() + } + + var ws: NWListener! + func startWs() { + // TODO: randomly generate port and report back to caller? + let port = NWEndpoint.Port(rawValue: 9991)! + + let parameters = NWParameters(tls: nil) + parameters.allowLocalEndpointReuse = true + parameters.includePeerToPeer = true + // for security ? so people outside your computer can't hijack TabFS at least + parameters.requiredInterfaceType = .loopback + + let opts = NWProtocolWebSocket.Options() + opts.autoReplyPing = true + parameters.defaultProtocolStack.applicationProtocols.insert(opts, at: 0) + + ws = try! NWListener(using: parameters, on: port) + ws.start(queue: .main) + } + + init() { + startFs() + startWs() + + var handleRequest: ((_ req: Data) -> Void)? + ws.newConnectionHandler = { conn in + conn.start(queue: .main) + + handleRequest = { req in + let metaData = NWProtocolWebSocket.Metadata(opcode: .text) + let context = NWConnection.ContentContext(identifier: "context", metadata: [metaData]) + conn.send(content: req, contentContext: context, completion: .contentProcessed({ err in + if err != nil { + os_log(.default, "req %{public}@ error: %{public}@", String(data: req, encoding: .utf8)!, err!.debugDescription as CVarArg) + // FIXME: ERROR + } + })) + } + + func read() { + conn.receiveMessage { (resp, context, isComplete, err) in + guard let resp = resp else { + // FIXME err + os_log(.default, "resp error: %{public}@", err!.debugDescription as CVarArg) + return + } + + // Send the response back to tabfs.c. + self.fsInput.write(withUnsafeBytes(of: UInt32(resp.count)) { Data($0) }) + self.fsInput.write(resp) + read() + } + } + read() + } + + DispatchQueue.global(qos: .default).async { + while true { + // Blocking read from the tabfs process. + let length = self.fsOutput.readData(ofLength: 4).withUnsafeBytes { $0.load(as: UInt32.self) } + let req = self.fsOutput.readData(ofLength: Int(length)) + + if let handleRequest = handleRequest { + // Send the request over the WebSocket connection to background.js in browser. + handleRequest(req) + } else { + // FIXME: ERROR + } + } + } + + // FIXME: notify + + } +} + +let server = TabFSServer() +dispatchMain() diff --git a/extension/safari/TabFS/TabFSService/TabFSService.swift b/extension/safari/TabFS/TabFSService/TabFSService.swift index 86c39f8..4a86dd6 100644 --- a/extension/safari/TabFS/TabFSService/TabFSService.swift +++ b/extension/safari/TabFS/TabFSService/TabFSService.swift @@ -10,105 +10,15 @@ import Network import os.log class TabFSService: NSObject, TabFSServiceProtocol { - var fs: Process! - var fsInput: FileHandle! - var fsOutput: FileHandle! - func startFs() { - let fileURL = URL(fileURLWithPath: #filePath) - let repoURL = fileURL.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent() - - fs = Process() - fs.executableURL = repoURL.appendingPathComponent("fs").appendingPathComponent("tabfs") - fs.currentDirectoryURL = fs.executableURL?.deletingLastPathComponent() - - fs.arguments = [] - - let inputPipe = Pipe(), outputPipe = Pipe() - fs.standardInput = inputPipe - fs.standardOutput = outputPipe - - fsInput = inputPipe.fileHandleForWriting - fsOutput = outputPipe.fileHandleForReading - - try! fs.run() - } - - var ws: NWListener! - func startWs() { - // TODO: randomly generate port and report back to caller? - let port = NWEndpoint.Port(rawValue: 9991)! - - let parameters = NWParameters(tls: nil) - parameters.allowLocalEndpointReuse = true - parameters.includePeerToPeer = true - // for security ? so people outside your computer can't hijack TabFS at least - parameters.requiredInterfaceType = .loopback - - let opts = NWProtocolWebSocket.Options() - opts.autoReplyPing = true - parameters.defaultProtocolStack.applicationProtocols.insert(opts, at: 0) - - ws = try! NWListener(using: parameters, on: port) - ws.start(queue: .main) - } - - override init() { - super.init() - - startFs() - startWs() - - var handleRequest: ((_ req: Data) -> Void)? - ws.newConnectionHandler = { conn in - conn.start(queue: .main) - - handleRequest = { req in - let metaData = NWProtocolWebSocket.Metadata(opcode: .text) - let context = NWConnection.ContentContext(identifier: "context", metadata: [metaData]) - conn.send(content: req, contentContext: context, completion: .contentProcessed({ err in - if err != nil { - os_log(.default, "req %{public}@ error: %{public}@", String(data: req, encoding: .utf8)!, err!.debugDescription as CVarArg) - // FIXME: ERROR - } - })) - } - - func read() { - conn.receiveMessage { (resp, context, isComplete, err) in - guard let resp = resp else { - // FIXME err - os_log(.default, "resp error: %{public}@", err!.debugDescription as CVarArg) - return - } - - // Send the response back to tabfs.c. - self.fsInput.write(withUnsafeBytes(of: UInt32(resp.count)) { Data($0) }) - self.fsInput.write(resp) - read() - } - } - read() - } - - DispatchQueue.global(qos: .default).async { - while true { - // Blocking read from the tabfs process. - let length = self.fsOutput.readData(ofLength: 4).withUnsafeBytes { $0.load(as: UInt32.self) } - let req = self.fsOutput.readData(ofLength: Int(length)) - - if let handleRequest = handleRequest { - // Send the request over the WebSocket connection to background.js in browser. - handleRequest(req) - } else { - // FIXME: ERROR - } - } - } - // FIXME: disable auto termination - } - func start(withReply reply: @escaping () -> Void) { // This XPC call is enough to just force the XPC service to be started. + os_log("HELLO") + let server = Process() + os_log("HOW ARE YOU?") + server.executableURL = Bundle.main.url(forResource: "TabFSServer", withExtension: "")! + os_log("I AM GOOD") + server.launch() + os_log("GREAT") reply() } }