From 4d70e7c4282cc3878d128bcec29022d401b669c3 Mon Sep 17 00:00:00 2001 From: Araozu Date: Sat, 30 Sep 2023 12:22:55 -0500 Subject: [PATCH] [BE] Correctly get & store online classroom cookie --- backend/Cargo.lock | 244 +++++++++++++++++++----- backend/Cargo.toml | 5 +- backend/src/online_classroom/session.rs | 96 +++------- backend/src/online_classroom/users.rs | 5 +- 4 files changed, 234 insertions(+), 116 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index cb263f6..41ec0d0 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -59,6 +59,17 @@ dependencies = [ "libc", ] +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -119,13 +130,14 @@ version = "0.1.0" dependencies = [ "chrono", "dotenvy", + "isahc", "once_cell", "reqwest", "rocket", "scraper", "serde", "sqlx", - "ureq", + "urlencoding", ] [[package]] @@ -203,6 +215,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "castaway" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" + [[package]] name = "cc" version = "1.0.83" @@ -233,6 +251,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "concurrent-queue" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-oid" version = "0.9.5" @@ -318,15 +345,6 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - [[package]] name = "crossbeam-queue" version = "0.3.8" @@ -379,6 +397,37 @@ dependencies = [ "syn 2.0.29", ] +[[package]] +name = "curl" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2 0.4.9", + "winapi", +] + +[[package]] +name = "curl-sys" +version = "0.4.66+curl-8.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70c44a72e830f0e40ad90dda8a6ab6ed6314d39776599a58a2e5e37fbc6db5b9" +dependencies = [ + "cc", + "libc", + "libnghttp2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "windows-sys", +] + [[package]] name = "der" version = "0.7.8" @@ -541,6 +590,15 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.0.0" @@ -561,16 +619,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "flate2" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - [[package]] name = "flume" version = "0.10.14" @@ -681,6 +729,21 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-sink" version = "0.3.28" @@ -1032,6 +1095,15 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "ipnet" version = "2.8.0" @@ -1049,6 +1121,34 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "isahc" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" +dependencies = [ + "async-channel", + "castaway", + "crossbeam-utils", + "curl", + "curl-sys", + "encoding_rs", + "event-listener", + "futures-lite", + "http", + "httpdate", + "log", + "mime", + "once_cell", + "polling", + "slab", + "sluice", + "tracing", + "tracing-futures", + "url", + "waker-fn", +] + [[package]] name = "itertools" version = "0.10.5" @@ -1094,6 +1194,16 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +[[package]] +name = "libnghttp2-sys" +version = "0.1.8+1.55.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fae956c192dadcdb5dace96db71fa0b827333cce7c7b38dc71446f024d8a340" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "libsqlite3-sys" version = "0.26.0" @@ -1105,6 +1215,18 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libz-sys" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.5" @@ -1411,6 +1533,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52c774a4c39359c1d1c52e43f73dd91a75a614652c825408eec30c95a9b2067" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1617,6 +1745,22 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1987,7 +2131,6 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36" dependencies = [ - "log", "ring", "rustls-webpki", "sct", @@ -2240,6 +2383,17 @@ dependencies = [ "autocfg", ] +[[package]] +name = "sluice" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" +dependencies = [ + "async-channel", + "futures-core", + "futures-io", +] + [[package]] name = "smallvec" version = "1.11.0" @@ -2356,7 +2510,7 @@ dependencies = [ "tokio-stream", "tracing", "url", - "webpki-roots 0.24.0", + "webpki-roots", ] [[package]] @@ -2599,7 +2753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", - "fastrand", + "fastrand 2.0.0", "redox_syscall", "rustix", "windows-sys", @@ -2838,6 +2992,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + [[package]] name = "tracing-log" version = "0.1.3" @@ -2949,22 +3113,6 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" -[[package]] -name = "ureq" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5ccd538d4a604753ebc2f17cd9946e89b77bf87f6a8e2309667c6f2e87855e3" -dependencies = [ - "base64", - "flate2", - "log", - "once_cell", - "rustls", - "rustls-webpki", - "url", - "webpki-roots 0.25.2", -] - [[package]] name = "url" version = "2.4.0" @@ -2976,6 +3124,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" @@ -3009,6 +3163,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + [[package]] name = "want" version = "0.3.1" @@ -3115,12 +3275,6 @@ dependencies = [ "rustls-webpki", ] -[[package]] -name = "webpki-roots" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" - [[package]] name = "whoami" version = "1.4.1" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 7285a46..8a65be9 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -14,4 +14,7 @@ dotenvy = "0.15.7" serde = "1.0.188" chrono = "0.4.27" scraper = "0.17.1" -ureq = "2.8.0" +isahc = { version = "1.7.2", features = ["cookies"] } +urlencoding = "2.1.3" + + diff --git a/backend/src/online_classroom/session.rs b/backend/src/online_classroom/session.rs index f4d3fe6..fbd97d7 100644 --- a/backend/src/online_classroom/session.rs +++ b/backend/src/online_classroom/session.rs @@ -1,12 +1,10 @@ use std::time::{SystemTime, UNIX_EPOCH}; +use isahc::{cookies::CookieJar, prelude::*, Request}; use once_cell::sync::OnceCell; -use reqwest::{cookie::Jar, Client, Url}; - -use std::io::prelude::*; /// Stores a client with a persistent cookie store -static SESSION_COOKIE: OnceCell = OnceCell::new(); +static SESSION_COOKIE: OnceCell = OnceCell::new(); /// Stores the last time a request was made, in seconds since UNIX epoch static SESSION_TIME: OnceCell = OnceCell::new(); @@ -17,31 +15,25 @@ pub async fn request(url: String) -> Result { ensure_session().await?; // Get the stored client - let client = SESSION_COOKIE + let jar = SESSION_COOKIE .get() .expect("SESSION_COOKIE was not set, even after calling ensure_session"); + let uri = format!("{}{}", classroom_url, url); + // Do the request - let req_builder = client - .get(format!("{}{}", classroom_url, url)) - .build() - .unwrap(); + let response = Request::get(uri) + .cookie_jar(jar.clone()) + .body(()) + .or_else(|err| Err(format!("Error creating request: {:?}", err)))? + .send(); - println!("{:?}", req_builder); - - let response = client.execute(req_builder).await; - - let response = match response { + let mut response = match response { Ok(r) => r, Err(err) => return Err(format!("Error sending request: {:?}", err)), }; - // Check if there's a new cookie - if let Some(session_cookie) = response.cookies().find(|c| c.name() == "ch_sid") { - println!("new cookie: {}", session_cookie.value()); - } - - match response.text().await { + match response.text() { Ok(t) => Ok(t), Err(err) => Err(format!("Error getting text from response: {:?}", err)), } @@ -81,57 +73,23 @@ async fn login() -> Result<(), String> { let classroom_password = std::env::var("CLASSROOM_PASSWORD").expect("CLASSROOM_PASSWORD env var is not set!"); - let params = [ - ("login", classroom_user), - ("password", classroom_password), - ("submitAuth", "".into()), - ("_qf__formLogin", "".into()), - ]; - let jar = Jar::default(); + let jar = CookieJar::new(); - - let client = Client::builder() - .cookie_store(true) - .cookie_provider(jar.into()) - .build() - .unwrap(); - // let client = Client::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(); - - let req = client - .post(format!("{}/index.php", classroom_url)) - .form(¶ms) - .build().unwrap(); - - println!("{:?}\n", req); - let body_bytes= req.body().unwrap().as_bytes().unwrap(); - println!("{:?}\n", std::str::from_utf8(body_bytes)); - - let result = client.execute(req) - .await; - - match result { - Ok(response) => { - println!("{:?}\n\n", response); - - let Some(session_cookie) = response.cookies().find(|c| c.name() == "ch_sid") else { - return Err("Response succeeded, but no session cookie was found".into()); - }; - - // Save the client with the session cookie - println!("Got a cookie: {}", session_cookie.value()); - - let text = response.text().await.unwrap(); - - // save text to file - let mut f = std::fs::File::create("scraps/test.html").unwrap(); - f.write_all(text.as_bytes()).unwrap(); - - match SESSION_COOKIE.set(client) { - Ok(_) => Ok(()), - Err(error) => Err(format!("Error saving client: {:?}", error)), - } - } + match response { + Ok(_) => match SESSION_COOKIE.set(jar.clone()) { + Ok(_) => Ok(()), + Err(error) => Err(format!("Error saving client: {:?}", error)), + }, Err(error) => Err(format!("Error connecting to online classroom: {:?}", error)), } } diff --git a/backend/src/online_classroom/users.rs b/backend/src/online_classroom/users.rs index cc7aa69..f0de860 100644 --- a/backend/src/online_classroom/users.rs +++ b/backend/src/online_classroom/users.rs @@ -2,6 +2,7 @@ use super::{json_result::JsonResult, session::request}; use rocket::{http::Status, serde::json::Json}; use scraper::{ElementRef, Html, Selector}; use serde::{Deserialize, Serialize}; +use urlencoding::encode; #[derive(Debug, Serialize, Deserialize)] pub struct ClassroomPerson { @@ -22,7 +23,7 @@ pub struct ClassroomPerson { pub async fn get_users(full_name: String) -> (Status, Json>>) { let html = request(format!( "/main/admin/user_list.php?keyword={}&submit=&_qf__search_simple=", - full_name + encode(full_name.as_str()) )) .await; @@ -52,6 +53,8 @@ fn parse_users(file: &str) -> Result, String> { let fragment = Html::parse_document(file); + // TODO: If no users are found, the form is not rendered, and the html is different + // Handle this case let form_element = match fragment.select(&form_selector).next() { Some(el) => el, None => return Err("Error selecting form#form_users_id: not found".into()),