mirror of
https://github.com/kenzok8/small.git
synced 2025-01-08 12:08:21 +08:00
334 lines
12 KiB
JavaScript
334 lines
12 KiB
JavaScript
/*
|
|
* SPDX-License-Identifier: GPL-2.0-only
|
|
*
|
|
* Copyright (C) 2022-2023 ImmortalWrt.org
|
|
*/
|
|
|
|
'use strict';
|
|
'require baseclass';
|
|
'require form';
|
|
'require fs';
|
|
'require rpc';
|
|
'require uci';
|
|
'require ui';
|
|
'require validation';
|
|
|
|
return baseclass.extend({
|
|
dns_strategy: {
|
|
'': _('Default'),
|
|
'prefer_ipv4': _('Prefer IPv4'),
|
|
'prefer_ipv6': _('Prefer IPv6'),
|
|
'ipv4_only': _('IPv4 only'),
|
|
'ipv6_only': _('IPv6 only')
|
|
},
|
|
|
|
shadowsocks_encrypt_methods: [
|
|
/* Stream */
|
|
'none',
|
|
/* AEAD */
|
|
'aes-128-gcm',
|
|
'aes-192-gcm',
|
|
'aes-256-gcm',
|
|
'chacha20-ietf-poly1305',
|
|
'xchacha20-ietf-poly1305',
|
|
/* AEAD 2022 */
|
|
'2022-blake3-aes-128-gcm',
|
|
'2022-blake3-aes-256-gcm',
|
|
'2022-blake3-chacha20-poly1305'
|
|
],
|
|
|
|
tls_cipher_suites: [
|
|
'TLS_RSA_WITH_AES_128_CBC_SHA',
|
|
'TLS_RSA_WITH_AES_256_CBC_SHA',
|
|
'TLS_RSA_WITH_AES_128_GCM_SHA256',
|
|
'TLS_RSA_WITH_AES_256_GCM_SHA384',
|
|
'TLS_AES_128_GCM_SHA256',
|
|
'TLS_AES_256_GCM_SHA384',
|
|
'TLS_CHACHA20_POLY1305_SHA256',
|
|
'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA',
|
|
'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA',
|
|
'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA',
|
|
'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA',
|
|
'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256',
|
|
'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384',
|
|
'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256',
|
|
'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384',
|
|
'TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256',
|
|
'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256'
|
|
],
|
|
|
|
tls_versions: [
|
|
'1.0',
|
|
'1.1',
|
|
'1.2',
|
|
'1.3'
|
|
],
|
|
|
|
calcStringMD5: function(e) {
|
|
/* Thanks to https://stackoverflow.com/a/41602636 */
|
|
function h(a, b) {
|
|
var c, d, e, f, g;
|
|
e = a & 2147483648;
|
|
f = b & 2147483648;
|
|
c = a & 1073741824;
|
|
d = b & 1073741824;
|
|
g = (a & 1073741823) + (b & 1073741823);
|
|
return c & d ? g ^ 2147483648 ^ e ^ f : c | d ? g & 1073741824 ? g ^ 3221225472 ^ e ^ f : g ^ 1073741824 ^ e ^ f : g ^ e ^ f;
|
|
}
|
|
function k(a, b, c, d, e, f, g) { a = h(a, h(h(b & c | ~b & d, e), g)); return h(a << f | a >>> 32 - f, b); }
|
|
function l(a, b, c, d, e, f, g) { a = h(a, h(h(b & d | c & ~d, e), g)); return h(a << f | a >>> 32 - f, b); }
|
|
function m(a, b, d, c, e, f, g) { a = h(a, h(h(b ^ d ^ c, e), g)); return h(a << f | a >>> 32 - f, b); }
|
|
function n(a, b, d, c, e, f, g) { a = h(a, h(h(d ^ (b | ~c), e), g)); return h(a << f | a >>> 32 - f, b); }
|
|
function p(a) {
|
|
var b = '', d = '';
|
|
for (var c = 0; 3 >= c; c++) d = a >>> 8 * c & 255, d = '0' + d.toString(16), b += d.substr(d.length - 2, 2);
|
|
return b;
|
|
}
|
|
|
|
var f = [], q, r, s, t, a, b, c, d;
|
|
e = function(a) {
|
|
a = a.replace(/\r\n/g, '\n');
|
|
for (var b = '', d = 0; d < a.length; d++) {
|
|
var c = a.charCodeAt(d);
|
|
128 > c ? b += String.fromCharCode(c) : (127 < c && 2048 > c ? b += String.fromCharCode(c >> 6 | 192) :
|
|
(b += String.fromCharCode(c >> 12 | 224), b += String.fromCharCode(c >> 6 & 63 | 128)),
|
|
b += String.fromCharCode(c & 63 | 128))
|
|
}
|
|
return b;
|
|
}(e);
|
|
f = function(b) {
|
|
var c = b.length, a = c + 8;
|
|
for (var d = 16 * ((a - a % 64) / 64 + 1), e = Array(d - 1), f = 0, g = 0; g < c;)
|
|
a = (g - g % 4) / 4, f = g % 4 * 8, e[a] |= b.charCodeAt(g) << f, g++;
|
|
a = (g - g % 4) / 4; e[a] |= 128 << g % 4 * 8; e[d - 2] = c << 3; e[d - 1] = c >>> 29;
|
|
return e;
|
|
}(e);
|
|
a = 1732584193;
|
|
b = 4023233417;
|
|
c = 2562383102;
|
|
d = 271733878;
|
|
|
|
for (e = 0; e < f.length; e += 16) q = a, r = b, s = c, t = d,
|
|
a = k(a, b, c, d, f[e + 0], 7, 3614090360), d = k(d, a, b, c, f[e + 1], 12, 3905402710),
|
|
c = k(c, d, a, b, f[e + 2], 17, 606105819), b = k(b, c, d, a, f[e + 3], 22, 3250441966),
|
|
a = k(a, b, c, d, f[e + 4], 7, 4118548399), d = k(d, a, b, c, f[e + 5], 12, 1200080426),
|
|
c = k(c, d, a, b, f[e + 6], 17, 2821735955), b = k(b, c, d, a, f[e + 7], 22, 4249261313),
|
|
a = k(a, b, c, d, f[e + 8], 7, 1770035416), d = k(d, a, b, c, f[e + 9], 12, 2336552879),
|
|
c = k(c, d, a, b, f[e + 10], 17, 4294925233), b = k(b, c, d, a, f[e + 11], 22, 2304563134),
|
|
a = k(a, b, c, d, f[e + 12], 7, 1804603682), d = k(d, a, b, c, f[e + 13], 12, 4254626195),
|
|
c = k(c, d, a, b, f[e + 14], 17, 2792965006), b = k(b, c, d, a, f[e + 15], 22, 1236535329),
|
|
a = l(a, b, c, d, f[e + 1], 5, 4129170786), d = l(d, a, b, c, f[e + 6], 9, 3225465664),
|
|
c = l(c, d, a, b, f[e + 11], 14, 643717713), b = l(b, c, d, a, f[e + 0], 20, 3921069994),
|
|
a = l(a, b, c, d, f[e + 5], 5, 3593408605), d = l(d, a, b, c, f[e + 10], 9, 38016083),
|
|
c = l(c, d, a, b, f[e + 15], 14, 3634488961), b = l(b, c, d, a, f[e + 4], 20, 3889429448),
|
|
a = l(a, b, c, d, f[e + 9], 5, 568446438), d = l(d, a, b, c, f[e + 14], 9, 3275163606),
|
|
c = l(c, d, a, b, f[e + 3], 14, 4107603335), b = l(b, c, d, a, f[e + 8], 20, 1163531501),
|
|
a = l(a, b, c, d, f[e + 13], 5, 2850285829), d = l(d, a, b, c, f[e + 2], 9, 4243563512),
|
|
c = l(c, d, a, b, f[e + 7], 14, 1735328473), b = l(b, c, d, a, f[e + 12], 20, 2368359562),
|
|
a = m(a, b, c, d, f[e + 5], 4, 4294588738), d = m(d, a, b, c, f[e + 8], 11, 2272392833),
|
|
c = m(c, d, a, b, f[e + 11], 16, 1839030562), b = m(b, c, d, a, f[e + 14], 23, 4259657740),
|
|
a = m(a, b, c, d, f[e + 1], 4, 2763975236), d = m(d, a, b, c, f[e + 4], 11, 1272893353),
|
|
c = m(c, d, a, b, f[e + 7], 16, 4139469664), b = m(b, c, d, a, f[e + 10], 23, 3200236656),
|
|
a = m(a, b, c, d, f[e + 13], 4, 681279174), d = m(d, a, b, c, f[e + 0], 11, 3936430074),
|
|
c = m(c, d, a, b, f[e + 3], 16, 3572445317), b = m(b, c, d, a, f[e + 6], 23, 76029189),
|
|
a = m(a, b, c, d, f[e + 9], 4, 3654602809), d = m(d, a, b, c, f[e + 12], 11, 3873151461),
|
|
c = m(c, d, a, b, f[e + 15], 16, 530742520), b = m(b, c, d, a, f[e + 2], 23, 3299628645),
|
|
a = n(a, b, c, d, f[e + 0], 6, 4096336452), d = n(d, a, b, c, f[e + 7], 10, 1126891415),
|
|
c = n(c, d, a, b, f[e + 14], 15, 2878612391), b = n(b, c, d, a, f[e + 5], 21, 4237533241),
|
|
a = n(a, b, c, d, f[e + 12], 6, 1700485571), d = n(d, a, b, c, f[e + 3], 10, 2399980690),
|
|
c = n(c, d, a, b, f[e + 10], 15, 4293915773), b = n(b, c, d, a, f[e + 1], 21, 2240044497),
|
|
a = n(a, b, c, d, f[e + 8], 6, 1873313359), d = n(d, a, b, c, f[e + 15], 10, 4264355552),
|
|
c = n(c, d, a, b, f[e + 6], 15, 2734768916), b = n(b, c, d, a, f[e + 13], 21, 1309151649),
|
|
a = n(a, b, c, d, f[e + 4], 6, 4149444226), d = n(d, a, b, c, f[e + 11], 10, 3174756917),
|
|
c = n(c, d, a, b, f[e + 2], 15, 718787259), b = n(b, c, d, a, f[e + 9], 21, 3951481745),
|
|
a = h(a, q), b = h(b, r), c = h(c, s), d = h(d, t);
|
|
return (p(a) + p(b) + p(c) + p(d)).toLowerCase();
|
|
},
|
|
|
|
decodeBase64Str: function(str) {
|
|
if (!str)
|
|
return null;
|
|
|
|
/* Thanks to luci-app-ssr-plus */
|
|
str = str.replace(/-/g, '+').replace(/_/g, '/');
|
|
var padding = (4 - str.length % 4) % 4;
|
|
if (padding)
|
|
str = str + Array(padding + 1).join('=');
|
|
|
|
return decodeURIComponent(Array.prototype.map.call(atob(str), (c) =>
|
|
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
|
|
).join(''));
|
|
},
|
|
|
|
getBuiltinFeatures: function() {
|
|
var callGetSingBoxFeatures = rpc.declare({
|
|
object: 'luci.homeproxy',
|
|
method: 'singbox_get_features',
|
|
expect: { '': {} }
|
|
});
|
|
|
|
return L.resolveDefault(callGetSingBoxFeatures(), {});
|
|
},
|
|
|
|
generateRand: function(type, length) {
|
|
var byteArr;
|
|
if (['base64', 'hex'].includes(type))
|
|
byteArr = crypto.getRandomValues(new Uint8Array(length));
|
|
switch (type) {
|
|
case 'base64':
|
|
/* Thanks to https://stackoverflow.com/questions/9267899 */
|
|
return btoa(String.fromCharCode.apply(null, byteArr));
|
|
case 'hex':
|
|
return Array.from(byteArr, (byte) =>
|
|
(byte & 255).toString(16).padStart(2, '0')
|
|
).join('');
|
|
case 'uuid':
|
|
/* Thanks to https://stackoverflow.com/a/2117523 */
|
|
return (location.protocol === 'https:') ? crypto.randomUUID() :
|
|
([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, (c) =>
|
|
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
|
);
|
|
default:
|
|
return null;
|
|
};
|
|
},
|
|
|
|
loadDefaultLabel: function(uciconfig, ucisection) {
|
|
var label = uci.get(uciconfig, ucisection, 'label');
|
|
if (label) {
|
|
return label;
|
|
} else {
|
|
uci.set(uciconfig, ucisection, 'label', ucisection);
|
|
return ucisection;
|
|
}
|
|
},
|
|
|
|
loadModalTitle: function(title, addtitle, uciconfig, ucisection) {
|
|
var label = uci.get(uciconfig, ucisection, 'label');
|
|
return label ? title + ' » ' + label : addtitle;
|
|
},
|
|
|
|
loadSubscriptionInfo: function(uciconfig) {
|
|
var subs = {};
|
|
for (var suburl of (uci.get(uciconfig, 'subscription', 'subscription_url') || [])) {
|
|
const url = new URL(suburl);
|
|
const urlhash = this.calcStringMD5(suburl.replace(/#.*$/, ''));
|
|
subs[urlhash] = {
|
|
"url": suburl.replace(/#.*$/, ''),
|
|
"name": url.hash ? decodeURIComponent(url.hash.slice(1)) : url.hostname
|
|
};
|
|
}
|
|
return subs;
|
|
},
|
|
|
|
loadNodesList: function(uciconfig, subinfo) {
|
|
var nodelist = {};
|
|
uci.sections(uciconfig, 'node', (res) => {
|
|
var nodeaddr = ((res.type === 'direct') ? res.override_address : res.address) || '',
|
|
nodeport = ((res.type === 'direct') ? res.override_port : res.port) || '';
|
|
|
|
nodelist[res['.name']] =
|
|
String.format('%s [%s] %s', res.grouphash ?
|
|
String.format('[%s]', subinfo[res.grouphash]?.name || res.grouphash) : '',
|
|
res.type, res.label || ((validation.parseIPv6(nodeaddr) ?
|
|
String.format('[%s]', nodeaddr) : nodeaddr) + ':' + nodeport));
|
|
});
|
|
return nodelist;
|
|
},
|
|
|
|
renderSectionAdd: function(section, prefmt, LC, extra_class) {
|
|
var el = form.GridSection.prototype.renderSectionAdd.apply(section, [ extra_class ]),
|
|
nameEl = el.querySelector('.cbi-section-create-name');
|
|
ui.addValidator(nameEl, 'uciname', true, (v) => {
|
|
var button = el.querySelector('.cbi-section-create > .cbi-button-add');
|
|
var uciconfig = section.uciconfig || section.map.config;
|
|
var prefix = prefmt?.prefix ? prefmt.prefix : '',
|
|
suffix = prefmt?.suffix ? prefmt.suffix : '';
|
|
|
|
if (!v) {
|
|
button.disabled = true;
|
|
return true;
|
|
} else if (LC && (v !== v.toLowerCase())) {
|
|
button.disabled = true;
|
|
return _('Expecting: %s').format(_('Lowercase only'));
|
|
} else if (uci.get(uciconfig, v)) {
|
|
button.disabled = true;
|
|
return _('Expecting: %s').format(_('unique UCI identifier'));
|
|
} else if (uci.get(uciconfig, prefix + v + suffix)) {
|
|
button.disabled = true;
|
|
return _('Expecting: %s').format(_('unique label'));
|
|
} else {
|
|
button.disabled = null;
|
|
return true;
|
|
}
|
|
}, 'blur', 'keyup');
|
|
|
|
return el;
|
|
},
|
|
|
|
handleAdd: function(section, prefmt, ev, name) {
|
|
var prefix = prefmt?.prefix ? prefmt.prefix : '',
|
|
suffix = prefmt?.suffix ? prefmt.suffix : '';
|
|
|
|
return form.GridSection.prototype.handleAdd.apply(section, [ ev, prefix + name + suffix ]);
|
|
},
|
|
|
|
uploadCertificate: function(option, type, filename, ev) {
|
|
var callWriteCertificate = rpc.declare({
|
|
object: 'luci.homeproxy',
|
|
method: 'certificate_write',
|
|
params: ['filename'],
|
|
expect: { '': {} }
|
|
});
|
|
|
|
return ui.uploadFile('/tmp/homeproxy_certificate.tmp', ev.target)
|
|
.then(L.bind((btn, res) => {
|
|
return L.resolveDefault(callWriteCertificate(filename), {}).then((ret) => {
|
|
if (ret.result === true)
|
|
ui.addNotification(null, E('p', _('Your %s was successfully uploaded. Size: %sB.').format(type, res.size)));
|
|
else
|
|
ui.addNotification(null, E('p', _('Failed to upload %s, error: %s.').format(type, ret.error)));
|
|
});
|
|
}, this, ev.target))
|
|
.catch((e) => { ui.addNotification(null, E('p', e.message)) });
|
|
},
|
|
|
|
validateBase64Key: function(length, section_id, value) {
|
|
/* Thanks to luci-proto-wireguard */
|
|
if (section_id && value)
|
|
if (value.length !== length || !value.match(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/) || value[length-1] !== '=')
|
|
return _('Expecting: %s').format(_('valid base64 key with %d characters').format(length));
|
|
|
|
return true;
|
|
},
|
|
|
|
validateUniqueValue: function(uciconfig, ucisection, ucioption, section_id, value) {
|
|
if (section_id) {
|
|
if (!value)
|
|
return _('Expecting: %s').format(_('non-empty value'));
|
|
|
|
var duplicate = false;
|
|
uci.sections(uciconfig, ucisection, (res) => {
|
|
if (res['.name'] !== section_id)
|
|
if (res[ucioption] === value)
|
|
duplicate = true
|
|
});
|
|
if (duplicate)
|
|
return _('Expecting: %s').format(_('unique value'));
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
validateUUID: function(section_id, value) {
|
|
if (section_id) {
|
|
if (!value)
|
|
return _('Expecting: %s').format(_('non-empty value'));
|
|
else if (value.match('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') === null)
|
|
return _('Expecting: %s').format(_('valid uuid'));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
});
|