///|
fn BidiProtocol::build_synthetic_fetch_request_plan(
self : BidiProtocol,
context_id : String,
url : String,
http_method : String,
request_headers : Array[Json],
request_value : Json?,
) -> Array[@crater_network.SyntheticFetchPlanEntry] {
let requests : Array[@crater_network.SyntheticFetchPlanEntry] = []
let actual_request_token = self.next_synthetic_network_request_id()
if synthetic_fetch_requires_preflight(
self.requested_navigation_url(context_id),
url,
http_method,
request_headers,
) {
requests.push({
url,
http_method: "OPTIONS",
request_token: self.next_synthetic_network_request_id(),
redirect_count: 0,
request_value: None,
})
}
requests.push({
url,
http_method,
request_token: actual_request_token,
redirect_count: 0,
request_value,
})
let path = synthetic_network_request_path(url)
let redirect_location = synthetic_network_first_query_value(url, "location")
let redirect_status = synthetic_network_int_query_param(url, "status", 302)
let should_follow_redirect = path.has_suffix("/redirect.py") ||
(
path.has_suffix("/cached.py") &&
(
redirect_status == 301 ||
redirect_status == 302 ||
redirect_status == 303 ||
redirect_status == 307 ||
redirect_status == 308
)
)
if should_follow_redirect {
match redirect_location {
Some(redirect_url) if redirect_url != "" =>
requests.push({
url: redirect_url,
http_method,
request_token: actual_request_token,
redirect_count: 1,
request_value: None,
})
_ => ()
}
}
requests
}
///|
fn synthetic_fetch_requires_preflight(
source_url : String,
target_url : String,
http_method : String,
request_headers : Array[Json],
) -> Bool {
let source_origin = match
@crater_network.canonicalize_network_http_url(source_url) {
Some(source) => source
None => return false
}
let target_origin = match
@crater_network.canonicalize_network_http_url(target_url) {
Some(target) => target
None => return false
}
if source_origin.scheme == target_origin.scheme &&
source_origin.host == target_origin.host &&
source_origin.port == target_origin.port {
return false
}
let method_upper = http_method.to_upper()
if method_upper != "GET" && method_upper != "HEAD" && method_upper != "POST" {
return true
}
for header in request_headers {
let name = match synthetic_network_header_entry_name(header) {
Some(name) => name.to_lower()
None => continue
}
let value = match synthetic_network_header_entry_string_value(header) {
Some(value) => value
None => ""
}
if name == "accept" ||
name == "accept-language" ||
name == "content-language" {
continue
}
if name == "content-type" &&
synthetic_fetch_is_cors_safelisted_content_type(value) {
continue
}
return true
}
false
}
///|
fn synthetic_fetch_is_cors_safelisted_content_type(value : String) -> Bool {
let raw_media_type = match find_substring(value, ";", 0) {
Some(idx) => value.unsafe_substring(start=0, end=idx).to_lower()
None => value.to_lower()
}
let media_type = raw_media_type.trim()
media_type == "application/x-www-form-urlencoded" ||
media_type == "multipart/form-data" ||
media_type == "text/plain"
}