From 61d2a497120dd40c644f76ecfab98046e47b7648 Mon Sep 17 00:00:00 2001 From: Grishka Date: Tue, 26 Sep 2023 04:58:09 +0300 Subject: [PATCH] Support receiving links and properly reject unsupported attachments closes #52, closes #73, closes #16, closes #3, closes #8 --- NearDrop.xcodeproj/project.pbxproj | 4 +- NearDrop/AppDelegate.swift | 4 +- NearbyShare/InboundNearbyConnection.swift | 81 +++++++++++++++-------- NearbyShare/NearbyConnection.swift | 10 ++- NearbyShare/NearbyConnectionManager.swift | 8 +++ ShareExtension/ShareViewController.swift | 2 +- 6 files changed, 77 insertions(+), 32 deletions(-) diff --git a/NearDrop.xcodeproj/project.pbxproj b/NearDrop.xcodeproj/project.pbxproj index b8b3cb5..9e2f371 100644 --- a/NearDrop.xcodeproj/project.pbxproj +++ b/NearDrop.xcodeproj/project.pbxproj @@ -561,7 +561,7 @@ CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 3; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = NearDrop; @@ -587,7 +587,7 @@ CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 3; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = NearDrop; diff --git a/NearDrop/AppDelegate.swift b/NearDrop/AppDelegate.swift index 1c78adf..9190c69 100644 --- a/NearDrop/AppDelegate.swift +++ b/NearDrop/AppDelegate.swift @@ -77,7 +77,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele notificationContent.title="NearDrop" notificationContent.subtitle=String(format:NSLocalizedString("PinCode", value: "PIN: %@", comment: ""), arguments: [transfer.pinCode!]) let fileStr:String - if transfer.files.count==1{ + if let textTitle=transfer.textDescription{ + fileStr=textTitle + }else if transfer.files.count==1{ fileStr=transfer.files[0].name }else{ fileStr=String.localizedStringWithFormat(NSLocalizedString("NFiles", value: "%d files", comment: ""), transfer.files.count) diff --git a/NearbyShare/InboundNearbyConnection.swift b/NearbyShare/InboundNearbyConnection.swift index f48025c..5f0eba8 100644 --- a/NearbyShare/InboundNearbyConnection.swift +++ b/NearbyShare/InboundNearbyConnection.swift @@ -10,6 +10,7 @@ import Network import CryptoKit import CommonCrypto import System +import AppKit import SwiftECC import BigInt @@ -20,6 +21,8 @@ class InboundNearbyConnection: NearbyConnection{ public var delegate:InboundNearbyConnectionDelegate? private var cipherCommitment:Data? + private var textPayloadID:Int64=0 + enum State{ case initial, receivedConnectionRequest, sentUkeyServerInit, receivedUkeyClientFinish, sentConnectionResponse, sentPairedKeyResult, receivedPairedKeyResult, waitingForUserConsent, receivingFiles, disconnected } @@ -114,6 +117,17 @@ class InboundNearbyConnection: NearbyConnection{ } } + override func processBytesPayload(payload: Data, id: Int64) throws -> Bool { + if id==textPayloadID{ + if let urlStr=String(data: payload, encoding: .utf8), let url=URL(string: urlStr){ + NSWorkspace.shared.open(url) + } + try sendDisconnectionAndDisconnect() + return true + } + return false + } + private func processConnectionRequestFrame(_ frame:Location_Nearby_Connections_OfflineFrame) throws{ guard frame.hasV1 && frame.v1.hasConnectionRequest && frame.v1.connectionRequest.hasEndpointInfo else { throw NearbyError.requiredFieldMissing } guard case .connectionRequest = frame.v1.type else { throw NearbyError.protocolError("Unexpected frame type \(frame.v1.type)") } @@ -259,31 +273,46 @@ class InboundNearbyConnection: NearbyConnection{ private func processIntroductionFrame(_ frame:Sharing_Nearby_Frame) throws{ guard frame.hasV1, frame.v1.hasIntroduction else { throw NearbyError.requiredFieldMissing } currentState = .waitingForUserConsent - let downloadsDirectory=(try FileManager.default.url(for: .downloadsDirectory, in: .userDomainMask, appropriateFor: nil, create: true)).resolvingSymlinksInPath() - for file in frame.v1.introduction.fileMetadata{ - var dest=downloadsDirectory.appendingPathComponent(file.name) - if FileManager.default.fileExists(atPath: dest.path){ - var counter=1 - var path:String - let ext=dest.pathExtension - let baseUrl=dest.deletingPathExtension() - repeat{ - path="\(baseUrl.path) (\(counter))" - if !ext.isEmpty{ - path+=".\(ext)" - } - counter+=1 - }while FileManager.default.fileExists(atPath: path) - dest=URL(fileURLWithPath: path) + if frame.v1.introduction.fileMetadata.count>0 && frame.v1.introduction.textMetadata.isEmpty{ + let downloadsDirectory=(try FileManager.default.url(for: .downloadsDirectory, in: .userDomainMask, appropriateFor: nil, create: true)).resolvingSymlinksInPath() + for file in frame.v1.introduction.fileMetadata{ + var dest=downloadsDirectory.appendingPathComponent(file.name) + if FileManager.default.fileExists(atPath: dest.path){ + var counter=1 + var path:String + let ext=dest.pathExtension + let baseUrl=dest.deletingPathExtension() + repeat{ + path="\(baseUrl.path) (\(counter))" + if !ext.isEmpty{ + path+=".\(ext)" + } + counter+=1 + }while FileManager.default.fileExists(atPath: path) + dest=URL(fileURLWithPath: path) + } + let info=InternalFileInfo(meta: FileMetadata(name: file.name, size: file.size, mimeType: file.mimeType), + payloadID: file.payloadID, + destinationURL: dest) + transferredFiles[file.payloadID]=info } - let info=InternalFileInfo(meta: FileMetadata(name: file.name, size: file.size, mimeType: file.mimeType), - payloadID: file.payloadID, - destinationURL: dest) - transferredFiles[file.payloadID]=info - } - let metadata=TransferMetadata(files: transferredFiles.map({$0.value.meta}), id: id, pinCode: pinCode) - DispatchQueue.main.async { - self.delegate?.obtainUserConsent(for: metadata, from: self.remoteDeviceInfo!, connection: self) + let metadata=TransferMetadata(files: transferredFiles.map({$0.value.meta}), id: id, pinCode: pinCode) + DispatchQueue.main.async { + self.delegate?.obtainUserConsent(for: metadata, from: self.remoteDeviceInfo!, connection: self) + } + }else if frame.v1.introduction.textMetadata.count==1{ + let meta=frame.v1.introduction.textMetadata[0] + if case .url=meta.type{ + let metadata=TransferMetadata(files: [], id: id, pinCode: pinCode, textDescription: meta.textTitle) + textPayloadID=meta.payloadID + DispatchQueue.main.async { + self.delegate?.obtainUserConsent(for: metadata, from: self.remoteDeviceInfo!, connection: self) + } + }else{ + rejectTransfer(with: .unsupportedAttachmentType) + } + }else{ + rejectTransfer(with: .unsupportedAttachmentType) } } @@ -325,11 +354,11 @@ class InboundNearbyConnection: NearbyConnection{ } } - private func rejectTransfer(){ + private func rejectTransfer(with reason:Sharing_Nearby_ConnectionResponseFrame.Status = .reject){ var frame=Sharing_Nearby_Frame() frame.version = .v1 frame.v1.type = .response - frame.v1.connectionResponse.status = .reject + frame.v1.connectionResponse.status = reason do{ try sendTransferSetupFrame(frame) try sendDisconnectionAndDisconnect() diff --git a/NearbyShare/NearbyConnection.swift b/NearbyShare/NearbyConnection.swift index a9ca441..964c504 100644 --- a/NearbyShare/NearbyConnection.swift +++ b/NearbyShare/NearbyConnection.swift @@ -91,6 +91,10 @@ class NearbyConnection{ protocolError() } + internal func processBytesPayload(payload:Data, id:Int64)throws ->Bool{ + return false + } + private func receiveFrameAsync(){ connection.receive(minimumIncompleteLength: 4, maximumLength: 4) { content, contentContext, isComplete, error in if self.connectionClosed{ @@ -278,8 +282,10 @@ class NearbyConnection{ } if (chunk.flags & 1)==1 { payloadBuffers.removeValue(forKey: payloadID) - let innerFrame=try Sharing_Nearby_Frame(serializedData: buffer as Data) - try processTransferSetupFrame(innerFrame) + if !(try processBytesPayload(payload: Data(buffer), id: payloadID)){ + let innerFrame=try Sharing_Nearby_Frame(serializedData: buffer as Data) + try processTransferSetupFrame(innerFrame) + } } }else if case .file = header.type{ try processFileChunk(frame: payloadTransfer) diff --git a/NearbyShare/NearbyConnectionManager.swift b/NearbyShare/NearbyConnectionManager.swift index 3307bd8..ed73f5f 100644 --- a/NearbyShare/NearbyConnectionManager.swift +++ b/NearbyShare/NearbyConnectionManager.swift @@ -65,6 +65,14 @@ public struct TransferMetadata{ public let files:[FileMetadata] public let id:String public let pinCode:String? + public let textDescription:String? + + init(files: [FileMetadata], id: String, pinCode: String?, textDescription: String?=nil){ + self.files = files + self.id = id + self.pinCode = pinCode + self.textDescription = textDescription + } } public struct FileMetadata{ diff --git a/ShareExtension/ShareViewController.swift b/ShareExtension/ShareViewController.swift index f52310c..6cfedb3 100644 --- a/ShareExtension/ShareViewController.swift +++ b/ShareExtension/ShareViewController.swift @@ -116,7 +116,7 @@ class ShareViewController: NSViewController, ShareExtensionDelegate{ private func urlsReady(){ for url in urls{ if url.isFileURL{ - var isDirectory=UnsafeMutablePointer.allocate(capacity: 1) + let isDirectory=UnsafeMutablePointer.allocate(capacity: 1) if FileManager.default.fileExists(atPath: url.path, isDirectory: isDirectory) && isDirectory.pointee.boolValue{ print("Canceling share request because URL \(url) is a directory") let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)