From 8e34e0f13835a8f1bbd99aa905ecb4609b1e847e Mon Sep 17 00:00:00 2001 From: Arijit Das Date: Tue, 5 Jan 2021 18:29:06 +0530 Subject: [PATCH] Remove ws.nim file. --- src/ws.nim | 571 ----------------------------------------------------- 1 file changed, 571 deletions(-) delete mode 100644 src/ws.nim diff --git a/src/ws.nim b/src/ws.nim deleted file mode 100644 index a54554e..0000000 --- a/src/ws.nim +++ /dev/null @@ -1,571 +0,0 @@ -import chronos, chronicles, httputils, strutils, base64, std/sha1, random, - streams, nativesockets, uri, times, chronos.timer, tables - -const - MaxHttpHeadersSize = 8192 # maximum size of HTTP headers in octets - MaxHttpRequestSize = 128 * 1024 # maximum size of HTTP body in octets - HttpHeadersTimeout = timer.seconds(120) # timeout for receiving headers (120 sec) - HttpBodyTimeout = timer.seconds(12) # timeout for receiving body (12 sec) - HeadersMark = @[byte(0x0D), byte(0x0A), byte(0x0D), byte(0x0A)] - WebsocketUserAgent* = "nim-ws (https://github.com/status-im/nim-ws)" - -type - ReadyState* = enum - Connecting = 0 # The connection is not yet open. - Open = 1 # The connection is open and ready to communicate. - Closing = 2 # The connection is in the process of closing. - Closed = 3 # The connection is closed or couldn't be opened. - - WebSocket* = ref object - tcpSocket*: StreamTransport - version*: int - key*: string - protocol*: string - readyState*: ReadyState - masked*: bool # send masked packets - header*: HttpHeaders - - AsyncCallback = proc (transp: StreamTransport, - header: HttpRequestHeader): Future[void] {.closure, gcsafe.} - HttpServer* = ref object of StreamServer - callback: AsyncCallback - - ReqStatus = enum - Success, Error, ErrorFailure - - WebSocketError* = object of IOError - - HttpHeaders* = ref object - table*: TableRef[string, seq[string]] - -type - AsyncHttpClient* = ref object - connected: bool - currentURL: Url ## Where we are currently connected. - headers: seq[HttpHeader] ## Headers to send in requests. - userAgent: string - contentTotal: BiggestInt - contentProgress: BiggestInt - oneSecondProgress: BiggestInt - transp: StreamTransport - buf: seq[byte] - -template `[]`(value: uint8, index: int): bool = - ## Get bits from uint8, uint8[2] gets 2nd bit. - (value and (1 shl (7 - index))) != 0 - -proc nibbleFromChar(c: char): int = - ## Converts hex chars like `0` to 0 and `F` to 15. - case c: - of '0'..'9': (ord(c) - ord('0')) - of 'a'..'f': (ord(c) - ord('a') + 10) - of 'A'..'F': (ord(c) - ord('A') + 10) - else: 255 - -proc nibbleToChar(value: int): char = - ## Converts number like 0 to `0` and 15 to `fg`. - case value: - of 0..9: char(value + ord('0')) - else: char(value + ord('a') - 10) - -proc decodeBase16*(str: string): string = - ## Base16 decode a string. - result = newString(str.len div 2) - for i in 0 ..< result.len: - result[i] = chr( - (nibbleFromChar(str[2 * i]) shl 4) or - nibbleFromChar(str[2 * i + 1])) - -proc encodeBase16*(str: string): string = - ## Base61 encode a string. - result = newString(str.len * 2) - for i, c in str: - result[i * 2] = nibbleToChar(ord(c) shr 4) - result[i * 2 + 1] = nibbleToChar(ord(c) and 0x0f) - -proc genMaskKey(): array[4, char] = - ## Generates a random key of 4 random chars. - proc r(): char = char(rand(255)) - [r(), r(), r(), r()] - -proc handshake*(ws: WebSocket, header: HttpRequestHeader) {.async.} = - ## Handles the websocket handshake. - ws.version = parseInt(header["Sec-WebSocket-Version"]) - ws.key = header["Sec-WebSocket-Key"].strip() - if header.contains("Sec-WebSocket-Protocol"): - let wantProtocol = header["Sec-WebSocket-Protocol"].strip() - if ws.protocol != wantProtocol: - raise newException(WebSocketError, - "Protocol mismatch (expected: " & ws.protocol & ", got: " & - wantProtocol & ")") - - let - sh = secureHash(ws.key & "258EAFA5-E914-47DA-95CA-C5AB0DC85B11") - acceptKey = base64.encode(decodeBase16($sh)) - - var response = "HTTP/1.1 101 Web Socket Protocol Handshake\c\L" - response.add("Sec-WebSocket-Accept: " & acceptKey & "\c\L") - response.add("Connection: Upgrade\c\L") - response.add("Upgrade: webSocket\c\L") - - if ws.protocol != "": - response.add("Sec-WebSocket-Protocol: " & ws.protocol & "\c\L") - response.add "\c\L" - - discard await ws.tcpSocket.write(response) - ws.readyState = Open - -proc newWebSocket*(header: HttpRequestHeader, transp: StreamTransport, - protocol: string = ""): Future[WebSocket] {.async.} = - ## Creates a new socket from a request. - try: - if not header.contains("Sec-WebSocket-Version"): - raise newException(WebSocketError, "Invalid WebSocket handshake") - var ws = WebSocket() - ws.masked = false - ws.protocol = protocol - ws.tcpSocket = transp - await ws.handshake(header) - return ws - except ValueError, KeyError: - # Wrap all exceptions in a WebSocketError so its easy to catch. - raise newException( - WebSocketError, - "Failed to create WebSocket from request: " & getCurrentExceptionMsg() - ) - -type - Opcode* = enum - ## 4 bits. Defines the interpretation of the "Payload data". - Cont = 0x0 ## Denotes a continuation frame. - Text = 0x1 ## Denotes a text frame. - Binary = 0x2 ## Denotes a binary frame. - # 3-7 are reserved for further non-control frames. - Close = 0x8 ## Denotes a connection close. - Ping = 0x9 ## Denotes a ping. - Pong = 0xa ## Denotes a pong. - # B-F are reserved for further control frames. - - #[ - +---------------------------------------------------------------+ - |0 1 2 3 | - |0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1| - +-+-+-+-+-------+-+-------------+-------------------------------+ - |F|R|R|R| opcode|M| Payload len | Extended payload length | - |I|S|S|S| (4) |A| (7) | (16/64) | - |N|V|V|V| |S| | (if payload len==126/127) | - | |1|2|3| |K| | | - +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + - | Extended payload length continued, if payload len == 127 | - + - - - - - - - - - - - - - - - +-------------------------------+ - | |Masking-key, if MASK set to 1 | - +-------------------------------+-------------------------------+ - | Masking-key (continued) | Payload Data | - +-------------------------------- - - - - - - - - - - - - - - - + - : Payload Data continued ... : - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - | Payload Data continued ... | - +---------------------------------------------------------------+ - ]# - Frame = tuple - fin: bool ## Indicates that this is the final fragment in a message. - rsv1: bool ## MUST be 0 unless negotiated that defines meanings - rsv2: bool ## MUST be 0 - rsv3: bool ## MUST be 0 - opcode: Opcode ## Defines the interpretation of the "Payload data". - mask: bool ## Defines whether the "Payload data" is masked. - data: string ## Payload data - -proc encodeFrame(f: Frame): string = - ## Encodes a frame into a string buffer. - ## See https://tools.ietf.org/html/rfc6455#section-5.2 - - var ret = newStringStream() - - var b0 = (f.opcode.uint8 and 0x0f) # 0th byte: opcodes and flags. - if f.fin: - b0 = b0 or 128u8 - - ret.write(b0) - - # Payload length can be 7 bits, 7+16 bits, or 7+64 bits. - # 1st byte: payload len start and mask bit. - var b1 = 0u8 - - if f.data.len <= 125: - b1 = f.data.len.uint8 - elif f.data.len > 125 and f.data.len <= 0xffff: - b1 = 126u8 - else: - b1 = 127u8 - - if f.mask: - b1 = b1 or (1 shl 7) - - ret.write(uint8 b1) - - # Only need more bytes if data len is 7+16 bits, or 7+64 bits. - if f.data.len > 125 and f.data.len <= 0xffff: - # Data len is 7+16 bits. - ret.write(htons(f.data.len.uint16)) - elif f.data.len > 0xffff: - # Data len is 7+64 bits. - var len = f.data.len - ret.write char((len shr 56) and 255) - ret.write char((len shr 48) and 255) - ret.write char((len shr 40) and 255) - ret.write char((len shr 32) and 255) - ret.write char((len shr 24) and 255) - ret.write char((len shr 16) and 255) - ret.write char((len shr 8) and 255) - ret.write char(len and 255) - - var data = f.data - - if f.mask: - # If we need to mask it generate random mask key and mask the data. - let maskKey = genMaskKey() - for i in 0.. 0: - answer.add("Content-Type: application/json\r\n") - answer.add("Content-Length: " & $len(data) & "\r\n") - answer.add("\r\n") - if len(data) > 0: - answer.add(data) - try: - let res = await transp.write(answer) - if res != len(answer): - result = false - result = true - except: - result = false - -proc validateRequest(transp: StreamTransport, - header: HttpRequestHeader): Future[ReqStatus] {.async.} = - if header.meth notin {MethodGet}: - # Request method is either PUT or DELETE. - debug "GET method is only allowed", address = transp.remoteAddress() - if await transp.sendHTTPResponse(header.version, Http405): - result = Error - else: - result = ErrorFailure - return - - if header.contentLength() > MaxHttpRequestSize: - # request length is more then `MaxHttpRequestSize`. - debug "Maximum size of request body reached", - address = transp.remoteAddress() - if await transp.sendHTTPResponse(header.version, Http413): - result = Error - else: - result = ErrorFailure - return - - result = Success - -proc serveClient(server: StreamServer, transp: StreamTransport) {.async.} = - ## Process transport data to the RPC server - var httpServer = cast[HttpServer](server) - var buffer = newSeq[byte](MaxHttpHeadersSize) - var header: HttpRequestHeader - var connection: string - - info "Received connection", address = $transp.remoteAddress() - try: - let hlenfut = transp.readUntil(addr buffer[0], MaxHttpHeadersSize, HeadersMark) - let ores = await withTimeout(hlenfut, HttpHeadersTimeout) - if not ores: - # Timeout - debug "Timeout expired while receiving headers", - address = transp.remoteAddress() - let res = await transp.sendHTTPResponse(HttpVersion11, Http408) - await transp.closeWait() - return - else: - let hlen = hlenfut.read() - buffer.setLen(hlen) - header = buffer.parseRequest() - if header.failed(): - # Header could not be parsed - debug "Malformed header received", - address = transp.remoteAddress() - let res = await transp.sendHTTPResponse(HttpVersion11, Http400) - await transp.closeWait() - return - except TransportLimitError: - # size of headers exceeds `MaxHttpHeadersSize` - debug "Maximum size of headers limit reached", - address = transp.remoteAddress() - let res = await transp.sendHTTPResponse(HttpVersion11, Http413) - await transp.closeWait() - return - except TransportIncompleteError: - # remote peer disconnected - debug "Remote peer disconnected", address = transp.remoteAddress() - await transp.closeWait() - return - except TransportOsError as exc: - debug "Problems with networking", address = transp.remoteAddress(), - error = exc.msg - await transp.closeWait() - return - except CatchableError as exc: - debug "Unknown exception", address = transp.remoteAddress(), - error = exc.msg - await transp.closeWait() - return - - let vres = await validateRequest(transp, header) - if vres == Success: - trace "Received valid RPC request", address = $transp.remoteAddress() - - # Call the user's callback. - if httpServer.callback != nil: - await httpServer.callback(transp, header) - await transp.closeWait() - elif vres == ErrorFailure: - debug "Remote peer disconnected", address = transp.remoteAddress() - await transp.closeWait() - -proc newHttpServer*(address: string, handler: AsyncCallback, - flags: set[ServerFlags] = {ReuseAddr}): HttpServer = - new result - let address = initTAddress(address) - result.callback = handler - result = cast[HttpServer](createStreamServer(address, serveClient, flags, - child = cast[StreamServer](result))) - -func toTitleCase(s: string): string = - result = newString(len(s)) - var upper = true - for i in 0..len(s) - 1: - result[i] = if upper: toUpperAscii(s[i]) else: toLowerAscii(s[i]) - upper = s[i] == '-' - -func toCaseInsensitive*(headers: HttpHeaders, s: string): string {.inline.} = - return toTitleCase(s) - -func newHttpHeaders*(): HttpHeaders = - ## Returns a new ``HttpHeaders`` object. if ``titleCase`` is set to true, - ## headers are passed to the server in title case (e.g. "Content-Length") - new result - result.table = newTable[string, seq[string]]() - -func newHttpHeaders*(keyValuePairs: - openArray[tuple[key: string, val: string]]): HttpHeaders = - ## Returns a new ``HttpHeaders`` object from an array. if ``titleCase`` is set to true, - ## headers are passed to the server in title case (e.g. "Content-Length") - new result - result.table = newTable[string, seq[string]]() - - for pair in keyValuePairs: - let key = result.toCaseInsensitive(pair.key) - {.cast(noSideEffect).}: - if key in result.table: - result.table[key].add(pair.val) - else: - result.table[key] = @[pair.val] - -proc newAsyncWebsocketClient*(uri: Uri, protocols: seq[string] = @[]): Future[WebSocket] {.async.} = - let - keyDec = align( - when declared(toUnix): - $getTime().toUnix - else: - $getTime().toSeconds.int64, 16, '#') - key = encode(keyDec) - - var uri = uri - case uri.scheme - of "ws": - uri.scheme = "http" - of "wss": - uri.scheme = "https" - else: - raise newException(WebSocketError, "uri scheme has to be " & - "'ws' for plaintext or 'wss' for websocket over ssl.") - - var headers = newHttpHeaders({ - "Connection": "Upgrade", - "Upgrade": "websocket", - "Cache-Control": "no-cache", - "Sec-WebSocket-Version": "13", - "Sec-WebSocket-Key": key - }) - - new(result) - -proc newAsyncWebsocketClient*(host: string, port: Port, path: string, - protocols: seq[string] = @[]): Future[WebSocket] = - let uri = "ws://" & host & ":" & $port & "/" & path - result = newAsyncWebsocketClient(parseUri(uri), protocols) -