Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions lib/parse3.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 34 additions & 10 deletions lib/schema3.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 9 additions & 3 deletions src/parse3.iced
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ exports.is_kid = (h) -> is_hex(h,35)
exports.is_int = is_int = (s) ->
n = Math.floor Number s
return typeof(s) is 'number' and (n isnt Infinity) and (n is s) and n >= 0
exports.decode_hex = decode_hex = (s) ->
return null unless typeof(s) is 'string'
buf = Buffer.from s, 'hex'
return null unless buf.toString('hex') is s.toLowerCase()
buf
exports.is_hex = is_hex = (h, l) ->
return false unless h?
if typeof(h) is 'string' then h = Buffer.from(h, 'hex')
else if not Buffer.isBuffer then return false
if typeof(h) is 'string'
return false unless (h = decode_hex(h))?
else if not Buffer.isBuffer(h) then return false
return (h.length is l)
exports.is_seqno = (s) ->
return false unless s?
Expand Down Expand Up @@ -75,6 +81,6 @@ exports.is_chain_type = (x) ->
exports.unhex = (b) ->
if not b? then null
else if Buffer.isBuffer(b) then b
else if typeof(b) is 'string' then Buffer.from(b, 'hex')
else if typeof(b) is 'string' and (h = decode_hex(b))? then h
else throw new Error "bad binary or hex string"

32 changes: 25 additions & 7 deletions src/schema3.iced
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@ parse = require './parse3'

mkerr = (path, err) -> new Error "At #{path.toString()}: #{err}"

# Native hasOwnProperty, captured so schema/input keys named
# "hasOwnProperty" cannot shadow the method. Use instead of obj[k] or
# obj.hasOwnProperty(k) — those see Object.prototype (constructor, toString, …).
_has_own = {}.constructor.prototype.hasOwnProperty
has_own = (o, k) -> _has_own.call o, k

# __proto__ is not a data key. `{ __proto__: x }` and `obj["__proto__"] = x`
# set [[Prototype]] instead of an own property. JSON.parse may store it as an
# own key; msgpack unpack may throw. Ban it as a schema and payload key.
# constructor, toString, hasOwnProperty, etc. are allowed; has_own treats them
# as own keys.
is_reserved_key_name = (k) -> k is '__proto__'

class Path
constructor : (v) ->
@_v = v or []
Expand Down Expand Up @@ -33,11 +46,13 @@ class Node

_check_value : ({checker, path, obj}) ->
if not obj? and checker.is_optional() then return null
if not obj? then mkerr path, "value cannot be null"
if not obj? then return mkerr path, "value cannot be null"
return checker._check { path, obj }

class Dict extends Node
constructor : ({keys}) ->
for k of keys
if is_reserved_key_name k then throw new Error "schema key name is not allowed: #{k}"
@_keys = keys
# do not fail if there are extra keys unknown to schema
@_allow_extra_keys = false
Expand All @@ -48,22 +63,24 @@ class Dict extends Node
return mkerr path, "need a dictionary"
for k,v of obj
new_path = path.extend(k)
if not (checker = @_keys[k])?
if is_reserved_key_name k then return mkerr new_path, "key name is not allowed"
if not (has_own(@_keys, k) and (checker = @_keys[k])?)
if @_allow_extra_keys then continue
return mkerr new_path, "key is not supported"
if (err = @_check_value { checker, path : new_path, obj : v }) then return err
for k,v of @_keys
new_path = path.extend(k)
if not obj[k]? and not v.is_optional() then return mkerr new_path, "key is missing but is mandatory"
if not has_own(obj, k) and not v.is_optional() then return mkerr new_path, "key is missing but is mandatory"
return null

debug_localize : (obj) ->
ret = {}
for k,v of @_keys when obj[k]?
for k,v of @_keys when has_own obj, k
ret[v._name or k] = v.debug_localize obj[k]
ret

set_key : (k,v) ->
if is_reserved_key_name k then throw new Error "schema key name is not allowed: #{k}"
@_keys[k] = v

allow_extra_keys : () ->
Expand Down Expand Up @@ -125,7 +142,7 @@ class Binary extends Node

_convert_and_check : ({path, obj}) ->
if @_convert and typeof(obj) is 'string'
obj = Buffer.from(obj, 'hex')
obj = parse.decode_hex(obj)
unless Buffer.isBuffer(obj) and obj.length is @_len
return [ (mkerr path, "value needs to be buffer of length #{@_len}"), null ]
if @_bottom_bytes?
Expand Down Expand Up @@ -186,10 +203,11 @@ class StringEnum extends Node
constructor : ({values}) ->
@_values = {}
for v in values
if is_reserved_key_name v then throw new Error "enum value is not allowed: #{v}"
@_values[v] = true
_check : ({path, obj}) ->
if typeof(obj) isnt 'string' then return mkerr path, "value must be a string"
if not @_values[obj] then return mkerr path, "unknown enum value (#{obj})"
if not has_own(@_values, obj) then return mkerr path, "unknown enum value (#{obj})"
return null

class Value extends Node
Expand Down Expand Up @@ -218,7 +236,7 @@ class Or extends Node
@_terms = terms
_check : ({path, obj}) ->
ok = false
for t in @_terms when t.check(obj)
for t in @_terms when not t.check(obj)
ok = true
break
if not ok then return mkerr path, "no structure worked"
Expand Down
51 changes: 51 additions & 0 deletions test/files/parse3.iced
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
parse3 = require '../../lib/parse3'
schema = require '../../lib/schema3'

exports.hex_rejects_trailing_junk = (T, cb) ->
uid = "00".repeat(15) + "19"
hash = "00".repeat(32)

uid_upper = "AA".repeat(15) + "19"

T.assert parse3.is_hex(uid, 16), "valid uid hex is accepted"
T.assert parse3.is_hex(uid_upper, 16), "uppercase uid hex is accepted"
T.assert not parse3.is_hex(uid + "zz", 16), "uid hex plus junk is rejected"
T.assert not parse3.is_hex(uid + "a", 16), "odd-length uid hex is rejected"
T.assert not parse3.is_hex(hash + "nothex", 32), "hash hex plus junk is rejected"

T.assert not parse3.is_uid(hash), "length has to match"

err = schema.uid().convert().check uid
T.assert not err?, "valid uid converts"

err = schema.uid().convert().check uid_upper
T.assert not err?, "uppercase uid converts"

err = schema.uid().convert().check hash
T.assert err?, "length has to match"

err = schema.uid().convert().check uid + "zz"
T.assert err?, "uid with trailing junk is rejected"

err = schema.uid().convert().check uid + "a"
T.assert err?, "odd-length uid is rejected"

err = schema.hash().convert().check hash + "zz"
T.assert err?, "hash with trailing junk is rejected"

out = parse3.unhex uid
T.assert Buffer.isBuffer(out) and out.length is 16, "unhex accepts valid uid"

out = parse3.unhex uid_upper
T.assert Buffer.isBuffer(out) and out.length is 16, "unhex accepts uppercase uid"

for bad in [uid + "zz", uid + "a"]
err = null
try
parse3.unhex bad
catch e
err = e
T.assert err?, "unhex rejects #{bad}"
T.assert err.toString().indexOf("bad binary or hex string") >= 0, "unhex error message"

cb null
Loading