1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
|
// Foundation/URLSession/TransferState.swift - URLSession & libcurl
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
// -----------------------------------------------------------------------------
///
/// The state of a single transfer.
/// These are libcurl helpers for the URLSession API code.
/// - SeeAlso: https://curl.haxx.se/libcurl/c/
/// - SeeAlso: URLSession.swift
///
// -----------------------------------------------------------------------------
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
import SwiftFoundation
#else
import Foundation
#endif
extension _NativeProtocol {
/// State related to an ongoing transfer.
///
/// This contains headers received so far, body data received so far, etc.
///
/// There's a strict 1-to-1 relationship between an `EasyHandle` and a
/// `TransferState`.
///
/// - TODO: Might move the `EasyHandle` into this `struct` ?
/// - SeeAlso: `URLSessionTask.EasyHandle`
internal struct _TransferState {
/// The URL that's being requested
let url: URL
/// Raw headers received.
let parsedResponseHeader: _ParsedResponseHeader
/// Once the headers is complete, this will contain the response
var response: URLResponse?
/// The body data to be sent in the request
let requestBodySource: _BodySource?
/// Body data received
let bodyDataDrain: _DataDrain
/// Describes what to do with received body data for this transfer:
}
}
extension _NativeProtocol {
enum _DataDrain {
/// Concatenate in-memory
case inMemory(NSMutableData?)
/// Write to file
case toFile(URL, FileHandle?)
/// Do nothing. Might be forwarded to delegate
case ignore
}
}
extension _NativeProtocol._TransferState {
/// Transfer state that can receive body data, but will not send body data.
init(url: URL, bodyDataDrain: _NativeProtocol._DataDrain) {
self.url = url
self.parsedResponseHeader = _NativeProtocol._ParsedResponseHeader()
self.response = nil
self.requestBodySource = nil
self.bodyDataDrain = bodyDataDrain
}
/// Transfer state that sends body data and can receive body data.
init(url: URL, bodyDataDrain: _NativeProtocol._DataDrain, bodySource: _BodySource) {
self.url = url
self.parsedResponseHeader = _NativeProtocol._ParsedResponseHeader()
self.response = nil
self.requestBodySource = bodySource
self.bodyDataDrain = bodyDataDrain
}
}
// specific to HTTP protocol
extension _HTTPURLProtocol._TransferState {
/// Appends a header line
///
/// Will set the complete response once the header is complete, i.e. the
/// return value's `isHeaderComplete` will then by `true`.
///
/// - Throws: When a parsing error occurs
func byAppendingHTTP(headerLine data: Data) throws -> _NativeProtocol._TransferState {
// If the line is empty, it marks the end of the header, and the result
// is a complete header. Otherwise it's a partial header.
// - Note: Appending a line to a complete header results in a partial
// header with just that line.
func isCompleteHeader(_ headerLine: String) -> Bool {
return headerLine.isEmpty
}
guard let h = parsedResponseHeader.byAppending(headerLine: data, onHeaderCompleted: isCompleteHeader) else {
throw _Error.parseSingleLineError
}
if case .complete(let lines) = h {
// Header is complete
let response = lines.createHTTPURLResponse(for: url)
guard response != nil else {
throw _Error.parseCompleteHeaderError
}
return _NativeProtocol._TransferState(url: url,
parsedResponseHeader: _NativeProtocol._ParsedResponseHeader(), response: response, requestBodySource: requestBodySource, bodyDataDrain: bodyDataDrain)
} else {
return _NativeProtocol._TransferState(url: url,
parsedResponseHeader: h, response: nil, requestBodySource: requestBodySource, bodyDataDrain: bodyDataDrain)
}
}
}
// specific to FTP
extension _FTPURLProtocol._TransferState {
enum FTPHeaderCode: Int {
case transferCompleted = 226
case openDataConnection = 150
case fileStatus = 213
case syntaxError = 500// 500 series FTP Syntax errors
case errorOccurred = 400 // 400 Series FTP transfer errors
}
/// Appends a header line
///
/// Will set the complete response once the header is complete, i.e. the
/// return value's `isHeaderComplete` will then by `true`.
///
/// - Throws: When a parsing error occurs
func byAppendingFTP(headerLine data: Data, expectedContentLength: Int64) throws -> _NativeProtocol._TransferState {
guard let line = String(data: data, encoding: String.Encoding.utf8) else {
fatalError("Data on command port is nil")
}
//FTP Status code 226 marks the end of the transfer
if (line.starts(with: String(FTPHeaderCode.transferCompleted.rawValue))) {
return self
}
//FTP Status code 213 marks the end of the header and start of the
//transfer on data port
func isCompleteHeader(_ headerLine: String) -> Bool {
return headerLine.starts(with: String(FTPHeaderCode.openDataConnection.rawValue))
}
guard let h = parsedResponseHeader.byAppending(headerLine: data, onHeaderCompleted: isCompleteHeader) else {
throw _NativeProtocol._Error.parseSingleLineError
}
if case .complete(let lines) = h {
let response = lines.createURLResponse(for: url, contentLength: expectedContentLength)
guard response != nil else {
throw _NativeProtocol._Error.parseCompleteHeaderError
}
return _NativeProtocol._TransferState(url: url, parsedResponseHeader: _NativeProtocol._ParsedResponseHeader(), response: response, requestBodySource: requestBodySource, bodyDataDrain: bodyDataDrain)
} else {
return _NativeProtocol._TransferState(url: url, parsedResponseHeader: _NativeProtocol._ParsedResponseHeader(), response: nil, requestBodySource: requestBodySource, bodyDataDrain: bodyDataDrain)
}
}
}
extension _NativeProtocol._TransferState {
enum _Error: Error {
case parseSingleLineError
case parseCompleteHeaderError
}
var isHeaderComplete: Bool {
return response != nil
}
/// Append body data
///
/// - Important: This will mutate the existing `NSMutableData` that the
/// struct may already have in place -- copying the data is too
/// expensive. This behaviour
func byAppending(bodyData buffer: Data) -> _NativeProtocol._TransferState {
switch bodyDataDrain {
case .inMemory(let bodyData):
let data: NSMutableData = bodyData ?? NSMutableData()
data.append(buffer)
let drain = _NativeProtocol._DataDrain.inMemory(data)
return _NativeProtocol._TransferState(url: url, parsedResponseHeader: parsedResponseHeader, response: response, requestBodySource: requestBodySource, bodyDataDrain: drain)
case .toFile(_, let fileHandle):
//TODO: Create / open the file for writing
// Append to the file
_ = fileHandle!.seekToEndOfFile()
fileHandle!.write(buffer)
return self
case .ignore:
return self
}
}
/// Sets the given body source on the transfer state.
///
/// This can be used to either set the initial body source, or to reset it
/// e.g. when restarting a transfer.
func bySetting(bodySource newSource: _BodySource) -> _NativeProtocol._TransferState {
return _NativeProtocol._TransferState(url: url,
parsedResponseHeader: parsedResponseHeader, response: response, requestBodySource: newSource, bodyDataDrain: bodyDataDrain)
}
}
|