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 //