Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
webssh
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
郑天保
webssh
Commits
4d77a6a0
Commit
4d77a6a0
authored
Aug 29, 2018
by
Sheng
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Updated main.js
parent
f4351318
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
129 additions
and
97 deletions
+129
-97
main.js
webssh/static/js/main.js
+129
-97
No files found.
webssh/static/js/main.js
View file @
4d77a6a0
...
@@ -10,6 +10,7 @@ jQuery(function($){
...
@@ -10,6 +10,7 @@ jQuery(function($){
style
=
{},
style
=
{},
title_text
=
'WebSSH'
,
title_text
=
'WebSSH'
,
title_element
=
document
.
querySelector
(
'title'
),
title_element
=
document
.
querySelector
(
'title'
),
debug
=
!
document
.
querySelector
(
'#hostname'
).
required
,
DISCONNECTED
=
0
,
DISCONNECTED
=
0
,
CONNECTING
=
1
,
CONNECTING
=
1
,
CONNECTED
=
2
,
CONNECTED
=
2
,
...
@@ -17,7 +18,7 @@ jQuery(function($){
...
@@ -17,7 +18,7 @@ jQuery(function($){
messages
=
{
1
:
'This client is connecting ...'
,
2
:
'This client is already connnected.'
},
messages
=
{
1
:
'This client is connecting ...'
,
2
:
'This client is already connnected.'
},
key_max_size
=
16384
,
key_max_size
=
16384
,
form_id
=
'#connect'
,
form_id
=
'#connect'
,
name
s
=
[
'hostname'
,
'port'
,
'username'
,
'password'
],
field
s
=
[
'hostname'
,
'port'
,
'username'
,
'password'
],
hostname_tester
=
/
((
^
\s
*
((([
0-9
]
|
[
1-9
][
0-9
]
|1
[
0-9
]{2}
|2
[
0-4
][
0-9
]
|25
[
0-5
])\.){3}([
0-9
]
|
[
1-9
][
0-9
]
|1
[
0-9
]{2}
|2
[
0-4
][
0-9
]
|25
[
0-5
]))\s
*$
)
|
(
^
\s
*
((([
0-9A-Fa-f
]{1,4}
:
){7}([
0-9A-Fa-f
]{1,4}
|:
))
|
(([
0-9A-Fa-f
]{1,4}
:
){6}(
:
[
0-9A-Fa-f
]{1,4}
|
((
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)(\.(
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)){3})
|:
))
|
(([
0-9A-Fa-f
]{1,4}
:
){5}(((
:
[
0-9A-Fa-f
]{1,4}){1,2})
|:
((
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)(\.(
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)){3})
|:
))
|
(([
0-9A-Fa-f
]{1,4}
:
){4}(((
:
[
0-9A-Fa-f
]{1,4}){1,3})
|
((
:
[
0-9A-Fa-f
]{1,4})?
:
((
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)(\.(
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)){3}))
|:
))
|
(([
0-9A-Fa-f
]{1,4}
:
){3}(((
:
[
0-9A-Fa-f
]{1,4}){1,4})
|
((
:
[
0-9A-Fa-f
]{1,4}){0,2}
:
((
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)(\.(
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)){3}))
|:
))
|
(([
0-9A-Fa-f
]{1,4}
:
){2}(((
:
[
0-9A-Fa-f
]{1,4}){1,5})
|
((
:
[
0-9A-Fa-f
]{1,4}){0,3}
:
((
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)(\.(
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)){3}))
|:
))
|
(([
0-9A-Fa-f
]{1,4}
:
){1}(((
:
[
0-9A-Fa-f
]{1,4}){1,6})
|
((
:
[
0-9A-Fa-f
]{1,4}){0,4}
:
((
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)(\.(
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)){3}))
|:
))
|
(
:
(((
:
[
0-9A-Fa-f
]{1,4}){1,7})
|
((
:
[
0-9A-Fa-f
]{1,4}){0,5}
:
((
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)(\.(
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)){3}))
|:
)))(
%.+
)?\s
*$
))
|
(
^
\s
*
((?=
.
{1,255}
$
)(?=
.*
[
A-Za-z
]
.*
)[
0-9A-Za-z
](?:(?:[
0-9A-Za-z
]
|
\b
-
){0,61}[
0-9A-Za-z
])?(?:\.[
0-9A-Za-z
](?:(?:[
0-9A-Za-z
]
|
\b
-
){0,61}[
0-9A-Za-z
])?)
*
)\s
*$
)
/
;
hostname_tester
=
/
((
^
\s
*
((([
0-9
]
|
[
1-9
][
0-9
]
|1
[
0-9
]{2}
|2
[
0-4
][
0-9
]
|25
[
0-5
])\.){3}([
0-9
]
|
[
1-9
][
0-9
]
|1
[
0-9
]{2}
|2
[
0-4
][
0-9
]
|25
[
0-5
]))\s
*$
)
|
(
^
\s
*
((([
0-9A-Fa-f
]{1,4}
:
){7}([
0-9A-Fa-f
]{1,4}
|:
))
|
(([
0-9A-Fa-f
]{1,4}
:
){6}(
:
[
0-9A-Fa-f
]{1,4}
|
((
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)(\.(
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)){3})
|:
))
|
(([
0-9A-Fa-f
]{1,4}
:
){5}(((
:
[
0-9A-Fa-f
]{1,4}){1,2})
|:
((
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)(\.(
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)){3})
|:
))
|
(([
0-9A-Fa-f
]{1,4}
:
){4}(((
:
[
0-9A-Fa-f
]{1,4}){1,3})
|
((
:
[
0-9A-Fa-f
]{1,4})?
:
((
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)(\.(
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)){3}))
|:
))
|
(([
0-9A-Fa-f
]{1,4}
:
){3}(((
:
[
0-9A-Fa-f
]{1,4}){1,4})
|
((
:
[
0-9A-Fa-f
]{1,4}){0,2}
:
((
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)(\.(
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)){3}))
|:
))
|
(([
0-9A-Fa-f
]{1,4}
:
){2}(((
:
[
0-9A-Fa-f
]{1,4}){1,5})
|
((
:
[
0-9A-Fa-f
]{1,4}){0,3}
:
((
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)(\.(
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)){3}))
|:
))
|
(([
0-9A-Fa-f
]{1,4}
:
){1}(((
:
[
0-9A-Fa-f
]{1,4}){1,6})
|
((
:
[
0-9A-Fa-f
]{1,4}){0,4}
:
((
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)(\.(
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)){3}))
|:
))
|
(
:
(((
:
[
0-9A-Fa-f
]{1,4}){1,7})
|
((
:
[
0-9A-Fa-f
]{1,4}){0,5}
:
((
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)(\.(
25
[
0-5
]
|2
[
0-4
]\d
|1
\d\d
|
[
1-9
]?\d)){3}))
|:
)))(
%.+
)?\s
*$
))
|
(
^
\s
*
((?=
.
{1,255}
$
)(?=
.*
[
A-Za-z
]
.*
)[
0-9A-Za-z
](?:(?:[
0-9A-Za-z
]
|
\b
-
){0,61}[
0-9A-Za-z
])?(?:\.[
0-9A-Za-z
](?:(?:[
0-9A-Za-z
]
|
\b
-
){0,61}[
0-9A-Za-z
])?)
*
)\s
*$
)
/
;
...
@@ -45,7 +46,7 @@ jQuery(function($){
...
@@ -45,7 +46,7 @@ jQuery(function($){
}
}
}
}
restore_items
(
name
s
);
restore_items
(
field
s
);
function
parse_xterm_style
()
{
function
parse_xterm_style
()
{
var
text
=
$
(
'.xterm-helpers style'
).
text
();
var
text
=
$
(
'.xterm-helpers style'
).
text
();
...
@@ -111,20 +112,24 @@ jQuery(function($){
...
@@ -111,20 +112,24 @@ jQuery(function($){
}
}
function
log_status
(
text
)
{
status
.
text
(
text
);
console
.
log
(
text
);
}
function
ajax_complete_callback
(
resp
)
{
function
ajax_complete_callback
(
resp
)
{
btn
.
prop
(
'disabled'
,
false
);
btn
.
prop
(
'disabled'
,
false
);
if
(
resp
.
status
!==
200
)
{
if
(
resp
.
status
!==
200
)
{
console
.
log
(
resp
);
log_status
(
resp
.
status
+
': '
+
resp
.
statusText
);
status
.
text
(
'Response code: '
+
resp
.
status
);
state
=
DISCONNECTED
;
state
=
DISCONNECTED
;
return
;
return
;
}
}
var
msg
=
resp
.
responseJSON
;
var
msg
=
resp
.
responseJSON
;
if
(
msg
.
status
)
{
if
(
msg
.
status
)
{
console
.
log
(
msg
);
log_status
(
msg
.
status
);
status
.
text
(
msg
.
status
);
state
=
DISCONNECTED
;
state
=
DISCONNECTED
;
return
;
return
;
}
}
...
@@ -133,16 +138,20 @@ jQuery(function($){
...
@@ -133,16 +138,20 @@ jQuery(function($){
join
=
(
ws_url
[
ws_url
.
length
-
1
]
===
'/'
?
''
:
'/'
),
join
=
(
ws_url
[
ws_url
.
length
-
1
]
===
'/'
?
''
:
'/'
),
url
=
ws_url
+
join
+
'ws?id='
+
msg
.
id
,
url
=
ws_url
+
join
+
'ws?id='
+
msg
.
id
,
sock
=
new
window
.
WebSocket
(
url
),
sock
=
new
window
.
WebSocket
(
url
),
encoding
,
decoder
,
encoding
=
'utf-8'
,
decoder
=
new
window
.
TextDecoder
(
'utf-8'
),
terminal
=
document
.
getElementById
(
'#terminal'
),
terminal
=
document
.
getElementById
(
'#terminal'
),
term
=
new
window
.
Terminal
({
term
=
new
window
.
Terminal
({
cursorBlink
:
true
,
cursorBlink
:
true
,
});
});
console
.
log
(
url
);
console
.
log
(
url
);
console
.
log
(
'The deault encoding of your server is '
+
msg
.
encoding
);
if
(
!
msg
.
encoding
)
{
// wssh.sock = sock;
console
.
log
(
'Unable to detect the default encoding of your server'
);
// wssh.term = term;
msg
.
encoding
=
encoding
;
}
else
{
console
.
log
(
'The deault encoding of your server is '
+
msg
.
encoding
);
}
function
resize_terminal
(
term
)
{
function
resize_terminal
(
term
)
{
var
geometry
=
current_geometry
();
var
geometry
=
current_geometry
();
...
@@ -161,14 +170,14 @@ jQuery(function($){
...
@@ -161,14 +170,14 @@ jQuery(function($){
function
set_encoding
(
new_encoding
)
{
function
set_encoding
(
new_encoding
)
{
// for console use
// for console use
if
(
new_encoding
===
undefined
)
{
if
(
!
new_encoding
)
{
console
.
log
(
'An encoding is required'
);
console
.
log
(
'An encoding is required'
);
return
;
return
;
}
}
try
{
try
{
decoder
=
new
window
.
TextDecoder
(
new_encoding
);
decoder
=
new
window
.
TextDecoder
(
new_encoding
);
encoding
=
new_
encoding
;
encoding
=
decoder
.
encoding
;
console
.
log
(
'Set encoding to '
+
encoding
);
console
.
log
(
'Set encoding to '
+
encoding
);
}
catch
(
RangeError
)
{
}
catch
(
RangeError
)
{
console
.
log
(
'Unknown encoding '
+
new_encoding
);
console
.
log
(
'Unknown encoding '
+
new_encoding
);
...
@@ -179,13 +188,13 @@ jQuery(function($){
...
@@ -179,13 +188,13 @@ jQuery(function($){
set_encoding
(
msg
.
encoding
);
set_encoding
(
msg
.
encoding
);
wssh
.
window_
geometry
=
function
()
{
wssh
.
geometry
=
function
()
{
// for console use
// for console use
var
geometry
=
current_geometry
();
var
geometry
=
current_geometry
();
console
.
log
(
'Current window geometry: '
+
JSON
.
stringify
(
geometry
));
console
.
log
(
'Current window geometry: '
+
JSON
.
stringify
(
geometry
));
};
};
wssh
.
websocket_
send
=
function
(
data
)
{
wssh
.
send
=
function
(
data
)
{
// for console use
// for console use
if
(
!
sock
)
{
if
(
!
sock
)
{
console
.
log
(
'Websocket was already closed'
);
console
.
log
(
'Websocket was already closed'
);
...
@@ -201,6 +210,7 @@ jQuery(function($){
...
@@ -201,6 +210,7 @@ jQuery(function($){
JSON
.
parse
(
data
);
JSON
.
parse
(
data
);
sock
.
send
(
data
);
sock
.
send
(
data
);
}
catch
(
SyntaxError
)
{
}
catch
(
SyntaxError
)
{
data
=
data
.
trim
()
+
'
\
r'
;
sock
.
send
(
JSON
.
stringify
({
'data'
:
data
}));
sock
.
send
(
JSON
.
stringify
({
'data'
:
data
}));
}
}
};
};
...
@@ -214,7 +224,7 @@ jQuery(function($){
...
@@ -214,7 +224,7 @@ jQuery(function($){
}
}
};
};
wssh
.
resize
_terminal
=
function
(
cols
,
rows
)
{
wssh
.
resize
=
function
(
cols
,
rows
)
{
// for console use
// for console use
if
(
term
===
undefined
)
{
if
(
term
===
undefined
)
{
console
.
log
(
'Terminal was already destroryed'
);
console
.
log
(
'Terminal was already destroryed'
);
...
@@ -287,21 +297,62 @@ jQuery(function($){
...
@@ -287,21 +297,62 @@ jQuery(function($){
}
}
function
connect_without_options
()
{
function
wrap_object
(
opts
){
if
(
state
!==
DISCONNECTED
)
{
var
obj
=
{};
console
.
log
(
messages
[
state
]);
return
;
obj
.
get
=
function
(
attr
)
{
return
opts
[
attr
]
||
''
;
};
return
obj
;
}
function
validate_form_data
(
data
)
{
var
hostname
=
data
.
get
(
'hostname'
),
port
=
data
.
get
(
'port'
),
username
=
data
.
get
(
'username'
),
pk
=
data
.
get
(
'privatekey'
),
result
=
{
'vaiid'
:
false
},
msg
,
size
;
if
(
!
hostname
)
{
msg
=
'Need value hostname'
;
}
else
if
(
!
port
)
{
msg
=
'Need value port'
;
}
else
if
(
!
username
)
{
msg
=
'Need value username'
;
}
else
if
(
!
hostname_tester
.
test
(
hostname
))
{
msg
=
'Invalid hostname: '
+
hostname
;
}
else
if
(
port
<=
0
||
port
>
63335
)
{
msg
=
'Invalid port: '
+
port
;
}
else
{
if
(
pk
)
{
size
=
pk
.
size
||
pk
.
length
;
if
(
size
>
key_max_size
)
{
msg
=
'Invalid private key: '
+
pk
.
name
||
pk
;
}
}
}
}
if
(
!
msg
||
debug
)
{
result
.
valid
=
true
;
msg
=
username
+
'@'
+
hostname
+
':'
+
port
;
}
result
.
msg
=
msg
;
return
result
;
}
function
connect_without_options
()
{
// use data from the form
var
form
=
document
.
querySelector
(
form_id
),
var
form
=
document
.
querySelector
(
form_id
),
url
=
form
.
action
,
url
=
form
.
action
,
data
=
new
FormData
(
form
),
data
=
new
FormData
(
form
),
hostname
=
data
.
get
(
'hostname'
),
pk
=
data
.
get
(
'privatekey'
);
port
=
data
.
get
(
'port'
),
username
=
data
.
get
(
'username'
);
function
ajax_post
()
{
function
ajax_post
()
{
store_items
(
name
s
,
data
);
store_items
(
field
s
,
data
);
status
.
text
(
''
);
status
.
text
(
''
);
btn
.
prop
(
'disabled'
,
true
);
btn
.
prop
(
'disabled'
,
true
);
...
@@ -315,88 +366,43 @@ jQuery(function($){
...
@@ -315,88 +366,43 @@ jQuery(function($){
contentType
:
false
,
contentType
:
false
,
processData
:
false
processData
:
false
});
});
state
=
CONNECTING
;
title_text
=
username
+
'@'
+
hostname
+
':'
+
port
;
}
}
if
(
!
hostname
||
!
port
||
!
username
)
{
var
result
=
validate_form_data
(
data
);
status
.
text
(
'Fields hostname, port and username are all required.'
);
if
(
!
result
.
valid
)
{
log_status
(
result
.
msg
);
return
;
return
;
}
}
if
(
!
hostname_tester
.
test
(
hostname
))
{
if
(
pk
&&
pk
.
size
&&
!
debug
)
{
status
.
text
(
'Invalid hostname: '
+
hostname
);
read_file_as_text
(
pk
,
function
(
text
)
{
return
;
if
(
text
===
undefined
)
{
}
log_status
(
'Invalid private key: '
+
pk
.
name
);
}
else
{
if
(
port
<=
0
||
port
>
63335
)
{
ajax_post
();
status
.
text
(
'Invalid port: '
+
port
);
}
return
;
});
}
var
pk
=
data
.
get
(
'privatekey'
);
if
(
pk
&&
pk
.
size
)
{
if
(
pk
.
size
>
key_max_size
)
{
console
.
log
(
'Invalid private key: '
+
pk
.
name
);
}
else
{
read_file_as_text
(
pk
,
function
(
text
)
{
if
(
text
===
undefined
)
{
console
.
log
(
'Invalid private key: '
+
pk
.
name
);
}
else
{
ajax_post
();
}
});
}
}
else
{
}
else
{
ajax_post
();
ajax_post
();
}
}
}
return
result
.
msg
;
}
function
connect_with_options
(
opts
)
{
if
(
state
!==
DISCONNECTED
)
{
console
.
log
(
messages
[
state
]);
return
;
}
function
connect_with_options
(
data
)
{
// use data from the arguments
var
form
=
document
.
querySelector
(
form_id
),
var
form
=
document
.
querySelector
(
form_id
),
xsrf
=
form
.
querySelector
(
'input[name="_xsrf"]'
).
value
,
url
=
data
.
url
||
form
.
action
,
url
=
opts
.
url
||
form
.
action
,
_xsrf
=
form
.
querySelector
(
'input[name="_xsrf"]'
);
hostname
=
opts
.
hostname
||
''
,
port
=
opts
.
port
||
''
,
username
=
opts
.
username
||
''
,
password
=
opts
.
password
||
''
,
privatekey
=
opts
.
privatekey
||
''
,
data
=
{
'_xsrf'
:
xsrf
,
'username'
:
username
,
'hostname'
:
hostname
,
'port'
:
port
,
'password'
:
password
,
'privatekey'
:
privatekey
};
if
(
!
hostname
||
!
port
||
!
username
)
{
console
.
log
(
'Fields hostname, port and username are all required.'
);
return
;
}
if
(
!
hostname_tester
.
test
(
hostname
))
{
console
.
log
(
'Invalid hostname: '
+
hostname
);
return
;
}
if
(
port
<=
0
||
port
>
63335
)
{
var
result
=
validate_form_data
(
wrap_object
(
data
));
console
.
log
(
'Invalid port: '
+
port
);
if
(
!
result
.
valid
)
{
console
.
log
(
result
.
msg
);
return
;
return
;
}
}
if
(
privatekey
&&
privatekey
.
length
>
key_max_size
)
{
data
.
_xsrf
=
_xsrf
.
value
;
console
.
log
(
'Invalid private key: '
+
privatekey
);
return
;
}
$
.
ajax
({
$
.
ajax
({
url
:
url
,
url
:
url
,
...
@@ -405,22 +411,48 @@ jQuery(function($){
...
@@ -405,22 +411,48 @@ jQuery(function($){
complete
:
ajax_complete_callback
complete
:
ajax_complete_callback
});
});
state
=
CONNECTING
;
return
result
.
msg
;
title_text
=
username
+
'@'
+
hostname
+
':'
+
port
;
}
}
wssh
.
connect
=
function
(
opts
)
{
if
(
opts
===
undefined
)
{
function
connect
(
hostname
,
port
,
username
,
password
,
privatekey
)
{
connect_without_options
();
// for console use
var
result
,
opts
;
if
(
state
!==
DISCONNECTED
)
{
console
.
log
(
messages
[
state
]);
return
;
}
if
(
hostname
===
undefined
)
{
result
=
connect_without_options
();
}
else
{
}
else
{
connect_with_options
(
opts
);
if
(
typeof
hostname
===
'string'
)
{
opts
=
{
hostname
:
hostname
,
port
:
port
,
username
:
username
,
password
:
password
,
privatekey
:
privatekey
};
}
else
{
opts
=
hostname
;
}
result
=
connect_with_options
(
opts
);
}
}
};
if
(
result
)
{
state
=
CONNECTING
;
title_text
=
result
;
}
}
wssh
.
connect
=
connect
;
$
(
form_id
).
submit
(
function
(
event
){
$
(
form_id
).
submit
(
function
(
event
){
event
.
preventDefault
();
event
.
preventDefault
();
connect
_without_options
();
connect
();
});
});
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment