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

wasi-http: Make child fields immutable #7524

Merged
merged 3 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use test_programs::wasi::http::types::{HeaderError, Headers};
use test_programs::wasi::http::types::{HeaderError, Headers, OutgoingRequest};

fn main() {
let hdrs = Headers::new();
Expand Down Expand Up @@ -57,4 +57,22 @@ fn main() {
Headers::from_list(&[("ok-header-name".to_owned(), b"bad\nvalue".to_vec())]),
Err(HeaderError::InvalidSyntax)
));

let req = OutgoingRequest::new(hdrs);
let hdrs = req.headers();

assert!(matches!(
hdrs.set(&"Content-Length".to_owned(), &[b"10".to_vec()]),
Err(HeaderError::Immutable),
));

assert!(matches!(
hdrs.append(&"Content-Length".to_owned(), &b"10".to_vec()),
Err(HeaderError::Immutable),
));

assert!(matches!(
hdrs.delete(&"Content-Length".to_owned()),
Err(HeaderError::Immutable),
));
}
3 changes: 3 additions & 0 deletions crates/wasi-http/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,9 @@ pub enum HostFields {
// always be registered as a child of the entry with the `parent` id. This ensures that the
// entry will always exist while this `HostFields::Ref` entry exists in the table, thus we
// don't need to account for failure when fetching the fields ref from the parent.
//
// NOTE: references are always considered immutable -- the only way to modify fields is to
// create an owned version first.
get_fields: for<'a> fn(elem: &'a mut (dyn Any + 'static)) -> &'a mut FieldMap,
},
Owned {
Expand Down
76 changes: 45 additions & 31 deletions crates/wasi-http/src/types_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,25 @@ use wasmtime_wasi::preview2::{
impl<T: WasiHttpView> crate::bindings::http::types::Host for T {}

/// Take ownership of the underlying [`FieldMap`] associated with this fields resource. If the
/// fields resource references another fields, the returned [`FieldMap`] will be cloned.
/// fields resource references another fields, the returned [`FieldMap`] will be cloned. We throw
/// away the `immutable` status of the original fields, as the new context will determine how the
/// [`FieldMap`] is used.
fn move_fields(table: &mut Table, id: Resource<HostFields>) -> wasmtime::Result<FieldMap> {
match table.delete(id)? {
HostFields::Ref { parent, get_fields } => {
let entry = table.get_any_mut(parent)?;
Ok(get_fields(entry).clone())
let fields = get_fields(entry);
Ok(fields.clone())
}

HostFields::Owned { fields } => Ok(fields),
HostFields::Owned { fields, .. } => Ok(fields),
}
}

fn get_fields_mut<'a>(
fn get_fields<'a>(
table: &'a mut Table,
id: &Resource<HostFields>,
) -> wasmtime::Result<&'a mut FieldMap> {
) -> wasmtime::Result<&'a FieldMap> {
let fields = table.get(&id)?;
if let HostFields::Ref { parent, get_fields } = *fields {
let entry = table.get_any_mut(parent)?;
Expand All @@ -51,6 +54,16 @@ fn get_fields_mut<'a>(
}
}

fn get_fields_mut<'a>(
table: &'a mut Table,
id: &Resource<HostFields>,
) -> wasmtime::Result<Result<&'a mut FieldMap, types::HeaderError>> {
match table.get_mut(&id)? {
HostFields::Owned { fields } => Ok(Ok(fields)),
HostFields::Ref { .. } => Ok(Err(types::HeaderError::Immutable)),
}
}

