noodlemagazine fix

This commit is contained in:
Simon
2026-03-20 21:05:18 +00:00
parent 46cd348148
commit 259a07686d
2 changed files with 63 additions and 9 deletions

View File

@@ -10,6 +10,7 @@ use crate::videos::{ServerOptions, VideoFormat, VideoItem};
use async_trait::async_trait; use async_trait::async_trait;
use error_chain::error_chain; use error_chain::error_chain;
use htmlentity::entity::{ICodedDataTrait, decode}; use htmlentity::entity::{ICodedDataTrait, decode};
use std::net::IpAddr;
use url::Url; use url::Url;
use std::vec; use std::vec;
use titlecase::Titlecase; use titlecase::Titlecase;
@@ -197,6 +198,31 @@ impl NoodlemagazineProvider {
.any(|ext| path.ends_with(ext)) .any(|ext| path.ends_with(ext))
} }
fn is_disallowed_thumb_host(host: &str) -> bool {
if host.eq_ignore_ascii_case("localhost") {
return true;
}
match host.parse::<IpAddr>() {
Ok(IpAddr::V4(ip)) => {
ip.is_private()
|| ip.is_loopback()
|| ip.is_link_local()
|| ip.is_broadcast()
|| ip.is_documentation()
|| ip.is_unspecified()
}
Ok(IpAddr::V6(ip)) => {
ip.is_loopback()
|| ip.is_unspecified()
|| ip.is_multicast()
|| ip.is_unique_local()
|| ip.is_unicast_link_local()
}
Err(_) => false,
}
}
fn is_allowed_thumb_url(&self, url: &str) -> bool { fn is_allowed_thumb_url(&self, url: &str) -> bool {
let Some(url) = Url::parse(url).ok() else { let Some(url) = Url::parse(url).ok() else {
return false; return false;
@@ -207,9 +233,8 @@ impl NoodlemagazineProvider {
let Some(host) = url.host_str() else { let Some(host) = url.host_str() else {
return false; return false;
}; };
let is_noodlemagazine_host = host == "noodlemagazine.com" || host.ends_with(".noodlemagazine.com");
is_noodlemagazine_host && Self::has_allowed_image_extension(url.path()) !Self::is_disallowed_thumb_host(host) && Self::has_allowed_image_extension(url.path())
} }
fn proxied_thumb(&self, options: &ServerOptions, thumb: &str) -> String { fn proxied_thumb(&self, options: &ServerOptions, thumb: &str) -> String {
@@ -395,7 +420,7 @@ mod tests {
} }
#[test] #[test]
fn drops_non_noodlemagazine_or_non_image_thumbs() { fn keeps_https_cdn_thumbs_but_drops_non_images() {
let provider = NoodlemagazineProvider::new(); let provider = NoodlemagazineProvider::new();
let options = options(); let options = options();
let html = r#" let html = r#"
@@ -422,6 +447,10 @@ mod tests {
let items = provider.get_video_items_from_html(html.to_string(), &options); let items = provider.get_video_items_from_html(html.to_string(), &options);
assert_eq!(items.len(), 2); assert_eq!(items.len(), 2);
assert!(items.iter().all(|item| item.thumb.is_empty())); assert_eq!(
items[0].thumb,
"https://example.com/proxy/noodlemagazine-thumb/cdn.example/thumb.jpg"
);
assert!(items[1].thumb.is_empty());
} }
} }

View File

@@ -4,6 +4,7 @@ use ntex::{
web::{self, HttpRequest, error}, web::{self, HttpRequest, error},
}; };
use serde_json::Value; use serde_json::Value;
use std::net::IpAddr;
use url::Url; use url::Url;
use wreq::Version; use wreq::Version;
@@ -110,6 +111,31 @@ impl NoodlemagazineProxy {
.any(|ext| path.ends_with(ext)) .any(|ext| path.ends_with(ext))
} }
fn is_disallowed_thumb_host(host: &str) -> bool {
if host.eq_ignore_ascii_case("localhost") {
return true;
}
match host.parse::<IpAddr>() {
Ok(IpAddr::V4(ip)) => {
ip.is_private()
|| ip.is_loopback()
|| ip.is_link_local()
|| ip.is_broadcast()
|| ip.is_documentation()
|| ip.is_unspecified()
}
Ok(IpAddr::V6(ip)) => {
ip.is_loopback()
|| ip.is_unspecified()
|| ip.is_multicast()
|| ip.is_unique_local()
|| ip.is_unicast_link_local()
}
Err(_) => false,
}
}
fn is_allowed_thumb_url(url: &str) -> bool { fn is_allowed_thumb_url(url: &str) -> bool {
let Some(url) = Url::parse(url).ok() else { let Some(url) = Url::parse(url).ok() else {
return false; return false;
@@ -121,8 +147,7 @@ impl NoodlemagazineProxy {
return false; return false;
}; };
(host == "noodlemagazine.com" || host.ends_with(".noodlemagazine.com")) !Self::is_disallowed_thumb_host(host) && Self::has_allowed_image_extension(url.path())
&& Self::has_allowed_image_extension(url.path())
} }
fn is_binary_image_content_type(content_type: &str) -> bool { fn is_binary_image_content_type(content_type: &str) -> bool {
@@ -388,18 +413,18 @@ mod tests {
} }
#[test] #[test]
fn allows_only_noodlemagazine_image_thumbs() { fn allows_https_image_thumbs_but_rejects_local_or_non_images() {
assert!(NoodlemagazineProxy::is_allowed_thumb_url( assert!(NoodlemagazineProxy::is_allowed_thumb_url(
"https://noodlemagazine.com/thumbs/example.webp" "https://noodlemagazine.com/thumbs/example.webp"
)); ));
assert!(NoodlemagazineProxy::is_allowed_thumb_url( assert!(NoodlemagazineProxy::is_allowed_thumb_url(
"https://img.noodlemagazine.com/previews/example.jpg" "https://cdn.example/previews/example.jpg"
)); ));
assert!(!NoodlemagazineProxy::is_allowed_thumb_url( assert!(!NoodlemagazineProxy::is_allowed_thumb_url(
"https://noodlemagazine.com/watch/-123_456" "https://noodlemagazine.com/watch/-123_456"
)); ));
assert!(!NoodlemagazineProxy::is_allowed_thumb_url( assert!(!NoodlemagazineProxy::is_allowed_thumb_url(
"https://cdn.example/thumb.jpg" "https://localhost/thumb.jpg"
)); ));
} }