mirror of
https://github.com/grishka/NearDrop.git
synced 2026-04-03 09:46:19 +02:00
Support receiving links and properly reject unsupported attachments
closes #52, closes #73, closes #16, closes #3, closes #8
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user