Move rentry logic to separate file

This commit is contained in:
ItsDrike 2024-04-18 20:31:36 +02:00
parent b76274e361
commit e577f01fe1
Signed by: ItsDrike
GPG key ID: FA2745890B7048C0
2 changed files with 181 additions and 122 deletions

View file

@ -1,129 +1,28 @@
use reqwest::{blocking::Client, blocking::ClientBuilder, header::HeaderValue, redirect::Policy}; use crate::rentry::{build_client, delete_paste, CSRFData};
use scraper::{Html, Selector};
const URL: &str = "https://rentry.co/Hyprland-controversy/edit"; mod rentry;
enum DeleteResult { const PASTE_NAME: &str = "Hyprland-controversy";
Success,
InvalidEditCode,
UnknownFailure(String),
RequestFailure(String),
}
#[derive(Debug)]
struct CSRFData {
cookie: String,
form: String,
}
fn get_cookie_value(header_value: &HeaderValue, cookie_name: &str) -> Option<String> {
let cookies_str = header_value.to_str().ok()?;
cookies_str.split(';').find_map(|cookie| {
let mut parts = cookie.split('=');
let name = parts.next()?.trim();
let value = parts.next()?.trim();
if name == cookie_name {
Some(value.to_string())
} else {
None
}
})
}
fn get_csrf_data(client: &Client) -> CSRFData {
let res = client.get(URL).send().unwrap();
let cookies = res.headers().get("Set-Cookie").unwrap();
let cookie_middleware_token = get_cookie_value(cookies, "csrftoken").unwrap();
let body = res.text().unwrap();
let document = Html::parse_document(&body);
let selector = Selector::parse("input[name=\"csrfmiddlewaretoken\"]").unwrap();
let form_middleware_token = document
.select(&selector)
.next()
.unwrap()
.value()
.attr("value")
.unwrap();
CSRFData {
cookie: cookie_middleware_token,
form: form_middleware_token.to_string(),
}
}
fn attempt_delete(client: &Client, csrf_data: &CSRFData, edit_code: &str) -> DeleteResult {
let res = client
.post(URL)
.header("Referer", URL)
.header(
"Cookie",
"csrftoken=".to_string() + csrf_data.cookie.as_str(),
)
.form(&[
("csrfmiddlewaretoken", csrf_data.form.as_str()),
("edit_code", edit_code),
("new_edit_code", ""),
("new_url", ""),
("text", ""),
("delete", "delete"),
])
.send()
.unwrap();
// Get these before obtaining text, as that consumes the response
let status = res.status();
let headers = res.headers().clone();
let raw_txt = res.text().unwrap();
let document = Html::parse_document(&raw_txt);
if status.is_redirection() {
let location = headers.get("Location").unwrap().to_str().unwrap();
if location == "/" {
return DeleteResult::Success;
}
return DeleteResult::RequestFailure("unexpected redirection: ".to_string() + location);
}
if status.is_success() {
let selector =
Selector::parse("fieldset > div.text-danger.messages > ul.errorlist > li").unwrap();
if let Some(element) = document.select(&selector).next() {
let txt = element.text().collect::<String>();
if txt == "Invalid edit code." {
return DeleteResult::InvalidEditCode;
}
return DeleteResult::UnknownFailure("form error: ".to_string() + &txt);
}
return DeleteResult::UnknownFailure(format!("unknown failure: {raw_txt}"));
}
let selector = Selector::parse("span").unwrap();
let err_txt = document
.select(&selector)
.next()
.unwrap()
.text()
.collect::<String>();
let err_txt = err_txt.trim().to_string();
DeleteResult::RequestFailure(err_txt)
}
fn main() { fn main() {
let client = ClientBuilder::new() let client = build_client();
.redirect(Policy::none()) let csrf_data = CSRFData::get(&client, PASTE_NAME);
.build()
.unwrap();
let csrf_data = get_csrf_data(&client);
println!("Token: {csrf_data:?}");
match attempt_delete(&client, &csrf_data, "qwack") { let edit_code = "f";
DeleteResult::Success => println!("Successfully deleted!"), match delete_paste(&client, &csrf_data, edit_code, PASTE_NAME) {
DeleteResult::InvalidEditCode => println!("Failed to delete: Invalid edit code!"), Ok(_) => println!("Paste deleted successfully"),
DeleteResult::RequestFailure(txt) => println!("Failed to delete (req): {txt}!"), Err(e) => match e {
DeleteResult::UnknownFailure(txt) => println!("Failed to delete (unknown): {txt}!"), rentry::DeleteError::RequestFailure(msg) => eprintln!("Request error (non 2XX): {msg}"),
rentry::DeleteError::UnexpectedRedirect(msg) => eprintln!("Unexpected redirect: {msg}"),
rentry::DeleteError::FormError(msg) => {
if msg == "Invalid edit code." {
eprintln!("Invalid edit code")
} else {
eprintln!("Form error: {msg}")
}
}
rentry::DeleteError::UnknownFailure(msg) => eprintln!("Unknown failure: {msg}"),
rentry::DeleteError::ReqwestError(e) => eprintln!("Request failed with: {e:?}"),
},
} }
} }

