Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add origin to CSP so Safari can do recursion #2708

Merged
merged 15 commits into from
Nov 22, 2023
12 changes: 12 additions & 0 deletions src/page_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,16 @@ pub(crate) struct PageConfig {
pub(crate) chain: Chain,
pub(crate) domain: Option<String>,
pub(crate) index_sats: bool,
pub(crate) content_security_policy_origin: Option<String>,
}

impl Default for PageConfig {
fn default() -> Self {
Self {
chain: Chain::Mainnet,
domain: None,
index_sats: false,
content_security_policy_origin: None,
}
}
}
79 changes: 67 additions & 12 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ pub(crate) struct Server {
help = "Request ACME TLS certificate for <ACME_DOMAIN>. This ord instance must be reachable at <ACME_DOMAIN>:443 to respond to Let's Encrypt ACME challenges."
)]
acme_domain: Vec<String>,
#[arg(
long,
help = "Origin to use for the content-security-policy header. Set this to the fully-qualified domain name of your ord instance. [default: None]"
)]
csp_origin: Option<String>,
#[arg(
long,
help = "Listen on <HTTP_PORT> for incoming HTTP requests. [default: 80]."
Expand Down Expand Up @@ -184,6 +189,7 @@ impl Server {
chain: options.chain(),
domain: acme_domains.first().cloned(),
index_sats: index.has_sat_index(),
content_security_policy_origin: self.csp_origin.clone(),
});

