parent
64da1a4344
commit
1fed598d8c
|
@ -0,0 +1,269 @@
|
||||||
|
## nim-ws
|
||||||
|
## Copyright (c) 2021 Status Research & Development GmbH
|
||||||
|
## Licensed under either of
|
||||||
|
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||||
|
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||||
|
## at your option.
|
||||||
|
## This file may not be copied, modified, or distributed except according to
|
||||||
|
## those terms.
|
||||||
|
|
||||||
|
import
|
||||||
|
pkg/[asynctest, chronos],
|
||||||
|
../ws/ext_utils
|
||||||
|
|
||||||
|
suite "extension parser":
|
||||||
|
test "single extension":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("permessage-deflate", app)
|
||||||
|
check res == true
|
||||||
|
check app.len == 1
|
||||||
|
if app.len == 1:
|
||||||
|
check app[0].name == "permessage-deflate"
|
||||||
|
|
||||||
|
test "single extension quoted bad syntax":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("\"zip\"", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "basic extensions no param":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("permessage-deflate, snappy, bzip", app)
|
||||||
|
check res == true
|
||||||
|
check app.len == 3
|
||||||
|
if app.len == 3:
|
||||||
|
check app[0].name == "permessage-deflate"
|
||||||
|
check app[1].name == "snappy"
|
||||||
|
check app[2].name == "bzip"
|
||||||
|
|
||||||
|
test "basic extensions no param bad syntax":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("permessage-deflate, ", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "basic extensions no param with trailing leading whitespaces":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt(" permessage-deflate, snappy, bzip ", app)
|
||||||
|
check res == true
|
||||||
|
check app.len == 3
|
||||||
|
if app.len == 3:
|
||||||
|
check app[0].name == "permessage-deflate"
|
||||||
|
check app[1].name == "snappy"
|
||||||
|
check app[2].name == "bzip"
|
||||||
|
|
||||||
|
test "basic extension with params":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("snappy; arg1noval; arg2 = 123; arg3 = \"hello\"", app)
|
||||||
|
check res == true
|
||||||
|
check app.len == 1
|
||||||
|
if app.len == 1:
|
||||||
|
check app[0].name == "snappy"
|
||||||
|
check app[0].params[0].name == "arg1noval"
|
||||||
|
check app[0].params[0].value == ""
|
||||||
|
|
||||||
|
check app[0].params[1].name == "arg2"
|
||||||
|
check app[0].params[1].value == "123"
|
||||||
|
|
||||||
|
check app[0].params[2].name == "arg3"
|
||||||
|
check app[0].params[2].value == "hello"
|
||||||
|
|
||||||
|
test "basic extension with param + fallback":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("snappy; arg = 123, snappy", app)
|
||||||
|
check res == true
|
||||||
|
check app.len == 2
|
||||||
|
if app.len == 2:
|
||||||
|
check app[0].name == "snappy"
|
||||||
|
check app[1].name == "snappy"
|
||||||
|
|
||||||
|
check app[0].params[0].name == "arg"
|
||||||
|
check app[0].params[0].value == "123"
|
||||||
|
|
||||||
|
test "extension param no value + fallback":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("snappy; arg, snappy", app)
|
||||||
|
check res == true
|
||||||
|
check app.len == 2
|
||||||
|
if app.len == 2:
|
||||||
|
check app[0].name == "snappy"
|
||||||
|
check app[1].name == "snappy"
|
||||||
|
|
||||||
|
check app[0].params[0].name == "arg"
|
||||||
|
check app[0].params[0].value == ""
|
||||||
|
|
||||||
|
test "extension param no value + fallback bad syntax":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("snappy; arg = , snappy", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "extension param no value + fallback bad syntax":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("snappy; arg = , snappy", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "extensions bad syntax":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("snappy; snappy; ", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "extension bad syntax":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("snappy; ", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "extension param no value bad syntax":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("snappy; arg = ", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "extension param no value":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("snappy; arg", app)
|
||||||
|
check res == true
|
||||||
|
check app.len == 1
|
||||||
|
if app.len == 1:
|
||||||
|
check app[0].name == "snappy"
|
||||||
|
check app[0].params[0].name == "arg"
|
||||||
|
check app[0].params[0].value == ""
|
||||||
|
|
||||||
|
test "extension param not closed quoted value":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("snappy; arg = \"wwww", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "inlwithasciifilename":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("inline; filename=\"foo.html\"", app)
|
||||||
|
check res == true
|
||||||
|
check app[0].params[0].value == "foo.html"
|
||||||
|
|
||||||
|
test "inlwithfnattach":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("inline; filename=\"Not an attachment!\"", app)
|
||||||
|
check res == true
|
||||||
|
check app[0].params[0].value == "Not an attachment!"
|
||||||
|
|
||||||
|
test "attwithasciifnescapedchar":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("attachment; filename=\"f\\oo.html\"", app)
|
||||||
|
check res == true
|
||||||
|
check app[0].params[0].value == "foo.html"
|
||||||
|
|
||||||
|
test "attwithasciifnescapedquote":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("attachment; filename=\"\\\"quoting\\\" tested.html\"", app)
|
||||||
|
check res == true
|
||||||
|
check app[0].params[0].value == "\"quoting\" tested.html"
|
||||||
|
|
||||||
|
test "attwithquotedsemicolon":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("attachment; filename=\"Here's a semicolon;.html\"", app)
|
||||||
|
check res == true
|
||||||
|
check app[0].params[0].value == "Here's a semicolon;.html"
|
||||||
|
|
||||||
|
test "attwithfilenameandextparamescaped":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("attachment; foo=\"\\\"\\\\\"", app)
|
||||||
|
check res == true
|
||||||
|
check app[0].params[0].value == "\"\\"
|
||||||
|
|
||||||
|
test "attwithasciifilenamenq":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("attachment; filename=foo.html", app)
|
||||||
|
check res == true
|
||||||
|
check app[0].params[0].value == "foo.html"
|
||||||
|
|
||||||
|
test "attemptyparam":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("attachment; ;filename=foo", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "attwithasciifilenamenqws":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("attachment; filename=foo bar.html", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "attwithfntokensq":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("attachment; filename='foo.bar'", app)
|
||||||
|
check res == true
|
||||||
|
check app[0].params[0].value == "'foo.bar'"
|
||||||
|
|
||||||
|
test "attfnbrokentoken":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("attachment; filename=foo[1](2).html", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "attfnbrokentokeniso":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("attachment; filename=foo-ä.html", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "attfnbrokentokenutf":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("attachment; filename=foo-ä.html", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "attmissingdisposition":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("filename=foo.html", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "attmissingdisposition2":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("x=y; filename=foo.html", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "attmissingdisposition3":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("\"foo; filename=bar;baz\"; filename=qux", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "attmissingdisposition4":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("filename=foo.html, filename=bar.html", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "emptydisposition":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt(" ; filename=foo.html", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "doublecolon":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt(": inline; attachment; filename=foo.html", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "attbrokenquotedfn":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt(" attachment; filename=\"foo.html\".txt", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "attbrokenquotedfn2":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("attachment; filename=\"bar", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "attbrokenquotedfn3":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("attachment; filename=foo\"bar;baz\"qux", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "attmissingdelim":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("attachment; foo=foo filename=bar", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "attmissingdelim2":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("attachment; filename=bar foo=foo", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "attmissingdelim3":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("attachment filename=bar", app)
|
||||||
|
check res == false
|
||||||
|
|
||||||
|
test "attreversed":
|
||||||
|
var app: seq[AppExt]
|
||||||
|
let res = parseExt("filename=foo.html; attachment", app)
|
||||||
|
check res == false
|
|
@ -2,3 +2,4 @@
|
||||||
|
|
||||||
import ./testframes
|
import ./testframes
|
||||||
import ./testutf8
|
import ./testutf8
|
||||||
|
import ./test_ext_utils
|
||||||
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
## nim-ws
|
||||||
|
## Copyright (c) 2021 Status Research & Development GmbH
|
||||||
|
## Licensed under either of
|
||||||
|
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||||
|
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||||
|
## at your option.
|
||||||
|
## This file may not be copied, modified, or distributed except according to
|
||||||
|
## those terms.
|
||||||
|
|
||||||
|
import
|
||||||
|
std/strutils,
|
||||||
|
pkg/httputils
|
||||||
|
|
||||||
|
type
|
||||||
|
ExtParam* = object
|
||||||
|
name* : string
|
||||||
|
value*: string
|
||||||
|
|
||||||
|
AppExt* = object
|
||||||
|
name* : string
|
||||||
|
params*: seq[ExtParam]
|
||||||
|
|
||||||
|
TokenKind = enum
|
||||||
|
tkError
|
||||||
|
tkSemcol
|
||||||
|
tkComma
|
||||||
|
tkEqual
|
||||||
|
tkName
|
||||||
|
tkQuoted
|
||||||
|
tkEof
|
||||||
|
|
||||||
|
Lexer = object
|
||||||
|
pos: int
|
||||||
|
token: string
|
||||||
|
tok: TokenKind
|
||||||
|
|
||||||
|
const
|
||||||
|
WHITES = {' ', '\t'}
|
||||||
|
LCHAR = {'a'..'z', 'A'..'Z', '-', '_', '0'..'9','.','\''}
|
||||||
|
SEPARATORS = {'`','~','!','@','#','$','%','^','&','*','(',')','+','=',
|
||||||
|
'[','{',']','}', ';',':','\'',',','<','.','>','/','?','|'}
|
||||||
|
QCHAR = WHITES + LCHAR + SEPARATORS
|
||||||
|
|
||||||
|
proc parseName[T: BChar](lex: var Lexer, data: openarray[T]) =
|
||||||
|
while lex.pos < data.len:
|
||||||
|
let cc = data[lex.pos]
|
||||||
|
if cc notin LCHAR:
|
||||||
|
break
|
||||||
|
lex.token.add cc
|
||||||
|
inc lex.pos
|
||||||
|
|
||||||
|
proc parseQuoted[T: BChar](lex: var Lexer, data: openarray[T]) =
|
||||||
|
while lex.pos < data.len:
|
||||||
|
let cc = data[lex.pos]
|
||||||
|
case cc:
|
||||||
|
of QCHAR:
|
||||||
|
lex.token.add cc
|
||||||
|
inc lex.pos
|
||||||
|
of '\\':
|
||||||
|
inc lex.pos
|
||||||
|
if lex.pos >= data.len:
|
||||||
|
lex.tok = tkError
|
||||||
|
return
|
||||||
|
lex.token.add data[lex.pos]
|
||||||
|
inc lex.pos
|
||||||
|
of '\"':
|
||||||
|
inc lex.pos
|
||||||
|
lex.tok = tkQuoted
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
lex.tok = tkError
|
||||||
|
return
|
||||||
|
|
||||||
|
lex.tok = tkError
|
||||||
|
|
||||||
|
proc next[T: BChar](lex: var Lexer, data: openarray[T]) =
|
||||||
|
while lex.pos < data.len:
|
||||||
|
if data[lex.pos] notin WHITES:
|
||||||
|
break
|
||||||
|
inc lex.pos
|
||||||
|
lex.token.setLen(0)
|
||||||
|
|
||||||
|
if lex.pos >= data.len:
|
||||||
|
lex.tok = tkEof
|
||||||
|
return
|
||||||
|
|
||||||
|
let c = data[lex.pos]
|
||||||
|
case c
|
||||||
|
of ';':
|
||||||
|
inc lex.pos
|
||||||
|
lex.tok = tkSemcol
|
||||||
|
return
|
||||||
|
of ',':
|
||||||
|
inc lex.pos
|
||||||
|
lex.tok = tkComma
|
||||||
|
return
|
||||||
|
of '=':
|
||||||
|
inc lex.pos
|
||||||
|
lex.tok = tkEqual
|
||||||
|
return
|
||||||
|
of LCHAR:
|
||||||
|
lex.parseName(data)
|
||||||
|
lex.tok = tkName
|
||||||
|
return
|
||||||
|
of '\"':
|
||||||
|
inc lex.pos
|
||||||
|
lex.parseQuoted(data)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
lex.tok = tkError
|
||||||
|
return
|
||||||
|
|
||||||
|
proc parseExt*[T: BChar](data: openarray[T], output: var seq[AppExt]): bool =
|
||||||
|
var lex: Lexer
|
||||||
|
var ext: AppExt
|
||||||
|
lex.next(data)
|
||||||
|
|
||||||
|
while lex.tok notin {tkEof, tkError}:
|
||||||
|
if lex.tok != tkName:
|
||||||
|
return false
|
||||||
|
ext.name = system.move(lex.token)
|
||||||
|
|
||||||
|
lex.next(data)
|
||||||
|
var param: ExtParam
|
||||||
|
while lex.tok == tkSemCol:
|
||||||
|
lex.next(data)
|
||||||
|
if lex.tok in {tkEof, tkError}:
|
||||||
|
return false
|
||||||
|
if lex.tok != tkName:
|
||||||
|
return false
|
||||||
|
param.name = system.move(lex.token)
|
||||||
|
lex.next(data)
|
||||||
|
if lex.tok == tkEqual:
|
||||||
|
lex.next(data)
|
||||||
|
if lex.tok notin {tkName, tkQuoted}:
|
||||||
|
return false
|
||||||
|
param.value = system.move(lex.token)
|
||||||
|
lex.next(data)
|
||||||
|
ext.params.setLen(ext.params.len + 1)
|
||||||
|
ext.params[^1].name = system.move(param.name)
|
||||||
|
ext.params[^1].value = system.move(param.value)
|
||||||
|
|
||||||
|
if lex.tok notin {tkSemCol, tkComma, tkEof}:
|
||||||
|
return false
|
||||||
|
|
||||||
|
output.setLen(output.len + 1)
|
||||||
|
output[^1].name = system.move(ext.name)
|
||||||
|
output[^1].params = system.move(ext.params)
|
||||||
|
|
||||||
|
if lex.tok == tkEof:
|
||||||
|
return true
|
||||||
|
|
||||||
|
if lex.tok == tkComma:
|
||||||
|
lex.next(data)
|
||||||
|
if lex.tok != tkName:
|
||||||
|
return false
|
||||||
|
continue
|
||||||
|
|
||||||
|
lex.tok != tkError
|
Loading…
Reference in New Issue