mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-14 05:11:19 -04:00
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:
parent
95952830ba
commit
d735c0fbf9
3 changed files with 209 additions and 61 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue