|
| 1 | +// This source file is part of the Swift.org open source project |
| 2 | +// |
| 3 | +// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors |
| 4 | +// Licensed under Apache License v2.0 with Runtime Library Exception |
| 5 | +// |
| 6 | +// See http://swift.org/LICENSE.txt for license information |
| 7 | +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| 8 | +// |
| 9 | + |
| 10 | +import CoreFoundation |
| 11 | +import Dispatch |
| 12 | + |
| 13 | +internal class _FTPURLProtocol: _NativeProtocol { |
| 14 | + |
| 15 | + public required init(task: URLSessionTask, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) { |
| 16 | + super.init(task: task, cachedResponse: cachedResponse, client: client) |
| 17 | + } |
| 18 | + |
| 19 | + public required init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) { |
| 20 | + super.init(request: request, cachedResponse: cachedResponse, client: client) |
| 21 | + } |
| 22 | + |
| 23 | + override class func canInit(with request: URLRequest) -> Bool { |
| 24 | + guard request.url?.scheme == "ftp" || request.url?.scheme == "ftps" || request.url?.scheme == "sftp" else { return false } |
| 25 | + return true |
| 26 | + } |
| 27 | + |
| 28 | + override class func canonicalRequest(for request: URLRequest) -> URLRequest { |
| 29 | + return request |
| 30 | + } |
| 31 | + |
| 32 | + override func startLoading() { |
| 33 | + resume() |
| 34 | + } |
| 35 | + |
| 36 | + override func stopLoading() { |
| 37 | + if task?.state == .suspended { |
| 38 | + suspend() |
| 39 | + } else { |
| 40 | + self.internalState = .transferFailed |
| 41 | + guard let error = self.task?.error else { fatalError() } |
| 42 | + completeTask(withError: error) |
| 43 | + } |
| 44 | + } |
| 45 | + |
| 46 | + override func didReceive(headerData data: Data, contentLength: Int64) -> _EasyHandle._Action { |
| 47 | + guard case .transferInProgress(let ts) = internalState else { fatalError("Received body data, but no transfer in progress.") } |
| 48 | + guard let task = task else { fatalError("Received header data but no task available.") } |
| 49 | + task.countOfBytesExpectedToReceive = contentLength > 0 ? contentLength : NSURLSessionTransferSizeUnknown |
| 50 | + do { |
| 51 | + let newTS = try ts.byAppendingFTP(headerLine: data, expectedContentLength: contentLength) |
| 52 | + internalState = .transferInProgress(newTS) |
| 53 | + let didCompleteHeader = !ts.isHeaderComplete && newTS.isHeaderComplete |
| 54 | + if didCompleteHeader { |
| 55 | + // The header is now complete, but wasn't before. |
| 56 | + didReceiveResponse() |
| 57 | + } |
| 58 | + return .proceed |
| 59 | + } catch { |
| 60 | + return .abort |
| 61 | + } |
| 62 | + } |
| 63 | + |
| 64 | + override func seekInputStream(to position: UInt64) throws { |
| 65 | + // NSUnimplemented() |
| 66 | + } |
| 67 | + |
| 68 | + override func updateProgressMeter(with propgress: _EasyHandle._Progress) { |
| 69 | + //NSUnimplemented() |
| 70 | + } |
| 71 | + |
| 72 | + override func configureEasyHandle(for request: URLRequest) { |
| 73 | + easyHandle.set(verboseModeOn: enableLibcurlDebugOutput) |
| 74 | + easyHandle.set(debugOutputOn: enableLibcurlDebugOutput, task: task!) |
| 75 | + easyHandle.set(skipAllSignalHandling: true) |
| 76 | + guard let url = request.url else { fatalError("No URL in request.") } |
| 77 | + easyHandle.set(url: url) |
| 78 | + easyHandle.set(preferredReceiveBufferSize: Int.max) |
| 79 | + do { |
| 80 | + switch (task?.body, try task?.body.getBodyLength()) { |
| 81 | + case (.some(URLSessionTask._Body.none), _): |
| 82 | + set(requestBodyLength: .noBody) |
| 83 | + case (_, .some(let length)): |
| 84 | + set(requestBodyLength: .length(length)) |
| 85 | + task!.countOfBytesExpectedToSend = Int64(length) |
| 86 | + case (_, .none): |
| 87 | + set(requestBodyLength: .unknown) |
| 88 | + } |
| 89 | + } catch let e { |
| 90 | + // Fail the request here. |
| 91 | + // TODO: We have multiple options: |
| 92 | + // NSURLErrorNoPermissionsToReadFile |
| 93 | + // NSURLErrorFileDoesNotExist |
| 94 | + self.internalState = .transferFailed |
| 95 | + failWith(errorCode: errorCode(fileSystemError: e), request: request) |
| 96 | + return |
| 97 | + } |
| 98 | + let timeoutHandler = DispatchWorkItem { [weak self] in |
| 99 | + guard let _ = self?.task else { fatalError("Timeout on a task that doesn't exist") } //this guard must always pass |
| 100 | + self?.internalState = .transferFailed |
| 101 | + let urlError = URLError(_nsError: NSError(domain: NSURLErrorDomain, code: NSURLErrorTimedOut, userInfo: nil)) |
| 102 | + self?.completeTask(withError: urlError) |
| 103 | + self?.client?.urlProtocol(self!, didFailWithError: urlError) |
| 104 | + } |
| 105 | + guard let task = self.task else { fatalError() } |
| 106 | + easyHandle.timeoutTimer = _TimeoutSource(queue: task.workQueue, milliseconds: Int(request.timeoutInterval) * 1000, handler: timeoutHandler) |
| 107 | + |
| 108 | + easyHandle.set(automaticBodyDecompression: true) |
| 109 | + } |
| 110 | + |
| 111 | +} |
| 112 | + |
| 113 | + |
| 114 | +/// Response processing |
| 115 | +internal extension _FTPURLProtocol { |
| 116 | + /// Whenever we receive a response (i.e. a complete header) from libcurl, |
| 117 | + /// this method gets called. |
| 118 | + func didReceiveResponse() { |
| 119 | + guard let _ = task as? URLSessionDataTask else { return } |
| 120 | + guard case .transferInProgress(let ts) = self.internalState else { fatalError("Transfer not in progress.") } |
| 121 | + guard let response = ts.response else { fatalError("Header complete, but not URL response.") } |
| 122 | + guard let session = task?.session as? URLSession else { fatalError() } |
| 123 | + switch session.behaviour(for: self.task!) { |
| 124 | + case .noDelegate: |
| 125 | + break |
| 126 | + case .taskDelegate(_): |
| 127 | + //TODO: There's a problem with libcurl / with how we're using it. |
| 128 | + // We're currently unable to pause the transfer / the easy handle: |
| 129 | + // https://curl.haxx.se/mail/lib-2016-03/0222.html |
| 130 | + // |
| 131 | + // For now, we'll notify the delegate, but won't pause the transfer, |
| 132 | + // and we'll disregard the completion handler: |
| 133 | + |
| 134 | + self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) |
| 135 | + |
| 136 | + case .dataCompletionHandler: |
| 137 | + break |
| 138 | + case .downloadCompletionHandler: |
| 139 | + break |
| 140 | + } |
| 141 | + } |
| 142 | + |
| 143 | +} |
| 144 | + |
| 145 | + |
| 146 | + |
| 147 | + |
| 148 | + |
0 commit comments