use goose::goose::GooseResponse;
use goose::prelude::*;
use log::info;
use regex::Regex;
use reqwest::header::HeaderMap;
pub mod drupal;
#[derive(Clone, Debug)]
pub struct Validate<'a> {
status: Option<u16>,
title: Option<&'a str>,
texts: Vec<&'a str>,
headers: Vec<&'a Header<'a>>,
}
impl<'a> Validate<'a> {
pub fn new(
status: Option<u16>,
title: Option<&'a str>,
texts: Vec<&'a str>,
headers: Vec<&'a Header<'a>>,
) -> Validate<'a> {
Validate {
status,
title,
texts,
headers,
}
}
pub fn status(status: u16) -> Validate<'a> {
Validate::new(Some(status), None, vec![], vec![])
}
pub fn title(title: &'a str) -> Validate<'a> {
Validate::new(None, Some(title), vec![], vec![])
}
pub fn status_title(status: u16, title: &'a str) -> Validate<'a> {
Validate::new(Some(status), Some(title), vec![], vec![])
}
pub fn text(text: &'a str) -> Validate<'a> {
Validate::new(None, None, vec![text], vec![])
}
pub fn status_text(status: u16, text: &'a str) -> Validate<'a> {
Validate::new(Some(status), None, vec![text], vec![])
}
pub fn title_text(title: &'a str, text: &'a str) -> Validate<'a> {
Validate::new(None, Some(title), vec![text], vec![])
}
pub fn status_title_text(status: u16, title: &'a str, text: &'a str) -> Validate<'a> {
Validate::new(Some(status), Some(title), vec![text], vec![])
}
pub fn texts(texts: Vec<&'a str>) -> Validate<'a> {
Validate::new(None, None, texts, vec![])
}
pub fn status_texts(status: u16, texts: Vec<&'a str>) -> Validate<'a> {
Validate::new(Some(status), None, texts, vec![])
}
pub fn title_texts(title: &'a str, texts: Vec<&'a str>) -> Validate<'a> {
Validate::new(None, Some(title), texts, vec![])
}
pub fn status_title_texts(status: u16, title: &'a str, texts: Vec<&'a str>) -> Validate<'a> {
Validate::new(Some(status), Some(title), texts, vec![])
}
pub fn header(header: &'a Header<'a>) -> Validate<'a> {
Validate::new(None, None, vec![], vec![header])
}
pub fn status_header(status: u16, header: &'a Header<'a>) -> Validate<'a> {
Validate::new(Some(status), None, vec![], vec![header])
}
pub fn title_header(title: &'a str, header: &'a Header<'a>) -> Validate<'a> {
Validate::new(None, Some(title), vec![], vec![header])
}
pub fn status_title_header(
status: u16,
title: &'a str,
header: &'a Header<'a>,
) -> Validate<'a> {
Validate::new(Some(status), Some(title), vec![], vec![header])
}
pub fn text_header(text: &'a str, header: &'a Header<'a>) -> Validate<'a> {
Validate::new(None, None, vec![text], vec![header])
}
pub fn status_text_header(status: u16, text: &'a str, header: &'a Header<'a>) -> Validate<'a> {
Validate::new(Some(status), None, vec![text], vec![header])
}
pub fn title_text_header(
title: &'a str,
text: &'a str,
header: &'a Header<'a>,
) -> Validate<'a> {
Validate::new(None, Some(title), vec![text], vec![header])
}
pub fn status_title_text_header(
status: u16,
title: &'a str,
text: &'a str,
header: &'a Header<'a>,
) -> Validate<'a> {
Validate::new(Some(status), Some(title), vec![text], vec![header])
}
pub fn headers(headers: Vec<&'a Header<'a>>) -> Validate<'a> {
Validate::new(None, None, vec![], headers)
}
pub fn status_headers(status: u16, headers: Vec<&'a Header<'a>>) -> Validate<'a> {
Validate::new(Some(status), None, vec![], headers)
}
pub fn title_headers(title: &'a str, headers: Vec<&'a Header<'a>>) -> Validate<'a> {
Validate::new(None, Some(title), vec![], headers)
}
pub fn status_title_headers(
status: u16,
title: &'a str,
headers: Vec<&'a Header<'a>>,
) -> Validate<'a> {
Validate::new(Some(status), Some(title), vec![], headers)
}
pub fn text_headers(text: &'a str, headers: Vec<&'a Header<'a>>) -> Validate<'a> {
Validate::new(None, None, vec![text], headers)
}
pub fn status_text_headers(
status: u16,
text: &'a str,
headers: Vec<&'a Header<'a>>,
) -> Validate<'a> {
Validate::new(Some(status), None, vec![text], headers)
}
pub fn title_text_headers(
title: &'a str,
text: &'a str,
headers: Vec<&'a Header<'a>>,
) -> Validate<'a> {
Validate::new(None, Some(title), vec![text], headers)
}
pub fn texts_headers(texts: Vec<&'a str>, headers: Vec<&'a Header<'a>>) -> Validate<'a> {
Validate::new(None, None, texts, headers)
}
pub fn status_texts_headers(
status: u16,
texts: Vec<&'a str>,
headers: Vec<&'a Header<'a>>,
) -> Validate<'a> {
Validate::new(Some(status), None, texts, headers)
}
pub fn title_texts_headers(
title: &'a str,
texts: Vec<&'a str>,
headers: Vec<&'a Header<'a>>,
) -> Validate<'a> {
Validate::new(None, Some(title), texts, headers)
}
pub fn status_title_texts_headers(
status: u16,
title: &'a str,
texts: Vec<&'a str>,
headers: Vec<&'a Header<'a>>,
) -> Validate<'a> {
Validate::new(Some(status), Some(title), texts, headers)
}
}
#[derive(Clone, Debug)]
pub struct Header<'a> {
name: &'a str,
value: Option<&'a str>,
}
impl<'a> Header<'a> {
pub fn new(name: &'a str, value: Option<&'a str>) -> Header<'a> {
Header { name, value }
}
pub fn name(name: &'a str) -> Header<'a> {
Header::new(name, None)
}
pub fn name_value(name: &'a str, value: &'a str) -> Header<'a> {
Header::new(name, Some(value))
}
}
pub fn valid_title(html: &str, title: &str) -> bool {
html.to_ascii_lowercase()
.contains(&("<title>".to_string() + title.to_ascii_lowercase().as_str()))
}
pub fn valid_text(html: &str, text: &str) -> bool {
html.contains(text)
}
pub fn header_is_set(headers: &HeaderMap, header: &Header) -> bool {
headers.contains_key(header.name)
}
pub fn valid_header_value(headers: &HeaderMap, header: &Header) -> bool {
if header_is_set(headers, header) {
if let Some(value_to_validate) = header.value {
let header_value = match headers.get(header.name) {
Some(v) => v.to_str().unwrap_or(""),
None => "",
};
if header_value.contains(value_to_validate) {
true
} else {
info!(
r#"header does not contain expected value: "{}: {}""#,
header.name, header_value
);
false
}
} else {
false
}
} else {
info!("header ({}) not set", header.name);
false
}
}
pub async fn load_static_elements(user: &GooseUser, html: &str) {
let image = Regex::new(r#"src="(.*?)""#).unwrap();
let mut urls = Vec::new();
for url in image.captures_iter(&html) {
if url[1].starts_with("/sites") || url[1].starts_with("/core") {
urls.push(url[1].to_string());
}
}
let css = Regex::new(r#"href="(/sites/default/files/css/.*?)""#).unwrap();
for url in css.captures_iter(&html) {
urls.push(url[1].to_string());
}
for asset in &urls {
let _ = user.get_named(asset, "static asset").await;
}
}
pub async fn validate_and_load_static_assets<'a>(
user: &GooseUser,
mut goose: GooseResponse,
validate: &'a Validate<'a>,
) -> Result<String, GooseTaskError> {
let empty = "".to_string();
match goose.response {
Ok(response) => {
let response_status = response.status();
if let Some(status) = validate.status {
if response_status != status {
let headers = &response.headers().clone();
let html = response.text().await.unwrap_or_else(|_| "".to_string());
user.set_failure(
&format!(
"{}: response status != {}]: {}",
goose.request.raw.url, status, response_status
),
&mut goose.request,
Some(&headers),
Some(&html),
)?;
return Ok(html);
}
}
let headers = &response.headers().clone();
for header in &validate.headers {
if !header_is_set(headers, header) {
let html = response.text().await.unwrap_or_else(|_| "".to_string());
user.set_failure(
&format!(
"{}: header not included in response: {:?}",
goose.request.raw.url, header
),
&mut goose.request,
Some(&headers),
Some(&html),
)?;
return Ok(html);
}
if let Some(h) = header.value {
if !valid_header_value(headers, header) {
let html = response.text().await.unwrap_or_else(|_| "".to_string());
user.set_failure(
&format!(
"{}: header does not contain expected value: {:?}",
goose.request.raw.url, h
),
&mut goose.request,
Some(&headers),
Some(&html),
)?;
return Ok(html);
}
}
}
match response.text().await {
Ok(html) => {
if let Some(title) = validate.title {
if !valid_title(&html, &title) {
user.set_failure(
&format!("{}: title not found: {}", goose.request.raw.url, title),
&mut goose.request,
Some(&headers),
Some(&html),
)?;
return Ok(html);
}
}
for text in &validate.texts {
if !valid_text(&html, text) {
user.set_failure(
&format!(
"{}: text not found on page: {}",
goose.request.raw.url, text
),
&mut goose.request,
Some(&headers),
Some(&html),
)?;
return Ok(html);
}
}
load_static_elements(user, &html).await;
Ok(html)
}
Err(e) => {
user.set_failure(
&format!("{}: failed to parse page: {}", goose.request.raw.url, e),
&mut goose.request,
Some(&headers),
None,
)?;
Ok(empty)
}
}
}
Err(e) => {
user.set_failure(
&format!("{}: no response from server: {}", goose.request.raw.url, e),
&mut goose.request,
None,
None,
)?;
Ok(empty)
}
}
}