///|
/// Basic syntax check for navigate/reload URL parameter.
fn is_valid_navigation_target_url(url : String) -> Bool {
if url.has_prefix("http://") {
return has_valid_http_host(url, 7)
}
if url.has_prefix("https://") {
return has_valid_http_host(url, 8)
}
true
}
///|
/// Detect navigation targets that should fail with unknown error after navigationStarted.
fn should_fail_navigation_with_unknown_error(url : String) -> Bool {
if url.has_prefix("thisprotocoldoesnotexist://") {
return true
}
if url.has_prefix("https://doesnotexist.localhost") ||
url.has_prefix("http://doesnotexist.localhost") {
return true
}
if has_invalid_http_zero_port(url) {
return true
}
false
}
///|
/// Reject explicit `:0` ports as unreachable hosts.
fn has_invalid_http_zero_port(url : String) -> Bool {
let scheme_len = if url.has_prefix("https://") {
8
} else if url.has_prefix("http://") {
7
} else {
return false
}
if url.length() <= scheme_len {
return false
}
let chars = url.to_array()
let mut host_end = chars.length()
for i = scheme_len; i < chars.length(); i = i + 1 {
if chars[i] == '/' || chars[i] == '?' || chars[i] == '#' {
host_end = i
break
}
}
let host_port = url.unsafe_substring(start=scheme_len, end=host_end)
host_port.has_suffix(":0")
}
///|
/// Check whether value carries CSP/COEP markers that block cross-origin frame navigations.
fn has_embedder_blocking_policy_marker(value : String) -> Bool {
value.contains("Content-Security-Policy") ||
value.contains("Cross-Origin-Embedder-Policy") ||
value.contains("content-security-policy") ||
value.contains("cross-origin-embedder-policy") ||
value.contains("default-src") ||
value.contains("require-corp")
}
///|
/// Whether a navigated document enforces cross-origin iframe blocking.
fn navigation_blocks_cross_origin_iframe_navigation(url : String) -> Bool {
if has_embedder_blocking_policy_marker(url) {
return true
}
match parse_data_url(url) {
Some((_content_type, body)) => has_embedder_blocking_policy_marker(body)
None => false
}
}
///|
/// Whether URL/content carries X-Frame-Options: DENY.
fn has_x_frame_options_deny_marker(value : String) -> Bool {
(value.contains("X-Frame-Options") || value.contains("x-frame-options")) &&
(value.contains("DENY") || value.contains("deny"))
}
///|
/// Whether URL/content carries X-Frame-Options: SAMEORIGIN.
fn has_x_frame_options_sameorigin_marker(value : String) -> Bool {
(value.contains("X-Frame-Options") || value.contains("x-frame-options")) &&
(value.contains("SAMEORIGIN") || value.contains("sameorigin"))
}
///|
/// Compare origins for cross-origin iframe checks.
fn is_cross_origin_navigation_url(
source_url : String,
target_url : String,
) -> Bool {
let source_origin = derive_realm_origin(source_url)
let target_origin = derive_realm_origin(target_url)
if source_origin == "null" || target_origin == "null" {
return false
}
source_origin != target_origin
}
///|
/// Determine whether iframe navigation should be blocked by CSP/COEP/XFO policies.
fn BidiProtocol::is_iframe_navigation_blocked(
self : BidiProtocol,
ctx_id : String,
target_url : String,
) -> Bool {
let parent_ctx_id = match self.context_parent.get(ctx_id) {
Some(parent_id) => parent_id
None => return false
}
let mut target_has_xfo_deny = has_x_frame_options_deny_marker(target_url)
let mut target_has_xfo_sameorigin = has_x_frame_options_sameorigin_marker(
target_url,
)
match parse_data_url(target_url) {
Some((_content_type, body)) => {
if has_x_frame_options_deny_marker(body) {
target_has_xfo_deny = true
}
if has_x_frame_options_sameorigin_marker(body) {
target_has_xfo_sameorigin = true
}
}
None => ()
}
if target_has_xfo_deny {
return true
}
let parent_url = match self.manager.get_session(parent_ctx_id) {
Some(parent_session) => parent_session.get_url()
None => "about:blank"
}
let mut parent_blocks_cross_origin = self.context_blocks_cross_origin_iframe_navigation
.get(parent_ctx_id)
.unwrap_or(false)
if navigation_blocks_cross_origin_iframe_navigation(parent_url) {
parent_blocks_cross_origin = true
}
if parent_blocks_cross_origin &&
is_cross_origin_navigation_url(parent_url, target_url) {
return true
}
if target_has_xfo_sameorigin &&
is_cross_origin_navigation_url(parent_url, target_url) {
return true
}
false
}
///|
/// Ensure HTTP(S) URL host starts with a valid character.
fn has_valid_http_host(url : String, scheme_len : Int) -> Bool {
if url.length() <= scheme_len {
return false
}
let chars = url.to_array()
let head = chars[scheme_len]
if head == ':' || head == '#' || head == '/' || head == '?' {
return false
}
true
}