snowflake/proxy/util.js
2019-07-10 10:49:53 +02:00

325 lines
7.8 KiB
JavaScript

/*
A JavaScript WebRTC snowflake proxy
Contains helpers for parsing query strings and other utilities.
*/
class Util {
static mightBeTBB() {
return Util.TBB_UAS.indexOf(window.navigator.userAgent) > -1 && (window.navigator.mimeTypes && window.navigator.mimeTypes.length === 0);
}
static genSnowflakeID() {
return Math.random().toString(36).substring(2);
}
static snowflakeIsDisabled(cookieName) {
var cookies;
cookies = Parse.cookie(document.cookie);
// Do nothing if snowflake has not been opted in by user.
if (cookies[cookieName] !== '1') {
log('Not opted-in. Please click the badge to change options.');
return true;
}
// Also do nothing if running in Tor Browser.
if (Util.mightBeTBB()) {
log('Will not run within Tor Browser.');
return true;
}
return false;
}
static featureDetect() {
return typeof PeerConnection === 'function';
}
};
// It would not be effective for Tor Browser users to run the proxy.
// Do we seem to be running in Tor Browser? Check the user-agent string and for
// no listing of supported MIME types.
Util.TBB_UAS = [
'Mozilla/5.0 (Windows NT 6.1; rv:10.0) Gecko/20100101 Firefox/10.0',
'Mozilla/5.0 (Windows NT 6.1; rv:17.0) Gecko/20100101 Firefox/17.0',
'Mozilla/5.0 (Windows NT 6.1; rv:24.0) Gecko/20100101 Firefox/24.0',
'Mozilla/5.0 (Windows NT 6.1; rv:31.0) Gecko/20100101 Firefox/31.0'
];
class Query {
/*
Parse a URL query string or application/x-www-form-urlencoded body. The
return type is an object mapping string keys to string values. By design,
this function doesn't support multiple values for the same named parameter,
for example 'a=1&a=2&a=3'; the first definition always wins. Returns null on
error.
Always decodes from UTF-8, not any other encoding.
http://dev.w3.org/html5/spec/Overview.html#url-encoded-form-data
*/
static parse(qs) {
var i, j, len, name, result, string, strings, value;
result = {};
strings = [];
if (qs) {
strings = qs.split('&');
}
if (0 === strings.length) {
return result;
}
for (i = 0, len = strings.length; i < len; i++) {
string = strings[i];
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;
}
// params is a list of (key, value) 2-tuples.
static buildString(params) {
var i, len, param, parts;
parts = [];
for (i = 0, len = params.length; i < len; i++) {
param = params[i];
parts.push(encodeURIComponent(param[0]) + '=' + encodeURIComponent(param[1]));
}
return parts.join('&');
}
};
class Parse {
// Parse a cookie data string (usually document.cookie). The return type is an
// object mapping cookies names to values. Returns null on error.
// http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-8747038
static cookie(cookies) {
var i, j, len, name, result, string, strings, value;
result = {};
strings = [];
if (cookies) {
strings = cookies.split(';');
}
for (i = 0, len = strings.length; i < len; i++) {
string = strings[i];
j = string.indexOf('=');
if (-1 === j) {
return null;
}
name = decodeURIComponent(string.substr(0, j).trim());
value = decodeURIComponent(string.substr(j + 1).trim());
if (!(name in result)) {
result[name] = value;
}
}
return result;
}
// Parse an address in the form 'host:port'. Returns an Object with keys 'host'
// (String) and 'port' (int). Returns null on error.
static address(spec) {
var host, m, port;
m = null;
if (!m) {
// IPv6 syntax.
m = spec.match(/^\[([\0-9a-fA-F:.]+)\]:([0-9]+)$/);
}
if (!m) {
// IPv4 syntax.
m = spec.match(/^([0-9.]+):([0-9]+)$/);
}
if (!m) {
// TODO: Domain match
return null;
}
host = m[1];
port = parseInt(m[2], 10);
if (isNaN(port) || port < 0 || port > 65535) {
return null;
}
return {
host: host,
port: port
};
}
// Parse a count of bytes. A suffix of 'k', 'm', or 'g' (or uppercase)
// does what you would think. Returns null on error.
static byteCount(spec) {
var UNITS, count, matches, units;
UNITS = {
k: 1024,
m: 1024 * 1024,
g: 1024 * 1024 * 1024,
K: 1024,
M: 1024 * 1024,
G: 1024 * 1024 * 1024
};
matches = spec.match(/^(\d+(?:\.\d*)?)(\w*)$/);
if (null === matches) {
return null;
}
count = Number(matches[1]);
if (isNaN(count)) {
return null;
}
if ('' === matches[2]) {
units = 1;
} else {
units = UNITS[matches[2]];
if (null === units) {
return null;
}
}
return count * Number(units);
}
// Parse a connection-address out of the "c=" Connection Data field of a
// session description. Return undefined if none is found.
// https://tools.ietf.org/html/rfc4566#section-5.7
static ipFromSDP(sdp) {
var i, len, m, pattern, ref;
ref = [/^c=IN IP4 ([\d.]+)(?:(?:\/\d+)?\/\d+)?(:? |$)/m, /^c=IN IP6 ([0-9A-Fa-f:.]+)(?:\/\d+)?(:? |$)/m];
for (i = 0, len = ref.length; i < len; i++) {
pattern = ref[i];
m = pattern.exec(sdp);
if (m != null) {
return m[1];
}
}
}
};
class Params {
static getBool(query, param, defaultValue) {
var val;
val = query[param];
if (void 0 === val) {
return defaultValue;
}
if ('true' === val || '1' === val || '' === val) {
return true;
}
if ('false' === val || '0' === val) {
return false;
}
return null;
}
// Get an object value and parse it as a byte count. Example byte counts are
// '100' and '1.3m'. Returns |defaultValue| if param is not a key. Return null
// on a parsing error.
static getByteCount(query, param, defaultValue) {
var spec;
spec = query[param];
if (void 0 === spec) {
return defaultValue;
}
return Parse.byteCount(spec);
}
// Get an object value and parse it as an address spec. Returns |defaultValue|
// if param is not a key. Returns null on a parsing error.
static getAddress(query, param, defaultValue) {
var val;
val = query[param];
if (void 0 === val) {
return defaultValue;
}
return Parse.address(val);
}
// Get an object value and return it as a string. Returns default_val if param
// is not a key.
static getString(query, param, defaultValue) {
var val;
val = query[param];
if (void 0 === val) {
return defaultValue;
}
return val;
}
};
class BucketRateLimit {
constructor(capacity, time) {
this.capacity = capacity;
this.time = time;
}
age() {
var delta, now;
now = new Date();
delta = (now - this.lastUpdate) / 1000.0;
this.lastUpdate = now;
this.amount -= delta * this.capacity / this.time;
if (this.amount < 0.0) {
return this.amount = 0.0;
}
}
update(n) {
this.age();
this.amount += n;
return this.amount <= this.capacity;
}
// How many seconds in the future will the limit expire?
when() {
this.age();
return (this.amount - this.capacity) / (this.capacity / this.time);
}
isLimited() {
this.age();
return this.amount > this.capacity;
}
};
BucketRateLimit.prototype.amount = 0.0;
BucketRateLimit.prototype.lastUpdate = new Date();
// A rate limiter that never limits.
class DummyRateLimit {
constructor(capacity, time) {
this.capacity = capacity;
this.time = time;
}
update(n) {
return true;
}
when() {
return 0.0;
}
isLimited() {
return false;
}
};