Browse Source

Added OpCode decoding (web sockets).
If a date frame from client does not have masking flag set or valid opcode value, the client's connection is closed.

Damian Kołakowski 10 năm trước cách đây
mục cha
commit
b47c00cf91

+ 6 - 4
Sources/DemoServer.swift

@@ -112,9 +112,11 @@ public func demoServer(publicDir: String?) -> HttpServer {
         })
     }
     
-    server["/websocket"] = HttpHandlers.websocket() {
-        print("new message: \($0)")
-    }
-
+    server["/websocket"] = HttpHandlers.websocket({ text in
+        print("Text message: \(text)")
+    }, { binary in
+        print("Binary message: \(binary)")
+    })
+    
     return server
 }

+ 3 - 2
Sources/HttpHandlers+Files.swift

@@ -27,7 +27,7 @@ extension HttpHandlers {
             if let rangeHeader = r.headers["range"] {
                 
                 guard rangeHeader.hasPrefix(HttpHandlers.rangePrefix) else {
-                    return HttpResponse.BadRequest
+                    return .BadRequest(.Text("Invalid value of 'Range' header: \(r.headers["range"])"))
                 }
                 
                 #if os(Linux)
@@ -35,10 +35,11 @@ extension HttpHandlers {
                 #else
                     let rangeString = rangeHeader.substringFromIndex(rangeHeader.startIndex.advancedBy(HttpHandlers.rangePrefix.characters.count))
                 #endif
+                
                 let rangeStringExploded = rangeString.split("-")
                 
                 guard rangeStringExploded.count == 2 else {
-                    return HttpResponse.BadRequest
+                    return .BadRequest(.Text("Invalid value of 'Range' header: \(r.headers["range"])"))
                 }
                 
                 let startStr = rangeStringExploded[0]

+ 47 - 19
Sources/HttpHandlers+WebSockets.swift

@@ -9,34 +9,42 @@ import Foundation
 
 extension HttpHandlers {
     
-    public class func websocket(handle:(String) -> ()) -> (HttpRequest -> HttpResponse) {
+    public class func websocket(text:(String -> Void)?, _ binary:([UInt8] -> Void)?) -> (HttpRequest -> HttpResponse) {
         return { r in
             guard r.headers["upgrade"] == "websocket" else {
-                return .BadRequest
+                return .BadRequest(.Text("Invalid value of 'Upgrade' header: \(r.headers["upgrade"])"))
             }
-            
             guard r.headers["connection"] == "Upgrade" else {
-                return .BadRequest
+                return .BadRequest(.Text("Invalid value of 'Connection' header: \(r.headers["connection"])"))
             }
             guard let secWebSocketKey = r.headers["sec-websocket-key"] else {
-                return .BadRequest
+                return .BadRequest(.Text("Invalid value of 'Sec-Websocket-Key' header: \(r.headers["sec-websocket-key"])"))
             }
-            let accept = String.encodeToBase64((secWebSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").SHA1())
-            let upgradeHeaders = [ "Upgrade": "WebSocket", "Connection": "Upgrade", "Sec-WebSocket-Accept": accept]
-            return HttpResponse.SwitchProtocols(upgradeHeaders) { socket  in
-                let parser = WebSocketParser()
-                while let frame = try? parser.readFrame(socket) {
-                    handle(String.fromUInt8(frame.payload))
+            let protocolSessionClosure: (Socket -> Void) = { socket in
+                let session = WebSocketSession(socket)
+                while let frame = try? session.readFrame(socket) {
+                    switch frame.opcode {
+                    case .TEXT:
+                        if let handleText = text {
+                            handleText(String.fromUInt8(frame.payload))
+                        }
+                    case .BINARY:
+                        if let handleBinary = binary {
+                            handleBinary(frame.payload)
+                        }
+                    default: break
+                    }
                 }
             }
+            let secWebSocketAccept = String.encodeToBase64((secWebSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").SHA1())
+            let headers = [ "Upgrade": "WebSocket", "Connection": "Upgrade", "Sec-WebSocket-Accept": secWebSocketAccept]
+            return HttpResponse.SwitchProtocols(headers, protocolSessionClosure)
         }
     }
     
-    public class WebSocketParser {
+    public class WebSocketSession {
         
-        enum Error: ErrorType {
-            case UnknownOpCode(String)
-        }
+        public enum Error: ErrorType { case UnknownOpCode(String), UnMaskedFrame }
         
         public enum OpCode { case CONTINUE, CLOSE, PING, PONG, TEXT, BINARY }
         
@@ -45,6 +53,20 @@ extension HttpHandlers {
             public var fin = false
             public var payload = [UInt8]()
         }
+
+        private let socket: Socket
+        
+        init(_ socket: Socket) {
+            self.socket = socket
+        }
+        
+        public func writeText(text: String) -> Void {
+            
+        }
+        
+        public func writeBinary(binary: [UInt8]) -> Void {
+            
+        }
         
         public func readFrame(socket: Socket) throws -> Frame {
             let frm = Frame()
@@ -58,12 +80,19 @@ extension HttpHandlers {
                 case 0x08: frm.opcode = OpCode.CLOSE
                 case 0x09: frm.opcode = OpCode.PING
                 case 0x0A: frm.opcode = OpCode.PONG
+                // "If an unknown opcode is received, the receiving endpoint MUST _Fail the WebSocket Connection_."
+                // http://tools.ietf.org/html/rfc6455#section-5.2 ( Page 29 )
                 default  : throw Error.UnknownOpCode("\(opc)")
             }
             let sec = try socket.read()
             let msk = sec & 0x0F != 0
+            guard msk else {
+                // "...a client MUST mask all frames that it sends to the serve.."
+                // http://tools.ietf.org/html/rfc6455#section-5.1
+                throw Error.UnMaskedFrame
+            }
             var len = UInt64(sec & 0x7F)
-            if len == 0x7E {1
+            if len == 0x7E {
                 let b0 = UInt64(try socket.read())
                 let b1 = UInt64(try socket.read())
                 len = UInt64(littleEndian: b0 << 8 | b1)
@@ -78,10 +107,9 @@ extension HttpHandlers {
                 let b7 = UInt64(try socket.read())
                 len = UInt64(littleEndian: b0 << 54 | b1 << 48 | b2 << 40 | b3 << 32 | b4 << 24 | b5 << 16 | b6 << 8 | b7)
             }
-            let mask = msk ? [try socket.read(), try socket.read(), try socket.read(), try socket.read()] : []
+            let mask = [try socket.read(), try socket.read(), try socket.read(), try socket.read()]
             for i in 0..<len {
-                let byte = try socket.read()
-                frm.payload.append(msk ? byte ^ mask[Int(i % 4)] : byte)
+                frm.payload.append(try socket.read() ^ mask[Int(i % 4)])
             }
             return frm
         }

+ 4 - 3
Sources/HttpResponse.swift

@@ -67,9 +67,9 @@ public enum HttpResponse {
     case SwitchProtocols([String: String], Socket -> Void)
     case OK(HttpResponseBody), Created, Accepted
     case MovedPermanently(String)
-    case BadRequest, Unauthorized, Forbidden, NotFound
+    case BadRequest(HttpResponseBody?), Unauthorized, Forbidden, NotFound
     case InternalServerError
-    case RAW(Int, String, [String:String]?, ((HttpResponseBodyWriter) -> Void)? )
+    case RAW(Int, String, [String:String]?, (HttpResponseBodyWriter -> Void)? )
     
     func statusCode() -> Int {
         switch self {
@@ -132,12 +132,13 @@ public enum HttpResponse {
     func content() -> (length: Int, writeClosure: ((HttpResponseBodyWriter) throws -> Void)?) {
         switch self {
         case .OK(let body)             : return body.content()
+        case .BadRequest(let body)     : return body?.content() ?? (-1, nil)
         case .RAW(_, _, _, let writer) : return (-1, writer)
         default                        : return (-1, nil)
         }
     }
     
-    func protocolHandler() -> (Socket -> Void)?  {
+    func socketSession() -> (Socket -> Void)?  {
         switch self {
         case SwitchProtocols(_, let handler) : return handler
         default: return nil

+ 2 - 2
Sources/HttpServerIO.swift

@@ -53,8 +53,8 @@ public class HttpServerIO {
                 print("Failed to send response: \(error)")
                 break
             }
-            if let handler = response.protocolHandler() {
-                handler(socket)
+            if let session = response.socketSession() {
+                session(socket)
                 break
             }
             if !keepConnection { break }

BIN
Swifter.xcodeproj/project.xcworkspace/xcuserdata/damiankolakowski.xcuserdatad/UserInterfaceState.xcuserstate


+ 19 - 35
Swifter.xcodeproj/xcuserdata/damiankolakowski.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist

@@ -1699,13 +1699,13 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Sources/HttpHandlers+WebSockets.swift"
-            timestampString = "475797167.424719"
+            timestampString = "475842615.4941"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "93"
-            endingLineNumber = "93"
-            landmarkName = "HttpHandlers"
-            landmarkType = "3">
+            startingLineNumber = "115"
+            endingLineNumber = "115"
+            landmarkName = "readFrame(_:)"
+            landmarkType = "5">
             <Locations>
                <Location
                   shouldBeEnabled = "No"
@@ -1747,11 +1747,11 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Sources/HttpHandlers+WebSockets.swift"
-            timestampString = "475797167.424719"
+            timestampString = "475842615.4941"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "99"
-            endingLineNumber = "99"
+            startingLineNumber = "121"
+            endingLineNumber = "121"
             landmarkName = "HttpHandlers"
             landmarkType = "3">
          </BreakpointContent>
@@ -1763,13 +1763,13 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Sources/HttpHandlers+WebSockets.swift"
-            timestampString = "475797167.424719"
+            timestampString = "475842615.4941"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "92"
-            endingLineNumber = "92"
-            landmarkName = "HttpHandlers"
-            landmarkType = "3">
+            startingLineNumber = "114"
+            endingLineNumber = "114"
+            landmarkName = "readFrame(_:)"
+            landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>
       <BreakpointProxy
@@ -1939,13 +1939,13 @@
             ignoreCount = "0"
             continueAfterRunningActions = "No"
             filePath = "Sources/HttpHandlers+WebSockets.swift"
-            timestampString = "475797167.424719"
+            timestampString = "475842615.4941"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "91"
-            endingLineNumber = "91"
-            landmarkName = "HttpHandlers"
-            landmarkType = "3">
+            startingLineNumber = "113"
+            endingLineNumber = "113"
+            landmarkName = "readFrame(_:)"
+            landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>
       <BreakpointProxy
@@ -2328,22 +2328,6 @@
             landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>
-      <BreakpointProxy
-         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
-         <BreakpointContent
-            shouldBeEnabled = "No"
-            ignoreCount = "0"
-            continueAfterRunningActions = "No"
-            filePath = "Sources/HttpHandlers+WebSockets.swift"
-            timestampString = "475797519.716601"
-            startingColumnNumber = "9223372036854775807"
-            endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "27"
-            endingLineNumber = "27"
-            landmarkName = "websocket(_:)"
-            landmarkType = "5">
-         </BreakpointContent>
-      </BreakpointProxy>
       <BreakpointProxy
          BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
          <BreakpointContent
@@ -2356,7 +2340,7 @@
             endingColumnNumber = "9223372036854775807"
             startingLineNumber = "13"
             endingLineNumber = "13"
-            landmarkName = "websocket(_:)"
+            landmarkName = "websocket(_:_:)"
             landmarkType = "5">
          </BreakpointContent>
       </BreakpointProxy>