var iface = 'wg0';
function parse_placeholder(val) {
return val;
}
function parse_boolean(val) {
if (val === 'true')
return true;
if (val === 'false')
return false;
throw new Error('Cannot parse as a boolean.');
}
function parse_boolean_or_string(val) {
if (typeof val == 'boolean') {
return val;
}
return parse_boolean(val);
}
function try_parse_boolean_or_string(val) {
try {
return parse_boolean_or_string(val);
} catch (err) {
return null;
}
}
function parse_positive_integer(src) {
var val = parseInt(src);
if (isNaN(val) || val < 1)
throw new Error('Cannot parse as a positive integer.');
return val;
}
function parse_keepalive(src) {
return parse_positive_integer(src);
}
function parse_tunnel_everything(src) {
return parse_boolean_or_string(src);
}
function parse_endpoint(val) {
val = val.trim();
if (val.length == 0)
throw new Error('Server endpoint cannot be an empty string.');
if (!val.match(/^.+:[0-9]+$/))
throw new Error('Please specify a host and a port in the HOST:PORT format.');
return val;
}
function parse_key(val) {
val = val.trim();
if (val.length == 0)
throw new Error('Key as used by WireGuard cannot be an empty string.');
if (!val.match(/^[0-9a-zA-Z+/=]+$/))
throw new Error('Key as used by WireGuard must be Base64-encoded.');
return val;
}
var ip_address_lib = require('ip-address');
function parse_ip_internal(src, parser) {
src = src.trim();
if (src.length == 0)
throw new Error('IP address cannot be an empty string.');
try {
var result = new parser(src);
} catch (err) {
throw new Error('Sorry, IP address validation failed with the following error:\n' + err.message);
}
return result;
}
function parse_ip(src, parser) {
var result = parse_ip_internal(src, parser);
if (result.parsedSubnet)
throw new Error('Please specify address without the netmask.')
return result.correctForm();
}
function parse_ip_cidr(src, parser) {
var result = parse_ip_internal(src, parser);
if (!result.parsedSubnet)
throw new Error('Please specify address using the CIDR format (including the netmask).');
var addr = result.correctForm();
var netmask = result.subnetMask;
var network_id = result.startAddress().correctForm();
return [addr, netmask, network_id];
}
function parse_ipv4(src) {
var result = parse_ip(src, ip_address_lib.Address4);
return result;
}
function parse_ipv4_cidr(src) {
var parser = ip_address_lib.Address4;
var result = parse_ip_cidr(src, ip_address_lib.Address4);
var addr = result[0];
var netmask = result[1];
var network_id = result[2];
var broadcast_addr = new parser(src).endAddress().correctForm();
if (addr == network_id && netmask != 32)
throw new Error('Please use a real host IP address, not a network identifier.');
if (addr == broadcast_addr && netmask != 32)
throw new Error('Please use a real host IP address, not a broadcast address.');
return result;
}
function parse_ipv6(val) {
return parse_ip(val, ip_address_lib.Address6);
}
function parse_ipv6_cidr(val) {
return parse_ip_cidr(val, ip_address_lib.Address6);
}
function parse_ip_list(val, addr_parser) {
var parts = val.split(/, */);
var result = [];
parts.forEach(function(part) {
result.push(addr_parser(part));
});
return result;
}
var Input = function(name) {
this.name = name;
if (this.is_checkbox()) {
// Keep the checked property and the value consistent.
$(this.input_id()).change(function(evt) {
var elem = evt.target;
elem.value = elem.checked;
});
}
}
Input.prototype.input_id = function() {
return '#param_' + this.name;
}
Input.prototype.error_id = function() {
return this.input_id() + '_error';
}
Input.prototype.container_id = function() {
return this.input_id() + '_container';
}
Input.prototype.is_checkbox = function() {
return $(this.input_id()).attr('type') == 'checkbox';
}
Input.prototype.value = function() {
return $(this.input_id()).val();
}
Input.prototype.has_value = function() {
return this.value().length > 0;
}
Input.prototype.set = function(value) {
if (this.is_checkbox() && try_parse_boolean_or_string(value))
$(this.input_id()).prop('checked', true);
$(this.input_id()).val(value);
}
Input.prototype.show = function() {
$(this.container_id()).show();
}
Input.prototype.hide = function() {
$(this.container_id()).hide();
}
Input.prototype.show_error = function(msg) {
this.show();
$(this.error_id()).text(msg);
$(this.error_id()).show();
}
Input.prototype.hide_error = function() {
$(this.error_id()).hide();
}
var Value = function(name, parser) {
this.name = name;
this.input = new Input(name);
this.parser = parser;
this.optional = false;
this.advanced = false;
this.value = null;
this.error = null;
}
Value.prototype.parse = function() {
return this.parse_from(this.input.value());
}
Value.prototype.parse_from = function(src) {
try {
var value = src;
switch (typeof value) {
case 'boolean':
value = this.parser(value);
break;
default:
if (this.optional && value.length == 0)
break;
value = this.parser(value);
break;
}
this.input.set(src);
this.set_value(value);
return true;
} catch (err) {
this.set_error(err.message);
return false;
}
}
Value.prototype.set_value = function(value) {
this.value = value;
this.error = null;
this.input.hide_error();
}
Value.prototype.set_error = function(msg) {
this.value = null;
this.error = msg;
this.input.show_error(this.error);
}
Value.prototype.is_set = function() {
return this.input.has_value();
}
Value.prototype.show = function() {
this.input.show();
}
Value.prototype.hide = function() {
this.input.hide();
}
var Endpoint = function(name) {
Value.call(this, name, parse_endpoint);
}
Endpoint.prototype = Object.create(Value.prototype);
Endpoint.prototype.constructor = Endpoint;
var Key = function(name) {
Value.call(this, name, parse_key);
}
Key.prototype = Object.create(Value.prototype);
Key.prototype.constructor = Key;
var IPAddr = function(name, parser) {
Value.call(this, name, parser);
}
IPAddr.prototype = Object.create(Value.prototype);
IPAddr.prototype.constructor = IPAddr;
IPAddr.prototype.set_value = function(value) {
Value.prototype.set_value.call(this, value);
this.addr = this.value[0];
this.netmask = this.value[1];
this.network_id = this.value[2];
}
IPAddr.prototype.full_address = function() {
return this.addr + '/' + this.netmask;
}
IPAddr.prototype.allowed_ips_client = function() {
// As of this writing, _every_ tool accepts something like 192.168.0.1/24
// here, _except_ for the Android app, which _requires_ the least
// significant bits to be zeroed out.
return this.network_id + '/' + this.netmask;
}
var IPv4 = function(name) {
IPAddr.call(this, name, parse_ipv4_cidr);
}
IPv4.prototype = Object.create(IPAddr.prototype);
IPv4.prototype.constructor = IPv4;
IPv4.prototype.allowed_ips_server = function() {
return this.addr + '/' + 32;
}
var IPv6 = function(name) {
IPAddr.call(this, name, parse_ipv6_cidr);
}
IPv6.prototype = Object.create(IPAddr.prototype);
IPv6.prototype.constructor = IPv6;
IPv6.prototype.allowed_ips_server = function() {
return this.addr + '/' + 128;
}
var Keepalive = function(name) {
Value.call(this, name, parse_keepalive);
this.optional = true;
this.advanced = true;
}
Keepalive.prototype = Object.create(Value.prototype);
Keepalive.prototype.constructor = Keepalive;
var TunnelEverything = function(name) {
Value.call(this, name, parse_tunnel_everything);
this.optional = true;
this.advanced = true;
}
TunnelEverything.prototype = Object.create(Value.prototype);
TunnelEverything.prototype.constructor = TunnelEverything;
var IPAddrList = function(name, parser) {
Value.call(this, name, function(val) {
return parse_ip_list(val, parser);
});
}
IPAddrList.prototype = Object.create(Value.prototype);
IPAddrList.prototype.constructor = IPAddrList;
var DNS_IPv4 = function(name) {
IPAddrList.call(this, name, parse_ipv4);
this.optional = true;
this.advanced = true;
}
DNS_IPv4.prototype = Object.create(IPAddrList.prototype);
DNS_IPv4.prototype.constructor = DNS_IPv4;
var DNS_IPv6 = function(name) {
IPAddrList.call(this, name, parse_ipv6);
this.optional = true;
this.advanced = true;
}
DNS_IPv6.prototype = Object.create(IPAddrList.prototype);
DNS_IPv6.prototype.constructor = DNS_IPv6;
var Data = function() {
this.server_public = new Key('server_public');
this.server_endpoint = new Endpoint('server_endpoint');
this.preshared = new Key('preshared');
this.client_public = new Key('client_public');
this.client_private = new Key('client_private');
this.client_ipv4 = new IPv4('client_ipv4');
this.client_ipv6 = new IPv6('client_ipv6');
this.keepalive = new Keepalive('keepalive');
this.tunnel_everything = new TunnelEverything('tunnel_everything');
this.dns_ipv4 = new DNS_IPv4('dns_ipv4');
this.dns_ipv6 = new DNS_IPv6('dns_ipv6');
this.values = [
this.server_public,
this.server_endpoint,
this.preshared,
this.client_public,
this.client_private,
this.client_ipv4,
this.client_ipv6,
this.keepalive,
this.tunnel_everything,
this.dns_ipv4,
this.dns_ipv6
];
}
Data.prototype.set_from_url = function(url) {
var url = new URL(url);
var params = new URLSearchParams(url.search);
var has = false;
this.values.forEach(function(value) {
if (params.has(value.name)) {
has = true;
value.input.set(params.get(value.name));
}
});
return has;
}
Data.prototype.set_from_this_url = function() {
return this.set_from_url(window.location.href);
};
Data.prototype.get_perma_url = function() {
var url = new URL(window.location.href);
url.search = '';
var params = new URLSearchParams();
this.values.forEach(function(value) {
params.append(value.name, value.input.value());
});
url.search = params.toString();
return url.toString();
}
Data.prototype.parse = function() {
var success = true;
this.values.forEach(function(value) {
if (!value.parse())
success = false;
});
if (success) {
if (!$('#advanced_params').prop('checked'))
this.hide_advanced();
this.hide_error();
} else {
this.show_error();
}
return success;
}
Data.prototype.show_error = function() {
$('#params_error').text('Please correct the input errors above first.');
$('#params_error').show();
}
Data.prototype.hide_error = function() {
$('#params_error').hide();
}
Data.prototype.show_advanced = function() {
this.values.forEach(function(value) {
if (!value.advanced)
return;
value.show();
});
}
Data.prototype.hide_advanced = function() {
this.values.forEach(function(value) {
if (!value.advanced)
return;
value.hide();
});
}
function basename(path) {
return path.substring(path.lastIndexOf('/') + 1);
}
function download(file_name, text) {
// The type must be application/octet-stream; if it's text/plain, the
// Android Chrome appends the .txt suffix to the downloaded file names.
var blob = new Blob([text], {type: 'application/octet-stream'});
var url = URL.createObjectURL(blob);
var link = $('');
link.prop('href', url);
link.prop('download', file_name);
// Whoever thought of this [0] is fucking crazy:
// https://stackoverflow.com/a/36483380/514684
// It literally silently does nothing if this is omitted.
link[0].click();
}
function copy_to_clipboard(elem) {
// This is rather neat, thanks:
// https://stackoverflow.com/a/22581382/514684
// Apparently, you can only do execCommand('copy') from and
//