123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- //////////////////////////////////////////////////////////////////////////////////////////////////
- //
- // HTTPHandler.swift
- // Starscream
- //
- // Created by Dalton Cherry on 1/24/19.
- // Copyright © 2019 Vluxe. All rights reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- //
- //////////////////////////////////////////////////////////////////////////////////////////////////
- import Foundation
- public enum HTTPUpgradeError: Error {
- case notAnUpgrade(Int, [String: String])
- case invalidData
- }
- public struct HTTPWSHeader {
- static let upgradeName = "Upgrade"
- static let upgradeValue = "websocket"
- static let hostName = "Host"
- static let connectionName = "Connection"
- static let connectionValue = "Upgrade"
- static let protocolName = "Sec-WebSocket-Protocol"
- static let versionName = "Sec-WebSocket-Version"
- static let versionValue = "13"
- static let extensionName = "Sec-WebSocket-Extensions"
- static let keyName = "Sec-WebSocket-Key"
- static let originName = "Origin"
- static let acceptName = "Sec-WebSocket-Accept"
- static let switchProtocolCode = 101
- static let defaultSSLSchemes = ["wss", "https"]
-
- /// Creates a new URLRequest based off the source URLRequest.
- /// - Parameter request: the request to "upgrade" the WebSocket request by adding headers.
- /// - Parameter supportsCompression: set if the client support text compression.
- /// - Parameter secKeyName: the security key to use in the WebSocket request. https://tools.ietf.org/html/rfc6455#section-1.3
- /// - returns: A URLRequest request to be converted to data and sent to the server.
- public static func createUpgrade(request: URLRequest, supportsCompression: Bool, secKeyValue: String) -> URLRequest {
- guard let url = request.url, let parts = url.getParts() else {
- return request
- }
-
- var req = request
- if request.value(forHTTPHeaderField: HTTPWSHeader.originName) == nil {
- var origin = url.absoluteString
- if let hostUrl = URL (string: "/", relativeTo: url) {
- origin = hostUrl.absoluteString
- origin.remove(at: origin.index(before: origin.endIndex))
- }
- req.setValue(origin, forHTTPHeaderField: HTTPWSHeader.originName)
- }
- req.setValue(HTTPWSHeader.upgradeValue, forHTTPHeaderField: HTTPWSHeader.upgradeName)
- req.setValue(HTTPWSHeader.connectionValue, forHTTPHeaderField: HTTPWSHeader.connectionName)
- req.setValue(HTTPWSHeader.versionValue, forHTTPHeaderField: HTTPWSHeader.versionName)
- req.setValue(secKeyValue, forHTTPHeaderField: HTTPWSHeader.keyName)
-
- if req.allHTTPHeaderFields?["Cookie"] == nil {
- if let cookies = HTTPCookieStorage.shared.cookies(for: url), !cookies.isEmpty {
- let headers = HTTPCookie.requestHeaderFields(with: cookies)
- for (key, val) in headers {
- req.setValue(val, forHTTPHeaderField: key)
- }
- }
- }
-
- if supportsCompression {
- let val = "permessage-deflate; client_max_window_bits; server_max_window_bits=15"
- req.setValue(val, forHTTPHeaderField: HTTPWSHeader.extensionName)
- }
- let hostValue = req.allHTTPHeaderFields?[HTTPWSHeader.hostName] ?? "\(parts.host):\(parts.port)"
- req.setValue(hostValue, forHTTPHeaderField: HTTPWSHeader.hostName)
- return req
- }
-
- // generateWebSocketKey 16 random characters between a-z and return them as a base64 string
- public static func generateWebSocketKey() -> String {
- return Data((0..<16).map{ _ in UInt8.random(in: 97...122) }).base64EncodedString()
- }
- }
- public enum HTTPEvent {
- case success([String: String])
- case failure(Error)
- }
- public protocol HTTPHandlerDelegate: AnyObject {
- func didReceiveHTTP(event: HTTPEvent)
- }
- public protocol HTTPHandler {
- func register(delegate: HTTPHandlerDelegate)
- func convert(request: URLRequest) -> Data
- func parse(data: Data) -> Int
- }
- public protocol HTTPServerDelegate: AnyObject {
- func didReceive(event: HTTPEvent)
- }
- public protocol HTTPServerHandler {
- func register(delegate: HTTPServerDelegate)
- func parse(data: Data)
- func createResponse(headers: [String: String]) -> Data
- }
- public struct URLParts {
- let port: Int
- let host: String
- let isTLS: Bool
- }
- public extension URL {
- /// isTLSScheme returns true if the scheme is https or wss
- var isTLSScheme: Bool {
- guard let scheme = self.scheme else {
- return false
- }
- return HTTPWSHeader.defaultSSLSchemes.contains(scheme)
- }
-
- /// getParts pulls host and port from the url.
- func getParts() -> URLParts? {
- guard let host = self.host else {
- return nil // no host, this isn't a valid url
- }
- let isTLS = isTLSScheme
- var port = self.port ?? 0
- if self.port == nil {
- if isTLS {
- port = 443
- } else {
- port = 80
- }
- }
- return URLParts(port: port, host: host, isTLS: isTLS)
- }
- }
|