160
src/rentry.rs Normal file
View file

@ -0,0 +1,160 @@
use reqwest::{
blocking::Client,
blocking::{ClientBuilder, Response},
header::HeaderValue,
redirect::Policy,
};
use scraper::{Html, Selector};
const BASE_URL: &str = "https://rentry.co/";
#[derive(Debug)]
pub struct CSRFData {
cookie: String,
form: String,
}
fn get_cookie_value(header_value: &HeaderValue, cookie_name: &str) -> Option<String> {
let cookies_str = header_value.to_str().ok()?;
cookies_str.split(';').find_map(|cookie| {
let mut parts = cookie.split('=');
let name = parts.next()?.trim();
let value = parts.next()?.trim();
if name == cookie_name {
Some(value.to_string())
} else {
None
}
})
}
impl From<Response> for CSRFData {
fn from(res: Response) -> Self {
let cookies = res.headers().get("Set-Cookie").unwrap();
let cookie_middleware_token = get_cookie_value(cookies, "csrftoken").unwrap();
let body = res.text().unwrap();
let document = Html::parse_document(&body);
let selector = Selector::parse("input[name=\"csrfmiddlewaretoken\"]").unwrap();
let form_middleware_token = document
.select(&selector)
.next()
.unwrap()
.value()
.attr("value")
.unwrap();
CSRFData {
cookie: cookie_middleware_token,
form: form_middleware_token.to_string(),
}
}
}
impl CSRFData {
pub fn get(client: &Client, paste_name: &str) -> Self {
let url = BASE_URL.to_string() + paste_name + "/edit";
let res = client.get(url).send().unwrap();
CSRFData::from(res)
}
}
#[derive(Debug)]
pub enum DeleteError {
ReqwestError(reqwest::Error),
UnexpectedRedirect(String),
FormError(String),
RequestFailure(String),
UnknownFailure(String),
}
impl From<reqwest::Error> for DeleteError {
fn from(err: reqwest::Error) -> Self {
DeleteError::ReqwestError(err)
}
}
impl From<Response> for DeleteError {
fn from(res: Response) -> Self {
let status = res.status();
let headers = res.headers().clone();
let raw_txt = res.text().unwrap();
let document = Html::parse_document(&raw_txt);
if status.is_redirection() {
let location = headers.get("Location").unwrap().to_str().unwrap();
if location == "/" {
// This is supposed to be handled from delete_paste function,
// This is an error enum, we can't represent success
panic!("response is successfult");
}
return Self::UnexpectedRedirect(location.to_string());
}
// 2XX
if status.is_success() {
let selector =
Selector::parse("fieldset > div.text-danger.messages > ul.errorlist > li").unwrap();
if let Some(element) = document.select(&selector).next() {
let txt = element.text().collect::<String>();
return DeleteError::FormError(txt);
}
return DeleteError::UnknownFailure(format!("unknown failure (2XX): {raw_txt}"));
}
let selector = Selector::parse("span").unwrap();
let err_txt = document
.select(&selector)
.next()
.unwrap()
.text()
.collect::<String>();
let err_txt = err_txt.trim().to_string();
DeleteError::RequestFailure(err_txt)
}
}
pub fn delete_paste(
client: &Client,
csrf_data: &CSRFData,
edit_code: &str,
paste_name: &str,
) -> Result<(), DeleteError> {
let url = BASE_URL.to_string() + paste_name + "/edit";
let res = client
.post(url.clone())
.header("Referer", url)
.header(
"Cookie",
"csrftoken=".to_string() + csrf_data.cookie.as_str(),
)
.form(&[
("csrfmiddlewaretoken", csrf_data.form.as_str()),
("edit_code", edit_code),
("new_edit_code", ""),
("new_url", ""),
("text", ""),
("delete", "delete"),
])
.send()?;
if res.status().is_redirection() {
let location = res.headers().get("Location").unwrap().to_str().unwrap();
if location == "/" {
return Ok(());
}
}
Err(DeleteError::from(res))
}
pub fn build_client() -> Client {
ClientBuilder::new()
.redirect(Policy::none())
.build()
.unwrap()
}