RadrootsAppleBackgroundTransferFileResolver.swift (3650B)
1 import Foundation 2 3 public struct RadrootsAppleBackgroundTransferFileResolver: RadrootsBackgroundTransferFileResolver, 4 Sendable { 5 private let roots: RadrootsAppleFileRoots 6 7 public init(roots: RadrootsAppleFileRoots) { 8 self.roots = roots 9 } 10 11 public func prepareUploadLease( 12 for request: RadrootsBackgroundTransferRequest, executionID: UUID, 13 existing: RadrootsStagedBlobReference? 14 ) throws -> RadrootsStagedBlobLease { 15 guard case let .upload(source) = request.operation else { throw RadrootsBackgroundTransferError.invalidRequest } 16 let owner = RadrootsAppleFileAccess(roots: roots) 17 let identifier = executionID.uuidString.lowercased() 18 if let existing { 19 guard request.expectedSourceSHA256 == nil || request.expectedSourceSHA256 == existing.blobID else { 20 throw RadrootsBackgroundTransferError.invalidRequest 21 } 22 return try owner.leaseStagedBlob(existing, expectedSHA256: existing.blobID, identifier: identifier) 23 } 24 let bytes = try read(source, maximumBytes: Int(request.maximumTransferBytes)) 25 let digest = RadrootsAppleFileDigest.sha256(bytes) 26 guard request.expectedSourceSHA256 == nil || request.expectedSourceSHA256 == digest else { 27 throw RadrootsBackgroundTransferError.invalidRequest 28 } 29 return try owner.leaseUploadBytes(bytes, identifier: identifier) 30 } 31 32 public func releaseUploadLease(executionID: UUID) throws { 33 try RadrootsAppleFileAccess(roots: roots).releaseUploadLease(executionID: executionID) 34 } 35 36 public func resolve(_ file: RadrootsBackgroundTransferLocalFile) throws -> URL { 37 let candidate: URL 38 let root: URL 39 switch file { 40 case let .file(reference): 41 candidate = try roots.resolvedURL(for: reference) 42 root = roots.root(for: reference.scope) 43 case let .stagedBlob(blob): 44 candidate = try roots.stagedBlobURL(for: blob) 45 root = roots.stagedBlobsRoot 46 } 47 let resolvedRoot = root.resolvingSymlinksInPath().standardizedFileURL 48 let resolvedCandidate = candidate.resolvingSymlinksInPath().standardizedFileURL 49 guard resolvedCandidate.path.hasPrefix(resolvedRoot.path + "/") else { 50 throw RadrootsBackgroundTransferError.invalidRequest 51 } 52 return candidate 53 } 54 55 public func read(_ file: RadrootsBackgroundTransferLocalFile, maximumBytes: Int) throws -> Data { 56 guard maximumBytes >= 0 else { 57 throw RadrootsBackgroundTransferError.invalidRequest 58 } 59 let root: URL 60 let relativePath: String 61 let expectedBytes: Int? 62 switch file { 63 case let .file(reference): 64 root = roots.root(for: reference.scope) 65 relativePath = reference.relativePath 66 expectedBytes = nil 67 case let .stagedBlob(blob): 68 root = roots.stagedBlobsRoot 69 relativePath = blob.blobID 70 expectedBytes = blob.sizeBytes 71 } 72 do { 73 let data = try RadrootsGovernedFileReader.read( 74 root: root, 75 relativePath: relativePath, 76 maximumBytes: maximumBytes 77 ) 78 guard expectedBytes == nil || expectedBytes == data.count else { 79 throw RadrootsBackgroundTransferError.invalidRequest 80 } 81 return data 82 } catch let error as RadrootsBackgroundTransferError { 83 throw error 84 } catch { 85 throw RadrootsBackgroundTransferError.invalidRequest 86 } 87 } 88 }