Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .woodpecker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ steps:
- "# If you can't see all output, then use the download button"
when:
- event: pull_request
status: failure
status: [failure]

publish_release_docker:
image: woodpeckerci/plugin-docker-buildx
Expand Down Expand Up @@ -295,7 +295,7 @@ steps:
- "curl -d'Lemmy CI build failed: ${CI_PIPELINE_URL}' ntfy.sh/lemmy_drone_ci"
when:
- event: [pull_request, tag]
status: failure
status: [failure]

notify_on_tag_deploy:
image: alpine:3
Expand All @@ -312,3 +312,6 @@ services:
environment:
POSTGRES_USER: lemmy
POSTGRES_PASSWORD: password
# Woodpecker's services ending is now causing pipeline failures:
# https://github.com/woodpecker-ci/woodpecker/issues/6372
failure: ignore
52 changes: 33 additions & 19 deletions crates/api_common/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use serde::{Deserialize, Serialize};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use tokio::net::lookup_host;
use tracing::{info, warn};
use url::Url;
use url::{Host, Url};
use urlencoding::encode;
use webpage::HTML;

Expand All @@ -64,24 +64,7 @@ pub async fn fetch_link_metadata(
return Err(LemmyErrorType::InvalidUrl.into());
}

// Resolve the domain and throw an error if it points to any internal IP,
// using logic from nightly IpAddr::is_global.
if !cfg!(debug_assertions) {
// TODO: Replace with IpAddr::is_global() once stabilized
// https://doc.rust-lang.org/std/net/enum.IpAddr.html#method.is_global
let domain = url.domain().ok_or(LemmyErrorType::UrlWithoutDomain)?;
let invalid_ip =
lookup_host((domain.to_owned(), 80))
.await?
.any(|addr| match addr.ip().to_canonical() {
IpAddr::V4(addr) => v4_is_invalid(addr),
IpAddr::V6(addr) => v6_is_invalid(addr),
});
if invalid_ip {
return Err(LemmyErrorType::InvalidUrl.into());
}
}

validate_link_ip(url).await?;
info!("Fetching site metadata for url: {}", url);
// We only fetch the first MB of data in order to not waste bandwidth especially for large
// binary files. This high limit is particularly needed for youtube, which includes a lot of
Expand Down Expand Up @@ -161,6 +144,37 @@ pub async fn fetch_link_metadata(
})
}

/// Resolve the domain and throw an error if it points to any internal IP.
pub async fn validate_link_ip(url: &Url) -> LemmyResult<()> {
if cfg!(debug_assertions) {
return Ok(());
}
// Resolve the domain and throw an error if it points to any internal IP,
// using logic from nightly IpAddr::is_global.

// TODO: Replace with IpAddr::is_global() once stabilized
// https://doc.rust-lang.org/std/net/enum.IpAddr.html#method.is_global
let mut ip = vec![];
match url.host().ok_or(LemmyErrorType::UrlWithoutDomain)? {
Host::Domain(domain) => ip.extend(
lookup_host((domain.to_owned(), 80))
.await?
.map(|s| s.ip().to_canonical()),
),
Host::Ipv4(ipv4) => ip.push(ipv4.into()),
Host::Ipv6(ipv6) => ip.push(ipv6.into()),
};

let invalid_ip = ip.into_iter().any(|addr| match addr {
IpAddr::V4(addr) => v4_is_invalid(addr),
IpAddr::V6(addr) => v6_is_invalid(addr),
});
if invalid_ip {
return Err(LemmyErrorType::InvalidUrl.into());
}
Ok(())
}

fn v4_is_invalid(v4: Ipv4Addr) -> bool {
v4.is_private()
|| v4.is_loopback()
Expand Down
4 changes: 2 additions & 2 deletions crates/api_crud/src/post/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use lemmy_api_common::{
build_response::build_post_response,
context::LemmyContext,
post::{CreatePost, PostResponse},
request::generate_post_link_metadata,
request::{generate_post_link_metadata, validate_link_ip},
send_activity::SendActivityData,
utils::{
check_community_user_action,
Expand Down Expand Up @@ -178,7 +178,7 @@ pub async fn create_post(
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;

if let Some(url) = inserted_post.url.clone() {
if community.visibility == CommunityVisibility::Public {
if community.visibility == CommunityVisibility::Public && validate_link_ip(&url).await.is_ok() {
spawn_try_task(async move {
let mut webmention =
Webmention::new::<Url>(inserted_post.ap_id.clone().into(), url.clone().into())?;
Expand Down