Support receiving links and properly reject unsupported attachments

closes #52, closes #73, closes #16, closes #3, closes #8
This commit is contained in:
Grishka
2023-09-26 04:58:09 +03:00
parent df43d58045
commit 61d2a49712
6 changed files with 77 additions and 32 deletions

View File

@@ -561,7 +561,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 3;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NearDrop; INFOPLIST_KEY_CFBundleDisplayName = NearDrop;
@@ -587,7 +587,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 3;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NearDrop; INFOPLIST_KEY_CFBundleDisplayName = NearDrop;

View File

@@ -77,7 +77,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
notificationContent.title="NearDrop" notificationContent.title="NearDrop"
notificationContent.subtitle=String(format:NSLocalizedString("PinCode", value: "PIN: %@", comment: ""), arguments: [transfer.pinCode!]) notificationContent.subtitle=String(format:NSLocalizedString("PinCode", value: "PIN: %@", comment: ""), arguments: [transfer.pinCode!])
let fileStr:String 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 fileStr=transfer.files[0].name
}else{ }else{
fileStr=String.localizedStringWithFormat(NSLocalizedString("NFiles", value: "%d files", comment: ""), transfer.files.count) fileStr=String.localizedStringWithFormat(NSLocalizedString("NFiles", value: "%d files", comment: ""), transfer.files.count)

View File

@@ -10,6 +10,7 @@ import Network
import CryptoKit import CryptoKit
import CommonCrypto import CommonCrypto
import System import System
import AppKit
import SwiftECC import SwiftECC
import BigInt import BigInt
@@ -20,6 +21,8 @@ class InboundNearbyConnection: NearbyConnection{
public var delegate:InboundNearbyConnectionDelegate? public var delegate:InboundNearbyConnectionDelegate?
private var cipherCommitment:Data? private var cipherCommitment:Data?
private var textPayloadID:Int64=0
enum State{ enum State{
case initial, receivedConnectionRequest, sentUkeyServerInit, receivedUkeyClientFinish, sentConnectionResponse, sentPairedKeyResult, receivedPairedKeyResult, waitingForUserConsent, receivingFiles, disconnected 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{ private func processConnectionRequestFrame(_ frame:Location_Nearby_Connections_OfflineFrame) throws{
guard frame.hasV1 && frame.v1.hasConnectionRequest && frame.v1.connectionRequest.hasEndpointInfo else { throw NearbyError.requiredFieldMissing } 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)") } 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{ private func processIntroductionFrame(_ frame:Sharing_Nearby_Frame) throws{
guard frame.hasV1, frame.v1.hasIntroduction else { throw NearbyError.requiredFieldMissing } guard frame.hasV1, frame.v1.hasIntroduction else { throw NearbyError.requiredFieldMissing }
currentState = .waitingForUserConsent currentState = .waitingForUserConsent
let downloadsDirectory=(try FileManager.default.url(for: .downloadsDirectory, in: .userDomainMask, appropriateFor: nil, create: true)).resolvingSymlinksInPath() if frame.v1.introduction.fileMetadata.count>0 && frame.v1.introduction.textMetadata.isEmpty{
for file in frame.v1.introduction.fileMetadata{ let downloadsDirectory=(try FileManager.default.url(for: .downloadsDirectory, in: .userDomainMask, appropriateFor: nil, create: true)).resolvingSymlinksInPath()
var dest=downloadsDirectory.appendingPathComponent(file.name) for file in frame.v1.introduction.fileMetadata{
if FileManager.default.fileExists(atPath: dest.path){ var dest=downloadsDirectory.appendingPathComponent(file.name)
var counter=1 if FileManager.default.fileExists(atPath: dest.path){
var path:String var counter=1
let ext=dest.pathExtension var path:String
let baseUrl=dest.deletingPathExtension() let ext=dest.pathExtension
repeat{ let baseUrl=dest.deletingPathExtension()
path="\(baseUrl.path) (\(counter))" repeat{
if !ext.isEmpty{ path="\(baseUrl.path) (\(counter))"
path+=".\(ext)" if !ext.isEmpty{
} path+=".\(ext)"
counter+=1 }
}while FileManager.default.fileExists(atPath: path) counter+=1
dest=URL(fileURLWithPath: path) }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), let metadata=TransferMetadata(files: transferredFiles.map({$0.value.meta}), id: id, pinCode: pinCode)
payloadID: file.payloadID, DispatchQueue.main.async {
destinationURL: dest) self.delegate?.obtainUserConsent(for: metadata, from: self.remoteDeviceInfo!, connection: self)
transferredFiles[file.payloadID]=info }
} }else if frame.v1.introduction.textMetadata.count==1{
let metadata=TransferMetadata(files: transferredFiles.map({$0.value.meta}), id: id, pinCode: pinCode) let meta=frame.v1.introduction.textMetadata[0]
DispatchQueue.main.async { if case .url=meta.type{
self.delegate?.obtainUserConsent(for: metadata, from: self.remoteDeviceInfo!, connection: self) 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() var frame=Sharing_Nearby_Frame()
frame.version = .v1 frame.version = .v1
frame.v1.type = .response frame.v1.type = .response
frame.v1.connectionResponse.status = .reject frame.v1.connectionResponse.status = reason
do{ do{
try sendTransferSetupFrame(frame) try sendTransferSetupFrame(frame)
try sendDisconnectionAndDisconnect() try sendDisconnectionAndDisconnect()

View File

@@ -91,6 +91,10 @@ class NearbyConnection{
protocolError() protocolError()
} }
internal func processBytesPayload(payload:Data, id:Int64)throws ->Bool{
return false
}
private func receiveFrameAsync(){ private func receiveFrameAsync(){
connection.receive(minimumIncompleteLength: 4, maximumLength: 4) { content, contentContext, isComplete, error in connection.receive(minimumIncompleteLength: 4, maximumLength: 4) { content, contentContext, isComplete, error in
if self.connectionClosed{ if self.connectionClosed{
@@ -278,8 +282,10 @@ class NearbyConnection{
} }
if (chunk.flags & 1)==1 { if (chunk.flags & 1)==1 {
payloadBuffers.removeValue(forKey: payloadID) payloadBuffers.removeValue(forKey: payloadID)
let innerFrame=try Sharing_Nearby_Frame(serializedData: buffer as Data) if !(try processBytesPayload(payload: Data(buffer), id: payloadID)){
try processTransferSetupFrame(innerFrame) let innerFrame=try Sharing_Nearby_Frame(serializedData: buffer as Data)
try processTransferSetupFrame(innerFrame)
}
} }
}else if case .file = header.type{ }else if case .file = header.type{
try processFileChunk(frame: payloadTransfer) try processFileChunk(frame: payloadTransfer)

View File

@@ -65,6 +65,14 @@ public struct TransferMetadata{
public let files:[FileMetadata] public let files:[FileMetadata]
public let id:String public let id:String
public let pinCode: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{ public struct FileMetadata{

View File

@@ -116,7 +116,7 @@ class ShareViewController: NSViewController, ShareExtensionDelegate{
private func urlsReady(){ private func urlsReady(){
for url in urls{ for url in urls{
if url.isFileURL{ if url.isFileURL{
var isDirectory=UnsafeMutablePointer<ObjCBool>.allocate(capacity: 1) let isDirectory=UnsafeMutablePointer<ObjCBool>.allocate(capacity: 1)
if FileManager.default.fileExists(atPath: url.path, isDirectory: isDirectory) && isDirectory.pointee.boolValue{ if FileManager.default.fileExists(atPath: url.path, isDirectory: isDirectory) && isDirectory.pointee.boolValue{
print("Canceling share request because URL \(url) is a directory") print("Canceling share request because URL \(url) is a directory")
let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil) let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)