import Foundation public class HTTPProxySocket: ProxySocket { enum HTTPProxyReadStatus: CustomStringConvertible { case invalid, readingFirstHeader, pendingFirstHeader, readingHeader, readingContent, stopped var description: String { switch self { case .invalid: return "invalid" case .readingFirstHeader: return "reading first header" case .pendingFirstHeader: return "waiting to send first header" case .readingHeader: return "reading header (forwarding)" case .readingContent: return "reading content (forwarding)" case .stopped: return "stopped" } } } enum HTTPProxyWriteStatus: CustomStringConvertible { case invalid, sendingConnectResponse, forwarding, stopped var description: String { switch self { case .invalid: return "invalid" case .sendingConnectResponse: return "sending response header for CONNECT" case .forwarding: return "waiting to begin forwarding data" case .stopped: return "stopped" } } } /// The remote host to connect to. public var destinationHost: String! /// The remote port to connect to. public var destinationPort: Int! private var currentHeader: HTTPHeader! private let scanner: HTTPStreamScanner = HTTPStreamScanner() private var readStatus: HTTPProxyReadStatus = .invalid private var writeStatus: HTTPProxyWriteStatus = .invalid public var isConnectCommand = false public var readStatusDescription: String { return readStatus.description } public var writeStatusDescription: String { return writeStatus.description } /** Begin reading and processing data from the socket. */ override public func openSocket() { super.openSocket() guard !isCancelled else { return } readStatus = .readingFirstHeader socket.readDataTo(data: Utils.HTTPData.DoubleCRLF) } override public func readData() { guard !isCancelled else { return } // Return the first header we read when the socket was opened if the proxy command is not CONNECT. if readStatus == .pendingFirstHeader { delegate?.didRead(data: currentHeader.toData(), from: self) readStatus = .readingContent return } switch scanner.nextAction { case .readContent(let length): readStatus = .readingContent if length > 0 { socket.readDataTo(length: length) } else { socket.readData() } case .readHeader: readStatus = .readingHeader socket.readDataTo(data: Utils.HTTPData.DoubleCRLF) case .stop: readStatus = .stopped disconnect() } } // swiftlint:disable function_body_length // swiftlint:disable cyclomatic_complexity /** The socket did read some data. - parameter data: The data read from the socket. - parameter from: The socket where the data is read from. */ override public func didRead(data: Data, from: RawTCPSocketProtocol) { super.didRead(data: data, from: from) let result: HTTPStreamScanner.Result do { result = try scanner.input(data) } catch let error { disconnect(becauseOf: error) return } switch (readStatus, result) { case (.readingFirstHeader, .header(let header)): currentHeader = header currentHeader.removeProxyHeader() currentHeader.rewriteToRelativePath() destinationHost = currentHeader.host destinationPort = currentHeader.port isConnectCommand = currentHeader.isConnect if !isConnectCommand { readStatus = .pendingFirstHeader } else { readStatus = .readingContent } session = ConnectSession(host: destinationHost!, port: destinationPort!) observer?.signal(.receivedRequest(session!, on: self)) delegate?.didReceive(session: session!, from: self) case (.readingHeader, .header(let header)): currentHeader = header currentHeader.removeProxyHeader() currentHeader.rewriteToRelativePath() delegate?.didRead(data: currentHeader.toData(), from: self) case (.readingContent, .content(let content)): delegate?.didRead(data: content, from: self) default: return } } /** The socket did send some data. - parameter data: The data which have been sent to remote (acknowledged). Note this may not be available since the data may be released to save memory. - parameter by: The socket where the data is sent out. */ override public func didWrite(data: Data?, by: RawTCPSocketProtocol) { super.didWrite(data: data, by: by) switch writeStatus { case .sendingConnectResponse: writeStatus = .forwarding observer?.signal(.readyForForward(self)) delegate?.didBecomeReadyToForwardWith(socket: self) default: delegate?.didWrite(data: data, by: self) } } /** Response to the `AdapterSocket` on the other side of the `Tunnel` which has succefully connected to the remote server. - parameter adapter: The `AdapterSocket`. */ public override func respondTo(adapter: AdapterSocket) { super.respondTo(adapter: adapter) guard !isCancelled else { return } if isConnectCommand { writeStatus = .sendingConnectResponse write(data: Utils.HTTPData.ConnectSuccessResponse) } else { writeStatus = .forwarding observer?.signal(.readyForForward(self)) delegate?.didBecomeReadyToForwardWith(socket: self) } } }