208 lines
6.4 KiB
Swift
Executable File
Vendored
208 lines
6.4 KiB
Swift
Executable File
Vendored
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)
|
|
}
|
|
}
|
|
}
|