use lazy_static::lazy_static; use std::time::{SystemTime, UNIX_EPOCH}; use isahc::{cookies::CookieJar, prelude::*, Request}; use std::sync::RwLock; struct CookieHold { pub jar: CookieJar, } lazy_static! { /// Stores a client with a persistent cookie store static ref SESSION_COOKIE: RwLock = RwLock::new(CookieHold { jar: CookieJar::new()}); /// Stores the last time a request was made, in seconds since UNIX epoch static ref SESSION_TIME: RwLock = RwLock::new(0); } /// Makes a request to the online classroom, and returns the html string pub async fn request(url: String) -> Result { let classroom_url = std::env::var("CLASSROOM_URL").expect("CLASSROOM_URL env var is not set!"); ensure_session().await?; // Get the stored client let jar = SESSION_COOKIE.read().unwrap().jar.clone(); let uri = format!("{}{}", classroom_url, url); // Do the request let response = Request::get(uri) .cookie_jar(jar.clone()) .body(()) .or_else(|err| Err(format!("Error creating request: {:?}", err)))? .send(); let mut response = match response { Ok(r) => r, Err(err) => return Err(format!("Error sending request: {:?}", err)), }; match response.text() { Ok(t) => Ok(t), Err(err) => Err(format!("Error getting text from response: {:?}", err)), } } /// Handles request for creating a user. /// /// Returns `Ok("")` if the request was redirected (i.e. the user was created successfully /// /// Returns `Ok(html)` if the request was not redirected (i.e. the user was not created successfully) /// /// Returns `Err(err)` if there was an error pub async fn create_user_request(url: String, body: String) -> Result { let classroom_url = std::env::var("CLASSROOM_URL").expect("CLASSROOM_URL env var is not set!"); ensure_session().await?; // Get the stored client let jar = SESSION_COOKIE.read().unwrap().jar.clone(); let uri = format!("{}{}", classroom_url, url); // Do the request let response = Request::post(uri) .header("Content-Type", "multipart/form-data; boundary=---------------------------83919643214156711801978607619") .cookie_jar(jar.clone()) .body(body) .or_else(|err| Err(format!("Error creating request: {:?}", err)))? .send(); let mut response = match response { Ok(r) => r, Err(err) => return Err(format!("Error sending request: {:?}", err)), }; if response.status() == isahc::http::StatusCode::FOUND { println!("Redirected!"); return Ok("".into()) } match response.text() { Ok(t) => Ok(t), Err(err) => Err(format!("Error getting text from response: {:?}", err)), } } /// Makes sure that the session cookie is set, and that it is valid pub async fn ensure_session() -> Result<(), String> { let last_usage_time = SESSION_TIME.read().unwrap().clone(); // Get current time in seconds let current_time = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("Time went backwards") .as_secs(); let time_passed = current_time - last_usage_time; // Default PHP session timeout is 1440 seconds. Use a 1400 seconds timeout to be safe if time_passed > 1400 { login().await?; let mut time_ptr = SESSION_TIME.write().unwrap(); *time_ptr = current_time; } Ok(()) } /// Logins to the online classroom, and sets the session cookie async fn login() -> Result<(), String> { let classroom_url = std::env::var("CLASSROOM_URL").expect("CLASSROOM_URL env var is not set!"); let classroom_user = std::env::var("CLASSROOM_USER").expect("CLASSROOM_USER env var is not set!"); let classroom_password = std::env::var("CLASSROOM_PASSWORD").expect("CLASSROOM_PASSWORD env var is not set!"); let jar = CookieJar::new(); let response = Request::post(format!("{}/index.php", classroom_url)) .header("Content-Type", "application/x-www-form-urlencoded") .cookie_jar(jar.clone()) .body(format!( "login={}&password={}&submitAuth=&_qf__formLogin=", classroom_user, classroom_password )) .unwrap() .send(); match response { Ok(_) => { SESSION_COOKIE.write().unwrap().jar = jar.clone(); Ok(()) } Err(error) => Err(format!("Error connecting to online classroom: {:?}", error)), } }