fn is_forbidden_header<T: WasiHttpView>(view: &mut T, name: &HeaderName) -> bool {
static FORBIDDEN_HEADERS: [HeaderName; 9] = [
hyper::header::CONNECTION,
Expand Down Expand Up @@ -83,7 +96,7 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostFields for T {
&mut self,
entries: Vec<(String, Vec<u8>)>,
) -> wasmtime::Result<Result<Resource<HostFields>, types::HeaderError>> {
let mut map = hyper::HeaderMap::new();
let mut fields = hyper::HeaderMap::new();

for (header, value) in entries {
let header = match hyper::header::HeaderName::from_bytes(header.as_bytes()) {
Expand All @@ -100,12 +113,12 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostFields for T {
Err(_) => return Ok(Err(types::HeaderError::InvalidSyntax)),
};

map.append(header, value);
fields.append(header, value);
}

let id = self
.table()
.push(HostFields::Owned { fields: map })
.push(HostFields::Owned { fields })
.context("[new_fields] pushing fields")?;

Ok(Ok(id))
Expand All @@ -128,7 +141,7 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostFields for T {
Err(_) => return Ok(vec![]),
};

let res = get_fields_mut(self.table(), &fields)
let res = get_fields(self.table(), &fields)
.context("[fields_get] getting fields")?
.get_all(header)
.into_iter()
Expand Down Expand Up @@ -160,14 +173,15 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostFields for T {
}
}

let m =
get_fields_mut(self.table(), &fields).context("[fields_set] getting mutable fields")?;
m.remove(&header);
for value in values {
m.append(&header, value);
}

Ok(Ok(()))
Ok(get_fields_mut(self.table(), &fields)
.context("[fields_set] getting mutable fields")?
.map(|fields| {
fields.remove(&header);
for value in values {
fields.append(&header, value);
}
()
}))
}

fn delete(
Expand All @@ -184,9 +198,10 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostFields for T {
return Ok(Err(types::HeaderError::Forbidden));
}

let m = get_fields_mut(self.table(), &fields)?;
m.remove(header);
Ok(Ok(()))
Ok(get_fields_mut(self.table(), &fields)?.map(|fields| {
fields.remove(header);
()
}))
}

fn append(
Expand All @@ -209,27 +224,26 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostFields for T {
Err(_) => return Ok(Err(types::HeaderError::InvalidSyntax)),
};

let m = get_fields_mut(self.table(), &fields)
.context("[fields_append] getting mutable fields")?;

m.append(header, value);
Ok(Ok(()))
Ok(get_fields_mut(self.table(), &fields)
.context("[fields_append] getting mutable fields")?
.map(|fields| {
fields.append(header, value);
()
}))
}

fn entries(
&mut self,
fields: Resource<HostFields>,
) -> wasmtime::Result<Vec<(String, Vec<u8>)>> {
let fields = get_fields_mut(self.table(), &fields)?;
let result = fields
Ok(get_fields(self.table(), &fields)?
.iter()
.map(|(name, value)| (name.as_str().to_owned(), value.as_bytes().to_owned()))
.collect();
Ok(result)
.collect())
}

fn clone(&mut self, fields: Resource<HostFields>) -> wasmtime::Result<Resource<HostFields>> {
let fields = get_fields_mut(self.table(), &fields)
let fields = get_fields(self.table(), &fields)
.context("[fields_clone] getting fields")?
.clone();

Expand Down Expand Up @@ -837,7 +851,7 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostOutgoingBody for T {
.expect("outgoing-body trailer_sender consumed by a non-owning function");

let message = if let Some(ts) = ts {
FinishMessage::Trailers(get_fields_mut(self.table(), &ts)?.clone().into())
FinishMessage::Trailers(move_fields(self.table(), ts)?)
} else {
FinishMessage::Finished
};
Expand Down
1 change: 1 addition & 0 deletions crates/wasi-http/wit/deps/http/types.wit
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ interface types {
variant header-error {
invalid-syntax,
forbidden,
immutable
}

/// Field keys are always strings.
Expand Down
1 change: 1 addition & 0 deletions crates/wasi/wit/deps/http/types.wit
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ interface types {
variant header-error {
invalid-syntax,
forbidden,
immutable
}

/// Field keys are always strings.
Expand Down