I'm batman
This commit has no parents
This commit is contained in:
commit
7cbd19f49a
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
**/*.rs.bk
|
1490
Cargo.lock
generated
Normal file
1490
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "rub-login"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Valentin Brandl <vbrandl@riseup.net>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
html5ever = "0.22.3"
|
||||||
|
reqwest = "0.9.2"
|
||||||
|
serde = "1.0.79"
|
||||||
|
serde_derive = "1.0.79"
|
||||||
|
structopt = "0.2.11"
|
||||||
|
failure = "0.1.2"
|
14
LICENSE
Normal file
14
LICENSE
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||||
|
Version 2, December 2004
|
||||||
|
|
||||||
|
Copyright (C) 2018 Valentin Brandl <spam@vbrandl.net>
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim or modified
|
||||||
|
copies of this license document, and changing it is allowed as long
|
||||||
|
as the name is changed.
|
||||||
|
|
||||||
|
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||||
|
|
16
README.md
Normal file
16
README.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# RUB Login
|
||||||
|
|
||||||
|
Login helper for the form at https://login.rz.rub.de/
|
||||||
|
|
||||||
|
Since the internet in my university and dormitory requires a login via the form on https://login.rz.rub.de/, I tried to
|
||||||
|
automate that task. The tool parses the login mask and sends a login request using the supplied username and password.
|
||||||
|
The tool also supports logging out but I have never needed that feature.
|
||||||
|
|
||||||
|
The parsing is done using [html5ever](https://github.com/servo/html5ever). While it should also be possible using simple
|
||||||
|
RegExes or even string operations, I thought it would be fun to properly parse the website.
|
||||||
|
|
||||||
|
Nothing more to say.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the [WTFPL](./LICENSE).
|
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())),
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user