var iface = 'wg0'; function parse_placeholder(val) { return true; } 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_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(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); } 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 parser = ip_address_lib.Address4; var result = parse_ip(src, parser); 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 [addr, netmask, network_id]; } function parse_ipv6(val) { return parse_ip(val, ip_address_lib.Address6); } var Input = function(name) { this.name = name; } 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.value = function() { return $(this.input_id()).val(); } Input.prototype.has_value = function() { return this.value().length > 0; } Input.prototype.set = function(value) { $(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() { try { var value = this.input.value(); if (!this.optional || value.length > 0) value = this.parser(value); 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.set = function(value) { this.input.set(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); } 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); } 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 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.values = [ this.server_public, this.server_endpoint, this.preshared, this.client_public, this.client_private, this.client_ipv4, this.client_ipv6, this.keepalive ]; } 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)) { value.set(params.get(value.name)); has = true; } }); 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 edit_btn_init(btn) { btn.empty(); btn.append(''); } function edit_btn_save(btn) { btn.empty(); btn.append(''); } function edit_btn_on_click(btn, pre) { var editable = pre.prop('isContentEditable'); pre.prop('contentEditable', !editable); if (editable) { edit_btn_init(btn); btn.blur(); // a.k.a. unfocus } else { edit_btn_save(btn); pre.focus(); } } function dload_btn_init(btn) { btn.empty(); btn.append(''); } function basename(path) { return path.substring(path.lastIndexOf('/') + 1); } function dload_btn_on_click(btn, path, pre) { // 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([pre.text()], {type: 'application/octet-stream'}); var url = URL.createObjectURL(blob); var link = $(''); link.prop('href', url); link.prop('download', basename(path)); // 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 //