Add initialization of crypto values
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
|
||||
@@ -6,7 +6,11 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
derive_builder = "0.11"
|
||||
rand = { version = "0.8", features = ["getrandom"] }
|
||||
regex = "1.5"
|
||||
reqwest = { version = "0.11", features = ["json", "cookies"] }
|
||||
thiserror = "1.0"
|
||||
|
||||
[[bin]]
|
||||
name = "vodafone_runner"
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
pub mod station;
|
||||
|
||||
#[macro_use]
|
||||
extern crate derive_builder;
|
||||
|
||||
146
src/station.rs
146
src/station.rs
@@ -1,43 +1,157 @@
|
||||
pub mod station {
|
||||
pub use reqwest::Client;
|
||||
|
||||
use derive_builder::UninitializedFieldError;
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
use regex::Regex;
|
||||
use reqwest::header;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StationError {
|
||||
#[error("request error")]
|
||||
Request(#[from] reqwest::Error),
|
||||
#[error("could not parse data: {0}")]
|
||||
Parse(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Builder)]
|
||||
#[builder(build_fn(error = "StationError"))]
|
||||
struct CryptoValues {
|
||||
session_id: String,
|
||||
nonce: String,
|
||||
init_vector: String,
|
||||
salt: String,
|
||||
}
|
||||
|
||||
impl From<UninitializedFieldError> for StationError {
|
||||
fn from(ufe: UninitializedFieldError) -> StationError {
|
||||
StationError::Parse(ufe.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Builder)]
|
||||
struct LoginState {
|
||||
csrf_nonce: String,
|
||||
key: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VodafoneStation {
|
||||
host: String,
|
||||
client: Client,
|
||||
session_id: String,
|
||||
nonce: String,
|
||||
csrf_nonce: String,
|
||||
init_vector: String,
|
||||
salt: String,
|
||||
key: String,
|
||||
cookie: String,
|
||||
crypto: Option<CryptoValues>,
|
||||
state: Option<LoginState>,
|
||||
}
|
||||
|
||||
impl VodafoneStation {
|
||||
pub fn new(host: String) -> Self {
|
||||
pub fn new(host: &str) -> Self {
|
||||
let mut headers = header::HeaderMap::new();
|
||||
headers.insert("X-Requested-With", header::HeaderValue::from_static("XMLHttpRequest"));
|
||||
headers.insert(header::REFERER,
|
||||
headers.insert(
|
||||
"X-Requested-With",
|
||||
header::HeaderValue::from_static("XMLHttpRequest"),
|
||||
);
|
||||
headers.insert(
|
||||
header::REFERER,
|
||||
header::HeaderValue::from_str(&format!("http://{}/?overview", host))
|
||||
.expect("Host name is not valid ASCII!"));
|
||||
headers.insert(header::ORIGIN,
|
||||
.expect("Host name is not valid ASCII!"),
|
||||
);
|
||||
headers.insert(
|
||||
header::ORIGIN,
|
||||
header::HeaderValue::from_str(&format!("http://{}", host))
|
||||
.expect("Host name is not valid ASCII!"));
|
||||
.expect("Host name is not valid ASCII!"),
|
||||
);
|
||||
|
||||
let client = Client::builder()
|
||||
.default_headers(headers)
|
||||
.user_agent("Mozilla/5.0 (Windows NT 6.1; rv:91.0) Gecko/20100101 Firefox/91.0")
|
||||
.cookie_store(true)
|
||||
.timeout(Duration::from_secs(10))
|
||||
.build().expect("Could not construct reqwest client.");
|
||||
.build()
|
||||
.expect("Could not construct reqwest client.");
|
||||
|
||||
VodafoneStation {
|
||||
host: host,
|
||||
|
||||
host: host.to_owned(),
|
||||
client,
|
||||
crypto: None,
|
||||
state: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn connect(&mut self) -> Result<(), StationError> {
|
||||
let response = self.client.get("/").send().await?;
|
||||
let text = response.text().await?;
|
||||
self.init_crypto(&text)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_crypto(&mut self, response: &str) -> Result<(), StationError> {
|
||||
let re = Regex::new(
|
||||
r"(?x) # Ignore whitespace; allow line comments
|
||||
(\W|^) [[:space:]]* # Start of Javascript statement
|
||||
var [[:space:]]+ (?P<varName>[[:word:]]+) # variable declaration
|
||||
[[:space:]]* = [[:space:]]* # Equals sign
|
||||
'(?P<strval>[^']*)' # String value
|
||||
[[:space:]]*; # End of statement
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut stbuild = CryptoValuesBuilder::default();
|
||||
|
||||
for caps in re.captures_iter(&response) {
|
||||
match &caps["varName"] {
|
||||
"currentSessionId" => {
|
||||
stbuild.session_id(caps["strval"].to_owned());
|
||||
}
|
||||
"myIv" => {
|
||||
stbuild.init_vector(caps["strval"].to_owned());
|
||||
}
|
||||
"mySalt" => {
|
||||
stbuild.salt(caps["strval"].to_owned());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
stbuild.nonce(OsRng.next_u32().to_string());
|
||||
|
||||
self.crypto = Some(stbuild.build()?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_init_crypto() {
|
||||
let mut station = VodafoneStation::new("INV");
|
||||
let session_id = "cool_session";
|
||||
let iv = "so_initialized";
|
||||
let salt = "extremely_salty";
|
||||
station
|
||||
.init_crypto(
|
||||
format!(
|
||||
r"var currentSessionId = '{}';
|
||||
var myIv = '{}';
|
||||
var mySalt = '{}';
|
||||
",
|
||||
&session_id, &iv, &salt
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
.expect("Couldn't parse Javascript variable declarations.");
|
||||
|
||||
assert!(station.crypto.is_some());
|
||||
let crypto = station.crypto.unwrap();
|
||||
assert_eq!(crypto.session_id, session_id);
|
||||
assert_eq!(crypto.init_vector, iv);
|
||||
assert_eq!(crypto.salt, salt);
|
||||
assert!(crypto.nonce.len() > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user