mirror of
https://github.com/lexmount/moli.git
synced 2026-09-30 08:01:36 +00:00
fix: validate RequestInit consistently before fetch abort
This commit is contained in:
@@ -5534,6 +5534,8 @@ fetch/api/crashtests/request.html
|
||||
fetch/api/policies/csp-blocked-worker.html
|
||||
fetch/api/policies/referrer-no-referrer-worker.html
|
||||
fetch/api/request/multi-globals/url-parsing.html
|
||||
fetch/api/request/request-error.any.js?moli-wpt-any=dedicatedworker
|
||||
fetch/api/request/request-error.any.js?moli-wpt-any=window
|
||||
fetch/api/request/url-encoding.html
|
||||
fetch/api/response/many-empty-chunks-crash.html
|
||||
fetch/api/response/multi-globals/url-parsing.html
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
async function runRequestInitValidationProbe(scenario, url) {
|
||||
const errors = [];
|
||||
const check = (value, label) => { if (!value) errors.push(label); };
|
||||
const controller = new AbortController();
|
||||
const reason = {probe: 'already aborted'};
|
||||
controller.abort(reason);
|
||||
const fetchError = async (input, init, expected, label, fetchFunction = fetch, receiver = self) => {
|
||||
let promise;
|
||||
try { promise = fetchFunction.call(receiver, input, init); }
|
||||
catch (error) { errors.push(label + ': synchronous throw ' + error); return; }
|
||||
try { await promise; errors.push(label + ': fulfilled'); }
|
||||
catch (error) {
|
||||
check(expected === reason ? error === reason : error instanceof expected,
|
||||
label + ': wrong rejection ' + error);
|
||||
}
|
||||
};
|
||||
const throwsTypeError = (input, init, label) => {
|
||||
try { new Request(input, init); errors.push(label + ': accepted'); }
|
||||
catch (error) { check(error instanceof TypeError, label + ': wrong exception ' + error); }
|
||||
};
|
||||
|
||||
if (scenario === 'invalid') {
|
||||
const invalid = [
|
||||
['navigate', {mode: 'navigate'}],
|
||||
['default mode with only-if-cached', {cache: 'only-if-cached'}],
|
||||
['cors with only-if-cached', {mode: 'cors', cache: 'only-if-cached'}],
|
||||
['no-cors with only-if-cached', {mode: 'no-cors', cache: 'only-if-cached'}],
|
||||
['invalid referrer', {referrer: 'http://:invalid'}],
|
||||
...['', 'IN VALID', ' GET', 'GET\t', 'a/b', 'a:b', 'é', '\u0100', 'TRACE']
|
||||
.map(method => ['method ' + JSON.stringify(method), {method}]),
|
||||
...[['cache', 'reload'], ['referrerPolicy', 'origin'], ['duplex', 'half'],
|
||||
['mode', 'cors'], ['credentials', 'omit'], ['redirect', 'follow']]
|
||||
.flatMap(([key, valid]) => ['BAD', null, valid.toUpperCase(), ' ' + valid]
|
||||
.map(value => [key + ' ' + JSON.stringify(value), {[key]: value}]))
|
||||
];
|
||||
for (const [label, init] of invalid) {
|
||||
throwsTypeError(url, init, label);
|
||||
await fetchError(url, init, TypeError, label + ' live');
|
||||
await fetchError(url, {...init, signal: controller.signal}, TypeError, label + ' aborted');
|
||||
}
|
||||
for (const credentials of ['user:pass', 'user', ':pass']) {
|
||||
const credentialURL = new URL(url);
|
||||
const [username, password = ''] = credentials.split(':');
|
||||
credentialURL.username = username;
|
||||
credentialURL.password = password;
|
||||
throwsTypeError(credentialURL.href, {}, 'URL credentials ' + credentials);
|
||||
await fetchError(credentialURL.href, {signal: controller.signal}, TypeError,
|
||||
'URL credentials ' + credentials + ' aborted');
|
||||
}
|
||||
} else if (scenario === 'valid') {
|
||||
const valid = [
|
||||
[{window: undefined}, {}], [{window: null}, {}],
|
||||
[{method: null}, {method: 'null'}], [{method: 'pOsT'}, {method: 'POST'}],
|
||||
[{method: "a!#$%&'*+-.^_`|~"}, {method: "a!#$%&'*+-.^_`|~"}],
|
||||
[{referrer: ''}, {referrer: ''}],
|
||||
[{referrer: 'about:client'}, {referrer: 'about:client'}],
|
||||
[{referrer: new URL('/referrer', url).href}, {referrer: new URL('/referrer', url).href}],
|
||||
[{referrer: 'https://other.invalid/'}, {referrer: 'about:client'}],
|
||||
[{duplex: 'half'}, {duplex: 'half'}],
|
||||
...['default', 'no-store', 'reload', 'no-cache', 'force-cache', 'only-if-cached']
|
||||
.map(cache => [{cache, mode: 'same-origin'}, {cache, mode: 'same-origin'}]),
|
||||
...['', 'no-referrer', 'no-referrer-when-downgrade', 'same-origin', 'origin',
|
||||
'strict-origin', 'origin-when-cross-origin', 'strict-origin-when-cross-origin', 'unsafe-url']
|
||||
.map(referrerPolicy => [{referrerPolicy}, {referrerPolicy}])
|
||||
];
|
||||
for (const [init, expected] of valid) {
|
||||
const request = new Request(url, init);
|
||||
for (const [key, value] of Object.entries(expected)) {
|
||||
check(request[key] === value, JSON.stringify(init) + ': ' + key + '=' + request[key]);
|
||||
}
|
||||
await fetchError(url, {...init, signal: controller.signal}, reason, JSON.stringify(init));
|
||||
}
|
||||
} else if (scenario === 'inheritance') {
|
||||
const makeSource = () => new Request(url, {
|
||||
mode: 'same-origin', cache: 'only-if-cached', method: 'POST', body: 'original body'
|
||||
});
|
||||
for (const init of [{mode: 'cors'}, {mode: 'no-cors'}, {mode: 'navigate'},
|
||||
{referrer: 'http://:invalid'}, {cache: 'BAD'}, {referrerPolicy: null}]) {
|
||||
const source = makeSource();
|
||||
throwsTypeError(source, init, 'inherited Request ' + JSON.stringify(init));
|
||||
check(!source.bodyUsed, 'failed constructor consumed input body');
|
||||
if (!source.bodyUsed) check(await source.text() === 'original body', 'constructor input body remains readable');
|
||||
for (const aborted of [false, true]) {
|
||||
const source = makeSource();
|
||||
const promise = fetchError(source, {...init, ...(aborted ? {signal: controller.signal} : {})},
|
||||
TypeError, 'inherited fetch ' + JSON.stringify(init) + ' aborted=' + aborted);
|
||||
check(!source.bodyUsed, 'failed fetch synchronously consumed input body');
|
||||
await promise;
|
||||
check(!source.bodyUsed, 'failed fetch consumed input body');
|
||||
if (!source.bodyUsed) check(await source.text() === 'original body', 'fetch input body remains readable');
|
||||
}
|
||||
}
|
||||
const cached = new Request(url, {mode: 'same-origin', cache: 'only-if-cached'});
|
||||
for (const init of [{}, {mode: undefined, cache: undefined}, {cache: 'reload', mode: 'cors'}]) {
|
||||
const copy = new Request(cached, init);
|
||||
check(copy.mode === (init.mode || 'same-origin'), 'inherited effective mode');
|
||||
check(copy.cache === (init.cache || 'only-if-cached'), 'inherited effective cache');
|
||||
await fetchError(cached, {...init, signal: controller.signal}, reason, 'valid inheritance');
|
||||
}
|
||||
} else if (scenario === 'realm') {
|
||||
if (document.readyState === 'loading') {
|
||||
await new Promise(resolve => addEventListener('load', resolve, {once: true}));
|
||||
}
|
||||
const frame = document.createElement('iframe');
|
||||
const loaded = new Promise(resolve => frame.onload = resolve);
|
||||
frame.src = 'about:blank';
|
||||
document.body.append(frame);
|
||||
await loaded;
|
||||
const child = frame.contentWindow;
|
||||
for (const [fn, receiver, ErrorConstructor, PromiseConstructor] of [
|
||||
[fetch, child, TypeError, Promise],
|
||||
[child.fetch, self, child.TypeError, child.Promise]
|
||||
]) {
|
||||
for (const init of [{cache: 'BAD'}, {referrerPolicy: null}, {duplex: 'full'}, {redirect: null}]) {
|
||||
await fetchError(url, {...init, signal: controller.signal}, ErrorConstructor,
|
||||
'conversion realm ' + JSON.stringify(init), fn, receiver);
|
||||
}
|
||||
for (const init of [{mode: 'navigate'}, {cache: 'only-if-cached'},
|
||||
{referrer: 'http://:invalid'}]) {
|
||||
await fetchError(url, {...init, signal: controller.signal}, ErrorConstructor,
|
||||
'construction realm ' + JSON.stringify(init), fn, receiver);
|
||||
const promise = fn.call(receiver, url, init);
|
||||
promise.catch(() => {});
|
||||
check(promise instanceof PromiseConstructor, 'construction rejection promise realm');
|
||||
}
|
||||
}
|
||||
frame.remove();
|
||||
} else if (scenario === 'getters') {
|
||||
for (const init of [{mode: 'navigate'}, {mode: 'cors', cache: 'only-if-cached'},
|
||||
{window: 1}, {referrer: 'http://:invalid'}]) {
|
||||
for (const member of ['body', 'signal']) {
|
||||
const marker = {member};
|
||||
const options = {method: 'POST', ...init, get [member]() { throw marker; }};
|
||||
try { new Request(url, options); errors.push(member + ' getter accepted'); }
|
||||
catch (error) { check(error === marker, member + ' getter exception replaced'); }
|
||||
}
|
||||
}
|
||||
const source = new Request(url, {signal: controller.signal});
|
||||
const copy = new Request(source, {signal: undefined});
|
||||
check(copy.signal.aborted && copy.signal.reason === reason, 'undefined signal preserves inheritance');
|
||||
} else if (scenario === 'window') {
|
||||
// Fetch's RequestInit.window is still specified and covered by WPT, even
|
||||
// though Chromium 145 ignores the member.
|
||||
const invalid = [
|
||||
['window string', {window: 'null'}],
|
||||
['window false', {window: false}],
|
||||
['window zero', {window: 0}],
|
||||
['window object', {window: {toString() { throw new Error('must not coerce window'); }}}],
|
||||
];
|
||||
for (const [label, init] of invalid) {
|
||||
throwsTypeError(url, init, label);
|
||||
await fetchError(url, init, TypeError, label + ' live');
|
||||
await fetchError(url, {...init, signal: controller.signal}, TypeError, label + ' aborted');
|
||||
const source = new Request(url, {method: 'POST', body: 'original body'});
|
||||
throwsTypeError(source, init, label + ' inherited');
|
||||
check(!source.bodyUsed, 'window rejection consumed constructor input');
|
||||
if (!source.bodyUsed) {
|
||||
await fetchError(source, {...init, signal: controller.signal}, TypeError, label + ' inherited aborted');
|
||||
check(!source.bodyUsed, 'window rejection consumed fetch input');
|
||||
if (!source.bodyUsed) check(await source.text() === 'original body', 'window input remains readable');
|
||||
}
|
||||
}
|
||||
let reads = 0;
|
||||
const init = {get window() { ++reads; return null; }};
|
||||
new Request(url, init);
|
||||
check(reads === 1, 'Request reads window once');
|
||||
reads = 0;
|
||||
await fetchError(url, {get window() { ++reads; return null; }, signal: controller.signal},
|
||||
reason, 'fetch reads window');
|
||||
check(reads === 1, 'fetch reads window once');
|
||||
} else {
|
||||
throw new Error('unknown scenario ' + scenario);
|
||||
}
|
||||
return {errors};
|
||||
}
|
||||
@@ -22,6 +22,8 @@ mod abort_signal;
|
||||
mod callback_cleanup;
|
||||
#[path = "web_apis/event_dispatch.rs"]
|
||||
mod event_dispatch;
|
||||
#[path = "web_apis/request_init.rs"]
|
||||
mod request_init;
|
||||
|
||||
fn diagnostic_global<'a>(
|
||||
page: &'a moli_core::page::Page,
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
use super::*;
|
||||
|
||||
async fn assert_request_init_validation(scenario: &str, targets: &[&str]) -> Result<()> {
|
||||
let server = FixtureServer::spawn().await?;
|
||||
let browser = Browser::new(AppConfig::default())?;
|
||||
let source = format!(
|
||||
"{}\nrunRequestInitValidationProbe({}, {}).then(finish, error => finish({{error: String(error)}}));",
|
||||
include_str!("../fixtures/runtime/request_init_validation.js"),
|
||||
serde_json::to_string(scenario)?,
|
||||
serde_json::to_string(&server.url("/compat/child-dynamic-markup-document"))?,
|
||||
);
|
||||
for target in targets {
|
||||
let observed = tokio::time::timeout(
|
||||
Duration::from_secs(20),
|
||||
super::event_dispatch::run_probe(&browser, &server, target, &source),
|
||||
)
|
||||
.await??;
|
||||
assert_eq!(
|
||||
observed,
|
||||
serde_json::json!({"errors": []}),
|
||||
"{scenario}/{target}"
|
||||
);
|
||||
}
|
||||
server.shutdown().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn request_and_fetch_reject_invalid_init_before_abort() -> Result<()> {
|
||||
assert_request_init_validation("invalid", &["window", "child", "worker"]).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn request_and_fetch_accept_valid_init_and_preserve_abort_reason() -> Result<()> {
|
||||
assert_request_init_validation("valid", &["window", "child", "worker"]).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn request_and_fetch_validate_inherited_modes_without_consuming_invalid_input() -> Result<()>
|
||||
{
|
||||
assert_request_init_validation("inheritance", &["window", "child", "worker"]).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn fetch_init_errors_and_rejected_promises_use_the_function_realm() -> Result<()> {
|
||||
assert_request_init_validation("realm", &["window", "child"]).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn request_init_window_accepts_only_null_without_consuming_invalid_input() -> Result<()> {
|
||||
assert_request_init_validation("window", &["window", "child", "worker"]).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn request_init_getter_exceptions_precede_construction_validation() -> Result<()> {
|
||||
assert_request_init_validation("getters", &["window", "child", "worker"]).await
|
||||
}
|
||||
@@ -131,7 +131,7 @@ pub(crate) use self::request::{FetchArgumentError, RequestUrlError, convert_fetc
|
||||
pub(crate) use self::request::{
|
||||
mark_request_input_body_used_for_fetch, request_input_snapshot,
|
||||
try_resolve_request_constructor_url, try_resolve_request_constructor_url_for_base,
|
||||
try_resolve_request_constructor_url_for_child,
|
||||
try_resolve_request_constructor_url_for_child, validate_request_url_credentials,
|
||||
};
|
||||
pub(crate) use self::request::{
|
||||
parse_fetch_init, parse_request_redirect_mode_label, request_object_credentials_mode,
|
||||
|
||||
@@ -7,9 +7,9 @@ use self::paths::{
|
||||
reject_csp_fetch, reject_offline_fetch, reject_url_policy_fetch, resolve_local_fetch,
|
||||
spawn_network_fetch,
|
||||
};
|
||||
use self::request::prepare_window_fetch_request;
|
||||
use self::request::{PreparedWindowFetchRequest, prepare_window_fetch_request};
|
||||
use self::service_worker::dispatch_service_worker_fetch;
|
||||
use super::input::{ParsedWindowFetchInput, parse_window_fetch_input};
|
||||
use super::input::parse_window_fetch_input;
|
||||
use super::promise::{make_rejected_promise, make_rejected_promise_with_value};
|
||||
use super::*;
|
||||
use crate::native_bridge::abort::abort_error_value;
|
||||
@@ -181,25 +181,38 @@ pub(crate) fn window_fetch_callback<'s>(
|
||||
);
|
||||
return;
|
||||
};
|
||||
if let Some(request_body_owner) = parsed.request_body_owner.take() {
|
||||
let request_body_owner = v8::Local::new(scope, request_body_owner);
|
||||
mark_request_input_body_used_for_fetch(scope, request_body_owner);
|
||||
}
|
||||
let fetch_context = crate::native_bridge::WindowFetchContext::from_realm(binding);
|
||||
let relevant_context = {
|
||||
let context = fetch_context.script_realm().context(scope);
|
||||
v8::Global::new(scope, context)
|
||||
};
|
||||
let relevant_context = v8::Local::new(scope, &relevant_context);
|
||||
let request_body_owner = parsed.request_body_owner.take();
|
||||
let prepared = {
|
||||
let scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
prepare_window_fetch_request(scope, parsed, fetch_context, unsafe { &*host_ptr })
|
||||
};
|
||||
let prepared = match prepared {
|
||||
Ok(prepared) => prepared,
|
||||
Err(error) => {
|
||||
// Request construction uses the receiver's settings, but Blink's
|
||||
// binding rejects construction exceptions in the function realm.
|
||||
rv.set(make_rejected_promise(scope, &error.to_string()).into());
|
||||
return;
|
||||
}
|
||||
};
|
||||
let scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
window_fetch_callback_in_relevant_realm(scope, parsed, signal, fetch_context, rv);
|
||||
if let Some(request_body_owner) = request_body_owner {
|
||||
let request_body_owner = v8::Local::new(scope, request_body_owner);
|
||||
mark_request_input_body_used_for_fetch(scope, request_body_owner);
|
||||
}
|
||||
window_fetch_callback_in_relevant_realm(scope, prepared, signal, rv);
|
||||
}
|
||||
|
||||
fn window_fetch_callback_in_relevant_realm<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
parsed: ParsedWindowFetchInput,
|
||||
prepared: PreparedWindowFetchRequest,
|
||||
signal: Option<v8::Global<v8::Object>>,
|
||||
fetch_context: crate::native_bridge::WindowFetchContext,
|
||||
mut rv: v8::ReturnValue<'s, v8::Value>,
|
||||
) {
|
||||
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
||||
@@ -209,13 +222,6 @@ fn window_fetch_callback_in_relevant_realm<'s>(
|
||||
|
||||
let host = unsafe { &mut *host_ptr };
|
||||
let signal = signal.as_ref().map(|signal| v8::Local::new(scope, signal));
|
||||
let prepared = match prepare_window_fetch_request(scope, parsed, fetch_context, host) {
|
||||
Ok(prepared) => prepared,
|
||||
Err(error) => {
|
||||
rv.set(make_rejected_promise(scope, &error.to_string()).into());
|
||||
return;
|
||||
}
|
||||
};
|
||||
host.break_on_dom_debugger_xhr_or_fetch_network_request(prepared.resolved_url.as_str());
|
||||
if let Some(signal) = signal
|
||||
&& host.abort_signal_aborted(scope, signal)
|
||||
|
||||
@@ -11,6 +11,13 @@ pub(super) enum FetchPrepareError {
|
||||
PolicyContextUnavailable,
|
||||
ReportContextUnavailable,
|
||||
Url(ResolveContextUrlError),
|
||||
WebIdl(crate::webidl::WebIdlError),
|
||||
}
|
||||
|
||||
impl From<crate::webidl::WebIdlError> for FetchPrepareError {
|
||||
fn from(error: crate::webidl::WebIdlError) -> Self {
|
||||
Self::WebIdl(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResolveContextUrlError> for FetchPrepareError {
|
||||
@@ -38,6 +45,7 @@ impl fmt::Display for FetchPrepareError {
|
||||
f.write_str("fetch: document report context is unavailable")
|
||||
}
|
||||
Self::Url(error) => error.fmt(f),
|
||||
Self::WebIdl(error) => error.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,6 +54,7 @@ impl std::error::Error for FetchPrepareError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Self::Url(error) => Some(error),
|
||||
Self::WebIdl(error) => Some(error),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -125,6 +134,11 @@ pub(super) fn prepare_window_fetch_request<'s>(
|
||||
let request_headers =
|
||||
merge_byte_string_request_headers(host.extra_http_headers(), &request_headers);
|
||||
let resolved_url = resolve_context_url(&base_url, &parsed.url, None)?;
|
||||
validate_request_url_credentials(&resolved_url)?;
|
||||
let referrer = parsed
|
||||
.init_validation
|
||||
.validate(scope, parsed.request_mode.as_ref(), &parsed.cache)?
|
||||
.unwrap_or(parsed.referrer);
|
||||
|
||||
Ok(PreparedWindowFetchRequest {
|
||||
frame_id,
|
||||
@@ -147,7 +161,7 @@ pub(super) fn prepare_window_fetch_request<'s>(
|
||||
redirect_mode: parsed.redirect_mode,
|
||||
priority: parsed.priority,
|
||||
cache: parsed.cache,
|
||||
referrer: parsed.referrer,
|
||||
referrer,
|
||||
referrer_policy: parsed.referrer_policy,
|
||||
integrity: parsed.integrity,
|
||||
keepalive: parsed.keepalive,
|
||||
|
||||
@@ -17,6 +17,7 @@ pub(super) struct ParsedWindowFetchInput {
|
||||
pub(super) integrity: String,
|
||||
pub(super) keepalive: bool,
|
||||
pub(super) request_body_owner: Option<v8::Global<v8::Object>>,
|
||||
pub(super) init_validation: super::super::request::RequestInitValidation,
|
||||
}
|
||||
|
||||
pub(super) fn parse_window_fetch_input<'s>(
|
||||
@@ -56,7 +57,8 @@ pub(super) fn parse_window_fetch_input<'s>(
|
||||
}
|
||||
let inherited_credentials = request_object_credentials_mode(scope, req_obj)?;
|
||||
let request_mode = init
|
||||
.request_mode
|
||||
.validation
|
||||
.mode
|
||||
.or_else(|| moli_fetch::RequestMode::from_str(&inherited.mode).ok())
|
||||
.unwrap_or(moli_fetch::RequestMode::Cors);
|
||||
validate_no_cors_method(request_mode, &method)?;
|
||||
@@ -85,16 +87,20 @@ pub(super) fn parse_window_fetch_input<'s>(
|
||||
redirect_mode,
|
||||
priority,
|
||||
cache: init.cache.unwrap_or(inherited.cache),
|
||||
referrer: init.referrer.unwrap_or(inherited.referrer),
|
||||
referrer: inherited.referrer,
|
||||
referrer_policy: init.referrer_policy.unwrap_or(inherited.referrer_policy),
|
||||
integrity: init.integrity.unwrap_or(inherited.integrity),
|
||||
keepalive: init.keepalive.unwrap_or(inherited.keepalive),
|
||||
request_body_owner,
|
||||
init_validation: init.validation,
|
||||
})
|
||||
} else {
|
||||
let url = fetch_request_info_url(scope, arg0)?;
|
||||
let init = parse_fetch_init(scope, args, 1)?;
|
||||
let request_mode = init.request_mode.unwrap_or(moli_fetch::RequestMode::Cors);
|
||||
let request_mode = init
|
||||
.validation
|
||||
.mode
|
||||
.unwrap_or(moli_fetch::RequestMode::Cors);
|
||||
validate_no_cors_method(request_mode, &init.method)?;
|
||||
let headers = if request_mode == moli_fetch::RequestMode::NoCors {
|
||||
filter_headers_for_guard(&init.headers, HeadersGuard::RequestNoCors)
|
||||
@@ -117,11 +123,12 @@ pub(super) fn parse_window_fetch_input<'s>(
|
||||
redirect_mode,
|
||||
priority: init.priority,
|
||||
cache: init.cache.unwrap_or_else(|| "default".to_owned()),
|
||||
referrer: init.referrer.unwrap_or_else(|| "about:client".to_owned()),
|
||||
referrer: "about:client".to_owned(),
|
||||
referrer_policy: init.referrer_policy.unwrap_or_default(),
|
||||
integrity: init.integrity.unwrap_or_default(),
|
||||
keepalive: init.keepalive.unwrap_or(false),
|
||||
request_body_owner: None,
|
||||
init_validation: init.validation,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,13 @@ pub(crate) use self::bindings::request_constructor_callback;
|
||||
pub(crate) use self::error::{FetchArgumentError, RequestUrlError};
|
||||
pub(crate) use self::init::convert_fetch_arguments;
|
||||
pub(in crate::network_host) use self::init::request_credentials_mode_label;
|
||||
pub(crate) use self::init::{parse_fetch_init, request_object_credentials_mode};
|
||||
pub(crate) use self::init::{
|
||||
RequestInitValidation, parse_fetch_init, request_object_credentials_mode,
|
||||
};
|
||||
pub(crate) use self::init::{parse_request_redirect_mode_label, request_redirect_mode_label};
|
||||
pub(in crate::network_host) use self::input::normalize_request_method;
|
||||
pub(crate) use self::input::{
|
||||
mark_request_input_body_used_for_fetch, request_input_snapshot,
|
||||
try_resolve_request_constructor_url, try_resolve_request_constructor_url_for_base,
|
||||
try_resolve_request_constructor_url_for_child,
|
||||
try_resolve_request_constructor_url_for_child, validate_request_url_credentials,
|
||||
};
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use super::super::init::RequestInitMembers;
|
||||
use super::super::input::{
|
||||
normalize_request_method, normalize_request_referrer, request_body_already_used_error,
|
||||
normalize_fetch_request_method, request_body_already_used_error,
|
||||
request_headers_guard_for_mode, request_input_snapshot_for_constructor, request_input_url,
|
||||
request_method_allows_body, request_signal_snapshot_from_value,
|
||||
try_resolve_request_constructor_url_for_scope,
|
||||
try_resolve_request_constructor_url_for_scope, validate_request_url_credentials,
|
||||
};
|
||||
use super::super::*;
|
||||
use crate::web_api_interfaces;
|
||||
use crate::webidl;
|
||||
use moli_fetch::{RequestMode, RequestRedirectMode};
|
||||
use moli_fetch::RequestRedirectMode;
|
||||
|
||||
pub(super) struct RequestConstructionState {
|
||||
pub(super) url_resolved: String,
|
||||
@@ -51,6 +51,9 @@ pub(super) fn request_initial_state<'s>(
|
||||
.map_err(|_| {
|
||||
webidl::WebIdlError::custom_message("Failed to construct 'Request': invalid URL")
|
||||
})?;
|
||||
if let Ok(url) = url::Url::parse(&url_resolved) {
|
||||
validate_request_url_credentials(&url)?;
|
||||
}
|
||||
let method = inherited
|
||||
.as_ref()
|
||||
.map(|snapshot| snapshot.method.clone())
|
||||
@@ -134,10 +137,17 @@ pub(super) fn apply_request_init_overrides<'s>(
|
||||
state: &mut RequestConstructionState,
|
||||
) -> Result<(), webidl::WebIdlError> {
|
||||
let parsed = webidl::parse_dictionary_object::<RequestInitMembers>(scope, init)?;
|
||||
let validation = parsed.validation();
|
||||
if let Some(mode) = validation.mode {
|
||||
state.mode = mode.as_ref().to_owned();
|
||||
}
|
||||
if let Some(cache) = parsed.cache {
|
||||
state.cache = cache.0.to_owned();
|
||||
}
|
||||
|
||||
let method_overridden = parsed.method.is_some();
|
||||
if let Some(method) = parsed.method {
|
||||
state.method = normalize_request_method(&method)?;
|
||||
state.method = normalize_fetch_request_method(&method)?;
|
||||
}
|
||||
let init_body_value = webidl::property_result(
|
||||
scope,
|
||||
@@ -168,20 +178,19 @@ pub(super) fn apply_request_init_overrides<'s>(
|
||||
if let Some(headers) = parsed.headers {
|
||||
state.headers = headers;
|
||||
}
|
||||
let signal_key = v8str(scope, "signal");
|
||||
if init.has(scope, signal_key.into()).unwrap_or(false) {
|
||||
let signal = init
|
||||
.get(scope, signal_key.into())
|
||||
.unwrap_or_else(|| v8::undefined(scope).into());
|
||||
if let Some(signal) = webidl::property_result(
|
||||
scope,
|
||||
init,
|
||||
"signal",
|
||||
webidl::Context::member("RequestInit", "signal"),
|
||||
)?
|
||||
.filter(|value| !value.is_undefined())
|
||||
{
|
||||
state.signal = request_signal_snapshot_from_value(scope, signal)?;
|
||||
}
|
||||
if let Some(mode) = parsed.mode.map(|value| value.0) {
|
||||
if mode == RequestMode::Navigate {
|
||||
return Err(webidl::WebIdlError::custom_message(
|
||||
"Cannot construct a Request with a RequestInit whose mode member is \"navigate\".",
|
||||
));
|
||||
}
|
||||
state.mode = mode.as_ref().to_owned();
|
||||
if let Some(referrer) = validation.validate(scope, &state.mode, &state.cache)?
|
||||
{
|
||||
state.referrer = referrer;
|
||||
}
|
||||
if state.mode == "no-cors" && !moli_fetch::is_cors_safelisted_method(&state.method) {
|
||||
return Err(webidl::WebIdlError::custom_message(
|
||||
@@ -191,17 +200,11 @@ pub(super) fn apply_request_init_overrides<'s>(
|
||||
if let Some(credentials_mode) = parsed.credentials_mode.map(|value| value.0) {
|
||||
state.credentials = request_credentials_mode_label(credentials_mode).to_owned();
|
||||
}
|
||||
if let Some(cache) = parsed.cache {
|
||||
state.cache = cache;
|
||||
}
|
||||
if let Some(redirect) = parsed.redirect {
|
||||
state.redirect_mode = redirect.0;
|
||||
}
|
||||
if let Some(referrer) = parsed.referrer {
|
||||
state.referrer = normalize_request_referrer(scope, &referrer);
|
||||
}
|
||||
if let Some(referrer_policy) = parsed.referrer_policy {
|
||||
state.referrer_policy = referrer_policy;
|
||||
state.referrer_policy = referrer_policy.0.to_owned();
|
||||
}
|
||||
if let Some(integrity) = parsed.integrity {
|
||||
state.integrity = integrity;
|
||||
@@ -209,8 +212,8 @@ pub(super) fn apply_request_init_overrides<'s>(
|
||||
if let Some(priority) = parsed.priority {
|
||||
state.priority = priority.0;
|
||||
}
|
||||
if let Some(duplex) = parsed.duplex {
|
||||
state.duplex = duplex;
|
||||
if parsed.duplex.is_some() {
|
||||
state.duplex = "half".to_owned();
|
||||
}
|
||||
if parsed.keepalive == Some(true) && init_body_is_readable_stream {
|
||||
return Err(webidl::WebIdlError::custom_message(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::input::{normalize_request_method, normalize_request_referrer};
|
||||
use super::input::{normalize_fetch_request_method, normalize_request_referrer};
|
||||
use super::*;
|
||||
use crate::webidl;
|
||||
use moli_fetch::{FetchPriorityHint, RequestCredentialsMode, RequestMode, RequestRedirectMode};
|
||||
@@ -40,12 +40,11 @@ pub(crate) struct ParsedFetchInit {
|
||||
pub(crate) body_content_type: Option<String>,
|
||||
pub(crate) headers: Vec<(String, String)>,
|
||||
pub(crate) headers_present: bool,
|
||||
pub(crate) request_mode: Option<RequestMode>,
|
||||
pub(crate) validation: RequestInitValidation,
|
||||
pub(crate) credentials_mode: Option<RequestCredentialsMode>,
|
||||
pub(crate) redirect_mode: Option<RequestRedirectMode>,
|
||||
pub(crate) priority: Option<FetchPriorityHint>,
|
||||
pub(crate) cache: Option<String>,
|
||||
pub(crate) referrer: Option<String>,
|
||||
pub(crate) referrer_policy: Option<String>,
|
||||
pub(crate) integrity: Option<String>,
|
||||
pub(crate) keepalive: Option<bool>,
|
||||
@@ -61,12 +60,11 @@ impl Default for ParsedFetchInit {
|
||||
body_content_type: None,
|
||||
headers: Vec::new(),
|
||||
headers_present: false,
|
||||
request_mode: None,
|
||||
validation: RequestInitValidation::default(),
|
||||
credentials_mode: None,
|
||||
redirect_mode: None,
|
||||
priority: None,
|
||||
cache: None,
|
||||
referrer: None,
|
||||
referrer_policy: None,
|
||||
integrity: None,
|
||||
keepalive: None,
|
||||
@@ -74,6 +72,42 @@ impl Default for ParsedFetchInit {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct RequestInitValidation {
|
||||
pub(crate) mode: Option<RequestMode>,
|
||||
window_non_null: bool,
|
||||
referrer: Option<String>,
|
||||
}
|
||||
|
||||
impl RequestInitValidation {
|
||||
// Run construction checks after dictionary conversion, using the effective
|
||||
// inherited/overridden values and the request's relevant realm. Fetch must
|
||||
// complete these checks before consuming an input body or observing abort.
|
||||
pub(crate) fn validate(
|
||||
&self,
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
mode: &str,
|
||||
cache: &str,
|
||||
) -> Result<Option<String>, webidl::WebIdlError> {
|
||||
if self.window_non_null {
|
||||
return Err(webidl::WebIdlError::custom_message("RequestInit's window member must be null"));
|
||||
}
|
||||
let referrer = self
|
||||
.referrer
|
||||
.as_deref()
|
||||
.map(|value| normalize_request_referrer(scope, value))
|
||||
.transpose()
|
||||
.map_err(|_| webidl::WebIdlError::custom_message("Request referrer is not a valid URL"))?;
|
||||
if self.mode == Some(RequestMode::Navigate) {
|
||||
return Err(webidl::WebIdlError::custom_message("Cannot construct a Request with mode navigate"));
|
||||
}
|
||||
if cache == "only-if-cached" && mode != "same-origin" {
|
||||
return Err(webidl::WebIdlError::custom_message("Request cache only-if-cached requires mode same-origin"));
|
||||
}
|
||||
Ok(referrer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, webidl::WebIdlEnum)]
|
||||
#[webidl(name = "RequestCredentials", parse_with = parse_request_credentials_mode_webidl)]
|
||||
pub(super) struct RequestCredentialsModeWebIdl(pub(super) RequestCredentialsMode);
|
||||
@@ -90,25 +124,40 @@ pub(super) struct RequestRedirectModeWebIdl(pub(super) RequestRedirectMode);
|
||||
#[webidl(name = "RequestPriority", parse_with = parse_request_priority_webidl)]
|
||||
pub(super) struct RequestPriorityWebIdl(pub(super) FetchPriorityHint);
|
||||
|
||||
#[derive(Clone, Copy, webidl::WebIdlEnum)]
|
||||
#[webidl(name = "RequestCache", parse_with = parse_request_cache_webidl)]
|
||||
pub(super) struct RequestCacheWebIdl(pub(super) &'static str);
|
||||
|
||||
#[derive(Clone, Copy, webidl::WebIdlEnum)]
|
||||
#[webidl(name = "ReferrerPolicy", parse_with = parse_referrer_policy_webidl)]
|
||||
pub(super) struct ReferrerPolicyWebIdl(pub(super) &'static str);
|
||||
|
||||
#[derive(Clone, Copy, webidl::WebIdlEnum)]
|
||||
#[webidl(name = "RequestDuplex")]
|
||||
pub(super) enum RequestDuplexWebIdl {
|
||||
#[webidl(token = "half")]
|
||||
Half,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlDictionary)]
|
||||
#[webidl(prefix = "RequestInit")]
|
||||
pub(super) struct RequestInitMembers {
|
||||
#[webidl(legacy_nullish)]
|
||||
#[webidl(converter = "byte_string")]
|
||||
pub(super) method: Option<String>,
|
||||
#[webidl(legacy_nullish)]
|
||||
pub(super) cache: Option<String>,
|
||||
#[webidl(converter = "enum")]
|
||||
pub(super) cache: Option<RequestCacheWebIdl>,
|
||||
#[webidl(converter = "enum")]
|
||||
pub(super) mode: Option<RequestModeWebIdl>,
|
||||
#[webidl(legacy_nullish, converter = "enum")]
|
||||
#[webidl(converter = "enum")]
|
||||
pub(super) redirect: Option<RequestRedirectModeWebIdl>,
|
||||
#[webidl(legacy_nullish)]
|
||||
#[webidl(converter = "usv_string")]
|
||||
pub(super) referrer: Option<String>,
|
||||
#[webidl(legacy_nullish)]
|
||||
pub(super) referrer_policy: Option<String>,
|
||||
#[webidl(converter = "enum")]
|
||||
pub(super) referrer_policy: Option<ReferrerPolicyWebIdl>,
|
||||
#[webidl(legacy_nullish)]
|
||||
pub(super) integrity: Option<String>,
|
||||
#[webidl(legacy_nullish)]
|
||||
pub(super) duplex: Option<String>,
|
||||
#[webidl(converter = "enum")]
|
||||
pub(super) duplex: Option<RequestDuplexWebIdl>,
|
||||
#[webidl(with = request_init_headers_member)]
|
||||
pub(super) headers: Option<Vec<(String, String)>>,
|
||||
#[webidl(name = "credentials", converter = "enum")]
|
||||
@@ -116,6 +165,32 @@ pub(super) struct RequestInitMembers {
|
||||
#[webidl(converter = "enum")]
|
||||
pub(super) priority: Option<RequestPriorityWebIdl>,
|
||||
pub(super) keepalive: Option<bool>,
|
||||
#[webidl(name = "window", with = request_init_window_member)]
|
||||
window_non_null: bool,
|
||||
}
|
||||
|
||||
impl RequestInitMembers {
|
||||
pub(super) fn validation(&self) -> RequestInitValidation {
|
||||
RequestInitValidation {
|
||||
mode: self.mode.map(|value| value.0),
|
||||
window_non_null: self.window_non_null,
|
||||
referrer: self.referrer.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn request_init_window_member<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
_key: &str,
|
||||
) -> Result<bool, webidl::WebIdlError> {
|
||||
webidl::property_result(
|
||||
scope,
|
||||
object,
|
||||
"window",
|
||||
webidl::Context::member("RequestInit", "window"),
|
||||
)
|
||||
.map(|value| value.is_some_and(|value| !value.is_null_or_undefined()))
|
||||
}
|
||||
|
||||
fn request_init_headers_member<'s>(
|
||||
@@ -148,6 +223,7 @@ pub(crate) fn parse_fetch_init<'s>(
|
||||
};
|
||||
|
||||
let init = webidl::parse_dictionary_object::<RequestInitMembers>(scope, init_object)?;
|
||||
let validation = init.validation();
|
||||
let priority = init.priority.map(|value| value.0);
|
||||
let method_present = init_object
|
||||
.has(scope, v8str(scope, "method").into())
|
||||
@@ -156,7 +232,7 @@ pub(crate) fn parse_fetch_init<'s>(
|
||||
})?;
|
||||
let method = init
|
||||
.method
|
||||
.map(|s| normalize_request_method(&s))
|
||||
.map(|s| normalize_fetch_request_method(&s))
|
||||
.transpose()?
|
||||
.unwrap_or_else(|| "GET".to_owned());
|
||||
|
||||
@@ -197,13 +273,8 @@ pub(crate) fn parse_fetch_init<'s>(
|
||||
let mut extra_headers = init.headers.unwrap_or_default();
|
||||
append_default_body_content_type(&mut extra_headers, body_content_type.as_deref());
|
||||
|
||||
let request_mode = init.mode.map(|value| value.0);
|
||||
let credentials_mode = init.credentials_mode.map(|value| value.0);
|
||||
let redirect_mode = init.redirect.map(|value| value.0);
|
||||
let referrer = init
|
||||
.referrer
|
||||
.map(|referrer| normalize_request_referrer(scope, &referrer));
|
||||
|
||||
Ok(ParsedFetchInit {
|
||||
method,
|
||||
method_present,
|
||||
@@ -212,13 +283,12 @@ pub(crate) fn parse_fetch_init<'s>(
|
||||
body_content_type,
|
||||
headers: extra_headers,
|
||||
headers_present,
|
||||
request_mode,
|
||||
validation,
|
||||
credentials_mode,
|
||||
redirect_mode,
|
||||
priority,
|
||||
cache: init.cache,
|
||||
referrer,
|
||||
referrer_policy: init.referrer_policy,
|
||||
cache: init.cache.map(|value| value.0.to_owned()),
|
||||
referrer_policy: init.referrer_policy.map(|value| value.0.to_owned()),
|
||||
integrity: init.integrity,
|
||||
keepalive: init.keepalive,
|
||||
})
|
||||
@@ -272,6 +342,33 @@ fn parse_request_priority_webidl(value: &str) -> Option<RequestPriorityWebIdl> {
|
||||
.map(RequestPriorityWebIdl)
|
||||
}
|
||||
|
||||
fn parse_request_cache_webidl(value: &str) -> Option<RequestCacheWebIdl> {
|
||||
Some(RequestCacheWebIdl(match value {
|
||||
"default" => "default",
|
||||
"no-store" => "no-store",
|
||||
"reload" => "reload",
|
||||
"no-cache" => "no-cache",
|
||||
"force-cache" => "force-cache",
|
||||
"only-if-cached" => "only-if-cached",
|
||||
_ => return None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_referrer_policy_webidl(value: &str) -> Option<ReferrerPolicyWebIdl> {
|
||||
Some(ReferrerPolicyWebIdl(match value {
|
||||
"" => "",
|
||||
"no-referrer" => "no-referrer",
|
||||
"no-referrer-when-downgrade" => "no-referrer-when-downgrade",
|
||||
"same-origin" => "same-origin",
|
||||
"origin" => "origin",
|
||||
"strict-origin" => "strict-origin",
|
||||
"origin-when-cross-origin" => "origin-when-cross-origin",
|
||||
"strict-origin-when-cross-origin" => "strict-origin-when-cross-origin",
|
||||
"unsafe-url" => "unsafe-url",
|
||||
_ => return None,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -3,6 +3,34 @@ use super::*;
|
||||
use crate::web_api_interfaces;
|
||||
use crate::webidl;
|
||||
|
||||
pub(super) fn normalize_fetch_request_method(method: &str) -> Result<String, webidl::WebIdlError> {
|
||||
if method.is_empty()
|
||||
|| !method.bytes().all(|byte| {
|
||||
byte.is_ascii_alphanumeric()
|
||||
|| matches!(
|
||||
byte,
|
||||
b'!' | b'#'
|
||||
| b'$'
|
||||
| b'%'
|
||||
| b'&'
|
||||
| b'\''
|
||||
| b'*'
|
||||
| b'+'
|
||||
| b'-'
|
||||
| b'.'
|
||||
| b'^'
|
||||
| b'_'
|
||||
| b'`'
|
||||
| b'|'
|
||||
| b'~'
|
||||
)
|
||||
})
|
||||
{
|
||||
return Err(webidl::WebIdlError::custom_message("Request method is not a valid HTTP token"));
|
||||
}
|
||||
normalize_request_method(method)
|
||||
}
|
||||
|
||||
pub(in crate::network_host) fn normalize_request_method(
|
||||
method: &str,
|
||||
) -> Result<String, webidl::WebIdlError> {
|
||||
@@ -108,7 +136,7 @@ fn request_input_snapshot_inner<'s>(
|
||||
return Ok(None);
|
||||
};
|
||||
let method = defined_object_string_property(scope, object, "method")
|
||||
.map(|value| normalize_request_method(&value))
|
||||
.map(|value| normalize_fetch_request_method(&value))
|
||||
.transpose()?
|
||||
.unwrap_or_else(|| "GET".to_owned());
|
||||
let mode =
|
||||
@@ -169,7 +197,7 @@ fn request_input_snapshot_from_private_slots<'s>(
|
||||
) -> Result<RequestInputSnapshot, webidl::WebIdlError> {
|
||||
let url = request_slot_string(scope, object, REQUEST_URL_SLOT).unwrap_or_default();
|
||||
let method = request_slot_string(scope, object, REQUEST_METHOD_SLOT)
|
||||
.map(|value| normalize_request_method(&value))
|
||||
.map(|value| normalize_fetch_request_method(&value))
|
||||
.transpose()?
|
||||
.unwrap_or_else(|| "GET".to_owned());
|
||||
let mode =
|
||||
@@ -306,11 +334,11 @@ fn object_bool_property(
|
||||
.map(|value| value.boolean_value(scope))
|
||||
}
|
||||
|
||||
pub(super) fn resolve_request_constructor_url(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
input: &str,
|
||||
) -> String {
|
||||
try_resolve_request_constructor_url(scope, input).unwrap_or_else(|_| input.to_owned())
|
||||
pub(crate) fn validate_request_url_credentials(url: &url::Url) -> Result<(), webidl::WebIdlError> {
|
||||
if !url.username().is_empty() || url.password().is_some_and(|value| !value.is_empty()) {
|
||||
return Err(webidl::WebIdlError::custom_message("Request URL must not include credentials"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn try_resolve_request_constructor_url(
|
||||
@@ -378,19 +406,22 @@ pub(crate) fn try_resolve_request_constructor_url_for_scope(
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn normalize_request_referrer(scope: &mut v8::PinScope<'_, '_>, input: &str) -> String {
|
||||
pub(super) fn normalize_request_referrer(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
input: &str,
|
||||
) -> Result<String, RequestUrlError> {
|
||||
if input.is_empty() || input == "about:client" {
|
||||
return input.to_owned();
|
||||
return Ok(input.to_owned());
|
||||
}
|
||||
|
||||
let resolved = resolve_request_constructor_url(scope, input);
|
||||
let resolved = try_resolve_request_constructor_url(scope, input)?;
|
||||
let Some(context_url) = current_request_context_url(scope) else {
|
||||
return resolved;
|
||||
return Ok(resolved);
|
||||
};
|
||||
if moli_url::parsed_same_origin(&resolved, &context_url) {
|
||||
resolved
|
||||
Ok(resolved)
|
||||
} else {
|
||||
"about:client".to_owned()
|
||||
Ok("about:client".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1873,7 +1873,8 @@ pub(in crate::worker) fn resolve_worker_fetch_input<'s>(
|
||||
credentials_mode,
|
||||
redirect_mode,
|
||||
priority,
|
||||
metadata,
|
||||
mut metadata,
|
||||
init_validation,
|
||||
) = if let Some(inherited) = inherited {
|
||||
let req_obj = v8::Local::<v8::Object>::try_from(arg0).expect("request-like object");
|
||||
request_like = Some(req_obj);
|
||||
@@ -1900,7 +1901,8 @@ pub(in crate::worker) fn resolve_worker_fetch_input<'s>(
|
||||
}
|
||||
let inherited_credentials = request_object_credentials_mode(scope, req_obj)?;
|
||||
let request_mode = init
|
||||
.request_mode
|
||||
.validation
|
||||
.mode
|
||||
.or_else(|| moli_fetch::RequestMode::from_str(&inherited.mode).ok())
|
||||
.unwrap_or(moli_fetch::RequestMode::Cors);
|
||||
validate_worker_no_cors_method(request_mode, &method)?;
|
||||
@@ -1923,7 +1925,7 @@ pub(in crate::worker) fn resolve_worker_fetch_input<'s>(
|
||||
});
|
||||
let metadata = ServiceWorkerFetchRequestMetadata {
|
||||
cache: init.cache.unwrap_or(inherited.cache),
|
||||
referrer: init.referrer.unwrap_or(inherited.referrer),
|
||||
referrer: inherited.referrer,
|
||||
referrer_policy: init.referrer_policy.unwrap_or(inherited.referrer_policy),
|
||||
integrity: init.integrity.unwrap_or(inherited.integrity),
|
||||
keepalive: init.keepalive.unwrap_or(inherited.keepalive),
|
||||
@@ -1938,6 +1940,7 @@ pub(in crate::worker) fn resolve_worker_fetch_input<'s>(
|
||||
redirect_mode,
|
||||
priority,
|
||||
metadata,
|
||||
init.validation,
|
||||
)
|
||||
} else {
|
||||
let url = webidl::convert::<webidl::UsvString>(
|
||||
@@ -1947,7 +1950,10 @@ pub(in crate::worker) fn resolve_worker_fetch_input<'s>(
|
||||
)
|
||||
.map(String::from)?;
|
||||
let init = parse_fetch_init(scope, args, 1)?;
|
||||
let request_mode = init.request_mode.unwrap_or(moli_fetch::RequestMode::Cors);
|
||||
let request_mode = init
|
||||
.validation
|
||||
.mode
|
||||
.unwrap_or(moli_fetch::RequestMode::Cors);
|
||||
validate_worker_no_cors_method(request_mode, &init.method)?;
|
||||
let headers = if request_mode == moli_fetch::RequestMode::NoCors {
|
||||
filter_headers_for_guard(&init.headers, HeadersGuard::RequestNoCors)
|
||||
@@ -1960,7 +1966,7 @@ pub(in crate::worker) fn resolve_worker_fetch_input<'s>(
|
||||
let redirect_mode = init.redirect_mode.unwrap_or(RequestRedirectMode::Follow);
|
||||
let metadata = ServiceWorkerFetchRequestMetadata {
|
||||
cache: init.cache.unwrap_or_else(|| "default".to_owned()),
|
||||
referrer: init.referrer.unwrap_or_else(|| "about:client".to_owned()),
|
||||
referrer: "about:client".to_owned(),
|
||||
referrer_policy: init.referrer_policy.unwrap_or_default(),
|
||||
integrity: init.integrity.unwrap_or_default(),
|
||||
keepalive: init.keepalive.unwrap_or(false),
|
||||
@@ -1975,9 +1981,16 @@ pub(in crate::worker) fn resolve_worker_fetch_input<'s>(
|
||||
redirect_mode,
|
||||
init.priority,
|
||||
metadata,
|
||||
init.validation,
|
||||
)
|
||||
};
|
||||
let resolved_url = resolve_context_url(base_url, &url_input, None)?;
|
||||
crate::network_host::validate_request_url_credentials(&resolved_url)?;
|
||||
if let Some(referrer) =
|
||||
init_validation.validate(scope, request_mode.as_ref(), &metadata.cache)?
|
||||
{
|
||||
metadata.referrer = referrer;
|
||||
}
|
||||
let signal = worker_fetch_signal_option(scope, args, request_like)?;
|
||||
if consumes_request_body && let Some(request_like) = request_like {
|
||||
crate::network_host::mark_request_input_body_used_for_fetch(scope, request_like);
|
||||
|
||||
Reference in New Issue
Block a user