This commit is contained in:
Jovi Hsu 2021-11-10 22:41:11 +08:00
parent a0b1157c04
commit c8bb376d80
7 changed files with 232 additions and 36 deletions

View File

@ -1,3 +1,4 @@
use super::input::Input;
use anyhow;
use serde_json::{json, Value};
use std::fmt::Debug;
@ -116,28 +117,19 @@ impl Component for AuthComponents {
fn view(&self) -> Html {
let link = &self.link;
let update_uname = link.callback(|e: ChangeData| match e {
ChangeData::Value(val) => AuthMsg::UpdateUsername(val),
_ => panic!("unexpected message"),
});
let update_uname = link.callback(|v| AuthMsg::UpdateUsername(v));
let update_pword = link.callback(|e: ChangeData| match e {
ChangeData::Value(val) => AuthMsg::UpdatePassword(val),
_ => panic!("unexpected message"),
});
let update_pword = link.callback(|v| AuthMsg::UpdatePassword(v));
let auth_post = link.callback(|_| {
// ConsoleService::log("Auth post");
AuthMsg::AuthRequest
});
let auth_post = link.callback(|_| AuthMsg::AuthRequest);
html! {
<div class="horizontal-centre vertical-centre">
<label for="username">{"Username: "}</label>
<input id="username" type="text" placeholder="Username" onchange={update_uname} />
<Input id="username" type_="text" placeholder="Username" on_change={update_uname} />
<br />
<label for="password">{"Password: "}</label>
<input id="password" type="password" placeholder="Password" onchange={update_pword} />
<Input id="password" type_="password" placeholder="Password" on_change={update_pword} />
<br />
<button type="submit" onclick={auth_post}>{"Login"}</button>
<br />

View File

@ -0,0 +1,68 @@
use yew::prelude::*;
// message on update
pub enum InputMsg {
Update(String),
}
// props on_change
#[derive(Clone, PartialEq, Properties)]
pub struct InputProps {
pub on_change: Callback<String>,
pub id: String,
pub type_: String,
pub placeholder: String,
}
// component input
pub struct Input {
link: ComponentLink<Self>,
on_change: Callback<String>,
id: String,
type_: String,
placeholder: String,
}
impl Component for Input {
type Message = InputMsg;
type Properties = InputProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
Input {
link,
on_change: props.on_change,
id: props.id,
type_: props.type_,
placeholder: props.placeholder,
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
InputMsg::Update(text) => {
self.on_change.emit(text);
true
}
}
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
false
}
fn view(&self) -> Html {
let on_change = self.link.callback(|e: ChangeData| match e {
ChangeData::Value(v) => InputMsg::Update(v),
_ => panic!("unexpected message"),
});
html! {
<input
id={self.id.clone()}
type={self.type_.clone()}
placeholder={self.placeholder.clone()}
onchange={on_change}
/>
}
}
}

View File

@ -1,3 +1,4 @@
pub mod auth;
pub mod host;
pub mod input;
pub mod ws;

View File

@ -104,6 +104,7 @@ impl Component for WebsocketCtx {
fn rendered(&mut self, first_render: bool) {
if first_render && self.ws.is_none() {
ConsoleService::log(&format!("Start websocket"));
self.link.send_message(WebsocketMsg::Connect);
}
}

View File

@ -9,9 +9,8 @@ use yew::{
},
};
use crate::components::ws::WebsocketMsg;
use crate::{
components,
components::{self, input::Input, ws::WebsocketMsg},
protocal::{common::*, vnc::VncHandler},
utils::WeakComponentLink,
};
@ -24,6 +23,10 @@ pub struct PageRemote {
connected: bool,
handler: ProtocalHandler<VncHandler>,
ws_link: WeakComponentLink<components::ws::WebsocketCtx>,
request_username: bool,
request_password: bool,
username: String,
password: String,
}
#[derive(Clone, PartialEq, Properties)]
@ -35,6 +38,9 @@ pub enum RemoteMsg {
Connected,
Recv(Vec<u8>),
Send(Vec<u8>),
UpdateUsername(String),
UpdatePassword(String),
SendCredential,
}
impl Component for PageRemote {
@ -50,6 +56,10 @@ impl Component for PageRemote {
connected: false,
handler: ProtocalHandler::new(),
ws_link: WeakComponentLink::default(),
request_username: false,
request_password: false,
username: String::from(""),
password: String::from(""),
}
}
@ -115,6 +125,11 @@ impl Component for PageRemote {
self.link.send_message(RemoteMsg::Send(out));
false
}
ProtocalHandlerOutput::RequirePassword => {
self.request_password = true;
true
}
_ => unimplemented!(),
}
}
RemoteMsg::Send(v) => {
@ -125,6 +140,20 @@ impl Component for PageRemote {
.send_message(WebsocketMsg::Send(Ok(v)));
false
}
RemoteMsg::UpdateUsername(username) => {
self.username = username;
true
}
RemoteMsg::UpdatePassword(password) => {
self.password = password;
true
}
RemoteMsg::SendCredential => {
self.request_username = false;
self.request_password = false;
self.handler.set_credential(&self.username, &self.password);
true
}
}
}
@ -146,11 +175,61 @@ impl Component for PageRemote {
let ws_link = &self.ws_link;
html! {
<>
<components::ws::WebsocketCtx
weak_link=ws_link onrecv=recv_msg/>
{self.error_msg.clone()}
<div class="horizontal-centre vertical-centre">
{self.username_view()}
{self.password_view()}
{self.button_connect_view()}
<components::ws::WebsocketCtx
weak_link=ws_link onrecv=recv_msg/>
{self.error_msg.clone()}
</div>
</>
}
}
}
}
// impl PageRemote
impl PageRemote {
fn username_view(&self) -> Html {
if self.request_username {
let update_username = self.link.callback(|v| RemoteMsg::UpdateUsername(v));
html! {
<>
<Input id="username" type_="text" placeholder="username" on_change={update_username}/>
<br/>
</>
}
} else {
html! {}
}
}
fn password_view(&self) -> Html {
if self.request_password {
let update_password = self.link.callback(|v| RemoteMsg::UpdatePassword(v));
html! {
<>
<Input id="password" type_="password" placeholder="password" on_change={update_password}/>
<br/>
</>
}
} else {
html! {}
}
}
fn button_connect_view(&self) -> Html {
if self.request_username || self.request_password {
let send_credential = self.link.callback(|_| RemoteMsg::SendCredential);
html! {
<>
<button type="submit" onclick={send_credential}>{"Connect"}</button>
<br/>
</>
}
} else {
html! {}
}
}
}

