1use crate::FieldMap;
2use crate::p3::bindings::clocks::monotonic_clock::Duration;
3use crate::p3::bindings::http::types::{
4 ErrorCode, FieldName, FieldValue, Fields, HeaderError, Headers, Host, HostFields, HostRequest,
5 HostRequestOptions, HostRequestWithStore, HostResponse, HostResponseWithStore, Method, Request,
6 RequestOptions, RequestOptionsError, Response, Scheme, StatusCode, Trailers,
7};
8use crate::p3::body::Body;
9use crate::p3::{HeaderResult, HttpError, RequestOptionsResult};
10use crate::{WasiHttp, WasiHttpCtxView};
11use std::sync::Arc;
12use wasmtime::component::{Access, FutureReader, Resource, ResourceTable, StreamReader};
13use wasmtime::error::Context as _;
14
15fn get_fields<'a>(
16 table: &'a ResourceTable,
17 fields: &Resource<Fields>,
18) -> wasmtime::Result<&'a Fields> {
19 table
20 .get(&fields)
21 .context("failed to get fields from table")
22}
23
24fn get_fields_mut<'a>(
25 table: &'a mut ResourceTable,
26 fields: &Resource<Fields>,
27) -> HeaderResult<&'a mut Fields> {
28 table
29 .get_mut(&fields)
30 .context("failed to get fields from table")
31 .map_err(crate::p3::HeaderError::trap)
32}
33
34fn push_fields(table: &mut ResourceTable, fields: Fields) -> wasmtime::Result<Resource<Fields>> {
35 table.push(fields).context("failed to push fields to table")
36}
37
38fn delete_fields(table: &mut ResourceTable, fields: Resource<Fields>) -> wasmtime::Result<Fields> {
39 let mut fields = table
40 .delete(fields)
41 .context("failed to delete fields from table")?;
42 fields.set_immutable();
47 Ok(fields)
48}
49
50fn get_request<'a>(
51 table: &'a ResourceTable,
52 req: &Resource<Request>,
53) -> wasmtime::Result<&'a Request> {
54 table.get(req).context("failed to get request from table")
55}
56
57fn get_request_mut<'a>(
58 table: &'a mut ResourceTable,
59 req: &Resource<Request>,
60) -> wasmtime::Result<&'a mut Request> {
61 table
62 .get_mut(req)
63 .context("failed to get request from table")
64}
65
66fn get_response<'a>(
67 table: &'a ResourceTable,
68 res: &Resource<Response>,
69) -> wasmtime::Result<&'a Response> {
70 table.get(res).context("failed to get response from table")
71}
72
73fn get_response_mut<'a>(
74 table: &'a mut ResourceTable,
75 res: &Resource<Response>,
76) -> wasmtime::Result<&'a mut Response> {
77 table
78 .get_mut(res)
79 .context("failed to get response from table")
80}
81
82fn get_request_options<'a>(
83 table: &'a ResourceTable,
84 opts: &Resource<RequestOptions>,
85) -> wasmtime::Result<&'a RequestOptions> {
86 table
87 .get(opts)
88 .context("failed to get request options from table")
89}
90
91fn get_request_options_mut<'a>(
92 table: &'a mut ResourceTable,
93 opts: &Resource<RequestOptions>,
94) -> RequestOptionsResult<&'a mut RequestOptions> {
95 table
96 .get_mut(opts)
97 .context("failed to get request options from table")
98 .map_err(crate::p3::RequestOptionsError::trap)
99}
100
101fn push_request_options(
102 table: &mut ResourceTable,
103 opts: RequestOptions,
104) -> wasmtime::Result<Resource<RequestOptions>> {
105 table
106 .push(opts)
107 .context("failed to push request options to table")
108}
109
110fn delete_request_options(
111 table: &mut ResourceTable,
112 opts: Resource<RequestOptions>,
113) -> wasmtime::Result<RequestOptions> {
114 table
115 .delete(opts)
116 .context("failed to delete request options from table")
117}
118
119fn parse_authority(authority: String) -> Result<http::uri::Authority, ()> {
127 let has_port = match authority.rfind(']') {
128 Some(i) => authority[i..].contains(':'),
129 None => authority.contains(':'),
130 };
131 let authority = http::uri::Authority::try_from(authority).map_err(|_| ())?;
132 if has_port && authority.port_u16().is_none() {
133 return Err(());
134 }
135 Ok(authority)
136}
137
138impl HostFields for WasiHttpCtxView<'_> {
139 fn new(&mut self) -> wasmtime::Result<Resource<Fields>> {
140 push_fields(self.table, FieldMap::new_mutable(self.ctx.field_size_limit))
141 }
142
143 fn from_list(
144 &mut self,
145 entries: Vec<(FieldName, FieldValue)>,
146 ) -> HeaderResult<Resource<Fields>> {
147 let mut fields = FieldMap::new_mutable(self.ctx.field_size_limit);
148 for (name, value) in entries {
149 fields.append(self.hooks, name, value)?;
150 }
151 let fields = push_fields(self.table, fields).map_err(crate::p3::HeaderError::trap)?;
152 Ok(fields)
153 }
154
155 fn get(
156 &mut self,
157 fields: Resource<Fields>,
158 name: FieldName,
159 ) -> wasmtime::Result<Vec<FieldValue>> {
160 let fields = get_fields(self.table, &fields)?;
161 Ok(fields
162 .get_all(name)
163 .into_iter()
164 .map(|val| val.as_bytes().into())
165 .collect())
166 }
167
168 fn has(&mut self, fields: Resource<Fields>, name: FieldName) -> wasmtime::Result<bool> {
169 let fields = get_fields(self.table, &fields)?;
170 Ok(fields.contains_key(name))
171 }
172
173 fn set(
174 &mut self,
175 fields: Resource<Fields>,
176 name: FieldName,
177 values: Vec<FieldValue>,
178 ) -> HeaderResult<()> {
179 get_fields_mut(self.table, &fields)?.set(self.hooks, name, values)?;
180 Ok(())
181 }
182
183 fn delete(&mut self, fields: Resource<Fields>, name: FieldName) -> HeaderResult<()> {
184 get_fields_mut(self.table, &fields)?.remove_all(self.hooks, name)?;
185 Ok(())
186 }
187
188 fn get_and_delete(
189 &mut self,
190 fields: Resource<Fields>,
191 name: FieldName,
192 ) -> HeaderResult<Vec<FieldValue>> {
193 let name = name.parse().or(Err(HeaderError::InvalidSyntax))?;
194 let values = get_fields_mut(self.table, &fields)?
195 .remove_all(self.hooks, name)?
196 .into_iter();
197 Ok(values.map(|value| value.as_bytes().into()).collect())
198 }
199
200 fn append(
201 &mut self,
202 fields: Resource<Fields>,
203 name: FieldName,
204 value: FieldValue,
205 ) -> HeaderResult<()> {
206 get_fields_mut(self.table, &fields)?.append(self.hooks, name, value)?;
207 Ok(())
208 }
209
210 fn copy_all(
211 &mut self,
212 fields: Resource<Fields>,
213 ) -> wasmtime::Result<Vec<(FieldName, FieldValue)>> {
214 let fields = get_fields(self.table, &fields)?;
215 let fields = fields
216 .iter()
217 .map(|(name, value)| (name.as_str().into(), value.as_bytes().into()))
218 .collect();
219 Ok(fields)
220 }
221
222 fn clone(&mut self, fields: Resource<Fields>) -> wasmtime::Result<Resource<Fields>> {
223 let mut fields = get_fields(self.table, &fields)?.clone();
224 fields.set_mutable(self.ctx.field_size_limit);
225 push_fields(self.table, fields)
226 }
227
228 fn drop(&mut self, fields: Resource<Fields>) -> wasmtime::Result<()> {
229 delete_fields(self.table, fields)?;
230 Ok(())
231 }
232}
233
234impl<T> HostRequestWithStore<T> for WasiHttp {
235 fn new(
236 mut store: Access<T, Self>,
237 headers: Resource<Headers>,
238 contents: Option<StreamReader<u8>>,
239 trailers: FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
240 options: Option<Resource<RequestOptions>>,
241 ) -> wasmtime::Result<(Resource<Request>, FutureReader<Result<(), ErrorCode>>)> {
242 let (body, body_result) = Body::new_guest(&mut store, contents, trailers)?;
243 let WasiHttpCtxView { table, .. } = store.get();
244 let headers = delete_fields(table, headers)?;
245 let options = options
246 .map(|options| delete_request_options(table, options))
247 .transpose()?;
248 let req = Request {
249 method: http::Method::GET,
250 scheme: None,
251 authority: None,
252 path_with_query: None,
253 headers,
254 options: options.map(Into::into),
255 body,
256 };
257 let req = table.push(req).context("failed to push request to table")?;
258 Ok((req, body_result))
259 }
260
261 fn consume_body(
262 mut store: Access<T, Self>,
263 req: Resource<Request>,
264 fut: FutureReader<Result<(), ErrorCode>>,
265 ) -> wasmtime::Result<(
266 StreamReader<u8>,
267 FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
268 )> {
269 let getter = store.getter();
270 let Request { body, .. } = store
271 .get()
272 .table
273 .delete(req)
274 .context("failed to delete request from table")?;
275 body.consume(store, fut, getter)
276 }
277
278 fn drop(mut store: Access<'_, T, Self>, req: Resource<Request>) -> wasmtime::Result<()> {
279 let Request { body, .. } = store
280 .get()
281 .table
282 .delete(req)
283 .context("failed to delete request from table")?;
284 body.drop(store)?;
285 Ok(())
286 }
287}
288
289impl HostRequest for WasiHttpCtxView<'_> {
290 fn get_method(&mut self, req: Resource<Request>) -> wasmtime::Result<Method> {
291 let Request { method, .. } = get_request(self.table, &req)?;
292 Ok(method.into())
293 }
294
295 fn set_method(
296 &mut self,
297 req: Resource<Request>,
298 method: Method,
299 ) -> wasmtime::Result<Result<(), ()>> {
300 let req = get_request_mut(self.table, &req)?;
301 let Ok(method) = method.try_into() else {
302 return Ok(Err(()));
303 };
304 req.method = method;
305 Ok(Ok(()))
306 }
307
308 fn get_path_with_query(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<String>> {
309 let Request {
310 path_with_query, ..
311 } = get_request(self.table, &req)?;
312 Ok(path_with_query.as_ref().map(|pq| pq.as_str().into()))
313 }
314
315 fn set_path_with_query(
316 &mut self,
317 req: Resource<Request>,
318 path_with_query: Option<String>,
319 ) -> wasmtime::Result<Result<(), ()>> {
320 let req = get_request_mut(self.table, &req)?;
321 let Some(path_with_query) = path_with_query else {
322 req.path_with_query = None;
323 return Ok(Ok(()));
324 };
325 let Ok(path_with_query) = path_with_query.try_into() else {
326 return Ok(Err(()));
327 };
328 req.path_with_query = Some(path_with_query);
329 Ok(Ok(()))
330 }
331
332 fn get_scheme(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<Scheme>> {
333 let Request { scheme, .. } = get_request(self.table, &req)?;
334 Ok(scheme.as_ref().map(Into::into))
335 }
336
337 fn set_scheme(
338 &mut self,
339 req: Resource<Request>,
340 scheme: Option<Scheme>,
341 ) -> wasmtime::Result<Result<(), ()>> {
342 let req = get_request_mut(self.table, &req)?;
343 let Some(scheme) = scheme else {
344 req.scheme = None;
345 return Ok(Ok(()));
346 };
347 let Ok(scheme) = scheme.try_into() else {
348 return Ok(Err(()));
349 };
350 req.scheme = Some(scheme);
351 Ok(Ok(()))
352 }
353
354 fn get_authority(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<String>> {
355 let Request { authority, .. } = get_request(self.table, &req)?;
356 Ok(authority.as_ref().map(|auth| auth.as_str().into()))
357 }
358
359 fn set_authority(
360 &mut self,
361 req: Resource<Request>,
362 authority: Option<String>,
363 ) -> wasmtime::Result<Result<(), ()>> {
364 let req = get_request_mut(self.table, &req)?;
365 let Some(authority) = authority else {
366 req.authority = None;
367 return Ok(Ok(()));
368 };
369 let Ok(authority) = parse_authority(authority) else {
370 return Ok(Err(()));
371 };
372 req.authority = Some(authority);
373 Ok(Ok(()))
374 }
375
376 fn get_options(
377 &mut self,
378 req: Resource<Request>,
379 ) -> wasmtime::Result<Option<Resource<RequestOptions>>> {
380 let Request { options, .. } = get_request(self.table, &req)?;
381 if let Some(options) = options {
382 let options = push_request_options(
383 self.table,
384 RequestOptions::new_immutable(Arc::clone(options)),
385 )?;
386 Ok(Some(options))
387 } else {
388 Ok(None)
389 }
390 }
391
392 fn get_headers(&mut self, req: Resource<Request>) -> wasmtime::Result<Resource<Headers>> {
393 let Request { headers, .. } = get_request(self.table, &req)?;
394 push_fields(self.table, headers.clone())
395 }
396}
397
398impl HostRequestOptions for WasiHttpCtxView<'_> {
399 fn new(&mut self) -> wasmtime::Result<Resource<RequestOptions>> {
400 push_request_options(self.table, RequestOptions::new_mutable_default())
401 }
402
403 fn get_connect_timeout(
404 &mut self,
405 opts: Resource<RequestOptions>,
406 ) -> wasmtime::Result<Option<Duration>> {
407 let opts = get_request_options(self.table, &opts)?;
408 let Some(connect_timeout) = opts.connect_timeout else {
409 return Ok(None);
410 };
411 let ns = connect_timeout.as_nanos();
412 let ns = Duration::try_from(ns)
413 .context("connect timeout duration nanoseconds do not fit in u64")?;
414 Ok(Some(ns))
415 }
416
417 fn set_connect_timeout(
418 &mut self,
419 opts: Resource<RequestOptions>,
420 duration: Option<Duration>,
421 ) -> RequestOptionsResult<()> {
422 let opts = get_request_options_mut(self.table, &opts)?;
423 let opts = opts.get_mut().ok_or(RequestOptionsError::Immutable)?;
424 opts.connect_timeout = duration.map(core::time::Duration::from_nanos);
425 Ok(())
426 }
427
428 fn get_first_byte_timeout(
429 &mut self,
430 opts: Resource<RequestOptions>,
431 ) -> wasmtime::Result<Option<Duration>> {
432 let opts = get_request_options(self.table, &opts)?;
433 let Some(first_byte_timeout) = opts.first_byte_timeout else {
434 return Ok(None);
435 };
436 let ns = first_byte_timeout.as_nanos();
437 let ns = Duration::try_from(ns)
438 .context("first byte timeout duration nanoseconds do not fit in u64")?;
439 Ok(Some(ns))
440 }
441
442 fn set_first_byte_timeout(
443 &mut self,
444 opts: Resource<RequestOptions>,
445 duration: Option<Duration>,
446 ) -> RequestOptionsResult<()> {
447 let opts = get_request_options_mut(self.table, &opts)?;
448 let opts = opts.get_mut().ok_or(RequestOptionsError::Immutable)?;
449 opts.first_byte_timeout = duration.map(core::time::Duration::from_nanos);
450 Ok(())
451 }
452
453 fn get_between_bytes_timeout(
454 &mut self,
455 opts: Resource<RequestOptions>,
456 ) -> wasmtime::Result<Option<Duration>> {
457 let opts = get_request_options(self.table, &opts)?;
458 let Some(between_bytes_timeout) = opts.between_bytes_timeout else {
459 return Ok(None);
460 };
461 let ns = between_bytes_timeout.as_nanos();
462 let ns = Duration::try_from(ns)
463 .context("between bytes timeout duration nanoseconds do not fit in u64")?;
464 Ok(Some(ns))
465 }
466
467 fn set_between_bytes_timeout(
468 &mut self,
469 opts: Resource<RequestOptions>,
470 duration: Option<Duration>,
471 ) -> RequestOptionsResult<()> {
472 let opts = get_request_options_mut(self.table, &opts)?;
473 let opts = opts.get_mut().ok_or(RequestOptionsError::Immutable)?;
474 opts.between_bytes_timeout = duration.map(core::time::Duration::from_nanos);
475 Ok(())
476 }
477
478 fn clone(
479 &mut self,
480 opts: Resource<RequestOptions>,
481 ) -> wasmtime::Result<Resource<RequestOptions>> {
482 let opts = get_request_options(self.table, &opts)?;
483 push_request_options(self.table, RequestOptions::new_mutable(Arc::clone(opts)))
484 }
485
486 fn drop(&mut self, opts: Resource<RequestOptions>) -> wasmtime::Result<()> {
487 delete_request_options(self.table, opts)?;
488 Ok(())
489 }
490}
491
492impl<T> HostResponseWithStore<T> for WasiHttp {
493 fn new(
494 mut store: Access<T, Self>,
495 headers: Resource<Headers>,
496 contents: Option<StreamReader<u8>>,
497 trailers: FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
498 ) -> wasmtime::Result<(Resource<Response>, FutureReader<Result<(), ErrorCode>>)> {
499 let (body, body_result) = Body::new_guest(&mut store, contents, trailers)?;
500 let WasiHttpCtxView { table, .. } = store.get();
501 let headers = delete_fields(table, headers)?;
502 let res = Response {
503 status: http::StatusCode::OK,
504 headers,
505 body,
506 };
507 let res = table
508 .push(res)
509 .context("failed to push response to table")?;
510 Ok((res, body_result))
511 }
512
513 fn consume_body(
514 mut store: Access<T, Self>,
515 res: Resource<Response>,
516 fut: FutureReader<Result<(), ErrorCode>>,
517 ) -> wasmtime::Result<(
518 StreamReader<u8>,
519 FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
520 )> {
521 let getter = store.getter();
522 let Response { body, .. } = store
523 .get()
524 .table
525 .delete(res)
526 .context("failed to delete response from table")?;
527 body.consume(store, fut, getter)
528 }
529
530 fn drop(mut store: Access<'_, T, Self>, res: Resource<Response>) -> wasmtime::Result<()> {
531 let Response { body, .. } = store
532 .get()
533 .table
534 .delete(res)
535 .context("failed to delete response from table")?;
536 body.drop(store)?;
537 Ok(())
538 }
539}
540
541impl HostResponse for WasiHttpCtxView<'_> {
542 fn get_status_code(&mut self, res: Resource<Response>) -> wasmtime::Result<StatusCode> {
543 let res = get_response(self.table, &res)?;
544 Ok(res.status.into())
545 }
546
547 fn set_status_code(
548 &mut self,
549 res: Resource<Response>,
550 status_code: StatusCode,
551 ) -> wasmtime::Result<Result<(), ()>> {
552 let res = get_response_mut(self.table, &res)?;
553 match http::StatusCode::from_u16(status_code) {
554 Ok(status) if matches!(status_code, 100..=599) => {
555 res.status = status;
556 Ok(Ok(()))
557 }
558 _ => Ok(Err(())),
559 }
560 }
561
562 fn get_headers(&mut self, res: Resource<Response>) -> wasmtime::Result<Resource<Headers>> {
563 let Response { headers, .. } = get_response(self.table, &res)?;
564 push_fields(self.table, headers.clone())
565 }
566}
567
568impl Host for WasiHttpCtxView<'_> {
569 fn convert_error_code(&mut self, error: HttpError) -> wasmtime::Result<ErrorCode> {
570 error.downcast()
571 }
572
573 fn convert_header_error(
574 &mut self,
575 error: crate::p3::HeaderError,
576 ) -> wasmtime::Result<HeaderError> {
577 error.downcast()
578 }
579
580 fn convert_request_options_error(
581 &mut self,
582 error: crate::p3::RequestOptionsError,
583 ) -> wasmtime::Result<RequestOptionsError> {
584 error.downcast()
585 }
586}
587
588#[cfg(test)]
589mod tests {
590 #[test]
591 fn authority_accepts_ipv6_and_validates_ports() {
592 use super::parse_authority;
593
594 assert!(parse_authority("example.com".into()).is_ok());
596 assert!(parse_authority("example.com:443".into()).is_ok());
597 assert!(parse_authority("127.0.0.1:80".into()).is_ok());
598
599 assert!(parse_authority("[::1]".into()).is_ok());
602 assert!(parse_authority("[2001:db8::1]".into()).is_ok());
603 assert!(parse_authority("[::1]:443".into()).is_ok());
604
605 assert!(parse_authority("example.com:".into()).is_err());
607 assert!(parse_authority("example.com:abc".into()).is_err());
608 }
609}