I'm batman
This commit has no parents
This commit is contained in:
168
src/main.rs
Normal file
168
src/main.rs
Normal file
@ -0,0 +1,168 @@
|
||||
#[macro_use]
|
||||
extern crate failure;
|
||||
extern crate html5ever;
|
||||
extern crate reqwest;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate structopt;
|
||||
|
||||
use failure::Error;
|
||||
use html5ever::{
|
||||
driver::ParseOpts,
|
||||
parse_document,
|
||||
rcdom::{Node, NodeData, RcDom},
|
||||
tendril::TendrilSink,
|
||||
tree_builder::TreeBuilderOpts,
|
||||
};
|
||||
use std::{net::Ipv4Addr, str::FromStr};
|
||||
use structopt::StructOpt;
|
||||
|
||||
const MASK_URL: &str = "https://login.rz.ruhr-uni-bochum.de/cgi-bin/start";
|
||||
const API_URL: &str = "https://login.rz.ruhr-uni-bochum.de/cgi-bin/laklogin";
|
||||
|
||||
const LOGIN_OK_MARKER: &str = "Verbindung wurde hergestellt";
|
||||
const LOGOUT_OK_MARKER: &str = "Logout erfolgreich";
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
enum LocalError {
|
||||
#[fail(display = "Cannot parse value from HTML")]
|
||||
ParseError,
|
||||
#[fail(display = "Operation {} failed", _0)]
|
||||
OperationFailed(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
enum Opt {
|
||||
/// Perform a login operation
|
||||
#[structopt(name = "login")]
|
||||
Login { loginid: String, password: String },
|
||||
/// Perform a logout operation
|
||||
#[structopt(name = "logout")]
|
||||
Logout,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(tag = "action")]
|
||||
enum Request {
|
||||
Login {
|
||||
loginid: String,
|
||||
password: String,
|
||||
ipaddr: Ipv4Addr,
|
||||
},
|
||||
Logout,
|
||||
}
|
||||
|
||||
fn find_text(node: &Node, text: &str) -> bool {
|
||||
if let NodeData::Text { ref contents } = node.data {
|
||||
if contents.borrow().to_string() == text {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for chld in node.children.borrow().iter() {
|
||||
if find_text(chld, text) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn find_value<T>(node: &Node, tag: &str, attr_name: &str) -> Option<T>
|
||||
where
|
||||
T: FromStr,
|
||||
{
|
||||
if let NodeData::Element {
|
||||
ref name,
|
||||
ref attrs,
|
||||
..
|
||||
} = node.data
|
||||
{
|
||||
if name.local == *tag && attrs
|
||||
.borrow()
|
||||
.iter()
|
||||
.any(|attr| attr.name.local == *"name" && attr.value.to_string() == attr_name)
|
||||
{
|
||||
let t = attrs
|
||||
.borrow()
|
||||
.iter()
|
||||
.find(|attr| attr.name.local == *"value")
|
||||
.and_then(|attr| attr.value.parse().ok());
|
||||
if t.is_some() {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
for chld in node.children.borrow().iter() {
|
||||
let res = find_value(chld, tag, attr_name);
|
||||
if res.is_some() {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn login(loginid: String, password: String) -> Result<bool, Error> {
|
||||
let mut res = reqwest::get(MASK_URL)?;
|
||||
let opts = ParseOpts {
|
||||
tree_builder: TreeBuilderOpts {
|
||||
drop_doctype: true,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let dom = parse_document(RcDom::default(), opts.clone())
|
||||
.from_utf8()
|
||||
.read_from(&mut res)?;
|
||||
let ipaddr: Ipv4Addr =
|
||||
find_value(&dom.document, "input", "ipaddr").ok_or(LocalError::ParseError)?;
|
||||
let data = Request::Login {
|
||||
loginid,
|
||||
password,
|
||||
ipaddr,
|
||||
};
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let mut res = client.post(API_URL).form(&data).send()?;
|
||||
let dom = parse_document(RcDom::default(), opts)
|
||||
.from_utf8()
|
||||
.read_from(&mut res)?;
|
||||
Ok(find_text(&dom.document, LOGIN_OK_MARKER))
|
||||
}
|
||||
|
||||
fn logout() -> Result<bool, Error> {
|
||||
let data = Request::Logout;
|
||||
let client = reqwest::Client::new();
|
||||
let mut res = client.post(API_URL).form(&data).send()?;
|
||||
|
||||
let opts = ParseOpts {
|
||||
tree_builder: TreeBuilderOpts {
|
||||
drop_doctype: true,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
let dom = parse_document(RcDom::default(), opts.clone())
|
||||
.from_utf8()
|
||||
.read_from(&mut res)?;
|
||||
|
||||
Ok(find_text(&dom.document, LOGOUT_OK_MARKER))
|
||||
}
|
||||
|
||||
fn convert_operation_result(res: bool, op_name: String) -> Result<(), Error> {
|
||||
if !res {
|
||||
Err(LocalError::OperationFailed(op_name).into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let opt = Opt::from_args();
|
||||
match opt {
|
||||
Opt::Login { loginid, password } => login(loginid, password)
|
||||
.and_then(|res| convert_operation_result(res, "Login".to_owned())),
|
||||
Opt::Logout => logout().and_then(|res| convert_operation_result(res, "Logout".to_owned())),
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user