View File

@ -3,6 +3,8 @@ pub enum ProtocalHandlerOutput {
Ok,
WsBuf(Vec<u8>),
Err(String),
RequireUsername,
RequirePassword,
}
pub struct ProtocalHandler<T>
@ -23,11 +25,16 @@ where
pub fn handle(&mut self, input: &[u8]) -> ProtocalHandlerOutput {
self.inner.handle(input)
}
pub fn set_credential(&mut self, username: &str, password: &str) -> ProtocalHandlerOutput {
self.inner.set_credential(username, password)
}
}
pub trait ProtocalImpl {
fn new() -> Self;
fn handle(&mut self, input: &[u8]) -> ProtocalHandlerOutput;
fn set_credential(&mut self, username: &str, password: &str) -> ProtocalHandlerOutput;
}
pub struct StreamReader<'a> {
@ -73,6 +80,12 @@ impl<'a> StreamReader<'a> {
Some(Self::read_u32(self).map(|b| b as i32)?)
}
pub fn extract_slice(&mut self, len: usize, buf: &mut [u8]) {
for x in self.inner.by_ref().take(len).enumerate() {
buf[x.0] = *x.1;
}
}
pub fn read_string_with_len(&mut self, len: usize) -> Option<String> {
let mut buf = vec![0u8; len as usize];
self.inner
@ -85,11 +98,16 @@ impl<'a> StreamReader<'a> {
Some(String::from_utf8(buf).unwrap())
}
pub fn read_string(&mut self) -> Option<String> {
pub fn read_string_l16(&mut self) -> Option<String> {
let len = self.read_u16()? as usize;
Some(self.read_string_with_len(len)?)
}
pub fn read_string_l32(&mut self) -> Option<String> {
let len = self.read_u32()? as usize;
Some(self.read_string_with_len(len)?)
}
pub fn eof(&mut self) -> bool {
self.inner.next().is_none()
}

View File

@ -6,31 +6,42 @@ const VNC_RFB33: &[u8; 12] = b"RFB 003.003\n";
const VNC_RFB37: &[u8; 12] = b"RFB 003.007\n";
const VNC_RFB38: &[u8; 12] = b"RFB 003.008\n";
const VNC_VER_UNSUPPORTED: &str = "unsupported version";
const VNC_FAILED: &str = "Connection failed with unknow reason";
enum VncState {
Handshake,
Authentication,
ClientInit, // auth done
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum SecurityType {
Unknown(u8),
Invalid,
None,
VncAuth,
AppleRdp,
Invalid = 0,
None = 1,
VncAuth = 2,
RA2 = 5,
RA2ne = 6,
Tight = 16,
Ultra = 17,
TLS = 18,
VeNCrypt = 19,
}
pub struct VncHandler {
state: VncState,
// output: Vec<u8>,
challenge: [u8; 16],
security_type: SecurityType,
password: String,
}
impl ProtocalImpl for VncHandler {
fn new() -> Self {
VncHandler {
state: VncState::Handshake,
// output: Vec::new(),
challenge: [0u8; 16],
security_type: SecurityType::Invalid,
password: String::new(),
}
}
@ -47,11 +58,19 @@ impl ProtocalImpl for VncHandler {
}
VncState::Authentication => {
ConsoleService::log(&format!("{:?}", input));
return ProtocalHandlerOutput::Ok;
return self.start_authenticate(input);
}
_ => panic!("unsupported version"),
}
}
fn set_credential(&mut self, _username: &str, password: &str) -> ProtocalHandlerOutput {
ConsoleService::log(&format!("{:?}", password));
ConsoleService::log(&format!("{:?}", self.challenge));
// since vnc do not require username, so we just ignore it
self.password = password.to_string();
self.continue_authenticate()
}
}
// private methods
@ -59,17 +78,35 @@ impl VncHandler {
fn handle_handshake(&self, rfbversion: &[u8]) -> Result<&'static [u8], &'static str> {
match rfbversion {
b"RFB 003.003\n" => Ok(VNC_RFB33),
b"RFB 003.007\n" => Ok(VNC_RFB37),
b"RFB 003.008\n" => Ok(VNC_RFB38),
b"RFB 003.007\n" => Ok(VNC_RFB33),
b"RFB 003.008\n" => Ok(VNC_RFB33),
_ => Err(VNC_VER_UNSUPPORTED),
}
}
fn handle_authenticate(&mut self, auth: &[u8]) -> Result<&'static [u8], &'static str> {
match auth {
b"NONE\n" => Ok(b"\x00"),
b"VNCAUTH\n" => Ok(b"\x01"),
b"APPLETALK\n" => Ok(b"\x02"),
_ => Err(VNC_VER_UNSUPPORTED),
fn start_authenticate(&mut self, auth: &[u8]) -> ProtocalHandlerOutput {
let mut sr = StreamReader::new(auth);
match sr.read_u32() {
Some(0) => {
let err_msg = sr.read_string_l32().unwrap();
ProtocalHandlerOutput::Err(err_msg)
}
Some(1) => {
self.state = VncState::ClientInit;
self.client_initialisation()
}
Some(2) => {
sr.extract_slice(16, &mut self.challenge);
ProtocalHandlerOutput::RequirePassword
}
_ => ProtocalHandlerOutput::Err(VNC_FAILED.to_string()),
}
}
fn client_initialisation(&mut self) -> ProtocalHandlerOutput {
ProtocalHandlerOutput::Ok
}
fn continue_authenticate(&mut self) -> ProtocalHandlerOutput {
ProtocalHandlerOutput::Ok
}
}