fix connection and validation of server responce (#11)

* fix connection and validation of server responce

* fix typo, cleanup

* fix typo, better validation

* more valid validation ;)
This commit is contained in:
Volodymyr Melnychuk 2019-12-18 12:17:44 +02:00 committed by GitHub
parent c178f8c628
commit 09af7f0d97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,5 +1,5 @@
import strutils, streams, random, base64, uri, strformat, nativesockets, oids,
strtabs, std/sha1, net
strtabs, std/sha1, net, httpcore
when not declaredInScope(newsUseChronos):
# Currently chronos is second class citizen. To use this library in chronos-based
@ -9,7 +9,7 @@ when not declaredInScope(newsUseChronos):
const newsUseChronos = false
when newsUseChronos:
import chronos, chronos/streams/[asyncstream, tlsstream], httpcore
import chronos, chronos/streams/[asyncstream, tlsstream]
type Transport = object
reader: AsyncStreamReader
writer: AsyncStreamWriter
@ -36,6 +36,8 @@ else:
import httpcore, asyncdispatch, asyncnet, asynchttpserver
type Transport = AsyncSocket
const CRLF = "\c\l"
const GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
type
ReadyState* = enum
@ -118,23 +120,55 @@ when not newsUseChronos:
if req.headers.hasKey("sec-webSocket-protocol"):
ws.protocol = req.headers["sec-webSocket-protocol"].strip()
let sh = secureHash(ws.key & "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
let sh = secureHash(ws.key & GUID)
let acceptKey = base64.encode(decodeBase16($sh))
var responce = "HTTP/1.1 101 Web Socket Protocol Handshake\c\L"
responce.add("Sec-WebSocket-Accept: " & acceptKey & "\c\L")
responce.add("Connection: Upgrade\c\L")
responce.add("Upgrade: webSocket\c\L")
var response = "HTTP/1.1 101 Web Socket Protocol Handshake" & CRLF
response.add("Sec-WebSocket-Accept: " & acceptKey & CRLF)
response.add("Connection: Upgrade" & CRLF)
response.add("Upgrade: webSocket" & CRLF)
if not ws.protocol.len == 0:
responce.add("Sec-WebSocket-Protocol: " & ws.protocol & "\c\L")
responce.add "\c\L"
response.add("Sec-WebSocket-Protocol: " & ws.protocol & CRLF)
response.add CRLF
ws.transp = req.client
# await ws.transp.connect(uri.hostname, port)
await ws.transp.send(responce)
await ws.transp.send(response)
ws.readyState = Open
return ws
proc validateServerResponse(resp, secKey: string): string =
let respLines = resp.splitLines()
block statusCode:
const k = "HTTP/1.1 "
let i = respLines[0].find(k) + k.len
let v = respLines[0][i .. i + 2]
if v != "101":
return respLines[0][i ..< respLines[0].len]
var validatedHeaders: array[3, bool]
for i in 1 ..< respLines.len:
let h = parseHeader(respLines[i])
if h.key == "Upgrade":
if h.value[0].toLowerAscii != "websocket":
return "Upgrade header is invalid"
validatedHeaders[0] = true
elif h.key == "Connection":
if h.value[0].toLowerAscii != "upgrade":
return "Connection header is invalid"
validatedHeaders[1] = true
elif h.key == "Sec-WebSocket-Accept":
let sh = decodeBase16($secureHash(secKey & GUID))
if h.value[0].toLowerAscii != base64.encode(sh).toLowerAscii:
return "Secret key invalid"
validatedHeaders[2] = true
if not validatedHeaders[0]: return "Missing Upgrade header"
if not validatedHeaders[1]: return "Missing Connection header"
if not validatedHeaders[2]: return "Missing Sec-WebSocket-Accept header"
proc newWebSocket*(url: string, headers: StringTableRef = nil,
sslContext: SSLContext = getDefaultSslContext()): Future[WebSocket] {.async.} =
## Creates a client
@ -170,8 +204,12 @@ proc newWebSocket*(url: string, headers: StringTableRef = nil,
raise newException(WebSocketError, "SSL support is not available. Compile with -d:ssl to enable.")
await ws.transp.connect(uri.hostname, port)
var urlPath = uri.path
if uri.query.len > 0:
urlPath.add("?" & uri.query)
let secKey = encode($genOid())[16..^1]
let requestLine = &"GET {url} HTTP/1.1"
let requestLine = &"GET {urlPath} HTTP/1.1"
let predefinedHeaders = [
&"Host: {uri.hostname}:{$port}",
"Connection: Upgrade",
@ -179,7 +217,7 @@ proc newWebSocket*(url: string, headers: StringTableRef = nil,
"Sec-WebSocket-Version: 13",
&"Sec-WebSocket-Key: {secKey}"
]
const CRLF = "\c\l"
var customHeaders = ""
if not headers.isNil:
for k, v in headers:
@ -195,7 +233,9 @@ proc newWebSocket*(url: string, headers: StringTableRef = nil,
while not output.endsWith(static(CRLF & CRLF)):
output.add await ws.transp.recv(1)
# TODO: Validate server reply
let error = validateServerResponse(output, secKey)
if error.len > 0:
raise newException(WebSocketError, "WebSocket connection error: " & error)
ws.readyState = Open
ws.maskFrames = true