I'm batman

This commit has no parents
This commit is contained in:
Valentin Brandl
2018-11-07 20:28:46 +01:00
commit 7cbd19f49a
6 changed files with 1702 additions and 0 deletions

168
src/main.rs Normal file
View 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())),
}
}