snowflake proxy asks user for relay address. also:

- tests for cookiestring and querystring
- fixed Cakefile with a concat so it works fine in both browser and local test
This commit is contained in:
Serene Han 2016-01-11 10:49:31 -08:00
parent 95952830ba
commit d735c0fbf9
3 changed files with 209 additions and 61 deletions

View file

@ -2,7 +2,11 @@ fs = require 'fs'
{exec} = require 'child_process' {exec} = require 'child_process'
task 'test', 'test snowflake.coffee', () -> task 'test', 'snowflake unit tests', () ->
exec 'coffee snowflake_test.coffee -v', (err, stdout, stderr) -> testFile = 'test/snowflake.bundle.coffee'
exec 'cat snowflake.coffee snowflake_test.coffee | cat > ' + testFile, (err, stdout, stderr) ->
throw err if err
console.log stdout + stderr
exec 'coffee ' + testFile + ' -v', (err, stdout, stderr) ->
throw err if err throw err if err
console.log stdout + stderr console.log stdout + stderr

View file

@ -10,6 +10,16 @@ this must always act as the answerer.
TODO(keroserene): Complete the websocket + webrtc ProxyPair TODO(keroserene): Complete the websocket + webrtc ProxyPair
### ###
if 'undefined' == typeof module || 'undefined' != typeof module.exports
console.log 'not in browser.'
else
window.PeerConnection = window.RTCPeerConnection ||
window.mozRTCPeerConnection ||
window.webkitRTCPeerConnection
window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate;
window.RTCSessionDescription = window.RTCSessionDescription ||
window.mozRTCSessionDescription
Query = Query =
### ###
Parse a URL query string or application/x-www-form-urlencoded body. The Parse a URL query string or application/x-www-form-urlencoded body. The
@ -26,8 +36,7 @@ Query =
strings = [] strings = []
strings = qs.split '&' if qs strings = qs.split '&' if qs
return result if 0 == strings.length return result if 0 == strings.length
for i in [1..strings.length] for string in strings
string = strings[i]
j = string.indexOf '=' j = string.indexOf '='
if j == -1 if j == -1
name = string name = string
@ -37,7 +46,7 @@ Query =
value = string.substr(j + 1) value = string.substr(j + 1)
name = decodeURIComponent(name.replace(/\+/g, ' ')) name = decodeURIComponent(name.replace(/\+/g, ' '))
value = decodeURIComponent(value.replace(/\+/g, ' ')) value = decodeURIComponent(value.replace(/\+/g, ' '))
result[name] = value if !(name in result) result[name] = value if name not of result
result result
# params is a list of (key, value) 2-tuples. # params is a list of (key, value) 2-tuples.
@ -56,6 +65,22 @@ Params =
return false if "false" == val || "0" == val return false if "false" == val || "0" == val
return null return null
# 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
parseCookie: (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
# repr = (x) -> # repr = (x) ->
# return 'null' if null == x # return 'null' if null == x
# return 'undefined' if 'undefined' == typeof x # return 'undefined' if 'undefined' == typeof x
@ -72,9 +97,11 @@ Params =
safe_repr = (s) -> SAFE_LOGGING ? "[scrubbed]" : JSON.stringify(s) safe_repr = (s) -> SAFE_LOGGING ? "[scrubbed]" : JSON.stringify(s)
# HEADLESS is true if we are running not in a browser with a DOM. # HEADLESS is true if we are running not in a browser with a DOM.
query = Query.parse(window.location.search.substr(1)) DEBUG = false
if window && window.location
query = Query.parse(window.location.search.substr(1))
DEBUG = Params.getBool(query, "debug", false)
HEADLESS = "undefined" == typeof(document) HEADLESS = "undefined" == typeof(document)
DEBUG = Params.getBool(query, "debug", false)
# TODO: Different ICE servers. # TODO: Different ICE servers.
config = { config = {
@ -88,12 +115,6 @@ $chatlog = null
$send = null $send = null
$input = null $input = null
window.PeerConnection = window.RTCPeerConnection ||
window.mozRTCPeerConnection ||
window.webkitRTCPeerConnection
window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate;
window.RTCSessionDescription = window.RTCSessionDescription ||
window.mozRTCSessionDescription
# TODO: Implement # TODO: Implement
class Badge class Badge
@ -112,6 +133,7 @@ class Snowflake
pc: null pc: null
rateLimit: 0 rateLimit: 0
proxyPairs: [] proxyPairs: []
relayAddr: null
badge: null badge: null
$badge: null $badge: null
MAX_NUM_CLIENTS = 1 MAX_NUM_CLIENTS = 1
@ -126,8 +148,13 @@ class Snowflake
else else
@badge = new Badge() @badge = new Badge()
@$badgem = @badge.elem @$badgem = @badge.elem
if (@$badge) @$badge.setAttribute("id", "snowflake-badge") if (@$badge)
@$badge.setAttribute("id", "snowflake-badge")
setRelayAddr: (relayAddr) ->
# TODO: User-supplied for now, but should fetch from facilitator later.
@relayAddr = relayAddr
log "Input offer from the snowflake client:"
@beginWebRTC()
# Initialize WebRTC PeerConnection # Initialize WebRTC PeerConnection
beginWebRTC: -> beginWebRTC: ->
@ -240,6 +267,9 @@ class Snowflake
@badge.die() if @badge @badge.die() if @badge
DEFAULT_PORTS =
http: 80
https: 443
# Build an escaped URL string from unescaped components. Only scheme and host # Build an escaped URL string from unescaped components. Only scheme and host
# are required. See RFC 3986, section 3. # are required. See RFC 3986, section 3.
buildUrl = (scheme, host, port, path, params) -> buildUrl = (scheme, host, port, path, params) ->
@ -298,9 +328,6 @@ makeWebsocket = (addr) ->
# TODO: Implement # TODO: Implement
class ProxyPair class ProxyPair
# TODO: Hardcoded for now, but should fetch from facilitator later.
relayAddr: null
constructor: (@clientAddr, @relayAddr, @rateLimit) -> constructor: (@clientAddr, @relayAddr, @rateLimit) ->
# Assumes WebRTC part is already connected. # Assumes WebRTC part is already connected.
@ -405,7 +432,7 @@ snowflake = null
welcome = -> welcome = ->
log "== snowflake browser proxy ==" log "== snowflake browser proxy =="
log "Input offer from the snowflake client:" log "Input desired relay address:"
# Log to the message window. # Log to the message window.
log = (msg) -> log = (msg) ->
@ -419,6 +446,9 @@ Interface =
acceptInput: -> acceptInput: ->
msg = $input.value msg = $input.value
switch snowflake.state switch snowflake.state
when MODE.INIT
# Set target relay.
snowflake.setRelayAddr msg
when MODE.WEBRTC_CONNECTING when MODE.WEBRTC_CONNECTING
Signalling.receive msg Signalling.receive msg
when MODE.WEBRTC_READY when MODE.WEBRTC_READY
@ -453,6 +483,7 @@ Signalling =
snowflake.receiveOffer recv if desc snowflake.receiveOffer recv if desc
init = -> init = ->
$chatlog = document.getElementById('chatlog') $chatlog = document.getElementById('chatlog')
$chatlog.value = "" $chatlog.value = ""
@ -465,7 +496,6 @@ init = ->
if 13 == e.keyCode # enter if 13 == e.keyCode # enter
$send.onclick() $send.onclick()
snowflake = new Snowflake() snowflake = new Snowflake()
snowflake.beginWebRTC()
welcome() welcome()
window.onload = init window.onload = init if window

View file

@ -1,80 +1,194 @@
s = require './snowflake' # s = require './snowflake'
window = {}
VERBOSE = false VERBOSE = false
if process.argv.indexOf("-v") >= 0 if process.argv.indexOf('-v') >= 0
VERBOSE = true VERBOSE = true
numTests = 0 numTests = 0
numFailed = 0 numFailed = 0
announce = (testName) -> announce = (testName) ->
if VERBOSE if VERBOSE
# if (!top) console.log '\n --- ' + testName + ' ---'
# console.log();
console.log testName
# top = false
pass = (test) -> pass = (test) ->
numTests++; numTests++;
if VERBOSE if VERBOSE
console.log "PASS " + test console.log 'PASS ' + test
fail = (test, expected, actual) -> fail = (test, expected, actual) ->
numTests++ numTests++
numFailed++ numFailed++
console.log "FAIL " + test + " expected: " + expected + " actual: " + actual console.log 'FAIL ' + test +
' expected: ' + JSON.stringify(expected) +
' actual: ' + JSON.stringify(actual)
testBuildUrl = -> testBuildUrl = ->
TESTS = [{ TESTS = [{
args: ["http", "example.com"] args: ['http', 'example.com']
expected: "http://example.com" expected: 'http://example.com'
},{ },{
args: ["http", "example.com", 80] args: ['http', 'example.com', 80]
expected: "http://example.com" expected: 'http://example.com'
},{ },{
args: ["http", "example.com", 81], args: ['http', 'example.com', 81],
expected: "http://example.com:81" expected: 'http://example.com:81'
},{ },{
args: ["https", "example.com", 443] args: ['https', 'example.com', 443]
expected: "https://example.com" expected: 'https://example.com'
},{ },{
args: ["https", "example.com", 444] args: ['https', 'example.com', 444]
expected: "https://example.com:444" expected: 'https://example.com:444'
},{ },{
args: ["http", "example.com", 80, "/"] args: ['http', 'example.com', 80, '/']
expected: "http://example.com/" expected: 'http://example.com/'
},{ },{
args: ["http", "example.com", 80, "/test?k=%#v"] args: ['http', 'example.com', 80, '/test?k=%#v']
expected: "http://example.com/test%3Fk%3D%25%23v" expected: 'http://example.com/test%3Fk%3D%25%23v'
},{ },{
args: ["http", "example.com", 80, "/test", []] args: ['http', 'example.com', 80, '/test', []]
expected: "http://example.com/test?" expected: 'http://example.com/test?'
},{ },{
args: ["http", "example.com", 80, "/test", [["k", "%#v"]]] args: ['http', 'example.com', 80, '/test', [['k', '%#v']]]
expected: "http://example.com/test?k=%25%23v" expected: 'http://example.com/test?k=%25%23v'
},{ },{
args: ["http", "example.com", 80, "/test", [["a", "b"], ["c", "d"]]] args: ['http', 'example.com', 80, '/test', [['a', 'b'], ['c', 'd']]]
expected: "http://example.com/test?a=b&c=d" expected: 'http://example.com/test?a=b&c=d'
},{ },{
args: ["http", "1.2.3.4"] args: ['http', '1.2.3.4']
expected: "http://1.2.3.4" expected: 'http://1.2.3.4'
},{ },{
args: ["http", "1:2::3:4"] args: ['http', '1:2::3:4']
expected: "http://[1:2::3:4]" expected: 'http://[1:2::3:4]'
},{ },{
args: ["http", "bog][us"] args: ['http', 'bog][us']
expected: "http://bog%5D%5Bus" expected: 'http://bog%5D%5Bus'
},{ },{
args: ["http", "bog:u]s"] args: ['http', 'bog:u]s']
expected: "http://bog%3Au%5Ds" expected: 'http://bog%3Au%5Ds'
} }]
]
announce "-- testBuildUrl --" announce 'testBuildUrl'
for test in TESTS for test in TESTS
actual = s.buildUrl.apply undefined, test.args actual = buildUrl.apply undefined, test.args
if actual == test.expected if actual == test.expected
pass test.args pass test.args
else else
fail test.args, test.expected, actual fail test.args, test.expected, actual
###
This test only checks that things work for strings formatted like
document.cookie. Browsers maintain several properties about this string, for
example cookie names are unique with no trailing whitespace. See
http://www.ietf.org/rfc/rfc2965.txt for the grammar.
###
testParseCookieString = ->
TESTS = [{
cs: ''
expected: { }
},{
cs: 'a=b'
expected: { a: 'b'}
},{
cs: 'a=b=c'
expected: { a: 'b=c' }
},{
cs: 'a=b; c=d'
expected: { a: 'b', c: 'd' }
},{
cs: 'a=b ; c=d'
expected: { a: 'b', c: 'd' }
},{
cs: 'a= b',
expected: {a: 'b' }
},{
cs: 'a='
expected: { a: '' }
}, {
cs: 'key',
expected: null
}, {
cs: 'key=%26%20'
expected: { key: '& ' }
}, {
cs: 'a=\'\''
expected: { a: '\'\'' }
}]
announce 'testParseCookieString'
for test in TESTS
actual = Params.parseCookie test.cs
if JSON.stringify(actual) == JSON.stringify(test.expected)
pass test.cs
else
fail test.cs, test.expected, actual
testParseQueryString = ->
TESTS = [{
qs: ''
expected: {}
},{
qs: 'a=b'
expected: { a: 'b' }
},{
qs: 'a=b=c'
expected: { a: 'b=c' }
},{
qs: 'a=b&c=d'
expected: { a: 'b', c: 'd' }
},{
qs: 'client=&relay=1.2.3.4%3A9001'
expected: { client: '', relay: '1.2.3.4:9001' }
},{
qs: 'a=b%26c=d'
expected: { a: 'b&c=d' }
},{
qs: 'a%3db=d'
expected: { 'a=b': 'd' }
},{
qs: 'a=b+c%20d'
expected: { 'a': 'b c d' }
},{
qs: 'a=b+c%2bd'
expected: { 'a': 'b c+d' }
},{
qs: 'a+b=c'
expected: { 'a b': 'c' }
},{
qs: 'a=b+c+d'
expected: { a: 'b c d' }
# First appearance wins.
},{
qs: 'a=b&c=d&a=e'
expected: { a: 'b', c: 'd' }
},{
qs: 'a'
expected: { a: '' }
},{
qs: '=b',
expected: { '': 'b' }
},{
qs: '&a=b'
expected: { '': '', a: 'b' }
},{
qs: 'a=b&'
expected: { a: 'b', '':'' }
},{
qs: 'a=b&&c=d'
expected: { a: 'b', '':'', c: 'd' }
}]
announce 'testParseQueryString'
for test in TESTS
actual = Query.parse test.qs
if JSON.stringify(actual) == JSON.stringify(test.expected)
pass test.qs
else
fail test.qs, test.expected, actual
testBuildUrl() testBuildUrl()
testParseCookieString()
testParseQueryString()