mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-13 20: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'
|
||||
|
||||
task 'test', 'test snowflake.coffee', () ->
|
||||
exec 'coffee snowflake_test.coffee -v', (err, stdout, stderr) ->
|
||||
task 'test', 'snowflake unit tests', () ->
|
||||
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
|
||||
console.log stdout + stderr
|
||||
|
|
|
@ -10,6 +10,16 @@ this must always act as the answerer.
|
|||
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 =
|
||||
###
|
||||
Parse a URL query string or application/x-www-form-urlencoded body. The
|
||||
|
@ -26,8 +36,7 @@ Query =
|
|||
strings = []
|
||||
strings = qs.split '&' if qs
|
||||
return result if 0 == strings.length
|
||||
for i in [1..strings.length]
|
||||
string = strings[i]
|
||||
for string in strings
|
||||
j = string.indexOf '='
|
||||
if j == -1
|
||||
name = string
|
||||
|
@ -37,7 +46,7 @@ Query =
|
|||
value = string.substr(j + 1)
|
||||
name = decodeURIComponent(name.replace(/\+/g, ' '))
|
||||
value = decodeURIComponent(value.replace(/\+/g, ' '))
|
||||
result[name] = value if !(name in result)
|
||||
result[name] = value if name not of result
|
||||
result
|
||||
|
||||
# params is a list of (key, value) 2-tuples.
|
||||
|
@ -56,6 +65,22 @@ Params =
|
|||
return false if "false" == val || "0" == val
|
||||
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) ->
|
||||
# return 'null' if null == x
|
||||
# return 'undefined' if 'undefined' == typeof x
|
||||
|
@ -72,9 +97,11 @@ Params =
|
|||
safe_repr = (s) -> SAFE_LOGGING ? "[scrubbed]" : JSON.stringify(s)
|
||||
|
||||
# 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)
|
||||
DEBUG = Params.getBool(query, "debug", false)
|
||||
|
||||
# TODO: Different ICE servers.
|
||||
config = {
|
||||
|
@ -88,12 +115,6 @@ $chatlog = null
|
|||
$send = 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
|
||||
class Badge
|
||||
|
@ -112,6 +133,7 @@ class Snowflake
|
|||
pc: null
|
||||
rateLimit: 0
|
||||
proxyPairs: []
|
||||
relayAddr: null
|
||||
badge: null
|
||||
$badge: null
|
||||
MAX_NUM_CLIENTS = 1
|
||||
|
@ -126,8 +148,13 @@ class Snowflake
|
|||
else
|
||||
@badge = new Badge()
|
||||
@$badgem = @badge.elem
|
||||
if (@$badge)
|
||||
@$badge.setAttribute("id", "snowflake-badge")
|
||||
@$badge.setAttribute("id", "snowflake-badge") if (@$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
|
||||
beginWebRTC: ->
|
||||
|
@ -240,6 +267,9 @@ class Snowflake
|
|||
@badge.die() if @badge
|
||||
|
||||
|
||||
DEFAULT_PORTS =
|
||||
http: 80
|
||||
https: 443
|
||||
# Build an escaped URL string from unescaped components. Only scheme and host
|
||||
# are required. See RFC 3986, section 3.
|
||||
buildUrl = (scheme, host, port, path, params) ->
|
||||
|
@ -298,9 +328,6 @@ makeWebsocket = (addr) ->
|
|||
# TODO: Implement
|
||||
class ProxyPair
|
||||
|
||||
# TODO: Hardcoded for now, but should fetch from facilitator later.
|
||||
relayAddr: null
|
||||
|
||||
constructor: (@clientAddr, @relayAddr, @rateLimit) ->
|
||||
|
||||
# Assumes WebRTC part is already connected.
|
||||
|
@ -405,7 +432,7 @@ snowflake = null
|
|||
|
||||
welcome = ->
|
||||
log "== snowflake browser proxy =="
|
||||
log "Input offer from the snowflake client:"
|
||||
log "Input desired relay address:"
|
||||
|
||||
# Log to the message window.
|
||||
log = (msg) ->
|
||||
|
@ -419,6 +446,9 @@ Interface =
|
|||
acceptInput: ->
|
||||
msg = $input.value
|
||||
switch snowflake.state
|
||||
when MODE.INIT
|
||||
# Set target relay.
|
||||
snowflake.setRelayAddr msg
|
||||
when MODE.WEBRTC_CONNECTING
|
||||
Signalling.receive msg
|
||||
when MODE.WEBRTC_READY
|
||||
|
@ -453,6 +483,7 @@ Signalling =
|
|||
snowflake.receiveOffer recv if desc
|
||||
|
||||
init = ->
|
||||
|
||||
$chatlog = document.getElementById('chatlog')
|
||||
$chatlog.value = ""
|
||||
|
||||
|
@ -465,7 +496,6 @@ init = ->
|
|||
if 13 == e.keyCode # enter
|
||||
$send.onclick()
|
||||
snowflake = new Snowflake()
|
||||
snowflake.beginWebRTC()
|
||||
welcome()
|
||||
|
||||
window.onload = init
|
||||
window.onload = init if window
|
||||
|
|
|
@ -1,80 +1,194 @@
|
|||
s = require './snowflake'
|
||||
# s = require './snowflake'
|
||||
|
||||
window = {}
|
||||
|
||||
VERBOSE = false
|
||||
if process.argv.indexOf("-v") >= 0
|
||||
if process.argv.indexOf('-v') >= 0
|
||||
VERBOSE = true
|
||||
numTests = 0
|
||||
numFailed = 0
|
||||
|
||||
announce = (testName) ->
|
||||
if VERBOSE
|
||||
# if (!top)
|
||||
# console.log();
|
||||
console.log testName
|
||||
# top = false
|
||||
console.log '\n --- ' + testName + ' ---'
|
||||
|
||||
pass = (test) ->
|
||||
numTests++;
|
||||
if VERBOSE
|
||||
console.log "PASS " + test
|
||||
console.log 'PASS ' + test
|
||||
|
||||
fail = (test, expected, actual) ->
|
||||
numTests++
|
||||
numFailed++
|
||||
console.log "FAIL " + test + " expected: " + expected + " actual: " + actual
|
||||
console.log 'FAIL ' + test +
|
||||
' expected: ' + JSON.stringify(expected) +
|
||||
' actual: ' + JSON.stringify(actual)
|
||||
|
||||
|
||||
testBuildUrl = ->
|
||||
TESTS = [{
|
||||
args: ["http", "example.com"]
|
||||
expected: "http://example.com"
|
||||
args: ['http', 'example.com']
|
||||
expected: 'http://example.com'
|
||||
},{
|
||||
args: ["http", "example.com", 80]
|
||||
expected: "http://example.com"
|
||||
args: ['http', 'example.com', 80]
|
||||
expected: 'http://example.com'
|
||||
},{
|
||||
args: ["http", "example.com", 81],
|
||||
expected: "http://example.com:81"
|
||||
args: ['http', 'example.com', 81],
|
||||
expected: 'http://example.com:81'
|
||||
},{
|
||||
args: ["https", "example.com", 443]
|
||||
expected: "https://example.com"
|
||||
args: ['https', 'example.com', 443]
|
||||
expected: 'https://example.com'
|
||||
},{
|
||||
args: ["https", "example.com", 444]
|
||||
expected: "https://example.com:444"
|
||||
args: ['https', 'example.com', 444]
|
||||
expected: 'https://example.com:444'
|
||||
},{
|
||||
args: ["http", "example.com", 80, "/"]
|
||||
expected: "http://example.com/"
|
||||
args: ['http', 'example.com', 80, '/']
|
||||
expected: 'http://example.com/'
|
||||
},{
|
||||
args: ["http", "example.com", 80, "/test?k=%#v"]
|
||||
expected: "http://example.com/test%3Fk%3D%25%23v"
|
||||
args: ['http', 'example.com', 80, '/test?k=%#v']
|
||||
expected: 'http://example.com/test%3Fk%3D%25%23v'
|
||||
},{
|
||||
args: ["http", "example.com", 80, "/test", []]
|
||||
expected: "http://example.com/test?"
|
||||
args: ['http', 'example.com', 80, '/test', []]
|
||||
expected: 'http://example.com/test?'
|
||||
},{
|
||||
args: ["http", "example.com", 80, "/test", [["k", "%#v"]]]
|
||||
expected: "http://example.com/test?k=%25%23v"
|
||||
args: ['http', 'example.com', 80, '/test', [['k', '%#v']]]
|
||||
expected: 'http://example.com/test?k=%25%23v'
|
||||
},{
|
||||
args: ["http", "example.com", 80, "/test", [["a", "b"], ["c", "d"]]]
|
||||
expected: "http://example.com/test?a=b&c=d"
|
||||
args: ['http', 'example.com', 80, '/test', [['a', 'b'], ['c', 'd']]]
|
||||
expected: 'http://example.com/test?a=b&c=d'
|
||||
},{
|
||||
args: ["http", "1.2.3.4"]
|
||||
expected: "http://1.2.3.4"
|
||||
args: ['http', '1.2.3.4']
|
||||
expected: 'http://1.2.3.4'
|
||||
},{
|
||||
args: ["http", "1:2::3:4"]
|
||||
expected: "http://[1:2::3:4]"
|
||||
args: ['http', '1:2::3:4']
|
||||
expected: 'http://[1:2::3:4]'
|
||||
},{
|
||||
args: ["http", "bog][us"]
|
||||
expected: "http://bog%5D%5Bus"
|
||||
args: ['http', 'bog][us']
|
||||
expected: 'http://bog%5D%5Bus'
|
||||
},{
|
||||
args: ["http", "bog:u]s"]
|
||||
expected: "http://bog%3Au%5Ds"
|
||||
}
|
||||
]
|
||||
announce "-- testBuildUrl --"
|
||||
args: ['http', 'bog:u]s']
|
||||
expected: 'http://bog%3Au%5Ds'
|
||||
}]
|
||||
|
||||
announce 'testBuildUrl'
|
||||
for test in TESTS
|
||||
actual = s.buildUrl.apply undefined, test.args
|
||||
actual = buildUrl.apply undefined, test.args
|
||||
if actual == test.expected
|
||||
pass test.args
|
||||
else
|
||||
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()
|
||||
testParseCookieString()
|
||||
testParseQueryString()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue