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