treewide: import utility classes explicitly
[project/luci.git] / applications / luci-app-shadowsocks-libev / htdocs / luci-static / resources / shadowsocks-libev.js
1 'use strict';
2 'require baseclass';
3 'require uci';
4 'require form';
5 'require network';
6
7 var names_options_server = [
8 'server',
9 'server_port',
10 'method',
11 'key',
12 'password',
13 'plugin',
14 'plugin_opts',
15 ];
16
17 var names_options_client = [
18 'server',
19 'local_address',
20 'local_port',
21 ];
22
23 var names_options_common = [
24 'verbose',
25 'ipv6_first',
26 'fast_open',
27 'no_delay',
28 'reuse_port',
29 'mode',
30 'mtu',
31 'timeout',
32 'user',
33 ];
34
35 var modes = [
36 'tcp_only',
37 'tcp_and_udp',
38 'udp_only',
39 ];
40
41 var methods = [
42 // aead
43 'aes-128-gcm',
44 'aes-192-gcm',
45 'aes-256-gcm',
46 'chacha20-ietf-poly1305',
47 'xchacha20-ietf-poly1305',
48 // stream
49 'table',
50 'rc4',
51 'rc4-md5',
52 'aes-128-cfb',
53 'aes-192-cfb',
54 'aes-256-cfb',
55 'aes-128-ctr',
56 'aes-192-ctr',
57 'aes-256-ctr',
58 'bf-cfb',
59 'camellia-128-cfb',
60 'camellia-192-cfb',
61 'camellia-256-cfb',
62 'salsa20',
63 'chacha20',
64 'chacha20-ietf',
65 ];
66
67 function ucival_to_bool(val) {
68 return val === 'true' || val === '1' || val === 'yes' || val === 'on';
69 }
70
71 return baseclass.extend({
72 values_actions: function(o) {
73 o.value('bypass');
74 o.value('forward');
75 if (o.option !== 'dst_default') {
76 o.value('checkdst');
77 }
78 },
79 values_redir: function(o, xmode) {
80 uci.sections('shadowsocks-libev', 'ss_redir', function(sdata) {
81 var disabled = ucival_to_bool(sdata['disabled']),
82 sname = sdata['.name'],
83 mode = sdata['mode'] || 'tcp_only';
84 if (!disabled && mode.indexOf(xmode) !== -1) {
85 o.value(sname, sname + ' - ' + mode);
86 }
87 });
88 o.value('', '<unset>');
89 o.default = '';
90 },
91 values_serverlist: function(o) {
92 uci.sections('shadowsocks-libev', 'server', function(sdata) {
93 var sname = sdata['.name'],
94 server = sdata['server'],
95 server_port = sdata['server_port'];
96 if (server && server_port) {
97 var disabled = ucival_to_bool(sdata['.disabled']) ? ' - disabled' : '',
98 desc = '%s - %s:%s%s'.format(sname, server, server_port, disabled);
99 o.value(sname, desc);
100 }
101 });
102 },
103 values_ipaddr: function(o, netDevs) {
104 netDevs.forEach(function(v) {
105 v.getIPAddrs().forEach(function(a) {
106 var host = a.split('/')[0];
107 o.value(host, '%s (%s)'.format(host, v.getShortName()));
108 });
109 });
110 },
111 options_client: function(s, tab, netDevs) {
112 var o = s.taboption(tab, form.ListValue, 'server', _('Remote server'));
113 this.values_serverlist(o);
114 o = s.taboption(tab, form.Value, 'local_address', _('Local address'));
115 o.datatype = 'ipaddr';
116 o.placeholder = '0.0.0.0';
117 this.values_ipaddr(o, netDevs);
118 o = s.taboption(tab, form.Value, 'local_port', _('Local port'));
119 o.datatype = 'port';
120 },
121 options_server: function(s, opts) {
122 var o, optfunc,
123 tab = opts && opts.tab || null;
124
125 if (!tab) {
126 optfunc = function(/* ... */) {
127 var o = s.option.apply(s, arguments);
128 o.editable = true;
129 return o;
130 };
131 } else {
132 optfunc = function(/* ... */) {
133 var o = s.taboption.apply(s, L.varargs(arguments, 0, tab));
134 o.editable = true;
135 return o;
136 };
137 }
138
139 o = optfunc(form.Value, 'server', _('Server'));
140 o.datatype = 'host';
141 o.size = 16;
142
143 o = optfunc(form.Value, 'server_port', _('Server port'));
144 o.datatype = 'port';
145 o.size = 5;
146
147 o = optfunc(form.ListValue, 'method', _('Method'));
148 methods.forEach(function(m) {
149 o.value(m);
150 });
151
152 o = optfunc(form.Value, 'password', _('Password'));
153 o.password = true;
154 o.size = 12;
155
156 o = optfunc(form.Value, 'key', _('Key (base64)'));
157 o.datatype = 'base64';
158 o.password = true;
159 o.size = 12;
160 o.modalonly = true;;
161
162 optfunc(form.Value, 'plugin', _('Plugin')).modalonly = true;
163
164 optfunc(form.Value, 'plugin_opts', _('Plugin Options')).modalonly = true;
165 },
166 options_common: function(s, tab) {
167 var o = s.taboption(tab, form.ListValue, 'mode', _('Mode of operation'));
168 modes.forEach(function(m) {
169 o.value(m);
170 });
171 o.default = 'tcp_and_udp';
172 o = s.taboption(tab, form.Value, 'mtu', _('MTU'));
173 o.datatype = 'uinteger';
174 o = s.taboption(tab, form.Value, 'timeout', _('Timeout (sec)'));
175 o.datatype = 'uinteger';
176 s.taboption(tab, form.Value, 'user', _('Run as'));
177
178 s.taboption(tab, form.Flag, 'verbose', _('Verbose'));
179 s.taboption(tab, form.Flag, 'ipv6_first', _('IPv6 First'), _('Prefer IPv6 addresses when resolving names'));
180 s.taboption(tab, form.Flag, 'fast_open', _('Enable TCP Fast Open'));
181 s.taboption(tab, form.Flag, 'no_delay', _('Enable TCP_NODELAY'));
182 s.taboption(tab, form.Flag, 'reuse_port', _('Enable SO_REUSEPORT'));
183 },
184 ucival_to_bool: function(val) {
185 return ucival_to_bool(val);
186 },
187 cfgvalue_overview: function(sdata) {
188 var stype = sdata['.type'],
189 lines = [];
190
191 if (stype === 'ss_server') {
192 this.cfgvalue_overview_(sdata, lines, names_options_server);
193 this.cfgvalue_overview_(sdata, lines, names_options_common);
194 this.cfgvalue_overview_(sdata, lines, ['bind_address']);
195 } else if (stype === 'ss_local' || stype === 'ss_redir' || stype === 'ss_tunnel') {
196 this.cfgvalue_overview_(sdata, lines, names_options_client);
197 if (stype === 'ss_tunnel') {
198 this.cfgvalue_overview_(sdata, lines, ['tunnel_address']);
199 }
200 this.cfgvalue_overview_(sdata, lines, names_options_common);
201 } else {
202 return [];
203 }
204
205 return lines;
206 },
207 cfgvalue_overview_: function(sdata, lines, names) {
208 names.forEach(function(n) {
209 var v = sdata[n];
210 if (v) {
211 if (n === 'key' || n === 'password') {
212 v = _('<hidden>');
213 }
214 var fv = E('var', [v]);
215 if (sdata['.type'] !== 'ss_server' && n === 'server') {
216 fv = E('a', {
217 class: 'label',
218 href: L.url('admin/services/shadowsocks-libev/servers') + '#edit=' + v,
219 target: '_blank',
220 rel: 'noopener'
221 }, fv);
222 }
223 lines.push(n + ': ', fv, E('br'));
224 }
225 });
226 },
227 option_install_package: function(s, tab) {
228 var bin = s.sectiontype.replace('_', '-'),
229 opkg_package = 'shadowsocks-libev-' + bin, o;
230 if (tab) {
231 o = s.taboption(tab, form.Button, '_install');
232 } else {
233 o = s.option(form.Button, '_install');
234 }
235 o.title = _('Package is not installed');
236 o.inputtitle = _('Install package ' + opkg_package);
237 o.inputstyle = 'apply';
238 o.onclick = function() {
239 window.open(L.url('admin/system/opkg') +
240 '?query=' + opkg_package, '_blank', 'noopener');
241 };
242 },
243 parse_uri: function(uri) {
244 var scheme = 'ss://';
245 if (uri && uri.indexOf(scheme) === 0) {
246 var atPos = uri.indexOf('@'), hashPos = uri.lastIndexOf('#'), tag;
247 if (hashPos === -1) {
248 hashPos = undefined;
249 } else {
250 tag = uri.slice(hashPos + 1);
251 }
252
253 if (atPos !== -1) { // SIP002 format https://shadowsocks.org/en/spec/SIP002-URI-Scheme.html
254 var colonPos = uri.indexOf(':', atPos + 1), slashPos = uri.indexOf('/', colonPos + 1);
255 if (colonPos === -1) return null;
256 if (slashPos === -1) slashPos = undefined;
257
258 var userinfo = atob(uri.slice(scheme.length, atPos)
259 .replace(/-/g, '+').replace(/_/g, '/')),
260 i = userinfo.indexOf(':');
261 if (i === -1) return null;
262
263 var config = {
264 server: uri.slice(atPos + 1, colonPos),
265 server_port: uri.slice(colonPos + 1, slashPos ? slashPos : hashPos),
266 password: userinfo.slice(i + 1),
267 method: userinfo.slice(0, i)
268 };
269
270 if (slashPos) {
271 var search = uri.slice(slashPos + 1, hashPos);
272 if (search[0] === '?') search = search.slice(1);
273 search.split('&').forEach(function(s) {
274 var j = s.indexOf('=');
275 if (j !== -1) {
276 var k = s.slice(0, j), v = s.slice(j + 1);
277 if (k === 'plugin') {
278 v = decodeURIComponent(v);
279 var k = v.indexOf(';');
280 if (k !== -1) {
281 config['plugin'] = v.slice(0, k);
282 config['plugin_opts'] = v.slice(k + 1);
283 }
284 }
285 }
286 });
287 }
288 return [config, tag];
289 } else { // Legacy format https://shadowsocks.org/en/config/quick-guide.html
290 var plain = atob(uri.slice(scheme.length, hashPos)),
291 firstColonPos = plain.indexOf(':'),
292 lastColonPos = plain.lastIndexOf(':'),
293 atPos = plain.lastIndexOf('@', lastColonPos);
294 if (firstColonPos === -1 ||
295 lastColonPos === -1 ||
296 atPos === -1) return null;
297
298 var config = {
299 server: plain.slice(atPos + 1, lastColonPos),
300 server_port: plain.slice(lastColonPos + 1),
301 password: plain.slice(firstColonPos + 1, atPos),
302 method: plain.slice(0, firstColonPos)
303 };
304 return [config, tag];
305 }
306 }
307 return null;
308 }
309 });