let router = Router::new()
Expand Down Expand Up @@ -985,6 +991,7 @@ impl Server {
async fn content(
Extension(index): Extension<Arc<Index>>,
Extension(config): Extension<Arc<Config>>,
Extension(page_config): Extension<Arc<PageConfig>>,
Path(inscription_id): Path<InscriptionId>,
accept_encoding: AcceptEncoding,
) -> ServerResult<Response> {
Expand All @@ -997,7 +1004,7 @@ impl Server {
.ok_or_not_found(|| format!("inscription {inscription_id}"))?;

Ok(
Self::content_response(inscription, accept_encoding)?
Self::content_response(inscription, accept_encoding, &page_config)?
.ok_or_not_found(|| format!("inscription {inscription_id} content"))?
.into_response(),
)
Expand All @@ -1006,6 +1013,7 @@ impl Server {
fn content_response(
inscription: Inscription,
accept_encoding: AcceptEncoding,
page_config: &PageConfig,
) -> ServerResult<Option<(HeaderMap, Vec<u8>)>> {
let mut headers = HeaderMap::new();

Expand All @@ -1027,15 +1035,25 @@ impl Server {
}
}

headers.insert(
header::CONTENT_SECURITY_POLICY,
HeaderValue::from_static("default-src 'self' 'unsafe-eval' 'unsafe-inline' data: blob:"),
);

headers.append(
header::CONTENT_SECURITY_POLICY,
HeaderValue::from_static("default-src *:*/content/ *:*/blockheight *:*/blockhash *:*/blockhash/ *:*/blocktime *:*/r/ 'unsafe-eval' 'unsafe-inline' data: blob:"),
);
match &page_config.content_security_policy_origin {
None => {
headers.insert(
header::CONTENT_SECURITY_POLICY,
HeaderValue::from_static("default-src 'self' 'unsafe-eval' 'unsafe-inline' data: blob:"),
);
headers.append(
header::CONTENT_SECURITY_POLICY,
HeaderValue::from_static("default-src *:*/content/ *:*/blockheight *:*/blockhash *:*/blockhash/ *:*/blocktime *:*/r/ 'unsafe-eval' 'unsafe-inline' data: blob:"),
);
}
Some(origin) => {
let csp = format!("default-src {origin}/content/ {origin}/blockheight {origin}/blockhash {origin}/blockhash/ {origin}/blocktime {origin}/r/ 'unsafe-eval' 'unsafe-inline' data: blob:");
headers.insert(
header::CONTENT_SECURITY_POLICY,
HeaderValue::from_str(&csp).map_err(|err| ServerError::Internal(Error::from(err)))?,
);
}
}

headers.insert(
header::CACHE_CONTROL,
Expand All @@ -1052,6 +1070,7 @@ impl Server {
async fn preview(
Extension(index): Extension<Arc<Index>>,
Extension(config): Extension<Arc<Config>>,
Extension(page_config): Extension<Arc<PageConfig>>,
Path(inscription_id): Path<InscriptionId>,
accept_encoding: AcceptEncoding,
) -> ServerResult<Response> {
Expand Down Expand Up @@ -1089,7 +1108,7 @@ impl Server {
.into_response(),
),
Media::Iframe => Ok(
Self::content_response(inscription, accept_encoding)?
Self::content_response(inscription, accept_encoding, &page_config)?
.ok_or_not_found(|| format!("inscription {inscription_id} content"))?
.into_response(),
),
Expand Down Expand Up @@ -3131,7 +3150,8 @@ mod tests {
assert_eq!(
Server::content_response(
Inscription::new(Some("text/plain".as_bytes().to_vec()), None),
AcceptEncoding::default()
AcceptEncoding::default(),
&PageConfig::default(),
)
.unwrap(),
None
Expand All @@ -3143,6 +3163,7 @@ mod tests {
let (headers, body) = Server::content_response(
Inscription::new(Some("text/plain".as_bytes().to_vec()), Some(vec![1, 2, 3])),
AcceptEncoding::default(),
&PageConfig::default(),
)
.unwrap()
.unwrap();
Expand All @@ -3151,6 +3172,38 @@ mod tests {
assert_eq!(body, vec![1, 2, 3]);
}

#[test]
fn content_security_policy_no_origin() {
let (headers, _) = Server::content_response(
Inscription::new(Some("text/plain".as_bytes().to_vec()), Some(vec![1, 2, 3])),
AcceptEncoding::default(),
&PageConfig::default(),
)
.unwrap()
.unwrap();

assert_eq!(
headers["content-security-policy"],
HeaderValue::from_static("default-src 'self' 'unsafe-eval' 'unsafe-inline' data: blob:")
);
}

#[test]
fn content_security_policy_with_origin() {
let (headers, _) = Server::content_response(
Inscription::new(Some("text/plain".as_bytes().to_vec()), Some(vec![1, 2, 3])),
AcceptEncoding::default(),
&PageConfig {
content_security_policy_origin: Some("https://ordinals.com".into()),
..Default::default()
},
)
.unwrap()
.unwrap();

assert_eq!(headers["content-security-policy"], HeaderValue::from_static("default-src https://ordinals.com/content/ https://ordinals.com/blockheight https://ordinals.com/blockhash https://ordinals.com/blockhash/ https://ordinals.com/blocktime https://ordinals.com/r/ 'unsafe-eval' 'unsafe-inline' data: blob:"));
}

#[test]
fn code_preview() {
let server = TestServer::new_with_regtest();
Expand Down Expand Up @@ -3181,6 +3234,7 @@ mod tests {
let (headers, body) = Server::content_response(
Inscription::new(None, Some(Vec::new())),
AcceptEncoding::default(),
&PageConfig::default(),
)
.unwrap()
.unwrap();
Expand All @@ -3194,6 +3248,7 @@ mod tests {
let (headers, body) = Server::content_response(
Inscription::new(Some("\n".as_bytes().to_vec()), Some(Vec::new())),
AcceptEncoding::default(),
&PageConfig::default(),
)
.unwrap()
.unwrap();
Expand Down
4 changes: 4 additions & 0 deletions src/templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ mod tests {
chain: Chain::Mainnet,
domain: Some("signet.ordinals.com".into()),
index_sats: true,
content_security_policy_origin: Some("https://signet.ordinals.com".into()),
}),),
r"<!doctype html>
<html lang=en>
Expand Down Expand Up @@ -164,6 +165,7 @@ mod tests {
chain: Chain::Mainnet,
domain: None,
index_sats: true,
content_security_policy_origin: None,
}),),
r".*<nav class=links>\s*<a href=/>Ordinals<sup>alpha</sup></a>.*"
);
Expand All @@ -176,6 +178,7 @@ mod tests {
chain: Chain::Mainnet,
domain: None,
index_sats: false,
content_security_policy_origin: None
}),),
r".*<nav class=links>\s*<a href=/>Ordinals<sup>alpha</sup></a>.*<a href=/clock>Clock</a>\s*<form action=/search.*",
);
Expand All @@ -188,6 +191,7 @@ mod tests {
chain: Chain::Signet,
domain: None,
index_sats: true,
content_security_policy_origin: None,
}),),
r".*<nav class=links>\s*<a href=/>Ordinals<sup>signet</sup></a>.*"
);
Expand Down
Loading