[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [flashproxy/master] Move flash proxy badge files into a subdirectory.
commit c8bd5fd4fed1907817087ed8f7a6cba6511d1fb5
Author: David Fifield <david@xxxxxxxxxxxxxxx>
Date: Fri Sep 28 19:43:32 2012 -0700
Move flash proxy badge files into a subdirectory.
Now the root contains only files of interest to the most likely end
user: a client and not a web site owner, relay operator, or facilitator
operator.
---
Makefile | 1 -
badge.png | Bin 254 -> 0 bytes
badge.xcf | Bin 1441 -> 0 bytes
embed.html | 87 -----
flashproxy-test.js | 239 ------------
flashproxy.js | 898 ----------------------------------------------
proxy/README | 4 +
proxy/badge.png | Bin 0 -> 254 bytes
proxy/badge.xcf | Bin 0 -> 1441 bytes
proxy/embed.html | 87 +++++
proxy/flashproxy-test.js | 239 ++++++++++++
proxy/flashproxy.js | 898 ++++++++++++++++++++++++++++++++++++++++++++++
12 files changed, 1228 insertions(+), 1225 deletions(-)
diff --git a/Makefile b/Makefile
index 0effce4..55c5859 100644
--- a/Makefile
+++ b/Makefile
@@ -18,7 +18,6 @@ clean:
test:
./flashproxy-client-test
- ./flashproxy-test.js
DISTNAME = flashproxy-client-$(VERSION)
DISTDIR = dist/$(DISTNAME)
diff --git a/badge.png b/badge.png
deleted file mode 100644
index 068cf78..0000000
Binary files a/badge.png and /dev/null differ
diff --git a/badge.xcf b/badge.xcf
deleted file mode 100644
index d12ff1f..0000000
Binary files a/badge.xcf and /dev/null differ
diff --git a/embed.html b/embed.html
deleted file mode 100644
index 95821ce..0000000
--- a/embed.html
+++ /dev/null
@@ -1,87 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<meta charset="utf-8">
-<meta http-equiv="refresh" content="86400">
-<style type="text/css">
-html {
- width: 100%;
- height: 100%;
-}
-body {
- margin: 0;
- padding: 0;
- width: 100%;
- height: 100%;
-}
-#flashproxy-badge {
- width: 100%;
- height: 100%;
- margin: 0;
- padding: 0;
- border: 0;
- border-collapse: collapse;
- line-height: 0;
-}
-#flashproxy-badge.idle {
- background-color: #227;
-}
-#flashproxy-badge.active {
- background-color: #28f;
-}
-#flashproxy-badge.disabled {
- background-color: #777;
-}
-#flashproxy-badge.dead {
- background-color: #111;
-}
-#flashproxy-badge td {
- position: relative;
- margin: 0;
- padding: 0;
- vertical-align: middle;
- text-align: center;
-}
-#flashproxy-badge td .disable-button {
- position: absolute;
- margin: 0;
- padding: 0;
- border: 1px solid white;
- color: white;
- background-color: #B00;
- top: 1px;
- right: 1px;
- width: 11px;
- height: 11px;
- text-align: center;
- line-height: 11px;
- font-size: 11px;
- display: none;
-}
-#flashproxy-badge a img {
- border: 0;
-}
-#flashproxy-badge.debug {
- position: absolute;
- margin: 0;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- overflow: auto;
- color: #4c4;
- background-color: #021;
- line-height: inherit;
-}
-</style>
-</head>
-<body>
-<script type="text/javascript" src="flashproxy.js"></script>
-<script type="text/javascript">
-flashproxy_badge_insert().start();
-</script>
-<noscript>
-<table id="flashproxy-badge" class="disabled"><tr><td><a href="//crypto.stanford.edu/flashproxy/" target="_parent"><img src="badge.png" alt="Internet freedom"></a></td></tr></table>
-</noscript>
-</body>
-</html>
diff --git a/flashproxy-test.js b/flashproxy-test.js
deleted file mode 100755
index 2dc185a..0000000
--- a/flashproxy-test.js
+++ /dev/null
@@ -1,239 +0,0 @@
-#!/usr/bin/js
-
-/* To run this test program, install the Rhino JavaScript interpreter
- (apt-get install rhino). */
-
-var VERBOSE = false;
-if ("-v" in arguments)
- VERBOSE = true;
-
-var num_tests = 0;
-var num_failed = 0;
-
-var window = {location: {search: "?"}};
-
-load("flashproxy.js");
-
-function objects_equal(a, b)
-{
- if ((a === null) != (b === null))
- return false;
- if (typeof a != typeof b)
- return false;
- if (typeof a != "object")
- return a == b;
-
- for (var k in a) {
- if (!objects_equal(a[k], b[k]))
- return false;
- }
- for (var k in b) {
- if (!objects_equal(a[k], b[k]))
- return false;
- }
-
- return true;
-}
-
-var top = true;
-function announce(test_name)
-{
- if (VERBOSE) {
- if (!top)
- print();
- print(test_name);
- }
- top = false;
-}
-
-function pass(test)
-{
- num_tests++;
- if (VERBOSE)
- print("PASS " + repr(test));
-}
-
-function fail(test, expected, actual)
-{
- num_tests++;
- num_failed++;
- print("FAIL " + repr(test) + " expected: " + repr(expected) + " actual: " + repr(actual));
-}
-
-function test_build_url()
-{
- var TESTS = [
- { args: ["http", "example.com"],
- expected: "http://example.com" },
- { args: ["http", "example.com", 80],
- expected: "http://example.com" },
- { args: ["http", "example.com", 81],
- expected: "http://example.com:81" },
- { args: ["https", "example.com", 443],
- expected: "https://example.com" },
- { args: ["https", "example.com", 444],
- expected: "https://example.com:444" },
- { 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", []],
- 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", [["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", "bog][us"],
- expected: "http://bog%5D%5Bus" },
- { args: ["http", "bog:u]s"],
- expected: "http://bog%3Au%5Ds" },
- ];
-
- announce("test_build_url");
- for (var i = 0; i < TESTS.length; i++) {
- var test = TESTS[i];
- var actual;
-
- actual = build_url.apply(undefined, test.args);
- if (objects_equal(actual, test.expected))
- pass(test.args);
- else
- fail(test.args, test.expected, actual);
- }
-}
-
-function test_parse_query_string()
-{
- var 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("test_parse_query_string");
- for (var i = 0; i < TESTS.length; i++) {
- var test = TESTS[i];
- var actual;
-
- actual = parse_query_string(test.qs);
- if (objects_equal(actual, test.expected))
- pass(test.qs);
- else
- fail(test.qs, test.expected, actual);
- }
-}
-
-function test_parse_addr_spec()
-{
- var TESTS = [
- { spec: "",
- expected: null },
- { spec: "3.3.3.3:4444",
- expected: { host: "3.3.3.3", port: 4444 } },
- { spec: "3.3.3.3",
- expected: null },
- { spec: "3.3.3.3:0x1111",
- expected: null },
- { spec: "3.3.3.3:-4444",
- expected: null },
- { spec: "3.3.3.3:65536",
- expected: null },
- { spec: "[1:2::a:f]:4444",
- expected: { host: "1:2::a:f", port: 4444 } },
- { spec: "[1:2::a:f]",
- expected: null },
- { spec: "[1:2::a:f]:0x1111",
- expected: null },
- { spec: "[1:2::a:f]:-4444",
- expected: null },
- { spec: "[1:2::a:f]:65536",
- expected: null },
- { spec: "[1:2::ffff:1.2.3.4]:4444",
- expected: { host: "1:2::ffff:1.2.3.4", port: 4444 } },
- ];
-
- announce("test_parse_addr_spec");
- for (var i = 0; i < TESTS.length; i++) {
- var test = TESTS[i];
- var actual;
-
- actual = parse_addr_spec(test.spec);
- if (objects_equal(actual, test.expected))
- pass(test.spec);
- else
- fail(test.spec, test.expected, actual);
- }
-}
-
-function test_get_query_param_addr()
-{
- var DEFAULT = { host: "1.1.1.1", port: 2222 };
- var TESTS = [
- { query: { },
- expected: DEFAULT },
- { query: { addr: "3.3.3.3:4444" },
- expected: { host: "3.3.3.3", port: 4444 } },
- { query: { x: "3.3.3.3:4444" },
- expected: DEFAULT },
- { query: { addr: "---" },
- expected: null },
- ];
-
- announce("test_get_query_param_addr");
- for (var i = 0; i < TESTS.length; i++) {
- var test = TESTS[i];
- var actual;
-
- actual = get_query_param_addr(test.query, "addr", DEFAULT);
- if (objects_equal(actual, test.expected))
- pass(test.query);
- else
- fail(test.query, test.expected, actual);
- }
-}
-
-test_build_url();
-test_parse_query_string();
-test_parse_addr_spec();
-test_get_query_param_addr();
-
-if (num_failed == 0)
- quit(0);
-else
- quit(1);
diff --git a/flashproxy.js b/flashproxy.js
deleted file mode 100644
index a2e8863..0000000
--- a/flashproxy.js
+++ /dev/null
@@ -1,898 +0,0 @@
-/* Query string parameters. These change how the program runs from the outside.
- * For example:
- * http://www.example.com/embed.html?facilitator=127.0.0.1:9002&debug=1
- *
- * client=<HOST>:<PORT>
- * The address of the client to connect to. The proxy normally receives this
- * information from the facilitator. When this option is used, the facilitator
- * query is not done. The "relay" parameter must be given as well.
- *
- * debug=1
- * If set (to any value), show verbose terminal-like output instead of the
- * badge.
- *
- * facilitator=https://host:port/
- * The URL of the facilitator CGI script. By default it is
- * DEFAULT_FACILITATOR_URL.
- *
- * facilitator_poll_interval=<FLOAT>
- * How often to poll the facilitator, in seconds. The default is
- * DEFAULT_FACILITATOR_POLL_INTERVAL. There is a sanity-check minimum of 1.0 s.
- *
- * max_clients=<NUM>
- * How many clients to serve concurrently. The default is
- * DEFAULT_MAX_NUM_PROXY_PAIRS.
- *
- * relay=<HOST>:<PORT>
- * The address of the relay to connect to. The proxy normally receives this
- * information from the facilitator. When this option is used, the facilitator
- * query is not done. The "client" parameter must be given as well.
- *
- * ratelimit=<FLOAT>(<UNIT>)?|off
- * What rate to limit all proxy traffic combined to. The special value "off"
- * disables the limit. The default is DEFAULT_RATE_LIMIT. There is a
- * sanity-check minimum of "10K".
- */
-
-/* WebSocket links.
- *
- * The WebSocket Protocol
- * https://tools.ietf.org/html/rfc6455
- *
- * The WebSocket API
- * http://dev.w3.org/html5/websockets/
- *
- * MDN page with browser compatibility
- * https://developer.mozilla.org/en/WebSockets
- *
- * Implementation tests (including tests of binary messages)
- * http://autobahn.ws/testsuite/reports/clients/index.html
- */
-
-var DEFAULT_FACILITATOR_URL = "https://tor-facilitator.bamsoftware.com/";
-
-var DEFAULT_MAX_NUM_PROXY_PAIRS = 10;
-
-var DEFAULT_FACILITATOR_POLL_INTERVAL = 10.0;
-var MIN_FACILITATOR_POLL_INTERVAL = 1.0;
-
-/* Bytes per second. Set to undefined to disable limit. */
-var DEFAULT_RATE_LIMIT = undefined;
-var MIN_RATE_LIMIT = 10 * 1024;
-var RATE_LIMIT_HISTORY = 5.0;
-
-/* Firefox before version 11.0 uses the name MozWebSocket. Whether the global
- variable WebSocket is defined indicates whether WebSocket is supported at
- all. */
-var WebSocket = window.WebSocket || window.MozWebSocket;
-
-var query = parse_query_string(window.location.search.substr(1));
-var debug_div;
-
-if (query.debug) {
- debug_div = document.createElement("pre");
- debug_div.className = "debug";
-}
-
-function puts(s) {
- if (debug_div) {
- var at_bottom;
-
- /* http://www.w3.org/TR/cssom-view/#element-scrolling-members */
- at_bottom = (debug_div.scrollTop + debug_div.clientHeight === debug_div.scrollHeight);
- debug_div.appendChild(document.createTextNode(s + "\n"));
- if (at_bottom)
- debug_div.scrollTop = debug_div.scrollHeight;
- }
-}
-
-/* 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 */
-function parse_query_string(qs) {
- var strings;
- var result;
-
- result = {};
- if (qs)
- strings = qs.split("&");
- else
- strings = [];
- for (var i = 0; i < strings.length; i++) {
- var string = strings[i];
- var j, name, value;
-
- 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, " "));
- if (!(name in result))
- result[name] = value;
- }
-
- return result;
-}
-
-var 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. */
-function build_url(scheme, host, port, path, params) {
- var parts = []
-
- parts.push(encodeURIComponent(scheme));
- parts.push("://");
-
- /* If it contains a colon but no square brackets, treat it like an IPv6
- address. */
- if (host.match(/:/) && !host.match(/[[\]]/)) {
- parts.push("[");
- parts.push(host);
- parts.push("]");
- } else {
- parts.push(encodeURIComponent(host));
- }
- if (port !== undefined && port !== DEFAULT_PORTS[scheme]) {
- parts.push(":");
- parts.push(encodeURIComponent(port.toString()));
- }
-
- if (path !== undefined && path !== "") {
- if (!path.match(/^\//))
- path = "/" + path;
- /* Slash is significant so we must protect it from encodeURIComponent,
- while still encoding question mark and number sign. RFC 3986, section
- 3.3: "The path is terminated by the first question mark ('?') or
- number sign ('#') character, or by the end of the URI. ... A path
- consists of a sequence of path segments separated by a slash ('/')
- character." */
- path = path.replace(/[^\/]+/, function(m) {
- return encodeURIComponent(m);
- });
- parts.push(path);
- }
-
- if (params !== undefined) {
- parts.push("?");
- for (var i = 0; i < params.length; i++) {
- if (i > 0)
- parts.push("&");
- parts.push(encodeURIComponent(params[i][0]) + "=" + encodeURIComponent(params[i][1]));
- }
- }
-
- return parts.join("");
-}
-
-/* Get a query string parameter and return it as a string. Returns default_val
- if param is not defined in the query string. */
-function get_query_param_string(query, param, default_val) {
- var val;
-
- val = query[param];
- if (val === undefined)
- return default_val;
- else
- return val;
-}
-
-/* Get a query string parameter, or the given default, and parse it as an
- address spec. Returns null on a parsing error. */
-function get_query_param_addr(query, param, default_val) {
- var val;
-
- val = query[param];
- if (val === undefined)
- return default_val;
- else
- return parse_addr_spec(val);
-}
-
-/* Get an integer from the given movie parameter, or the given default. Returns
- null on error. */
-function get_query_param_integer(query, param, default_val) {
- var spec;
- var val;
-
- spec = query[param];
- if (spec === undefined) {
- return default_val;
- } else if (!spec.match(/^-?[0-9]+/)) {
- return null;
- } else {
- val = parseInt(spec, 10);
- if (isNaN(val))
- return null;
- else
- return val;
- }
-}
-
-/* Get a number from the given movie parameter, or the given default. Returns
- null on error. */
-function get_query_param_number(query, param, default_val) {
- var spec;
- var val;
-
- spec = query[param];
- if (spec === undefined) {
- return default_val;
- } else {
- val = Number(spec);
- if (isNaN(val))
- return null;
- else
- return val;
- }
-}
-
-/* Get a floating-point number of seconds from a time specification. The only
- time specification format is a decimal number of seconds. Returns null on
- error. */
-function get_query_param_timespec(query, param, default_val) {
- return get_query_param_number(query, param, default_val);
-}
-
-/* Parse a count of bytes. A suffix of "k", "m", or "g" (or uppercase)
- does what you would think. Returns null on error. */
-function parse_byte_count(spec) {
- var UNITS = {
- k: 1024, m: 1024 * 1024, g: 1024 * 1024 * 1024,
- K: 1024, M: 1024 * 1024, G: 1024 * 1024 * 1024
- };
- var count, units;
- var matches;
-
- matches = spec.match(/^(\d+(?:\.\d*)?)(\w*)$/);
- if (matches === null)
- return null;
-
- count = Number(matches[1]);
- if (isNaN(count))
- return null;
-
- if (matches[2] === "") {
- units = 1;
- } else {
- units = UNITS[matches[2]];
- if (units === null)
- return null;
- }
-
- return count * Number(units);
-}
-
-/* Get a count of bytes from a string specification like "100" or "1.3m".
- Returns null on error. */
-function get_query_param_byte_count(query, param, default_val) {
- var spec;
-
- spec = query[param];
- if (spec === undefined)
- return default_val;
- else
- return parse_byte_count(spec);
-}
-
-/* Parse an address in the form "host:port". Returns an Object with
- keys "host" (String) and "port" (int). Returns null on error. */
-function parse_addr_spec(spec) {
- var m, host, port;
-
- m = null;
- /* IPv6 syntax. */
- if (!m)
- m = spec.match(/^\[([\0-9a-fA-F:.]+)\]:([0-9]+)$/);
- /* IPv4 syntax. */
- if (!m)
- m = spec.match(/^([0-9.]+):([0-9]+)$/);
- if (!m)
- return null;
- host = m[1];
- port = parseInt(m[2], 10);
- if (isNaN(port) || port < 0 || port > 65535)
- return null;
-
- return { host: host, port: port }
-}
-
-function format_addr(addr) {
- return addr.host + ":" + addr.port;
-}
-
-/* Does the WebSocket implementation in this browser support binary frames? (RFC
- 6455 section 5.6.) If not, we have to use base64-encoded text frames. It is
- assumed that the client and relay endpoints always support binary frames. */
-function have_websocket_binary_frames() {
- var ua, matches;
-
- ua = window.navigator.userAgent;
- if (ua === null)
- return false;
-
- /* We are cool for Chrome 16 or Safari 6.0. */
-
- matches = ua.match(/\bchrome\/(\d+)/i);
- if (matches !== null && Number(matches[1]) >= 16)
- return true;
-
- matches = ua.match(/\bversion\/(\d+)/i);
- if (ua.match(/\bsafari\b/i) && !ua.match(/\bchrome\b/i)
- && Number(matches[1]) >= 6)
- return true;
-
- return false;
-}
-
-function make_websocket(addr) {
- var url;
- var ws;
-
- url = build_url("ws", addr.host, addr.port, "/");
-
- if (have_websocket_binary_frames())
- ws = new WebSocket(url);
- else
- ws = new WebSocket(url, "base64");
- /* "User agents can use this as a hint for how to handle incoming binary
- data: if the attribute is set to 'blob', it is safe to spool it to disk,
- and if it is set to 'arraybuffer', it is likely more efficient to keep
- the data in memory." */
- ws.binaryType = "arraybuffer";
-
- return ws;
-}
-
-function FlashProxy() {
- if (query.debug) {
- this.badge_elem = debug_div;
- } else {
- this.badge = new Badge();
- this.badge.elem.onmouseover = function(event) {
- this.badge.disable_button.style.display = "block";
- }.bind(this);
- this.badge.elem.onmouseout = function(event) {
- this.badge.disable_button.style.display = "none";
- }.bind(this);
- /* Click a button to disable the badge. */
- this.badge.disable_button.onclick = function(event) {
- this.disable();
- this.badge.disable_button.parentNode.removeChild(this.badge.disable_button);
- }.bind(this);
- this.badge_elem = this.badge.elem;
- }
- this.badge_elem.setAttribute("id", "flashproxy-badge");
-
- this.proxy_pairs = [];
-
- this.start = function() {
- var client_addr;
- var relay_addr;
- var rate_limit_bytes;
-
- this.fac_url = get_query_param_string(query, "facilitator", DEFAULT_FACILITATOR_URL);
-
- this.max_num_proxy_pairs = get_query_param_integer(query, "max_clients", DEFAULT_MAX_NUM_PROXY_PAIRS);
- if (this.max_num_proxy_pairs === null || this.max_num_proxy_pairs < 0) {
- puts("Error: max_clients must be a nonnegative integer.");
- this.die();
- return;
- }
-
- this.facilitator_poll_interval = get_query_param_timespec(query, "facilitator_poll_interval", DEFAULT_FACILITATOR_POLL_INTERVAL);
- if (this.facilitator_poll_interval === null || this.facilitator_poll_interval < MIN_FACILITATOR_POLL_INTERVAL) {
- puts("Error: facilitator_poll_interval must be a nonnegative number at least " + MIN_FACILITATOR_POLL_INTERVAL + ".");
- this.die();
- return;
- }
-
- if (query["ratelimit"] === "off")
- rate_limit_bytes = undefined;
- else
- rate_limit_bytes = get_query_param_byte_count(query, "ratelimit", DEFAULT_RATE_LIMIT);
- if (rate_limit_bytes === undefined) {
- this.rate_limit = new DummyRateLimit();
- } else if (rate_limit_bytes === null || rate_limit_bytes < MIN_FACILITATOR_POLL_INTERVAL) {
- puts("Error: ratelimit must be a nonnegative number at least " + MIN_RATE_LIMIT + ".");
- this.die();
- return;
- } else {
- this.rate_limit = new BucketRateLimit(rate_limit_bytes * RATE_LIMIT_HISTORY, RATE_LIMIT_HISTORY);
- }
-
- client_addr = get_query_param_addr(query, "client");
- relay_addr = get_query_param_addr(query, "relay");
- if (client_addr !== undefined && relay_addr !== undefined) {
- this.make_proxy_pair(client_addr, relay_addr);
- } else if (client_addr !== undefined) {
- puts("Error: the \"client\" parameter requires \"relay\" also.")
- this.die();
- return;
- } else if (relay_addr !== undefined) {
- puts("Error: the \"relay\" parameter requires \"client\" also.")
- this.die();
- return;
- } else {
- this.proxy_main();
- }
- };
-
- this.proxy_main = function() {
- var xhr;
-
- if (this.proxy_pairs.length >= this.max_num_proxy_pairs) {
- setTimeout(this.proxy_main.bind(this), this.facilitator_poll_interval);
- return;
- }
-
- xhr = new XMLHttpRequest();
- try {
- xhr.open("GET", this.fac_url);
- } catch (err) {
- /* An exception happens here when, for example, NoScript allows the
- domain on which the proxy badge runs, but not the domain to which
- it's trying to make the HTTP request. The exception message is
- like "Component returned failure code: 0x805e0006
- [nsIXMLHttpRequest.open]" on Firefox. */
- puts("Facilitator: exception while connecting: " + repr(err.message) + ".");
- this.die();
- return;
- }
- xhr.responseType = "text";
- xhr.onreadystatechange = function() {
- if (xhr.readyState === xhr.DONE) {
- if (xhr.status === 200)
- this.fac_complete(xhr.responseText);
- else
- puts("Facilitator: can't connect: got status " + repr(xhr.status) + " and status text " + repr(xhr.statusText) + ".");
- }
- }.bind(this);
- puts("Facilitator: connecting to " + this.fac_url + ".");
- xhr.send(null);
- };
-
- this.fac_complete = function(text) {
- var response;
- var client_addr;
- var relay_addr;
-
- setTimeout(this.proxy_main.bind(this), this.facilitator_poll_interval * 1000);
-
- response = parse_query_string(text);
-
- if (!response.client) {
- puts("No clients.");
- return;
- }
- client_addr = parse_addr_spec(response.client);
- if (client_addr === null) {
- puts("Error: can't parse client spec " + repr(response.client) + ".");
- return;
- }
- if (!response.relay) {
- puts("Error: missing relay in response.");
- return;
- }
- relay_addr = parse_addr_spec(response.relay);
- if (relay_addr === null) {
- puts("Error: can't parse relay spec " + repr(response.relay) + ".");
- return;
- }
- puts("Facilitator: got client:" + repr(client_addr) + " "
- + "relay:" + repr(relay_addr) + ".");
-
- this.make_proxy_pair(client_addr, relay_addr);
- };
-
- this.make_proxy_pair = function(client_addr, relay_addr) {
- var proxy_pair;
-
- proxy_pair = new ProxyPair(client_addr, relay_addr, this.rate_limit);
- this.proxy_pairs.push(proxy_pair);
- proxy_pair.complete_callback = function(event) {
- puts("Complete.");
- /* Delete from the list of active proxy pairs. */
- this.proxy_pairs.splice(this.proxy_pairs.indexOf(proxy_pair), 1);
- if (this.badge)
- this.badge.proxy_end();
- }.bind(this);
- try {
- proxy_pair.connect();
- } catch (err) {
- puts("ProxyPair: exception while connecting: " + repr(err.message) + ".");
- this.die();
- return;
- }
-
- if (this.badge)
- this.badge.proxy_begin();
- };
-
- /* Cease all network operations and prevent any future ones. */
- this.disable = function() {
- puts("Disabling.");
- this.proxy_main = function() { };
- this.make_proxy_pair = function(client_addr, relay_addr) { };
- while (this.proxy_pairs.length > 0)
- this.proxy_pairs.pop().close();
- if (this.badge)
- this.badge.disable();
- };
-
- this.die = function() {
- puts("Dying.");
- if (this.badge)
- this.badge.die();
- };
-}
-
-/* An instance of a client-relay connection. */
-function ProxyPair(client_addr, relay_addr, rate_limit) {
- var MAX_BUFFER = 10 * 1024 * 1024;
-
- function log(s)
- {
- puts(s)
- }
-
- this.client_addr = client_addr;
- this.relay_addr = relay_addr;
- this.rate_limit = rate_limit;
-
- this.c2r_schedule = [];
- this.r2c_schedule = [];
-
- this.running = true;
- this.flush_timeout_id = null;
-
- /* This callback function can be overridden by external callers. */
- this.complete_callback = function() {
- };
-
- /* Return a function that shows an error message and closes the other
- half of a communication pair. */
- this.make_onerror_callback = function(partner)
- {
- return function(event) {
- var ws = event.target;
-
- log(ws.label + ": error.");
- partner.close();
- }.bind(this);
- };
-
- this.onopen_callback = function(event) {
- var ws = event.target;
-
- log(ws.label + ": connected.");
- }.bind(this);
-
- this.onclose_callback = function(event) {
- var ws = event.target;
-
- log(ws.label + ": closed.");
- this.flush();
-
- if (this.running && is_closed(this.client_s) && is_closed(this.relay_s)) {
- this.running = false;
- this.complete_callback();
- }
- }.bind(this);
-
- this.onmessage_client_to_relay = function(event) {
- this.c2r_schedule.push(event.data);
- this.flush();
- }.bind(this);
-
- this.onmessage_relay_to_client = function(event) {
- this.r2c_schedule.push(event.data);
- this.flush();
- }.bind(this);
-
- this.connect = function() {
- log("Client: connecting.");
- this.client_s = make_websocket(this.client_addr);
-
- log("Relay: connecting.");
- this.relay_s = make_websocket(this.relay_addr);
-
- this.client_s.label = "Client";
- this.client_s.onopen = this.onopen_callback;
- this.client_s.onclose = this.onclose_callback;
- this.client_s.onerror = this.make_onerror_callback(this.relay_s);
- this.client_s.onmessage = this.onmessage_client_to_relay;
-
- this.relay_s.label = "Relay";
- this.relay_s.onopen = this.onopen_callback;
- this.relay_s.onclose = this.onclose_callback;
- this.relay_s.onerror = this.make_onerror_callback(this.client_s);
- this.relay_s.onmessage = this.onmessage_relay_to_client;
- };
-
- function is_open(ws)
- {
- return ws.readyState === ws.OPEN;
- }
-
- function is_closed(ws)
- {
- return ws.readyState === ws.CLOSED;
- }
-
- this.close = function() {
- this.client_s.close();
- this.relay_s.close();
- };
-
- /* Send as much data as the rate limit currently allows. */
- this.flush = function() {
- var busy;
-
- if (this.flush_timeout_id)
- clearTimeout(this.flush_timeout_id);
- this.flush_timeout_id = null;
-
- busy = true;
- while (busy && !this.rate_limit.is_limited()) {
- var chunk;
-
- busy = false;
- if (is_open(this.client_s) && this.client_s.bufferedAmount < MAX_BUFFER && this.r2c_schedule.length > 0) {
- chunk = this.r2c_schedule.shift();
- this.rate_limit.update(chunk.length);
- this.client_s.send(chunk);
- busy = true;
- }
- if (is_open(this.relay_s) && this.relay_s.bufferedAmount < MAX_BUFFER && this.c2r_schedule.length > 0) {
- chunk = this.c2r_schedule.shift();
- this.rate_limit.update(chunk.length);
- this.relay_s.send(chunk);
- busy = true;
- }
- }
-
- if (is_closed(this.relay_s) && !is_closed(this.client_s) && this.client_s.bufferedAmount === 0 && this.r2c_schedule.length === 0) {
- log("Client: closing.");
- this.client_s.close();
- }
- if (is_closed(this.client_s) && !is_closed(this.relay_s) && this.relay_s.bufferedAmount === 0 && this.c2r_schedule.length === 0) {
- log("Relay: closing.");
- this.relay_s.close();
- }
-
- if (this.r2c_schedule.length > 0 || this.client_s.bufferedAmount > 0
- || this.c2r_schedule.length > 0 || this.relay_s.bufferedAmount > 0)
- this.flush_timeout_id = setTimeout(this.flush.bind(this), this.rate_limit.when() * 1000);
- };
-}
-
-function BucketRateLimit(capacity, time) {
- this.amount = 0.0;
- /* capacity / time is the rate we are aiming for. */
- this.capacity = capacity;
- this.time = time;
- this.last_update = new Date();
-
- this.age = function() {
- var now;
- var delta;
-
- now = new Date();
- delta = (now - this.last_update) / 1000.0;
- this.last_update = now;
-
- this.amount -= delta * this.capacity / this.time;
- if (this.amount < 0.0)
- this.amount = 0.0;
- };
-
- this.update = function(n) {
- this.age();
- this.amount += n;
-
- return this.amount <= this.capacity;
- };
-
- /* How many seconds in the future will the limit expire? */
- this.when = function() {
- this.age();
-
- return (this.amount - this.capacity) / (this.capacity / this.time);
- }
-
- this.is_limited = function() {
- this.age();
-
- return this.amount > this.capacity;
- }
-}
-
-/* A rate limiter that never limits. */
-function DummyRateLimit(capacity, time) {
- this.update = function(n) {
- return true;
- };
-
- this.when = function() {
- return 0.0;
- }
-
- this.is_limited = function() {
- return false;
- }
-}
-
-var HTML_ESCAPES = {
- "&": "amp",
- "<": "lt",
- ">": "gt",
- "'": "apos",
- "\"": "quot"
-};
-function escape_html(s) {
- return s.replace(/&<>'"/, function(x) { return HTML_ESCAPES[x] });
-}
-
-/* The usual embedded HTML badge. The "elem" member is a DOM element that can be
- included elsewhere. */
-function Badge() {
- /* Number of proxy pairs currently connected. */
- this.num_proxy_pairs = 0;
-
- var table, tr, td, div, a, img;
-
- table = document.createElement("table");
- tr = document.createElement("tr");
- table.appendChild(tr);
- td = document.createElement("td");
- tr.appendChild(td);
- a = document.createElement("a");
- a.setAttribute("href", "http://crypto.stanford.edu/flashproxy/");
- a.setAttribute("target", "_parent");
- td.appendChild(a);
- img = document.createElement("img");
- img.setAttribute("src", "badge.png");
- img.setAttribute("alt", "Internet freedom");
- a.appendChild(img);
-
- this.elem = table;
- this.elem.className = "idle";
-
- a = document.createElement("a");
- a.setAttribute("href", "#");
- this.disable_button = document.createElement("div");
- /* HEAVY MULTIPLICATION X */
- this.disable_button.innerHTML = "✖";
- this.disable_button.className = "disable-button";
- a.appendChild(this.disable_button);
- td.appendChild(a);
-
- this.proxy_begin = function() {
- this.num_proxy_pairs++;
- this.elem.className = "active";
- };
-
- this.proxy_end = function() {
- this.num_proxy_pairs--;
- if (this.num_proxy_pairs <= 0) {
- this.elem.className = "idle";
- }
- }
-
- this.disable = function() {
- this.elem.className = "disabled";
- this.disable_button.style.display = "none";
- }
-
- this.die = function() {
- this.elem.className = "dead";
- this.disable_button.style.display = "none";
- }
-}
-
-function quote(s) {
- return "\"" + s.replace(/([\\\"])/, "\\$1") + "\"";
-}
-
-function maybe_quote(s) {
- if (!/^[a-zA-Z_]\w*$/.test(s))
- return quote(s);
- else
- return s;
-}
-
-function repr(x) {
- if (x === null) {
- return "null";
- } else if (typeof x === "undefined") {
- return "undefined";
- } else if (typeof x === "object") {
- var elems = [];
- for (var k in x)
- elems.push(maybe_quote(k) + ": " + repr(x[k]));
- return "{ " + elems.join(", ") + " }";
- } else if (typeof x === "string") {
- return quote(x);
- } else {
- return x.toString();
- }
-}
-
-/* Are circumstances such that we should self-disable and not be a
- proxy? We take a best-effort guess as to whether this device runs on
- a battery or the data transfer might be expensive.
-
- http://www.zytrax.com/tech/web/mobile_ids.html
- http://googlewebmastercentral.blogspot.com/2011/03/mo-better-to-also-detect-mobile-user.html
- http://search.cpan.org/~cmanley/Mobile-UserAgent-1.05/lib/Mobile/UserAgent.pm
-*/
-function flashproxy_should_disable() {
- var ua;
-
- ua = window.navigator.userAgent;
- if (ua !== null) {
- var UA_LIST = [
- /\bmobile\b/i,
- /\bandroid\b/i,
- /\bopera mobi\b/i,
- ];
-
- for (var i = 0; i < UA_LIST.length; i++) {
- var re = UA_LIST[i];
-
- if (ua.match(re)) {
- puts("Disable because User-Agent matches mobile pattern " + re + ".");
- return true;
- }
- }
- }
-
- if (ua.match(/\bsafari\b/i) && !ua.match(/\bchrome\b/i)
- && !ua.match(/\bversion\/[6789]\./i)) {
- /* Disable before Safari 6.0 because it doesn't have the hybi/RFC type
- of WebSockets. */
- puts("Disable because User-Agent is Safari before 6.0.");
- return true;
- }
-
- if (!WebSocket) {
- /* No WebSocket support. */
- puts("Disable because of no WebSocket support.");
- return true;
- }
-
- return false;
-}
-
-function flashproxy_badge_insert() {
- var fp;
- var e;
-
- fp = new FlashProxy();
- if (flashproxy_should_disable())
- fp.disable();
-
- /* http://intertwingly.net/blog/2006/11/10/Thats-Not-Write for this trick to
- insert right after the <script> element in the DOM. */
- e = document;
- while (e.lastChild && e.lastChild.nodeType === 1) {
- e = e.lastChild;
- }
- e.parentNode.appendChild(fp.badge_elem);
-
- return fp;
-}
diff --git a/proxy/README b/proxy/README
new file mode 100644
index 0000000..b9d9003
--- /dev/null
+++ b/proxy/README
@@ -0,0 +1,4 @@
+This directory contains the flash proxy JavaScript proxy program and
+associated HTML and media files. End users don't ahve to do anything
+with these files. They are meant to be installed on a centralized web
+server and then accessed through a browser.
diff --git a/proxy/badge.png b/proxy/badge.png
new file mode 100644
index 0000000..068cf78
Binary files /dev/null and b/proxy/badge.png differ
diff --git a/proxy/badge.xcf b/proxy/badge.xcf
new file mode 100644
index 0000000..d12ff1f
Binary files /dev/null and b/proxy/badge.xcf differ
diff --git a/proxy/embed.html b/proxy/embed.html
new file mode 100644
index 0000000..95821ce
--- /dev/null
+++ b/proxy/embed.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<meta http-equiv="refresh" content="86400">
+<style type="text/css">
+html {
+ width: 100%;
+ height: 100%;
+}
+body {
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+}
+#flashproxy-badge {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ border-collapse: collapse;
+ line-height: 0;
+}
+#flashproxy-badge.idle {
+ background-color: #227;
+}
+#flashproxy-badge.active {
+ background-color: #28f;
+}
+#flashproxy-badge.disabled {
+ background-color: #777;
+}
+#flashproxy-badge.dead {
+ background-color: #111;
+}
+#flashproxy-badge td {
+ position: relative;
+ margin: 0;
+ padding: 0;
+ vertical-align: middle;
+ text-align: center;
+}
+#flashproxy-badge td .disable-button {
+ position: absolute;
+ margin: 0;
+ padding: 0;
+ border: 1px solid white;
+ color: white;
+ background-color: #B00;
+ top: 1px;
+ right: 1px;
+ width: 11px;
+ height: 11px;
+ text-align: center;
+ line-height: 11px;
+ font-size: 11px;
+ display: none;
+}
+#flashproxy-badge a img {
+ border: 0;
+}
+#flashproxy-badge.debug {
+ position: absolute;
+ margin: 0;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+ color: #4c4;
+ background-color: #021;
+ line-height: inherit;
+}
+</style>
+</head>
+<body>
+<script type="text/javascript" src="flashproxy.js"></script>
+<script type="text/javascript">
+flashproxy_badge_insert().start();
+</script>
+<noscript>
+<table id="flashproxy-badge" class="disabled"><tr><td><a href="//crypto.stanford.edu/flashproxy/" target="_parent"><img src="badge.png" alt="Internet freedom"></a></td></tr></table>
+</noscript>
+</body>
+</html>
diff --git a/proxy/flashproxy-test.js b/proxy/flashproxy-test.js
new file mode 100755
index 0000000..2dc185a
--- /dev/null
+++ b/proxy/flashproxy-test.js
@@ -0,0 +1,239 @@
+#!/usr/bin/js
+
+/* To run this test program, install the Rhino JavaScript interpreter
+ (apt-get install rhino). */
+
+var VERBOSE = false;
+if ("-v" in arguments)
+ VERBOSE = true;
+
+var num_tests = 0;
+var num_failed = 0;
+
+var window = {location: {search: "?"}};
+
+load("flashproxy.js");
+
+function objects_equal(a, b)
+{
+ if ((a === null) != (b === null))
+ return false;
+ if (typeof a != typeof b)
+ return false;
+ if (typeof a != "object")
+ return a == b;
+
+ for (var k in a) {
+ if (!objects_equal(a[k], b[k]))
+ return false;
+ }
+ for (var k in b) {
+ if (!objects_equal(a[k], b[k]))
+ return false;
+ }
+
+ return true;
+}
+
+var top = true;
+function announce(test_name)
+{
+ if (VERBOSE) {
+ if (!top)
+ print();
+ print(test_name);
+ }
+ top = false;
+}
+
+function pass(test)
+{
+ num_tests++;
+ if (VERBOSE)
+ print("PASS " + repr(test));
+}
+
+function fail(test, expected, actual)
+{
+ num_tests++;
+ num_failed++;
+ print("FAIL " + repr(test) + " expected: " + repr(expected) + " actual: " + repr(actual));
+}
+
+function test_build_url()
+{
+ var TESTS = [
+ { args: ["http", "example.com"],
+ expected: "http://example.com" },
+ { args: ["http", "example.com", 80],
+ expected: "http://example.com" },
+ { args: ["http", "example.com", 81],
+ expected: "http://example.com:81" },
+ { args: ["https", "example.com", 443],
+ expected: "https://example.com" },
+ { args: ["https", "example.com", 444],
+ expected: "https://example.com:444" },
+ { 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", []],
+ 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", [["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", "bog][us"],
+ expected: "http://bog%5D%5Bus" },
+ { args: ["http", "bog:u]s"],
+ expected: "http://bog%3Au%5Ds" },
+ ];
+
+ announce("test_build_url");
+ for (var i = 0; i < TESTS.length; i++) {
+ var test = TESTS[i];
+ var actual;
+
+ actual = build_url.apply(undefined, test.args);
+ if (objects_equal(actual, test.expected))
+ pass(test.args);
+ else
+ fail(test.args, test.expected, actual);
+ }
+}
+
+function test_parse_query_string()
+{
+ var 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("test_parse_query_string");
+ for (var i = 0; i < TESTS.length; i++) {
+ var test = TESTS[i];
+ var actual;
+
+ actual = parse_query_string(test.qs);
+ if (objects_equal(actual, test.expected))
+ pass(test.qs);
+ else
+ fail(test.qs, test.expected, actual);
+ }
+}
+
+function test_parse_addr_spec()
+{
+ var TESTS = [
+ { spec: "",
+ expected: null },
+ { spec: "3.3.3.3:4444",
+ expected: { host: "3.3.3.3", port: 4444 } },
+ { spec: "3.3.3.3",
+ expected: null },
+ { spec: "3.3.3.3:0x1111",
+ expected: null },
+ { spec: "3.3.3.3:-4444",
+ expected: null },
+ { spec: "3.3.3.3:65536",
+ expected: null },
+ { spec: "[1:2::a:f]:4444",
+ expected: { host: "1:2::a:f", port: 4444 } },
+ { spec: "[1:2::a:f]",
+ expected: null },
+ { spec: "[1:2::a:f]:0x1111",
+ expected: null },
+ { spec: "[1:2::a:f]:-4444",
+ expected: null },
+ { spec: "[1:2::a:f]:65536",
+ expected: null },
+ { spec: "[1:2::ffff:1.2.3.4]:4444",
+ expected: { host: "1:2::ffff:1.2.3.4", port: 4444 } },
+ ];
+
+ announce("test_parse_addr_spec");
+ for (var i = 0; i < TESTS.length; i++) {
+ var test = TESTS[i];
+ var actual;
+
+ actual = parse_addr_spec(test.spec);
+ if (objects_equal(actual, test.expected))
+ pass(test.spec);
+ else
+ fail(test.spec, test.expected, actual);
+ }
+}
+
+function test_get_query_param_addr()
+{
+ var DEFAULT = { host: "1.1.1.1", port: 2222 };
+ var TESTS = [
+ { query: { },
+ expected: DEFAULT },
+ { query: { addr: "3.3.3.3:4444" },
+ expected: { host: "3.3.3.3", port: 4444 } },
+ { query: { x: "3.3.3.3:4444" },
+ expected: DEFAULT },
+ { query: { addr: "---" },
+ expected: null },
+ ];
+
+ announce("test_get_query_param_addr");
+ for (var i = 0; i < TESTS.length; i++) {
+ var test = TESTS[i];
+ var actual;
+
+ actual = get_query_param_addr(test.query, "addr", DEFAULT);
+ if (objects_equal(actual, test.expected))
+ pass(test.query);
+ else
+ fail(test.query, test.expected, actual);
+ }
+}
+
+test_build_url();
+test_parse_query_string();
+test_parse_addr_spec();
+test_get_query_param_addr();
+
+if (num_failed == 0)
+ quit(0);
+else
+ quit(1);
diff --git a/proxy/flashproxy.js b/proxy/flashproxy.js
new file mode 100644
index 0000000..a2e8863
--- /dev/null
+++ b/proxy/flashproxy.js
@@ -0,0 +1,898 @@
+/* Query string parameters. These change how the program runs from the outside.
+ * For example:
+ * http://www.example.com/embed.html?facilitator=127.0.0.1:9002&debug=1
+ *
+ * client=<HOST>:<PORT>
+ * The address of the client to connect to. The proxy normally receives this
+ * information from the facilitator. When this option is used, the facilitator
+ * query is not done. The "relay" parameter must be given as well.
+ *
+ * debug=1
+ * If set (to any value), show verbose terminal-like output instead of the
+ * badge.
+ *
+ * facilitator=https://host:port/
+ * The URL of the facilitator CGI script. By default it is
+ * DEFAULT_FACILITATOR_URL.
+ *
+ * facilitator_poll_interval=<FLOAT>
+ * How often to poll the facilitator, in seconds. The default is
+ * DEFAULT_FACILITATOR_POLL_INTERVAL. There is a sanity-check minimum of 1.0 s.
+ *
+ * max_clients=<NUM>
+ * How many clients to serve concurrently. The default is
+ * DEFAULT_MAX_NUM_PROXY_PAIRS.
+ *
+ * relay=<HOST>:<PORT>
+ * The address of the relay to connect to. The proxy normally receives this
+ * information from the facilitator. When this option is used, the facilitator
+ * query is not done. The "client" parameter must be given as well.
+ *
+ * ratelimit=<FLOAT>(<UNIT>)?|off
+ * What rate to limit all proxy traffic combined to. The special value "off"
+ * disables the limit. The default is DEFAULT_RATE_LIMIT. There is a
+ * sanity-check minimum of "10K".
+ */
+
+/* WebSocket links.
+ *
+ * The WebSocket Protocol
+ * https://tools.ietf.org/html/rfc6455
+ *
+ * The WebSocket API
+ * http://dev.w3.org/html5/websockets/
+ *
+ * MDN page with browser compatibility
+ * https://developer.mozilla.org/en/WebSockets
+ *
+ * Implementation tests (including tests of binary messages)
+ * http://autobahn.ws/testsuite/reports/clients/index.html
+ */
+
+var DEFAULT_FACILITATOR_URL = "https://tor-facilitator.bamsoftware.com/";
+
+var DEFAULT_MAX_NUM_PROXY_PAIRS = 10;
+
+var DEFAULT_FACILITATOR_POLL_INTERVAL = 10.0;
+var MIN_FACILITATOR_POLL_INTERVAL = 1.0;
+
+/* Bytes per second. Set to undefined to disable limit. */
+var DEFAULT_RATE_LIMIT = undefined;
+var MIN_RATE_LIMIT = 10 * 1024;
+var RATE_LIMIT_HISTORY = 5.0;
+
+/* Firefox before version 11.0 uses the name MozWebSocket. Whether the global
+ variable WebSocket is defined indicates whether WebSocket is supported at
+ all. */
+var WebSocket = window.WebSocket || window.MozWebSocket;
+
+var query = parse_query_string(window.location.search.substr(1));
+var debug_div;
+
+if (query.debug) {
+ debug_div = document.createElement("pre");
+ debug_div.className = "debug";
+}
+
+function puts(s) {
+ if (debug_div) {
+ var at_bottom;
+
+ /* http://www.w3.org/TR/cssom-view/#element-scrolling-members */
+ at_bottom = (debug_div.scrollTop + debug_div.clientHeight === debug_div.scrollHeight);
+ debug_div.appendChild(document.createTextNode(s + "\n"));
+ if (at_bottom)
+ debug_div.scrollTop = debug_div.scrollHeight;
+ }
+}
+
+/* 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 */
+function parse_query_string(qs) {
+ var strings;
+ var result;
+
+ result = {};
+ if (qs)
+ strings = qs.split("&");
+ else
+ strings = [];
+ for (var i = 0; i < strings.length; i++) {
+ var string = strings[i];
+ var j, name, value;
+
+ 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, " "));
+ if (!(name in result))
+ result[name] = value;
+ }
+
+ return result;
+}
+
+var 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. */
+function build_url(scheme, host, port, path, params) {
+ var parts = []
+
+ parts.push(encodeURIComponent(scheme));
+ parts.push("://");
+
+ /* If it contains a colon but no square brackets, treat it like an IPv6
+ address. */
+ if (host.match(/:/) && !host.match(/[[\]]/)) {
+ parts.push("[");
+ parts.push(host);
+ parts.push("]");
+ } else {
+ parts.push(encodeURIComponent(host));
+ }
+ if (port !== undefined && port !== DEFAULT_PORTS[scheme]) {
+ parts.push(":");
+ parts.push(encodeURIComponent(port.toString()));
+ }
+
+ if (path !== undefined && path !== "") {
+ if (!path.match(/^\//))
+ path = "/" + path;
+ /* Slash is significant so we must protect it from encodeURIComponent,
+ while still encoding question mark and number sign. RFC 3986, section
+ 3.3: "The path is terminated by the first question mark ('?') or
+ number sign ('#') character, or by the end of the URI. ... A path
+ consists of a sequence of path segments separated by a slash ('/')
+ character." */
+ path = path.replace(/[^\/]+/, function(m) {
+ return encodeURIComponent(m);
+ });
+ parts.push(path);
+ }
+
+ if (params !== undefined) {
+ parts.push("?");
+ for (var i = 0; i < params.length; i++) {
+ if (i > 0)
+ parts.push("&");
+ parts.push(encodeURIComponent(params[i][0]) + "=" + encodeURIComponent(params[i][1]));
+ }
+ }
+
+ return parts.join("");
+}
+
+/* Get a query string parameter and return it as a string. Returns default_val
+ if param is not defined in the query string. */
+function get_query_param_string(query, param, default_val) {
+ var val;
+
+ val = query[param];
+ if (val === undefined)
+ return default_val;
+ else
+ return val;
+}
+
+/* Get a query string parameter, or the given default, and parse it as an
+ address spec. Returns null on a parsing error. */
+function get_query_param_addr(query, param, default_val) {
+ var val;
+
+ val = query[param];
+ if (val === undefined)
+ return default_val;
+ else
+ return parse_addr_spec(val);
+}
+
+/* Get an integer from the given movie parameter, or the given default. Returns
+ null on error. */
+function get_query_param_integer(query, param, default_val) {
+ var spec;
+ var val;
+
+ spec = query[param];
+ if (spec === undefined) {
+ return default_val;
+ } else if (!spec.match(/^-?[0-9]+/)) {
+ return null;
+ } else {
+ val = parseInt(spec, 10);
+ if (isNaN(val))
+ return null;
+ else
+ return val;
+ }
+}
+
+/* Get a number from the given movie parameter, or the given default. Returns
+ null on error. */
+function get_query_param_number(query, param, default_val) {
+ var spec;
+ var val;
+
+ spec = query[param];
+ if (spec === undefined) {
+ return default_val;
+ } else {
+ val = Number(spec);
+ if (isNaN(val))
+ return null;
+ else
+ return val;
+ }
+}
+
+/* Get a floating-point number of seconds from a time specification. The only
+ time specification format is a decimal number of seconds. Returns null on
+ error. */
+function get_query_param_timespec(query, param, default_val) {
+ return get_query_param_number(query, param, default_val);
+}
+
+/* Parse a count of bytes. A suffix of "k", "m", or "g" (or uppercase)
+ does what you would think. Returns null on error. */
+function parse_byte_count(spec) {
+ var UNITS = {
+ k: 1024, m: 1024 * 1024, g: 1024 * 1024 * 1024,
+ K: 1024, M: 1024 * 1024, G: 1024 * 1024 * 1024
+ };
+ var count, units;
+ var matches;
+
+ matches = spec.match(/^(\d+(?:\.\d*)?)(\w*)$/);
+ if (matches === null)
+ return null;
+
+ count = Number(matches[1]);
+ if (isNaN(count))
+ return null;
+
+ if (matches[2] === "") {
+ units = 1;
+ } else {
+ units = UNITS[matches[2]];
+ if (units === null)
+ return null;
+ }
+
+ return count * Number(units);
+}
+
+/* Get a count of bytes from a string specification like "100" or "1.3m".
+ Returns null on error. */
+function get_query_param_byte_count(query, param, default_val) {
+ var spec;
+
+ spec = query[param];
+ if (spec === undefined)
+ return default_val;
+ else
+ return parse_byte_count(spec);
+}
+
+/* Parse an address in the form "host:port". Returns an Object with
+ keys "host" (String) and "port" (int). Returns null on error. */
+function parse_addr_spec(spec) {
+ var m, host, port;
+
+ m = null;
+ /* IPv6 syntax. */
+ if (!m)
+ m = spec.match(/^\[([\0-9a-fA-F:.]+)\]:([0-9]+)$/);
+ /* IPv4 syntax. */
+ if (!m)
+ m = spec.match(/^([0-9.]+):([0-9]+)$/);
+ if (!m)
+ return null;
+ host = m[1];
+ port = parseInt(m[2], 10);
+ if (isNaN(port) || port < 0 || port > 65535)
+ return null;
+
+ return { host: host, port: port }
+}
+
+function format_addr(addr) {
+ return addr.host + ":" + addr.port;
+}
+
+/* Does the WebSocket implementation in this browser support binary frames? (RFC
+ 6455 section 5.6.) If not, we have to use base64-encoded text frames. It is
+ assumed that the client and relay endpoints always support binary frames. */
+function have_websocket_binary_frames() {
+ var ua, matches;
+
+ ua = window.navigator.userAgent;
+ if (ua === null)
+ return false;
+
+ /* We are cool for Chrome 16 or Safari 6.0. */
+
+ matches = ua.match(/\bchrome\/(\d+)/i);
+ if (matches !== null && Number(matches[1]) >= 16)
+ return true;
+
+ matches = ua.match(/\bversion\/(\d+)/i);
+ if (ua.match(/\bsafari\b/i) && !ua.match(/\bchrome\b/i)
+ && Number(matches[1]) >= 6)
+ return true;
+
+ return false;
+}
+
+function make_websocket(addr) {
+ var url;
+ var ws;
+
+ url = build_url("ws", addr.host, addr.port, "/");
+
+ if (have_websocket_binary_frames())
+ ws = new WebSocket(url);
+ else
+ ws = new WebSocket(url, "base64");
+ /* "User agents can use this as a hint for how to handle incoming binary
+ data: if the attribute is set to 'blob', it is safe to spool it to disk,
+ and if it is set to 'arraybuffer', it is likely more efficient to keep
+ the data in memory." */
+ ws.binaryType = "arraybuffer";
+
+ return ws;
+}
+
+function FlashProxy() {
+ if (query.debug) {
+ this.badge_elem = debug_div;
+ } else {
+ this.badge = new Badge();
+ this.badge.elem.onmouseover = function(event) {
+ this.badge.disable_button.style.display = "block";
+ }.bind(this);
+ this.badge.elem.onmouseout = function(event) {
+ this.badge.disable_button.style.display = "none";
+ }.bind(this);
+ /* Click a button to disable the badge. */
+ this.badge.disable_button.onclick = function(event) {
+ this.disable();
+ this.badge.disable_button.parentNode.removeChild(this.badge.disable_button);
+ }.bind(this);
+ this.badge_elem = this.badge.elem;
+ }
+ this.badge_elem.setAttribute("id", "flashproxy-badge");
+
+ this.proxy_pairs = [];
+
+ this.start = function() {
+ var client_addr;
+ var relay_addr;
+ var rate_limit_bytes;
+
+ this.fac_url = get_query_param_string(query, "facilitator", DEFAULT_FACILITATOR_URL);
+
+ this.max_num_proxy_pairs = get_query_param_integer(query, "max_clients", DEFAULT_MAX_NUM_PROXY_PAIRS);
+ if (this.max_num_proxy_pairs === null || this.max_num_proxy_pairs < 0) {
+ puts("Error: max_clients must be a nonnegative integer.");
+ this.die();
+ return;
+ }
+
+ this.facilitator_poll_interval = get_query_param_timespec(query, "facilitator_poll_interval", DEFAULT_FACILITATOR_POLL_INTERVAL);
+ if (this.facilitator_poll_interval === null || this.facilitator_poll_interval < MIN_FACILITATOR_POLL_INTERVAL) {
+ puts("Error: facilitator_poll_interval must be a nonnegative number at least " + MIN_FACILITATOR_POLL_INTERVAL + ".");
+ this.die();
+ return;
+ }
+
+ if (query["ratelimit"] === "off")
+ rate_limit_bytes = undefined;
+ else
+ rate_limit_bytes = get_query_param_byte_count(query, "ratelimit", DEFAULT_RATE_LIMIT);
+ if (rate_limit_bytes === undefined) {
+ this.rate_limit = new DummyRateLimit();
+ } else if (rate_limit_bytes === null || rate_limit_bytes < MIN_FACILITATOR_POLL_INTERVAL) {
+ puts("Error: ratelimit must be a nonnegative number at least " + MIN_RATE_LIMIT + ".");
+ this.die();
+ return;
+ } else {
+ this.rate_limit = new BucketRateLimit(rate_limit_bytes * RATE_LIMIT_HISTORY, RATE_LIMIT_HISTORY);
+ }
+
+ client_addr = get_query_param_addr(query, "client");
+ relay_addr = get_query_param_addr(query, "relay");
+ if (client_addr !== undefined && relay_addr !== undefined) {
+ this.make_proxy_pair(client_addr, relay_addr);
+ } else if (client_addr !== undefined) {
+ puts("Error: the \"client\" parameter requires \"relay\" also.")
+ this.die();
+ return;
+ } else if (relay_addr !== undefined) {
+ puts("Error: the \"relay\" parameter requires \"client\" also.")
+ this.die();
+ return;
+ } else {
+ this.proxy_main();
+ }
+ };
+
+ this.proxy_main = function() {
+ var xhr;
+
+ if (this.proxy_pairs.length >= this.max_num_proxy_pairs) {
+ setTimeout(this.proxy_main.bind(this), this.facilitator_poll_interval);
+ return;
+ }
+
+ xhr = new XMLHttpRequest();
+ try {
+ xhr.open("GET", this.fac_url);
+ } catch (err) {
+ /* An exception happens here when, for example, NoScript allows the
+ domain on which the proxy badge runs, but not the domain to which
+ it's trying to make the HTTP request. The exception message is
+ like "Component returned failure code: 0x805e0006
+ [nsIXMLHttpRequest.open]" on Firefox. */
+ puts("Facilitator: exception while connecting: " + repr(err.message) + ".");
+ this.die();
+ return;
+ }
+ xhr.responseType = "text";
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === xhr.DONE) {
+ if (xhr.status === 200)
+ this.fac_complete(xhr.responseText);
+ else
+ puts("Facilitator: can't connect: got status " + repr(xhr.status) + " and status text " + repr(xhr.statusText) + ".");
+ }
+ }.bind(this);
+ puts("Facilitator: connecting to " + this.fac_url + ".");
+ xhr.send(null);
+ };
+
+ this.fac_complete = function(text) {
+ var response;
+ var client_addr;
+ var relay_addr;
+
+ setTimeout(this.proxy_main.bind(this), this.facilitator_poll_interval * 1000);
+
+ response = parse_query_string(text);
+
+ if (!response.client) {
+ puts("No clients.");
+ return;
+ }
+ client_addr = parse_addr_spec(response.client);
+ if (client_addr === null) {
+ puts("Error: can't parse client spec " + repr(response.client) + ".");
+ return;
+ }
+ if (!response.relay) {
+ puts("Error: missing relay in response.");
+ return;
+ }
+ relay_addr = parse_addr_spec(response.relay);
+ if (relay_addr === null) {
+ puts("Error: can't parse relay spec " + repr(response.relay) + ".");
+ return;
+ }
+ puts("Facilitator: got client:" + repr(client_addr) + " "
+ + "relay:" + repr(relay_addr) + ".");
+
+ this.make_proxy_pair(client_addr, relay_addr);
+ };
+
+ this.make_proxy_pair = function(client_addr, relay_addr) {
+ var proxy_pair;
+
+ proxy_pair = new ProxyPair(client_addr, relay_addr, this.rate_limit);
+ this.proxy_pairs.push(proxy_pair);
+ proxy_pair.complete_callback = function(event) {
+ puts("Complete.");
+ /* Delete from the list of active proxy pairs. */
+ this.proxy_pairs.splice(this.proxy_pairs.indexOf(proxy_pair), 1);
+ if (this.badge)
+ this.badge.proxy_end();
+ }.bind(this);
+ try {
+ proxy_pair.connect();
+ } catch (err) {
+ puts("ProxyPair: exception while connecting: " + repr(err.message) + ".");
+ this.die();
+ return;
+ }
+
+ if (this.badge)
+ this.badge.proxy_begin();
+ };
+
+ /* Cease all network operations and prevent any future ones. */
+ this.disable = function() {
+ puts("Disabling.");
+ this.proxy_main = function() { };
+ this.make_proxy_pair = function(client_addr, relay_addr) { };
+ while (this.proxy_pairs.length > 0)
+ this.proxy_pairs.pop().close();
+ if (this.badge)
+ this.badge.disable();
+ };
+
+ this.die = function() {
+ puts("Dying.");
+ if (this.badge)
+ this.badge.die();
+ };
+}
+
+/* An instance of a client-relay connection. */
+function ProxyPair(client_addr, relay_addr, rate_limit) {
+ var MAX_BUFFER = 10 * 1024 * 1024;
+
+ function log(s)
+ {
+ puts(s)
+ }
+
+ this.client_addr = client_addr;
+ this.relay_addr = relay_addr;
+ this.rate_limit = rate_limit;
+
+ this.c2r_schedule = [];
+ this.r2c_schedule = [];
+
+ this.running = true;
+ this.flush_timeout_id = null;
+
+ /* This callback function can be overridden by external callers. */
+ this.complete_callback = function() {
+ };
+
+ /* Return a function that shows an error message and closes the other
+ half of a communication pair. */
+ this.make_onerror_callback = function(partner)
+ {
+ return function(event) {
+ var ws = event.target;
+
+ log(ws.label + ": error.");
+ partner.close();
+ }.bind(this);
+ };
+
+ this.onopen_callback = function(event) {
+ var ws = event.target;
+
+ log(ws.label + ": connected.");
+ }.bind(this);
+
+ this.onclose_callback = function(event) {
+ var ws = event.target;
+
+ log(ws.label + ": closed.");
+ this.flush();
+
+ if (this.running && is_closed(this.client_s) && is_closed(this.relay_s)) {
+ this.running = false;
+ this.complete_callback();
+ }
+ }.bind(this);
+
+ this.onmessage_client_to_relay = function(event) {
+ this.c2r_schedule.push(event.data);
+ this.flush();
+ }.bind(this);
+
+ this.onmessage_relay_to_client = function(event) {
+ this.r2c_schedule.push(event.data);
+ this.flush();
+ }.bind(this);
+
+ this.connect = function() {
+ log("Client: connecting.");
+ this.client_s = make_websocket(this.client_addr);
+
+ log("Relay: connecting.");
+ this.relay_s = make_websocket(this.relay_addr);
+
+ this.client_s.label = "Client";
+ this.client_s.onopen = this.onopen_callback;
+ this.client_s.onclose = this.onclose_callback;
+ this.client_s.onerror = this.make_onerror_callback(this.relay_s);
+ this.client_s.onmessage = this.onmessage_client_to_relay;
+
+ this.relay_s.label = "Relay";
+ this.relay_s.onopen = this.onopen_callback;
+ this.relay_s.onclose = this.onclose_callback;
+ this.relay_s.onerror = this.make_onerror_callback(this.client_s);
+ this.relay_s.onmessage = this.onmessage_relay_to_client;
+ };
+
+ function is_open(ws)
+ {
+ return ws.readyState === ws.OPEN;
+ }
+
+ function is_closed(ws)
+ {
+ return ws.readyState === ws.CLOSED;
+ }
+
+ this.close = function() {
+ this.client_s.close();
+ this.relay_s.close();
+ };
+
+ /* Send as much data as the rate limit currently allows. */
+ this.flush = function() {
+ var busy;
+
+ if (this.flush_timeout_id)
+ clearTimeout(this.flush_timeout_id);
+ this.flush_timeout_id = null;
+
+ busy = true;
+ while (busy && !this.rate_limit.is_limited()) {
+ var chunk;
+
+ busy = false;
+ if (is_open(this.client_s) && this.client_s.bufferedAmount < MAX_BUFFER && this.r2c_schedule.length > 0) {
+ chunk = this.r2c_schedule.shift();
+ this.rate_limit.update(chunk.length);
+ this.client_s.send(chunk);
+ busy = true;
+ }
+ if (is_open(this.relay_s) && this.relay_s.bufferedAmount < MAX_BUFFER && this.c2r_schedule.length > 0) {
+ chunk = this.c2r_schedule.shift();
+ this.rate_limit.update(chunk.length);
+ this.relay_s.send(chunk);
+ busy = true;
+ }
+ }
+
+ if (is_closed(this.relay_s) && !is_closed(this.client_s) && this.client_s.bufferedAmount === 0 && this.r2c_schedule.length === 0) {
+ log("Client: closing.");
+ this.client_s.close();
+ }
+ if (is_closed(this.client_s) && !is_closed(this.relay_s) && this.relay_s.bufferedAmount === 0 && this.c2r_schedule.length === 0) {
+ log("Relay: closing.");
+ this.relay_s.close();
+ }
+
+ if (this.r2c_schedule.length > 0 || this.client_s.bufferedAmount > 0
+ || this.c2r_schedule.length > 0 || this.relay_s.bufferedAmount > 0)
+ this.flush_timeout_id = setTimeout(this.flush.bind(this), this.rate_limit.when() * 1000);
+ };
+}
+
+function BucketRateLimit(capacity, time) {
+ this.amount = 0.0;
+ /* capacity / time is the rate we are aiming for. */
+ this.capacity = capacity;
+ this.time = time;
+ this.last_update = new Date();
+
+ this.age = function() {
+ var now;
+ var delta;
+
+ now = new Date();
+ delta = (now - this.last_update) / 1000.0;
+ this.last_update = now;
+
+ this.amount -= delta * this.capacity / this.time;
+ if (this.amount < 0.0)
+ this.amount = 0.0;
+ };
+
+ this.update = function(n) {
+ this.age();
+ this.amount += n;
+
+ return this.amount <= this.capacity;
+ };
+
+ /* How many seconds in the future will the limit expire? */
+ this.when = function() {
+ this.age();
+
+ return (this.amount - this.capacity) / (this.capacity / this.time);
+ }
+
+ this.is_limited = function() {
+ this.age();
+
+ return this.amount > this.capacity;
+ }
+}
+
+/* A rate limiter that never limits. */
+function DummyRateLimit(capacity, time) {
+ this.update = function(n) {
+ return true;
+ };
+
+ this.when = function() {
+ return 0.0;
+ }
+
+ this.is_limited = function() {
+ return false;
+ }
+}
+
+var HTML_ESCAPES = {
+ "&": "amp",
+ "<": "lt",
+ ">": "gt",
+ "'": "apos",
+ "\"": "quot"
+};
+function escape_html(s) {
+ return s.replace(/&<>'"/, function(x) { return HTML_ESCAPES[x] });
+}
+
+/* The usual embedded HTML badge. The "elem" member is a DOM element that can be
+ included elsewhere. */
+function Badge() {
+ /* Number of proxy pairs currently connected. */
+ this.num_proxy_pairs = 0;
+
+ var table, tr, td, div, a, img;
+
+ table = document.createElement("table");
+ tr = document.createElement("tr");
+ table.appendChild(tr);
+ td = document.createElement("td");
+ tr.appendChild(td);
+ a = document.createElement("a");
+ a.setAttribute("href", "http://crypto.stanford.edu/flashproxy/");
+ a.setAttribute("target", "_parent");
+ td.appendChild(a);
+ img = document.createElement("img");
+ img.setAttribute("src", "badge.png");
+ img.setAttribute("alt", "Internet freedom");
+ a.appendChild(img);
+
+ this.elem = table;
+ this.elem.className = "idle";
+
+ a = document.createElement("a");
+ a.setAttribute("href", "#");
+ this.disable_button = document.createElement("div");
+ /* HEAVY MULTIPLICATION X */
+ this.disable_button.innerHTML = "✖";
+ this.disable_button.className = "disable-button";
+ a.appendChild(this.disable_button);
+ td.appendChild(a);
+
+ this.proxy_begin = function() {
+ this.num_proxy_pairs++;
+ this.elem.className = "active";
+ };
+
+ this.proxy_end = function() {
+ this.num_proxy_pairs--;
+ if (this.num_proxy_pairs <= 0) {
+ this.elem.className = "idle";
+ }
+ }
+
+ this.disable = function() {
+ this.elem.className = "disabled";
+ this.disable_button.style.display = "none";
+ }
+
+ this.die = function() {
+ this.elem.className = "dead";
+ this.disable_button.style.display = "none";
+ }
+}
+
+function quote(s) {
+ return "\"" + s.replace(/([\\\"])/, "\\$1") + "\"";
+}
+
+function maybe_quote(s) {
+ if (!/^[a-zA-Z_]\w*$/.test(s))
+ return quote(s);
+ else
+ return s;
+}
+
+function repr(x) {
+ if (x === null) {
+ return "null";
+ } else if (typeof x === "undefined") {
+ return "undefined";
+ } else if (typeof x === "object") {
+ var elems = [];
+ for (var k in x)
+ elems.push(maybe_quote(k) + ": " + repr(x[k]));
+ return "{ " + elems.join(", ") + " }";
+ } else if (typeof x === "string") {
+ return quote(x);
+ } else {
+ return x.toString();
+ }
+}
+
+/* Are circumstances such that we should self-disable and not be a
+ proxy? We take a best-effort guess as to whether this device runs on
+ a battery or the data transfer might be expensive.
+
+ http://www.zytrax.com/tech/web/mobile_ids.html
+ http://googlewebmastercentral.blogspot.com/2011/03/mo-better-to-also-detect-mobile-user.html
+ http://search.cpan.org/~cmanley/Mobile-UserAgent-1.05/lib/Mobile/UserAgent.pm
+*/
+function flashproxy_should_disable() {
+ var ua;
+
+ ua = window.navigator.userAgent;
+ if (ua !== null) {
+ var UA_LIST = [
+ /\bmobile\b/i,
+ /\bandroid\b/i,
+ /\bopera mobi\b/i,
+ ];
+
+ for (var i = 0; i < UA_LIST.length; i++) {
+ var re = UA_LIST[i];
+
+ if (ua.match(re)) {
+ puts("Disable because User-Agent matches mobile pattern " + re + ".");
+ return true;
+ }
+ }
+ }
+
+ if (ua.match(/\bsafari\b/i) && !ua.match(/\bchrome\b/i)
+ && !ua.match(/\bversion\/[6789]\./i)) {
+ /* Disable before Safari 6.0 because it doesn't have the hybi/RFC type
+ of WebSockets. */
+ puts("Disable because User-Agent is Safari before 6.0.");
+ return true;
+ }
+
+ if (!WebSocket) {
+ /* No WebSocket support. */
+ puts("Disable because of no WebSocket support.");
+ return true;
+ }
+
+ return false;
+}
+
+function flashproxy_badge_insert() {
+ var fp;
+ var e;
+
+ fp = new FlashProxy();
+ if (flashproxy_should_disable())
+ fp.disable();
+
+ /* http://intertwingly.net/blog/2006/11/10/Thats-Not-Write for this trick to
+ insert right after the <script> element in the DOM. */
+ e = document;
+ while (e.lastChild && e.lastChild.nodeType === 1) {
+ e = e.lastChild;
+ }
+ e.parentNode.appendChild(fp.badge_elem);
+
+ return fp;
+}
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits