Skip to content

Commit

Permalink
Rewrite byte parsing in full JS without depending on parseInt
Browse files Browse the repository at this point in the history
The parseInt function is full of surprises. Instead of trying to find
all its pitfalls, let's parse bytes manually.
  • Loading branch information
rs committed Mar 29, 2021
1 parent 6a3169c commit 3f19a05
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 17 deletions.
64 changes: 47 additions & 17 deletions lib/netmask.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,57 @@ long2ip = (long) ->
return [a, b, c, d].join('.')

ip2long = (ip) ->
b = (ip + '').split('.');
if b.length is 0 or b.length > 4 then throw new Error('Invalid IP')
for byte, i in b
if byte and byte[0] == '0'
if byte.length > 2 and (byte[1] == 'x' or byte[1] == 'x')
# make sure 0x prefixed bytes are parsed as hex
byte = parseInt(byte, 16)
else
# make sure 0 prefixed bytes are parsed as octal
byte = parseInt(byte, 8)
else if byte and (byte[0] == ' ' or byte[byte.length-1] == ' ')
throw new Error('Invalid IP')
else
byte = parseInt(byte, 10)
if isNaN(byte) then throw new Error("Invalid byte: #{byte}")
if byte < 0 or byte > 255 then throw new Error("Invalid byte: #{byte}")
b[i] = byte
b = []
for i in [0..3]
if ip.length == 0
break
if i > 0
if ip[0] != '.'
throw new Error('Invalid IP')
ip = ip.substring(1)
[n, c] = atob(ip)
ip = ip.substring(c)
b.push(n)
if ip.length != 0
throw new Error('Invalid IP')
while b.length < 4
b.unshift(0)
return (b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3]) >>> 0

atob = (s) ->
n = 0
base = 10
dmax = '9'
i = 0
if s.length > 1 and s[i] == '0'
if s[i+1] == 'x' or s[i+1] == 'X'
i+=2
base = 16
else if '0' <= s[i+1] and s[i+1] <= '7'

This comment has been minimized.

Copy link
@d-hat

d-hat Mar 31, 2021

Is it intentional that '08' is parsed as decimal but '018' is an error?

This comment has been minimized.

Copy link
@rs

rs Mar 31, 2021

Author Owner

It is not, good catch

i++
base = 8
dmax = '7'
start = i
chr = (b) -> return b.charCodeAt(0)
while s.length > 0
if '0' <= s[i] and s[i] <= dmax
n = n*base + (chr(s[i])-chr('0'))
else if base == 16
if 'a' <= s[i] and s[i] <= 'f'
n = n*base + (10+chr(s[i])-chr('a'))
else if 'A' <= s[i] and s[i] <= 'F'
n = n*base + (10+chr(s[i])-chr('A'))
else
break
else
break
if n > 0xFF
throw new Error('byte overflow')
i++
if i == start
throw new Error('empty octet')
return [n, i]

class Netmask
constructor: (net, mask) ->
throw new Error("Missing `net' parameter") unless typeof net is 'string'
Expand Down
3 changes: 3 additions & 0 deletions test/badnets.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,14 @@ vows.describe('IPs with bytes greater than 255')
vows.describe('Invalid IP format')
.addBatch
' 1.2.3.4': shouldFailWithError 'Invalid net'
' 1.2.3.4': shouldFailWithError 'Invalid net'
'1. 2.3.4': shouldFailWithError 'Invalid net'
'1.2. 3.4': shouldFailWithError 'Invalid net'
'1.2.3. 4': shouldFailWithError 'Invalid net'
'1.2.3.4 ': shouldFailWithError 'Invalid net'
'1 .2.3.4': shouldFailWithError 'Invalid net'
'018.0.0.0': shouldFailWithError 'Invalid net'
'0xfg.0.0.0': shouldFailWithError 'Invalid net'
.export(module)

vows.describe('Ranges that are a power-of-two big, but are not legal blocks')
Expand Down

0 comments on commit 3f19a05

Please sign in to comment.