snowflake/proxy/util.coffee

160 lines
4.6 KiB
CoffeeScript

###
A Coffeescript WebRTC snowflake proxy
Contains helpers for parsing query strings and other utilities.
###
Query =
###
Parse a URL query string or application/x-www-form-urlencoded body. The
return type is an object mapping string keys to string values. By design,
this function doesn't support multiple values for the same named parameter,
for example 'a=1&a=2&a=3'; the first definition always wins. Returns null on
error.
Always decodes from UTF-8, not any other encoding.
http://dev.w3.org/html5/spec/Overview.html#url-encoded-form-data
###
parse: (qs) ->
result = {}
strings = []
strings = qs.split '&' if qs
return result if 0 == strings.length
for string in strings
j = string.indexOf '='
if j == -1
name = string
value = ''
else
name = string.substr(0, j)
value = string.substr(j + 1)
name = decodeURIComponent(name.replace(/\+/g, ' '))
value = decodeURIComponent(value.replace(/\+/g, ' '))
result[name] = value if name not of result
result
# params is a list of (key, value) 2-tuples.
buildString: (params) ->
parts = []
for param in params
parts.push encodeURIComponent(param[0]) + '=' +
encodeURIComponent(param[1])
parts.join '&'
Parse =
# Parse a cookie data string (usually document.cookie). The return type is an
# object mapping cookies names to values. Returns null on error.
# http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-8747038
cookie: (cookies) ->
result = {}
strings = []
strings = cookies.split ';' if cookies
for string in strings
j = string.indexOf '='
return null if -1 == j
name = decodeURIComponent string.substr(0, j).trim()
value = decodeURIComponent string.substr(j + 1).trim()
result[name] = value if !(name in result)
result
# Parse an address in the form 'host:port'. Returns an Object with keys 'host'
# (String) and 'port' (int). Returns null on error.
address: (spec) ->
m = null
# IPv6 syntax.
m = spec.match(/^\[([\0-9a-fA-F:.]+)\]:([0-9]+)$/) if !m
# IPv4 syntax.
m = spec.match(/^([0-9.]+):([0-9]+)$/) if !m
return null if !m
host = m[1]
port = parseInt(m[2], 10)
if isNaN(port) || port < 0 || port > 65535
return null
{ host: host, port: port }
# Parse a count of bytes. A suffix of 'k', 'm', or 'g' (or uppercase)
# does what you would think. Returns null on error.
byteCount: (spec) ->
UNITS = {
k: 1024, m: 1024 * 1024, g: 1024 * 1024 * 1024
K: 1024, M: 1024 * 1024, G: 1024 * 1024 * 1024
}
matches = spec.match /^(\d+(?:\.\d*)?)(\w*)$/
return null if null == matches
count = Number matches[1]
return null if isNaN count
if '' == matches[2]
units = 1
else
units = UNITS[matches[2]]
return null if null == units
count * Number(units)
Params =
getBool: (query, param, defaultValue) ->
val = query[param]
return defaultValue if undefined == val
return true if 'true' == val || '1' == val || '' == val
return false if 'false' == val || '0' == val
return null
# Get an object value and parse it as a byte count. Example byte counts are
# '100' and '1.3m'. Returns |defaultValue| if param is not a key. Return null
# on a parsing error.
getByteCount: (query, param, defaultValue) ->
spec = query[param]
return defaultValue if undefined == spec
Parse.byteCount spec
# Get an object value and parse it as an address spec. Returns |defaultValue|
# if param is not a key. Returns null on a parsing error.
getAddress: (query, param, defaultValue) ->
val = query[param]
return defaultValue if undefined == val
Parse.address val
# Get an object value and return it as a string. Returns default_val if param
# is not a key.
getString: (query, param, defaultValue) ->
val = query[param]
return defaultValue if undefined == val
val
class BucketRateLimit
amount: 0.0
lastUpdate: new Date()
constructor: (@capacity, @time) ->
age: ->
now = new Date()
delta = (now - @lastUpdate) / 1000.0
@lastUpdate = now
@amount -= delta * @capacity / @time
@amount = 0.0 if @amount < 0.0
update: (n) ->
@age()
@amount += n
@amount <= @capacity
# How many seconds in the future will the limit expire?
when: ->
age()
(@amount - @capacity) / (@capacity / @time)
isLimited: ->
@age()
@amount > @capacity
# A rate limiter that never limits.
class DummyRateLimit
constructor: (@capacity, @time) ->
update: (n) -> true
when: -> 0.0
isLimited: -> false