use crate::fetchers::response::Response;
fn is_duckduckgo_host(host: &str) -> bool {
host == "duckduckgo.com" || host.ends_with(".duckduckgo.com")
}
#[must_use]
pub fn is_duckduckgo_blocked(response: &Response) -> bool {
let is_ddg = url::Url::parse(response.url())
.ok()
.and_then(|u| u.host_str().map(is_duckduckgo_host))
.unwrap_or(false);
if !is_ddg {
return false;
}
if response.status() == 202 {
return true;
}
response.is_success() && !response.text().contains("result__a")
}
#[must_use]
pub fn decode_duckduckgo_href(href: &str) -> String {
let full = if let Some(stripped) = href.strip_prefix("//") {
format!("https://{}", stripped)
} else {
href.to_string()
};
if let Ok(parsed) = url::Url::parse(&full) {
let is_ddg_redirect =
parsed.host_str().map(is_duckduckgo_host).unwrap_or(false) && parsed.path() == "/l/";
if is_ddg_redirect {
if let Some((_, target)) = parsed.query_pairs().find(|(k, _)| k == "uddg") {
return target.into_owned();
}
}
}
href.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
fn resp(status: u16, url: &str, body: &str) -> Response {
Response::new(
status,
"text/html".to_string(),
body.to_string(),
url.to_string(),
HashMap::new(),
)
}
#[test]
fn decodes_ddg_redirect_href() {
let href = "//duckduckgo.com/l/?uddg=https%3A%2F%2Fwww.andritz.com%2F&rut=abc";
assert_eq!(decode_duckduckgo_href(href), "https://www.andritz.com/");
}
#[test]
fn passes_through_non_ddg_href() {
assert_eq!(
decode_duckduckgo_href("https://example.com/page"),
"https://example.com/page"
);
}
#[test]
fn passes_through_ddg_non_redirect_path() {
let href = "//duckduckgo.com/about";
assert_eq!(decode_duckduckgo_href(href), href);
}
#[test]
fn detects_202_soft_block() {
let r = resp(
202,
"https://html.duckduckgo.com/html/?q=x",
"<html>home</html>",
);
assert!(is_duckduckgo_blocked(&r));
}
#[test]
fn detects_homepage_without_results() {
let r = resp(
200,
"https://html.duckduckgo.com/html/?q=x",
"<html>no results here</html>",
);
assert!(is_duckduckgo_blocked(&r));
}
#[test]
fn real_ddg_results_are_not_blocked() {
let r = resp(
200,
"https://html.duckduckgo.com/html/?q=x",
"<a class=\"result__a\" href=\"//duckduckgo.com/l/?uddg=https%3A%2F%2Fx\">X</a>",
);
assert!(!is_duckduckgo_blocked(&r));
}
#[test]
fn non_ddg_response_is_never_ddg_blocked() {
let r = resp(202, "https://example.com", "");
assert!(!is_duckduckgo_blocked(&r));
}
#[test]
fn url_substring_cannot_spoof_ddg_detection() {
for url in [
"https://evil.com/?ref=duckduckgo.com",
"https://duckduckgo.com.evil.com/html/?q=x",
"https://example.com/duckduckgo.com/page",
] {
let r = resp(202, url, "<html>home</html>");
assert!(
!is_duckduckgo_blocked(&r),
"{url} must not be treated as DuckDuckGo"
);
}
let r = resp(
202,
"https://html.duckduckgo.com/html/?q=x",
"<html></html>",
);
assert!(is_duckduckgo_blocked(&r));
}
}