7 var names_options_server
= [
17 var names_options_client
= [
23 var names_options_common
= [
46 'chacha20-ietf-poly1305',
47 'xchacha20-ietf-poly1305',
67 function ucival_to_bool(val
) {
68 return val
=== 'true' || val
=== '1' || val
=== 'yes' || val
=== 'on';
71 return baseclass
.extend({
72 values_actions: function(o
) {
75 if (o
.option
!== 'dst_default') {
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
);
88 o
.value('', '<unset>');
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
);
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()));
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'));
121 options_server: function(s
, opts
) {
123 tab
= opts
&& opts
.tab
|| null;
126 optfunc = function(/* ... */) {
127 var o
= s
.option
.apply(s
, arguments
);
132 optfunc = function(/* ... */) {
133 var o
= s
.taboption
.apply(s
, L
.varargs(arguments
, 0, tab
));
139 o
= optfunc(form
.Value
, 'server', _('Server'));
143 o
= optfunc(form
.Value
, 'server_port', _('Server port'));
147 o
= optfunc(form
.ListValue
, 'method', _('Method'));
148 methods
.forEach(function(m
) {
152 o
= optfunc(form
.Value
, 'password', _('Password'));
156 o
= optfunc(form
.Value
, 'key', _('Key (base64)'));
157 o
.datatype
= 'base64';
162 optfunc(form
.Value
, 'plugin', _('Plugin')).modalonly
= true;
164 optfunc(form
.Value
, 'plugin_opts', _('Plugin Options')).modalonly
= true;
166 options_common: function(s
, tab
) {
167 var o
= s
.taboption(tab
, form
.ListValue
, 'mode', _('Mode of operation'));
168 modes
.forEach(function(m
) {
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'));
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'));
184 ucival_to_bool: function(val
) {
185 return ucival_to_bool(val
);
187 cfgvalue_overview: function(sdata
) {
188 var stype
= sdata
['.type'],
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']);
200 this.cfgvalue_overview_(sdata
, lines
, names_options_common
);
207 cfgvalue_overview_: function(sdata
, lines
, names
) {
208 names
.forEach(function(n
) {
211 if (n
=== 'key' || n
=== 'password') {
214 var fv
= E('var', [v
]);
215 if (sdata
['.type'] !== 'ss_server' && n
=== 'server') {
218 href
: L
.url('admin/services/shadowsocks-libev/servers') + '#edit=' + v
,
223 lines
.push(n
+ ': ', fv
, E('br'));
227 option_install_package: function(s
, tab
) {
228 var bin
= s
.sectiontype
.replace('_', '-'),
229 opkg_package
= 'shadowsocks-libev-' + bin
, o
;
231 o
= s
.taboption(tab
, form
.Button
, '_install');
233 o
= s
.option(form
.Button
, '_install');
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');
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) {
250 tag
= uri
.slice(hashPos
+ 1);
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;
258 var userinfo
= atob(uri
.slice(scheme
.length
, atPos
)
259 .replace(/-/g, '+').replace(/_
/g, '/')),
260 i = userinfo.indexOf(':');
261 if (i === -1) return null;
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)
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('=');
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(';');
281 config['plugin
'] = v.slice(0, k);
282 config['plugin_opts
'] = v.slice(k + 1);
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;
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)
304 return [config, tag];