diff options
author | webbeef <me@webbeef.org> | 2024-09-29 11:23:48 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-29 18:23:48 +0000 |
commit | 58f34ad7a3db8c633871d568bfcef8b094217e3e (patch) | |
tree | 43c3f128c54f5da5d80d5feeaca495950e2de144 | |
parent | 013473f1d5a18f7d99183593ef370045dc58c978 (diff) | |
download | servo-58f34ad7a3db8c633871d568bfcef8b094217e3e.tar.gz servo-58f34ad7a3db8c633871d568bfcef8b094217e3e.zip |
Create HttpStatus to safely deal with HTTP responses status. (#33581)
Signed-off-by: webbeef <me@webbeef.org>
30 files changed, 347 insertions, 406 deletions
diff --git a/Cargo.lock b/Cargo.lock index 5245f54aa02..f0da1fac5da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1341,6 +1341,7 @@ dependencies = [ "http", "ipc-channel", "log", + "net_traits", "serde", "serde_json", "servo_config", @@ -1359,6 +1360,7 @@ dependencies = [ "ipc-channel", "malloc_size_of", "malloc_size_of_derive", + "net_traits", "serde", "servo_url", "uuid", diff --git a/components/constellation/network_listener.rs b/components/constellation/network_listener.rs index 9afe406b57a..f36411c01d7 100644 --- a/components/constellation/network_listener.rs +++ b/components/constellation/network_listener.rs @@ -146,8 +146,8 @@ impl NetworkListener { referrer: metadata.referrer.clone(), status_code: metadata .status - .as_ref() - .map(|&(code, _)| code) + .try_code() + .map(|code| code.as_u16()) .unwrap_or(200), }); diff --git a/components/devtools/Cargo.toml b/components/devtools/Cargo.toml index 484eff965f3..a74cbb88b9b 100644 --- a/components/devtools/Cargo.toml +++ b/components/devtools/Cargo.toml @@ -24,6 +24,7 @@ headers = { workspace = true } http = { workspace = true } ipc-channel = { workspace = true } log = { workspace = true } +net_traits = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } servo_config = { path = "../config" } diff --git a/components/devtools/actors/network_event.rs b/components/devtools/actors/network_event.rs index a0ba5cb0c15..ac931a9931f 100644 --- a/components/devtools/actors/network_event.rs +++ b/components/devtools/actors/network_event.rs @@ -11,7 +11,8 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use chrono::{Local, LocalResult, TimeZone}; use devtools_traits::{HttpRequest as DevtoolsHttpRequest, HttpResponse as DevtoolsHttpResponse}; use headers::{ContentType, Cookie, HeaderMapExt}; -use http::{header, HeaderMap, Method, StatusCode}; +use http::{header, HeaderMap, Method}; +use net_traits::http_status::HttpStatus; use serde::Serialize; use serde_json::{Map, Value}; @@ -32,7 +33,7 @@ struct HttpRequest { struct HttpResponse { headers: Option<HeaderMap>, - status: Option<(StatusCode, String)>, + status: HttpStatus, body: Option<Vec<u8>>, } @@ -350,7 +351,7 @@ impl NetworkEventActor { }, response: HttpResponse { headers: None, - status: None, + status: HttpStatus::default(), body: None, }, is_xhr: false, @@ -372,10 +373,7 @@ impl NetworkEventActor { pub fn add_response(&mut self, response: DevtoolsHttpResponse) { self.response.headers.clone_from(&response.headers); - self.response.status = response.status.as_ref().map(|&(s, ref st)| { - let status_text = String::from_utf8_lossy(st).into_owned(); - (StatusCode::from_u16(s).unwrap(), status_text) - }); + self.response.status = response.status; self.response.body = response.body; } @@ -409,20 +407,14 @@ impl NetworkEventActor { // TODO: Send the correct values for all these fields. let h_size_option = self.response.headers.as_ref().map(|headers| headers.len()); let h_size = h_size_option.unwrap_or(0); - let (status_code, status_message) = self - .response - .status - .as_ref() - .map_or((0, "".to_owned()), |(code, text)| { - (code.as_u16(), text.clone()) - }); + let status = &self.response.status; // TODO: Send the correct values for remoteAddress and remotePort and http_version. ResponseStartMsg { http_version: "HTTP/1.1".to_owned(), remote_address: "63.245.217.43".to_owned(), remote_port: 443, - status: status_code.to_string(), - status_text: status_message, + status: status.code().to_string(), + status_text: String::from_utf8_lossy(status.message()).to_string(), headers_size: h_size, discard_response_body: false, } diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index 2a2753a55c9..f5abcd3003c 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -19,6 +19,7 @@ use ipc_channel::ipc::{self, IpcReceiver}; use log::warn; use mime::{self, Mime}; use net_traits::filemanager_thread::{FileTokenCheck, RelativePos}; +use net_traits::http_status::HttpStatus; use net_traits::request::{ is_cors_safelisted_method, is_cors_safelisted_request_header, BodyChunkRequest, BodyChunkResponse, CredentialsMode, Destination, Origin, RedirectMode, Referrer, Request, @@ -599,8 +600,7 @@ fn create_blank_reply(url: ServoUrl, timing_type: ResourceTimingType) -> Respons .headers .typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8)); *response.body.lock().unwrap() = ResponseBody::Done(vec![]); - response.status = Some((StatusCode::OK, "OK".to_string())); - response.raw_status = Some((StatusCode::OK.as_u16(), b"OK".to_vec())); + response.status = HttpStatus::default(); response } @@ -681,13 +681,13 @@ async fn scheme_fetch( } } -fn is_null_body_status(status: &Option<(StatusCode, String)>) -> bool { +fn is_null_body_status(status: &HttpStatus) -> bool { matches!( - status, - Some((StatusCode::SWITCHING_PROTOCOLS, ..)) | - Some((StatusCode::NO_CONTENT, ..)) | - Some((StatusCode::RESET_CONTENT, ..)) | - Some((StatusCode::NOT_MODIFIED, ..)) + status.try_code(), + Some(StatusCode::SWITCHING_PROTOCOLS) | + Some(StatusCode::NO_CONTENT) | + Some(StatusCode::RESET_CONTENT) | + Some(StatusCode::NOT_MODIFIED) ) } diff --git a/components/net/http_cache.rs b/components/net/http_cache.rs index c8ec2be5f62..c158464c5bb 100644 --- a/components/net/http_cache.rs +++ b/components/net/http_cache.rs @@ -24,6 +24,7 @@ use malloc_size_of::{ Measurable, }; use malloc_size_of_derive::MallocSizeOf; +use net_traits::http_status::HttpStatus; use net_traits::request::Request; use net_traits::response::{HttpsState, Response, ResponseBody}; use net_traits::{FetchMetadata, Metadata, ResourceFetchTiming}; @@ -70,8 +71,7 @@ struct MeasurableCachedResource { metadata: CachedMetadata, location_url: Option<Result<ServoUrl, String>>, https_state: HttpsState, - status: Option<(StatusCode, String)>, - raw_status: Option<(u16, Vec<u8>)>, + status: HttpStatus, url_list: Vec<ServoUrl>, expires: Duration, last_validated: Instant, @@ -105,7 +105,7 @@ struct MeasurableCachedMetadata { /// Character set. pub charset: Option<String>, /// HTTP Status - pub status: Option<(u16, Vec<u8>)>, + pub status: HttpStatus, } impl MallocSizeOf for CachedMetadata { @@ -132,9 +132,9 @@ pub struct HttpCache { } /// Determine if a response is cacheable by default <https://tools.ietf.org/html/rfc7231#section-6.1> -fn is_cacheable_by_default(status_code: u16) -> bool { +fn is_cacheable_by_default(status_code: StatusCode) -> bool { matches!( - status_code, + status_code.as_u16(), 200 | 203 | 204 | 206 | 300 | 301 | 404 | 405 | 410 | 414 | 501 ) } @@ -214,7 +214,7 @@ fn get_response_expiry(response: &Response) -> Duration { } // Calculating Heuristic Freshness // <https://tools.ietf.org/html/rfc7234#section-4.2.2> - if let Some((ref code, _)) = response.raw_status { + if let Some(ref code) = response.status.try_code() { // <https://tools.ietf.org/html/rfc7234#section-5.5.4> // Since presently we do not generate a Warning header field with a 113 warn-code, // 24 hours minus response age is the max for heuristic calculation. @@ -318,9 +318,6 @@ fn create_cached_response( .location_url .clone_from(&cached_resource.data.location_url); response.status.clone_from(&cached_resource.data.status); - response - .raw_status - .clone_from(&cached_resource.data.raw_status); response.url_list.clone_from(&cached_resource.data.url_list); response.https_state = cached_resource.data.https_state; response.referrer = request.referrer.to_url().cloned(); @@ -357,8 +354,7 @@ fn create_resource_with_bytes_from_resource( metadata: resource.data.metadata.clone(), location_url: resource.data.location_url.clone(), https_state: resource.data.https_state, - status: Some((StatusCode::PARTIAL_CONTENT, "Partial Content".into())), - raw_status: Some((206, b"Partial Content".to_vec())), + status: StatusCode::PARTIAL_CONTENT.into(), url_list: resource.data.url_list.clone(), expires: resource.data.expires, last_validated: resource.data.last_validated, @@ -373,20 +369,12 @@ fn handle_range_request( range_spec: Vec<(Bound<u64>, Bound<u64>)>, done_chan: &mut DoneChannel, ) -> Option<CachedResponse> { - let mut complete_cached_resources = - candidates - .iter() - .filter(|resource| match resource.data.raw_status { - Some((ref code, _)) => *code == 200, - None => false, - }); - let partial_cached_resources = - candidates - .iter() - .filter(|resource| match resource.data.raw_status { - Some((ref code, _)) => *code == 206, - None => false, - }); + let mut complete_cached_resources = candidates + .iter() + .filter(|resource| resource.data.status == StatusCode::OK); + let partial_cached_resources = candidates + .iter() + .filter(|resource| resource.data.status == StatusCode::PARTIAL_CONTENT); match ( range_spec.first().unwrap(), complete_cached_resources.next(), @@ -659,9 +647,9 @@ impl HttpCache { // // TODO: Combining partial content to fulfill a non-Range request // see https://tools.ietf.org/html/rfc7234#section-3.3 - match cached_resource.data.raw_status { - Some((ref code, _)) => { - if *code == 206 { + match cached_resource.data.status.try_code() { + Some(ref code) => { + if *code == StatusCode::PARTIAL_CONTENT { continue; } }, @@ -699,7 +687,7 @@ impl HttpCache { if response.actual_response().is_network_error() { return *resource.body.lock().unwrap() == ResponseBody::Empty; } - resource.data.raw_status == response.raw_status + resource.data.status == response.status }); for cached_resource in relevant_cached_resources { @@ -735,7 +723,7 @@ impl HttpCache { response: Response, done_chan: &mut DoneChannel, ) -> Option<Response> { - assert_eq!(response.status.map(|s| s.0), Some(StatusCode::NOT_MODIFIED)); + assert_eq!(response.status, StatusCode::NOT_MODIFIED); let entry_key = CacheKey::new(request); if let Some(cached_resources) = self.entries.get_mut(&entry_key) { if let Some(cached_resource) = cached_resources.iter_mut().next() { @@ -774,8 +762,8 @@ impl HttpCache { constructed_response.referrer = request.referrer.to_url().cloned(); constructed_response.referrer_policy = request.referrer_policy; constructed_response - .raw_status - .clone_from(&cached_resource.data.raw_status); + .status + .clone_from(&cached_resource.data.status); constructed_response .url_list .clone_from(&cached_resource.data.url_list); @@ -874,7 +862,6 @@ impl HttpCache { location_url: response.location_url.clone(), https_state: response.https_state, status: response.status.clone(), - raw_status: response.raw_status.clone(), url_list: response.url_list.clone(), expires: expiry, last_validated: Instant::now(), diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index c506d7535bb..4ddba4203bb 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -35,6 +35,7 @@ use hyper_serde::Serde; use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; use log::{debug, error, info, log_enabled, warn}; +use net_traits::http_status::HttpStatus; use net_traits::pub_domains::reg_suffix; use net_traits::request::Origin::Origin as SpecificOrigin; use net_traits::request::{ @@ -381,7 +382,7 @@ fn send_response_to_devtools( devtools_chan: &Sender<DevtoolsControlMsg>, request_id: String, headers: Option<HeaderMap>, - status: Option<(u16, Vec<u8>)>, + status: HttpStatus, pipeline_id: PipelineId, ) { let response = DevtoolsHttpResponse { @@ -803,16 +804,11 @@ pub async fn http_fetch( if response .actual_response() .status - .as_ref() + .try_code() .is_some_and(is_redirect_status) { // Substep 1. - if response - .actual_response() - .status - .as_ref() - .map_or(true, |s| s.0 != StatusCode::SEE_OTHER) - { + if response.actual_response().status != StatusCode::SEE_OTHER { // TODO: send RST_STREAM frame } @@ -989,11 +985,7 @@ pub async fn http_redirect_fetch( } // Step 9 - if response - .actual_response() - .status - .as_ref() - .map_or(true, |s| s.0 != StatusCode::SEE_OTHER) && + if response.actual_response().status != StatusCode::SEE_OTHER && request.body.as_ref().is_some_and(|b| b.source_is_null()) { return Response::network_error(NetworkError::Internal("Request body is not done".into())); @@ -1008,11 +1000,11 @@ pub async fn http_redirect_fetch( if response .actual_response() .status - .as_ref() - .is_some_and(|(code, _)| { - ((*code == StatusCode::MOVED_PERMANENTLY || *code == StatusCode::FOUND) && + .try_code() + .is_some_and(|code| { + ((code == StatusCode::MOVED_PERMANENTLY || code == StatusCode::FOUND) && request.method == Method::POST) || - (*code == StatusCode::SEE_OTHER && + (code == StatusCode::SEE_OTHER && request.method != Method::HEAD && request.method != Method::GET) }) @@ -1500,20 +1492,14 @@ async fn http_network_or_cache_fetch( let forward_response = http_network_fetch(http_request, include_credentials, done_chan, context).await; // Substep 3 - if let Some((200..=399, _)) = forward_response.raw_status { - if !http_request.method.is_safe() { - if let Ok(mut http_cache) = context.state.http_cache.write() { - http_cache.invalidate(http_request, &forward_response); - } + if forward_response.status.in_range(200..=399) && !http_request.method.is_safe() { + if let Ok(mut http_cache) = context.state.http_cache.write() { + http_cache.invalidate(http_request, &forward_response); } } + // Substep 4 - if revalidating_flag && - forward_response - .status - .as_ref() - .is_some_and(|s| s.0 == StatusCode::NOT_MODIFIED) - { + if revalidating_flag && forward_response.status == StatusCode::NOT_MODIFIED { if let Ok(mut http_cache) = context.state.http_cache.write() { // Ensure done_chan is None, // since the network response will be replaced by the revalidated stored one. @@ -1616,8 +1602,8 @@ async fn http_network_or_cache_fetch( // Step 10 // FIXME: Figure out what to do with request window objects - if let (Some((StatusCode::UNAUTHORIZED, _)), false, true) = - (response.status.as_ref(), cors_flag, include_credentials) + if let (Some(StatusCode::UNAUTHORIZED), false, true) = + (response.status.try_code(), cors_flag, include_credentials) { // Substep 1 // TODO: Spec says requires testing on multiple WWW-Authenticate headers @@ -1653,7 +1639,7 @@ async fn http_network_or_cache_fetch( } // Step 11 - if let Some((StatusCode::PROXY_AUTHENTICATION_REQUIRED, _)) = response.status.as_ref() { + if response.status == StatusCode::PROXY_AUTHENTICATION_REQUIRED { // Step 1 if request_has_no_window { return Response::network_error(NetworkError::Internal( @@ -1836,15 +1822,15 @@ async fn http_network_fetch( let timing = context.timing.lock().unwrap().clone(); let mut response = Response::new(url.clone(), timing); - response.status = Some(( + response.status = HttpStatus::new( res.status(), - res.status().canonical_reason().unwrap_or("").into(), - )); + res.status() + .canonical_reason() + .unwrap_or("") + .as_bytes() + .to_vec(), + ); info!("got {:?} response for {:?}", res.status(), request.url()); - response.raw_status = Some(( - res.status().as_u16(), - res.status().canonical_reason().unwrap_or("").into(), - )); response.headers = res.headers().clone(); response.referrer = request.referrer.to_url().cloned(); response.referrer_policy = request.referrer_policy; @@ -2047,12 +2033,7 @@ async fn cors_preflight_fetch( let response = http_network_or_cache_fetch(&mut preflight, false, false, &mut None, context).await; // Step 7 - if cors_check(request, &response).is_ok() && - response - .status - .as_ref() - .is_some_and(|(status, _)| status.is_success()) - { + if cors_check(request, &response).is_ok() && response.status.code().is_success() { // Substep 1 let mut methods = if response .headers @@ -2231,9 +2212,9 @@ fn is_no_store_cache(headers: &HeaderMap) -> bool { } /// <https://fetch.spec.whatwg.org/#redirect-status> -pub fn is_redirect_status(status: &(StatusCode, String)) -> bool { +pub fn is_redirect_status(status: StatusCode) -> bool { matches!( - status.0, + status, StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND | StatusCode::SEE_OTHER | diff --git a/components/net/protocols/blob.rs b/components/net/protocols/blob.rs index c26f95bf8da..6059897c219 100644 --- a/components/net/protocols/blob.rs +++ b/components/net/protocols/blob.rs @@ -6,9 +6,10 @@ use std::future::{ready, Future}; use std::pin::Pin; use headers::{HeaderMapExt, Range}; -use http::{Method, StatusCode}; +use http::Method; use log::debug; use net_traits::blob_url_store::{parse_blob_url, BlobURLStoreError}; +use net_traits::http_status::HttpStatus; use net_traits::request::Request; use net_traits::response::{Response, ResponseBody}; use net_traits::{NetworkError, ResourceFetchTiming}; @@ -55,8 +56,7 @@ impl ProtocolHandler for BlobProtocolHander { }; let mut response = Response::new(url, ResourceFetchTiming::new(request.timing_type())); - response.status = Some((StatusCode::OK, "OK".to_string())); - response.raw_status = Some((StatusCode::OK.as_u16(), b"OK".to_vec())); + response.status = HttpStatus::default(); if is_range_request { partial_content(&mut response); diff --git a/components/net/protocols/data.rs b/components/net/protocols/data.rs index e154d8226ec..ab2baa83bff 100644 --- a/components/net/protocols/data.rs +++ b/components/net/protocols/data.rs @@ -7,7 +7,7 @@ use std::pin::Pin; use data_url::DataUrl; use headers::HeaderValue; -use http::StatusCode; +use net_traits::http_status::HttpStatus; use net_traits::request::Request; use net_traits::response::{Response, ResponseBody}; use net_traits::{NetworkError, ResourceFetchTiming}; @@ -40,8 +40,7 @@ impl ProtocolHandler for DataProtocolHander { http::header::CONTENT_TYPE, HeaderValue::from_str(&mime.to_string()).unwrap(), ); - response.status = Some((StatusCode::OK, "OK".to_string())); - response.raw_status = Some((StatusCode::OK.as_u16(), b"OK".to_vec())); + response.status = HttpStatus::default(); Some(response) }, Err(_) => None, diff --git a/components/net/protocols/mod.rs b/components/net/protocols/mod.rs index 77d7d04a26b..02b8f65614b 100644 --- a/components/net/protocols/mod.rs +++ b/components/net/protocols/mod.rs @@ -102,9 +102,7 @@ impl ProtocolRegistry { } pub fn range_not_satisfiable_error(response: &mut Response) { - let reason = "Range Not Satisfiable".to_owned(); - response.status = Some((StatusCode::RANGE_NOT_SATISFIABLE, reason.clone())); - response.raw_status = Some((StatusCode::RANGE_NOT_SATISFIABLE.as_u16(), reason.into())); + response.status = StatusCode::RANGE_NOT_SATISFIABLE.into(); } /// Get the range bounds if the `Range` header is present. @@ -132,7 +130,5 @@ pub fn get_range_request_bounds(range: Option<Range>) -> RangeRequestBounds { } pub fn partial_content(response: &mut Response) { - let reason = "Partial Content".to_owned(); - response.status = Some((StatusCode::PARTIAL_CONTENT, reason.clone())); - response.raw_status = Some((StatusCode::PARTIAL_CONTENT.as_u16(), reason.into())); + response.status = StatusCode::PARTIAL_CONTENT.into(); } diff --git a/components/net/tests/fetch.rs b/components/net/tests/fetch.rs index 9ffe6817d81..03d2826fc01 100644 --- a/components/net/tests/fetch.rs +++ b/components/net/tests/fetch.rs @@ -31,6 +31,7 @@ use net::protocols::ProtocolRegistry; use net::resource_thread::CoreResourceThreadPool; use net::test::HttpState; use net_traits::filemanager_thread::FileTokenCheck; +use net_traits::http_status::HttpStatus; use net_traits::request::{ Destination, Origin, RedirectMode, Referrer, Request, RequestBuilder, RequestMode, }; @@ -642,7 +643,7 @@ fn test_fetch_response_is_opaque_filtered() { assert!(fetch_response.url().is_none()); assert!(fetch_response.url_list.is_empty()); // this also asserts that status message is "the empty byte sequence" - assert!(fetch_response.status.is_none()); + assert!(fetch_response.status.is_error()); assert_eq!(fetch_response.headers, HeaderMap::new()); match *fetch_response.body.lock().unwrap() { ResponseBody::Empty => {}, @@ -694,7 +695,7 @@ fn test_fetch_response_is_opaque_redirect_filtered() { assert_eq!(fetch_response.response_type, ResponseType::OpaqueRedirect); // this also asserts that status message is "the empty byte sequence" - assert!(fetch_response.status.is_none()); + assert!(fetch_response.status.is_error()); assert_eq!(fetch_response.headers, HeaderMap::new()); match *fetch_response.body.lock().unwrap() { ResponseBody::Empty => {}, @@ -855,8 +856,7 @@ fn test_load_adds_host_to_hsts_list_when_url_is_https() { .internal_response .unwrap() .status - .unwrap() - .0 + .code() .is_success()); assert!(context .state @@ -922,7 +922,7 @@ fn test_fetch_self_signed() { let response = fetch_with_context(&mut request, &mut context); - assert!(response.status.unwrap().0.is_success()); + assert!(response.status.code().is_success()); let _ = server.close(); } @@ -1392,7 +1392,7 @@ fn test_fetch_with_devtools() { let httpresponse = DevtoolsHttpResponse { headers: Some(response_headers), - status: Some((200, b"OK".to_vec())), + status: HttpStatus::default(), body: None, pipeline_id: TEST_PIPELINE_ID, }; diff --git a/components/net/tests/http_cache.rs b/components/net/tests/http_cache.rs index 6d7848fad5e..9900295c891 100644 --- a/components/net/tests/http_cache.rs +++ b/components/net/tests/http_cache.rs @@ -39,7 +39,7 @@ fn test_refreshing_resource_sets_done_chan_the_appropriate_value() { // First, store the 'normal' response. cache.store(&request, &response); // Second, mutate the response into a 304 response, and refresh the stored one. - response.status = Some((StatusCode::NOT_MODIFIED, String::from("304"))); + response.status = StatusCode::NOT_MODIFIED.into(); let (send, recv) = unbounded(); let mut done_chan = Some((send, recv)); let refreshed_response = cache.refresh(&request, response.clone(), &mut done_chan); diff --git a/components/net/tests/http_loader.rs b/components/net/tests/http_loader.rs index e514eabebf3..c56618833aa 100644 --- a/components/net/tests/http_loader.rs +++ b/components/net/tests/http_loader.rs @@ -35,6 +35,7 @@ use net::cookie_storage::CookieStorage; use net::http_loader::determine_requests_referrer; use net::resource_thread::AuthCacheEntry; use net::test::replace_host_table; +use net_traits::http_status::HttpStatus; use net_traits::request::{ BodyChunkRequest, BodyChunkResponse, BodySource, CredentialsMode, Destination, Referrer, RequestBody, RequestBuilder, @@ -161,8 +162,7 @@ fn test_check_default_headers_loaded_in_every_request() { .internal_response .unwrap() .status - .unwrap() - .0 + .code() .is_success()); // Testing for method.POST @@ -188,8 +188,7 @@ fn test_check_default_headers_loaded_in_every_request() { .internal_response .unwrap() .status - .unwrap() - .0 + .code() .is_success()); let _ = server.close(); @@ -219,8 +218,7 @@ fn test_load_when_request_is_not_get_or_head_and_there_is_no_body_content_length .internal_response .unwrap() .status - .unwrap() - .0 + .code() .is_success()); let _ = server.close(); @@ -253,8 +251,7 @@ fn test_request_and_response_data_with_network_messages() { .internal_response .unwrap() .status - .unwrap() - .0 + .code() .is_success()); let _ = server.close(); @@ -312,7 +309,7 @@ fn test_request_and_response_data_with_network_messages() { let httpresponse = DevtoolsHttpResponse { headers: Some(response_headers), - status: Some((200, b"OK".to_vec())), + status: HttpStatus::default(), body: None, pipeline_id: TEST_PIPELINE_ID, }; @@ -341,13 +338,7 @@ fn test_request_and_response_message_from_devtool_without_pipeline_id() { let (devtools_chan, devtools_port) = unbounded(); let response = fetch(&mut request, Some(devtools_chan)); - assert!(response - .actual_response() - .status - .as_ref() - .unwrap() - .0 - .is_success()); + assert!(response.actual_response().status.code().is_success()); let _ = server.close(); @@ -393,7 +384,7 @@ fn test_redirected_request_to_devtools() { assert_eq!(devhttprequest.url, pre_url); assert_eq!( devhttpresponse.status, - Some((301, b"Moved Permanently".to_vec())) + HttpStatus::from(StatusCode::MOVED_PERMANENTLY) ); let devhttprequest = expect_devtools_http_request(&devtools_port); @@ -401,7 +392,7 @@ fn test_redirected_request_to_devtools() { assert_eq!(devhttprequest.method, Method::GET); assert_eq!(devhttprequest.url, post_url); - assert_eq!(devhttpresponse.status, Some((200, b"OK".to_vec()))); + assert_eq!(devhttpresponse.status, HttpStatus::default()); } #[test] @@ -435,7 +426,7 @@ fn test_load_when_redirecting_from_a_post_should_rewrite_next_request_as_get() { let _ = pre_server.close(); let _ = post_server.close(); - assert!(response.to_actual().status.unwrap().0.is_success()); + assert!(response.to_actual().status.code().is_success()); } #[test] @@ -466,7 +457,7 @@ fn test_load_should_decode_the_response_as_deflate_when_response_headers_have_co let _ = server.close(); let internal_response = response.internal_response.unwrap(); - assert!(internal_response.status.clone().unwrap().0.is_success()); + assert!(internal_response.status.clone().code().is_success()); assert_eq!( *internal_response.body.lock().unwrap(), ResponseBody::Done(b"Yay!".to_vec()) @@ -499,7 +490,7 @@ fn test_load_should_decode_the_response_as_gzip_when_response_headers_have_conte let _ = server.close(); let internal_response = response.internal_response.unwrap(); - assert!(internal_response.status.clone().unwrap().0.is_success()); + assert!(internal_response.status.clone().code().is_success()); assert_eq!( *internal_response.body.lock().unwrap(), ResponseBody::Done(b"Yay!".to_vec()) @@ -544,7 +535,7 @@ fn test_load_doesnt_send_request_body_on_any_redirect() { let _ = pre_server.close(); let _ = post_server.close(); - assert!(response.to_actual().status.unwrap().0.is_success()); + assert!(response.to_actual().status.code().is_success()); } #[test] @@ -576,8 +567,7 @@ fn test_load_doesnt_add_host_to_hsts_list_when_url_is_http_even_if_hsts_headers_ .internal_response .unwrap() .status - .unwrap() - .0 + .code() .is_success()); assert_eq!( context @@ -622,8 +612,7 @@ fn test_load_sets_cookies_in_the_resource_manager_when_it_get_set_cookie_header_ .internal_response .unwrap() .status - .unwrap() - .0 + .code() .is_success()); assert_cookie_for_domain( @@ -674,8 +663,7 @@ fn test_load_sets_requests_cookies_header_for_url_by_getting_cookies_from_the_re .internal_response .unwrap() .status - .unwrap() - .0 + .code() .is_success()); } @@ -720,8 +708,7 @@ fn test_load_sends_cookie_if_nonhttp() { .internal_response .unwrap() .status - .unwrap() - .0 + .code() .is_success()); } @@ -758,8 +745,7 @@ fn test_cookie_set_with_httponly_should_not_be_available_using_getcookiesforurl( .internal_response .unwrap() .status - .unwrap() - .0 + .code() .is_success()); assert_cookie_for_domain( @@ -802,13 +788,7 @@ fn test_when_cookie_received_marked_secure_is_ignored_for_http() { let _ = server.close(); - assert!(response - .actual_response() - .status - .as_ref() - .unwrap() - .0 - .is_success()); + assert!(response.actual_response().status.code().is_success()); assert_cookie_for_domain(&context.state.cookie_jar, url.as_str(), None); } @@ -844,8 +824,7 @@ fn test_load_sets_content_length_to_length_of_request_body() { .internal_response .unwrap() .status - .unwrap() - .0 + .code() .is_success()); } @@ -883,8 +862,7 @@ fn test_load_uses_explicit_accept_from_headers_in_load_data() { .internal_response .unwrap() .status - .unwrap() - .0 + .code() .is_success()); } @@ -919,8 +897,7 @@ fn test_load_sets_default_accept_to_html_xhtml_xml_and_then_anything_else() { .internal_response .unwrap() .status - .unwrap() - .0 + .code() .is_success()); } @@ -958,8 +935,7 @@ fn test_load_uses_explicit_accept_encoding_from_load_data_headers() { .internal_response .unwrap() .status - .unwrap() - .0 + .code() .is_success()); } @@ -994,8 +970,7 @@ fn test_load_sets_default_accept_encoding_to_gzip_and_deflate() { .internal_response .unwrap() .status - .unwrap() - .0 + .code() .is_success()); } @@ -1140,7 +1115,7 @@ fn test_load_follows_a_redirect() { let _ = post_server.close(); let internal_response = response.internal_response.unwrap(); - assert!(internal_response.status.clone().unwrap().0.is_success()); + assert!(internal_response.status.clone().code().is_success()); assert_eq!( *internal_response.body.lock().unwrap(), ResponseBody::Done(b"Yay!".to_vec()) @@ -1223,7 +1198,7 @@ fn test_redirect_from_x_to_y_provides_y_cookies_from_y() { let _ = server.close(); let internal_response = response.internal_response.unwrap(); - assert!(internal_response.status.clone().unwrap().0.is_success()); + assert!(internal_response.status.clone().code().is_success()); assert_eq!( *internal_response.body.lock().unwrap(), ResponseBody::Done(b"Yay!".to_vec()) @@ -1272,7 +1247,7 @@ fn test_redirect_from_x_to_x_provides_x_with_cookie_from_first_response() { let _ = server.close(); let internal_response = response.internal_response.unwrap(); - assert!(internal_response.status.clone().unwrap().0.is_success()); + assert!(internal_response.status.clone().code().is_success()); assert_eq!( *internal_response.body.lock().unwrap(), ResponseBody::Done(b"Yay!".to_vec()) @@ -1322,8 +1297,7 @@ fn test_if_auth_creds_not_in_url_but_in_cache_it_sets_it() { .internal_response .unwrap() .status - .unwrap() - .0 + .code() .is_success()); } @@ -1348,7 +1322,7 @@ fn test_auth_ui_needs_www_auth() { let _ = server.close(); assert_eq!( - response.internal_response.unwrap().status.unwrap().0, + response.internal_response.unwrap().status, StatusCode::UNAUTHORIZED ); } diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index dfe7bf39fac..bf98d5f5b1d 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -21,6 +21,7 @@ use ipc_channel::router::ROUTER; use js::jsapi::JSAutoRealm; use js::rust::HandleObject; use mime::{self, Mime}; +use net_traits::http_status::HttpStatus; use net_traits::image_cache::{ ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponse, PendingImageId, PendingImageResponse, UsePlaceholder, @@ -240,20 +241,24 @@ impl FetchResponseListener for ImageContext { } } - let status_code = metadata + let status = metadata .as_ref() - .and_then(|m| m.status.as_ref().map(|&(code, _)| code)) - .unwrap_or(0); - - self.status = match status_code { - 0 => Err(NetworkError::Internal( - "No http status code received".to_owned(), - )), - 200..=299 => Ok(()), // HTTP ok status codes - _ => Err(NetworkError::Internal(format!( - "HTTP error code {}", - status_code - ))), + .map(|m| m.status.clone()) + .unwrap_or_else(HttpStatus::new_error); + + self.status = { + if status.is_error() { + Err(NetworkError::Internal( + "No http status code received".to_owned(), + )) + } else if status.is_success() { + Ok(()) + } else { + Err(NetworkError::Internal(format!( + "HTTP error code {}", + status.code() + ))) + } }; } diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index 39afbb7a23d..3e57b4e2645 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -16,6 +16,7 @@ use euclid::default::Size2D; use headers::{ContentLength, ContentRange, HeaderMapExt}; use html5ever::{local_name, namespace_url, ns, LocalName, Prefix}; use http::header::{self, HeaderMap, HeaderValue}; +use http::StatusCode; use ipc_channel::ipc; use ipc_channel::router::ROUTER; use js::jsapi::JSAutoRealm; @@ -2731,13 +2732,14 @@ impl FetchResponseListener for HTMLMediaElementFetchListener { } } - let (status_is_ok, is_seekable) = self - .metadata - .as_ref() - .and_then(|m| m.status.as_ref()) - .map_or((true, false), |s| { - (s.0 >= 200 && s.0 < 300, s.0 == 206 || s.0 == 416) - }); + let (status_is_ok, is_seekable) = self.metadata.as_ref().map_or((true, false), |s| { + let status = &s.status; + ( + status.is_success(), + *status == StatusCode::PARTIAL_CONTENT || + *status == StatusCode::RANGE_NOT_SATISFIABLE, + ) + }); if is_seekable { // The server supports range requests, diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index e3120d0e570..87b8d8a384d 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -21,6 +21,7 @@ use ipc_channel::ipc; use ipc_channel::router::ROUTER; use js::jsval::UndefinedValue; use js::rust::{transform_str_to_source_text, CompileOptionsWrapper, HandleObject, Stencil}; +use net_traits::http_status::HttpStatus; use net_traits::request::{ CorsSettings, CredentialsMode, Destination, ParserMetadata, RequestBuilder, }; @@ -360,24 +361,25 @@ impl FetchResponseListener for ClassicContext { FetchMetadata::Filtered { unsafe_, .. } => unsafe_, }); - let status_code = self + let status = self .metadata .as_ref() - .and_then(|m| match m.status { - Some((c, _)) => Some(c), - _ => None, - }) - .unwrap_or(0); - - self.status = match status_code { - 0 => Err(NetworkError::Internal( - "No http status code received".to_owned(), - )), - 200..=299 => Ok(()), // HTTP ok status codes - _ => Err(NetworkError::Internal(format!( - "HTTP error code {}", - status_code - ))), + .map(|m| m.status.clone()) + .unwrap_or_else(HttpStatus::new_error); + + self.status = { + if status.is_error() { + Err(NetworkError::Internal( + "No http status code received".to_owned(), + )) + } else if status.is_success() { + Ok(()) + } else { + Err(NetworkError::Internal(format!( + "HTTP error code {}", + status.code() + ))) + } }; } diff --git a/components/script/dom/htmlvideoelement.rs b/components/script/dom/htmlvideoelement.rs index 61ee1a75e2e..3e6d609f07f 100644 --- a/components/script/dom/htmlvideoelement.rs +++ b/components/script/dom/htmlvideoelement.rs @@ -340,8 +340,7 @@ impl FetchResponseListener for PosterFrameFetchContext { let status_is_ok = metadata .as_ref() - .and_then(|m| m.status.as_ref()) - .map_or(true, |s| s.0 >= 200 && s.0 < 300); + .map_or(true, |m| m.status.in_range(200..300)); if !status_is_ok { self.cancelled = true; diff --git a/components/script/dom/response.rs b/components/script/dom/response.rs index 1b9646621d9..871248c0879 100644 --- a/components/script/dom/response.rs +++ b/components/script/dom/response.rs @@ -8,10 +8,10 @@ use std::str::FromStr; use dom_struct::dom_struct; use http::header::HeaderMap as HyperHeaders; -use http::StatusCode; use hyper_serde::Serde; use js::jsapi::JSObject; use js::rust::HandleObject; +use net_traits::http_status::HttpStatus; use servo_url::ServoUrl; use url::Position; @@ -37,11 +37,8 @@ use crate::script_runtime::{CanGc, JSContext as SafeJSContext, StreamConsumer}; pub struct Response { reflector_: Reflector, headers_reflector: MutNullableDom<Headers>, - /// `None` can be considered a StatusCode of `0`. - #[ignore_malloc_size_of = "Defined in hyper"] #[no_trace] - status: DomRefCell<Option<StatusCode>>, - raw_status: DomRefCell<Option<(u16, Vec<u8>)>>, + status: DomRefCell<HttpStatus>, response_type: DomRefCell<DOMResponseType>, #[no_trace] url: DomRefCell<Option<ServoUrl>>, @@ -64,8 +61,7 @@ impl Response { Response { reflector_: Reflector::new(), headers_reflector: Default::default(), - status: DomRefCell::new(Some(StatusCode::OK)), - raw_status: DomRefCell::new(Some((200, b"".to_vec()))), + status: DomRefCell::new(HttpStatus::default()), response_type: DomRefCell::new(DOMResponseType::Default), url: DomRefCell::new(None), url_list: DomRefCell::new(vec![]), @@ -119,11 +115,8 @@ impl Response { let r = Response::new_with_proto(global, proto, can_gc); - // Step 3 - *r.status.borrow_mut() = Some(StatusCode::from_u16(init.status).unwrap()); - - // Step 4 - *r.raw_status.borrow_mut() = Some((init.status, init.statusText.clone().into())); + // Step 3 & 4 + *r.status.borrow_mut() = HttpStatus::new_raw(init.status, init.statusText.clone().into()); // Step 5 if let Some(ref headers_member) = init.headers { @@ -177,7 +170,7 @@ impl Response { let r = Response::new(global); *r.response_type.borrow_mut() = DOMResponseType::Error; r.Headers().set_guard(Guard::Immutable); - *r.raw_status.borrow_mut() = Some((0, b"".to_vec())); + *r.status.borrow_mut() = HttpStatus::new_error(); r } @@ -207,8 +200,7 @@ impl Response { let r = Response::new(global); // Step 5 - *r.status.borrow_mut() = Some(StatusCode::from_u16(status).unwrap()); - *r.raw_status.borrow_mut() = Some((status, b"".to_vec())); + *r.status.borrow_mut() = HttpStatus::new_raw(status, vec![]); // Step 6 let url_bytestring = @@ -298,29 +290,17 @@ impl ResponseMethods for Response { // https://fetch.spec.whatwg.org/#dom-response-status fn Status(&self) -> u16 { - match *self.raw_status.borrow() { - Some((s, _)) => s, - None => 0, - } + self.status.borrow().raw_code() } // https://fetch.spec.whatwg.org/#dom-response-ok fn Ok(&self) -> bool { - match *self.status.borrow() { - Some(s) => { - let status_num = s.as_u16(); - (200..=299).contains(&status_num) - }, - None => false, - } + self.status.borrow().is_success() } // https://fetch.spec.whatwg.org/#dom-response-statustext fn StatusText(&self) -> ByteString { - match *self.raw_status.borrow() { - Some((_, ref st)) => ByteString::new(st.clone()), - None => ByteString::new(b"".to_vec()), - } + ByteString::new(self.status.borrow().message().to_vec()) } // https://fetch.spec.whatwg.org/#dom-response-headers @@ -345,11 +325,10 @@ impl ResponseMethods for Response { // Instead of storing a net_traits::Response internally, we // only store the relevant fields, and only clone them here *new_response.response_type.borrow_mut() = *self.response_type.borrow(); - *new_response.status.borrow_mut() = *self.status.borrow(); new_response - .raw_status + .status .borrow_mut() - .clone_from(&self.raw_status.borrow()); + .clone_from(&self.status.borrow()); new_response.url.borrow_mut().clone_from(&self.url.borrow()); new_response .url_list @@ -420,8 +399,8 @@ impl Response { }); } - pub fn set_raw_status(&self, status: Option<(u16, Vec<u8>)>) { - *self.raw_status.borrow_mut() = status; + pub fn set_status(&self, status: &HttpStatus) { + self.status.borrow_mut().clone_from(status); } pub fn set_final_url(&self, final_url: ServoUrl) { @@ -435,20 +414,17 @@ impl Response { fn set_response_members_by_type(&self, response_type: DOMResponseType) { match response_type { DOMResponseType::Error => { - *self.status.borrow_mut() = None; - self.set_raw_status(None); + *self.status.borrow_mut() = HttpStatus::new_error(); self.set_headers(None); }, DOMResponseType::Opaque => { *self.url_list.borrow_mut() = vec![]; - *self.status.borrow_mut() = None; - self.set_raw_status(None); + *self.status.borrow_mut() = HttpStatus::new_error(); self.set_headers(None); self.body_stream.set(None); }, DOMResponseType::Opaqueredirect => { - *self.status.borrow_mut() = None; - self.set_raw_status(None); + *self.status.borrow_mut() = HttpStatus::new_error(); self.set_headers(None); self.body_stream.set(None); }, diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index 26e47e4d23b..59a207d8ab6 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -26,6 +26,7 @@ use js::rust::wrappers::JS_ParseJSON; use js::rust::HandleObject; use js::typedarray::{ArrayBuffer, ArrayBufferU8}; use mime::{self, Mime, Name}; +use net_traits::http_status::HttpStatus; use net_traits::request::{CredentialsMode, Destination, Referrer, RequestBuilder, RequestMode}; use net_traits::CoreResourceMsg::Fetch; use net_traits::{ @@ -101,7 +102,7 @@ struct XHRContext { #[derive(Clone)] pub enum XHRProgress { /// Notify that headers have been received - HeadersReceived(GenerationId, Option<HeaderMap>, Option<(u16, Vec<u8>)>), + HeadersReceived(GenerationId, Option<HeaderMap>, HttpStatus), /// Partial progress (after receiving headers), containing portion of the response Loading(GenerationId, Vec<u8>), /// Loading is done @@ -129,8 +130,8 @@ pub struct XMLHttpRequest { with_credentials: Cell<bool>, upload: Dom<XMLHttpRequestUpload>, response_url: DomRefCell<String>, - status: Cell<u16>, - status_text: DomRefCell<ByteString>, + #[no_trace] + status: DomRefCell<HttpStatus>, response: DomRefCell<Vec<u8>>, response_type: Cell<XMLHttpRequestResponseType>, response_xml: MutNullableDom<Document>, @@ -189,8 +190,7 @@ impl XMLHttpRequest { with_credentials: Cell::new(false), upload: Dom::from_ref(&*XMLHttpRequestUpload::new(global)), response_url: DomRefCell::new(String::new()), - status: Cell::new(0), - status_text: DomRefCell::new(ByteString::new(vec![])), + status: DomRefCell::new(HttpStatus::new_error()), response: DomRefCell::new(vec![]), response_type: Cell::new(XMLHttpRequestResponseType::_empty), response_xml: Default::default(), @@ -442,8 +442,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest { *self.request_headers.borrow_mut() = HeaderMap::new(); self.send_flag.set(false); self.upload_listener.set(false); - *self.status_text.borrow_mut() = ByteString::new(vec![]); - self.status.set(0); + *self.status.borrow_mut() = HttpStatus::new_error(); // Step 13 if self.ready_state.get() != XMLHttpRequestState::Opened { @@ -835,12 +834,12 @@ impl XMLHttpRequestMethods for XMLHttpRequest { /// <https://xhr.spec.whatwg.org/#the-status-attribute> fn Status(&self) -> u16 { - self.status.get() + self.status.borrow().raw_code() } /// <https://xhr.spec.whatwg.org/#the-statustext-attribute> fn StatusText(&self) -> ByteString { - self.status_text.borrow().clone() + ByteString::new(self.status.borrow().message().to_vec()) } /// <https://xhr.spec.whatwg.org/#the-getresponseheader()-method> @@ -1132,9 +1131,8 @@ impl XMLHttpRequest { // Part of step 13, send() (processing response) // XXXManishearth handle errors, if any (substep 1) // Substep 2 - if let Some((code, reason)) = status { - self.status.set(code); - *self.status_text.borrow_mut() = ByteString::new(reason); + if !status.is_error() { + *self.status.borrow_mut() = status.clone(); } if let Some(h) = headers.as_ref() { *self.response_headers.borrow_mut() = h.clone(); diff --git a/components/script/fetch.rs b/components/script/fetch.rs index 91935b75a6c..675b6973dca 100644 --- a/components/script/fetch.rs +++ b/components/script/fetch.rs @@ -310,7 +310,7 @@ impl ResourceTimingListener for FetchContext { fn fill_headers_with_metadata(r: DomRoot<Response>, m: Metadata) { r.set_headers(m.headers); - r.set_raw_status(m.status); + r.set_status(&m.status); r.set_final_url(m.final_url); r.set_redirected(m.redirected); } diff --git a/components/script/script_module.rs b/components/script/script_module.rs index 6dfb0a64ac0..9ab3a38589e 100644 --- a/components/script/script_module.rs +++ b/components/script/script_module.rs @@ -34,6 +34,7 @@ use js::rust::{ transform_str_to_source_text, CompileOptionsWrapper, Handle, HandleValue, IntoHandle, }; use mime::Mime; +use net_traits::http_status::HttpStatus; use net_traits::request::{ CredentialsMode, Destination, ParserMetadata, Referrer, RequestBuilder, RequestMode, }; @@ -1055,24 +1056,25 @@ impl FetchResponseListener for ModuleContext { FetchMetadata::Filtered { unsafe_, .. } => unsafe_, }); - let status_code = self + let status = self .metadata .as_ref() - .and_then(|m| match m.status { - Some((c, _)) => Some(c), - _ => None, - }) - .unwrap_or(0); - - self.status = match status_code { - 0 => Err(NetworkError::Internal( - "No http status code received".to_owned(), - )), - 200..=299 => Ok(()), // HTTP ok status codes - _ => Err(NetworkError::Internal(format!( - "HTTP error code {}", - status_code - ))), + .map(|m| m.status.clone()) + .unwrap_or_else(HttpStatus::new_error); + + self.status = { + if status.is_error() { + Err(NetworkError::Internal( + "No http status code received".to_owned(), + )) + } else if status.is_success() { + Ok(()) + } else { + Err(NetworkError::Internal(format!( + "HTTP error code {}", + status.code() + ))) + } }; } diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 21145934c71..6da64d32786 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -3172,11 +3172,12 @@ impl ScriptThread { Some(idx) => { // https://html.spec.whatwg.org/multipage/#process-a-navigate-response // 2. If response's status is 204 or 205, then abort these steps. - if let Some(Metadata { - status: Some((204..=205, _)), - .. - }) = metadata - { + let is20x = match metadata { + Some(ref metadata) => metadata.status.in_range(204..=205), + _ => false, + }; + + if is20x { // If we have an existing window that is being navigated: if let Some(window) = self.documents.borrow().find_window(*id) { let window_proxy = window.window_proxy(); @@ -3767,8 +3768,6 @@ impl ScriptThread { .and_then(|h| h.typed_get::<ReferrerPolicyHeader>()) .map(ReferrerPolicy::from); - let status_code = metadata.status.map(|status| status.0).unwrap_or(200); - let document = Document::new( &window, HasBrowsingContext::Yes, @@ -3782,7 +3781,7 @@ impl ScriptThread { loader, referrer, referrer_policy, - Some(status_code), + Some(metadata.status.raw_code()), incomplete.canceller, can_gc, ); @@ -4146,7 +4145,7 @@ impl ScriptThread { let chunk = match js_eval_result { Some(JsEvalResult::Ok(content)) => content, Some(JsEvalResult::NoContent) => { - meta.status = Some((204, b"No Content".to_vec())); + meta.status = http::StatusCode::NO_CONTENT.into(); vec![] }, None => vec![], diff --git a/components/script/stylesheet_loader.rs b/components/script/stylesheet_loader.rs index aa876bf3c15..6b418b9a1e6 100644 --- a/components/script/stylesheet_loader.rs +++ b/components/script/stylesheet_loader.rs @@ -201,7 +201,7 @@ impl FetchResponseListener for StylesheetContext { // FIXME: Revisit once consensus is reached at: // https://github.com/whatwg/html/issues/1142 - successful = metadata.status.is_some_and(|(code, _)| code == 200); + successful = metadata.status == http::StatusCode::OK; } let owner = elem diff --git a/components/shared/devtools/Cargo.toml b/components/shared/devtools/Cargo.toml index 2e60c0fef94..5309d1cda67 100644 --- a/components/shared/devtools/Cargo.toml +++ b/components/shared/devtools/Cargo.toml @@ -18,6 +18,7 @@ http = { workspace = true } ipc-channel = { workspace = true } malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } +net_traits = { workspace = true } serde = { workspace = true } servo_url = { path = "../../url" } uuid = { workspace = true, features = ["serde"] } diff --git a/components/shared/devtools/lib.rs b/components/shared/devtools/lib.rs index 6e196cbac52..4931cc9f279 100644 --- a/components/shared/devtools/lib.rs +++ b/components/shared/devtools/lib.rs @@ -20,6 +20,7 @@ use bitflags::bitflags; use http::{HeaderMap, Method}; use ipc_channel::ipc::IpcSender; use malloc_size_of_derive::MallocSizeOf; +use net_traits::http_status::HttpStatus; use serde::{Deserialize, Serialize}; use servo_url::ServoUrl; use uuid::Uuid; @@ -350,7 +351,7 @@ pub struct HttpRequest { #[derive(Debug, PartialEq)] pub struct HttpResponse { pub headers: Option<HeaderMap>, - pub status: Option<(u16, Vec<u8>)>, + pub status: HttpStatus, pub body: Option<Vec<u8>>, pub pipeline_id: PipelineId, } diff --git a/components/shared/net/http_status.rs b/components/shared/net/http_status.rs new file mode 100644 index 00000000000..cebe9ef8a23 --- /dev/null +++ b/components/shared/net/http_status.rs @@ -0,0 +1,117 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#![deny(unsafe_code)] + +use std::ops::RangeBounds; + +use serde::{Deserialize, Serialize}; + +use crate::{MallocSizeOf, StatusCode}; + +/// A representation of a HTTP Status Code and Message that can be used for +/// DOM Response objects and other cases. +/// These objects are immutable once created. +#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub struct HttpStatus { + code: u16, + message: Vec<u8>, +} + +impl HttpStatus { + /// Creates a new HttpStatus for a valid status code. + pub fn new(code: StatusCode, message: Vec<u8>) -> Self { + Self { + code: code.as_u16(), + message, + } + } + + /// Creates a new HttpStatus from a raw status code, but will panic + /// if the code is not in the 100 to 599 valid range. + pub fn new_raw(code: u16, message: Vec<u8>) -> Self { + if !(100..=599).contains(&code) { + panic!( + "HttpStatus code must be in the range 100 to 599, inclusive, but is {}", + code + ); + } + + Self { code, message } + } + + /// Creates an instance that represents a Response.error() instance. + pub fn new_error() -> Self { + Self { + code: 0, + message: vec![], + } + } + + /// Returns the StatusCode for non-error cases, panics otherwise. + pub fn code(&self) -> StatusCode { + StatusCode::from_u16(self.code).expect("HttpStatus code is 0, can't return a StatusCode") + } + + /// Returns the StatusCode if not an error instance, or None otherwise. + pub fn try_code(&self) -> Option<StatusCode> { + StatusCode::from_u16(self.code).ok() + } + + /// Returns the u16 representation of the access code. This is usable both for + /// valid HTTP status codes and in the error case. + pub fn raw_code(&self) -> u16 { + self.code + } + + /// Get access to a reference of the message part. + pub fn message(&self) -> &[u8] { + &self.message + } + + /// Helper that relays is_success() from the underlying code. + pub fn is_success(&self) -> bool { + StatusCode::from_u16(self.code).map_or(false, |s| s.is_success()) + } + + /// True when the object was created with `new_error`. + pub fn is_error(&self) -> bool { + self.code == 0 + } + + /// Returns true if this status is in the given range. + /// Always return false for error statuses. + pub fn in_range<T: RangeBounds<u16>>(&self, range: T) -> bool { + self.code != 0 && range.contains(&self.code) + } +} + +impl Default for HttpStatus { + /// The default implementation creates a "200 OK" response. + fn default() -> Self { + Self { + code: 200, + message: b"OK".to_vec(), + } + } +} + +impl PartialEq<StatusCode> for HttpStatus { + fn eq(&self, other: &StatusCode) -> bool { + self.code == other.as_u16() + } +} + +impl From<StatusCode> for HttpStatus { + fn from(code: StatusCode) -> Self { + Self { + code: code.as_u16(), + message: code + .canonical_reason() + .unwrap_or_default() + .as_bytes() + .to_vec(), + } + } +} diff --git a/components/shared/net/lib.rs b/components/shared/net/lib.rs index 81ad50b517d..03c7adc08e2 100644 --- a/components/shared/net/lib.rs +++ b/components/shared/net/lib.rs @@ -26,12 +26,14 @@ use servo_rand::RngCore; use servo_url::{ImmutableOrigin, ServoUrl}; use crate::filemanager_thread::FileManagerThreadMsg; +use crate::http_status::HttpStatus; use crate::request::{Request, RequestBuilder}; use crate::response::{HttpsState, Response, ResponseInit}; use crate::storage_thread::StorageThreadMsg; pub mod blob_url_store; pub mod filemanager_thread; +pub mod http_status; pub mod image_cache; pub mod pub_domains; pub mod quality; @@ -658,7 +660,7 @@ pub struct Metadata { pub headers: Option<Serde<HeaderMap>>, /// HTTP Status - pub status: Option<(u16, Vec<u8>)>, + pub status: HttpStatus, /// Is successful HTTPS connection pub https_state: HttpsState, @@ -683,8 +685,7 @@ impl Metadata { content_type: None, charset: None, headers: None, - // https://fetch.spec.whatwg.org/#concept-response-status-message - status: Some((200, b"".to_vec())), + status: HttpStatus::default(), https_state: HttpsState::None, referrer: None, referrer_policy: None, diff --git a/components/shared/net/response.rs b/components/shared/net/response.rs index c4b009db72a..5e80b0cabea 100644 --- a/components/shared/net/response.rs +++ b/components/shared/net/response.rs @@ -8,13 +8,14 @@ use std::sync::atomic::AtomicBool; use std::sync::Mutex; use headers::{ContentType, HeaderMapExt}; -use http::{HeaderMap, StatusCode}; +use http::HeaderMap; use hyper_serde::Serde; use malloc_size_of_derive::MallocSizeOf; use serde::{Deserialize, Serialize}; use servo_arc::Arc; use servo_url::ServoUrl; +use crate::http_status::HttpStatus; use crate::{ FetchMetadata, FilteredMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming, ResourceTimingType, @@ -95,10 +96,7 @@ pub struct Response { pub termination_reason: Option<TerminationReason>, url: Option<ServoUrl>, pub url_list: Vec<ServoUrl>, - /// `None` can be considered a StatusCode of `0`. - #[ignore_malloc_size_of = "Defined in hyper"] - pub status: Option<(StatusCode, String)>, - pub raw_status: Option<(u16, Vec<u8>)>, + pub status: HttpStatus, #[ignore_malloc_size_of = "Defined in hyper"] pub headers: HeaderMap, #[ignore_malloc_size_of = "Mutex heap size undefined"] @@ -131,8 +129,7 @@ impl Response { termination_reason: None, url: Some(url), url_list: vec![], - status: Some((StatusCode::OK, "".to_string())), - raw_status: Some((200, b"".to_vec())), + status: HttpStatus::default(), headers: HeaderMap::new(), body: Arc::new(Mutex::new(ResponseBody::Empty)), cache_state: CacheState::None, @@ -153,9 +150,7 @@ impl Response { res.location_url = init.location_url; res.headers = init.headers; res.referrer = init.referrer; - res.status = StatusCode::from_u16(init.status_code) - .map(|s| (s, s.to_string())) - .ok(); + res.status = HttpStatus::new_raw(init.status_code, vec![]); res } @@ -165,8 +160,7 @@ impl Response { termination_reason: None, url: None, url_list: vec![], - status: None, - raw_status: None, + status: HttpStatus::new_error(), headers: HeaderMap::new(), body: Arc::new(Mutex::new(ResponseBody::Empty)), cache_state: CacheState::None, @@ -282,14 +276,14 @@ impl Response { response.url_list = vec![]; response.url = None; response.headers = HeaderMap::new(); - response.status = None; + response.status = HttpStatus::new_error(); response.body = Arc::new(Mutex::new(ResponseBody::Empty)); response.cache_state = CacheState::None; }, ResponseType::OpaqueRedirect => { response.headers = HeaderMap::new(); - response.status = None; + response.status = HttpStatus::new_error(); response.body = Arc::new(Mutex::new(ResponseBody::Empty)); response.cache_state = CacheState::None; }, @@ -310,7 +304,7 @@ impl Response { ); metadata.location_url.clone_from(&response.location_url); metadata.headers = Some(Serde(response.headers.clone())); - metadata.status.clone_from(&response.raw_status); + metadata.status.clone_from(&response.status); metadata.https_state = response.https_state; metadata.referrer.clone_from(&response.referrer); metadata.referrer_policy = response.referrer_policy; diff --git a/ports/servoshell/desktop/protocols/urlinfo.rs b/ports/servoshell/desktop/protocols/urlinfo.rs index 7671b27a4e8..a6efe5e57c4 100644 --- a/ports/servoshell/desktop/protocols/urlinfo.rs +++ b/ports/servoshell/desktop/protocols/urlinfo.rs @@ -6,9 +6,9 @@ use std::future::Future; use std::pin::Pin; use headers::{ContentType, HeaderMapExt}; -use http::StatusCode; use net::fetch::methods::{DoneChannel, FetchContext}; use net::protocols::ProtocolHandler; +use net_traits::http_status::HttpStatus; use net_traits::request::Request; use net_traits::response::{Response, ResponseBody}; use net_traits::ResourceFetchTiming; @@ -38,8 +38,7 @@ impl ProtocolHandler for UrlInfoProtocolHander { let mut response = Response::new(url, ResourceFetchTiming::new(request.timing_type())); *response.body.lock().unwrap() = ResponseBody::Done(content.as_bytes().to_vec()); response.headers.typed_insert(ContentType::text()); - response.status = Some((StatusCode::OK, "OK".to_string())); - response.raw_status = Some((StatusCode::OK.as_u16(), b"OK".to_vec())); + response.status = HttpStatus::default(); Box::pin(std::future::ready(response)) } diff --git a/tests/wpt/meta/wasm/webapi/status.any.js.ini b/tests/wpt/meta/wasm/webapi/status.any.js.ini index 854743f2e34..c0114e45a39 100644 --- a/tests/wpt/meta/wasm/webapi/status.any.js.ini +++ b/tests/wpt/meta/wasm/webapi/status.any.js.ini @@ -1,92 +1,5 @@ -[status.any.html] - [Response with status 300: compileStreaming] - expected: FAIL - - [Response with status 400: compileStreaming] - expected: FAIL - - [Response with status 404: compileStreaming] - expected: FAIL - - [Response with status 500: compileStreaming] - expected: FAIL - - [Response with status 600: compileStreaming] - expected: FAIL - - [Response with status 700: compileStreaming] - expected: FAIL - - [Response with status 999: compileStreaming] - expected: FAIL - - [Response with status 300: instantiateStreaming] - expected: FAIL - - [Response with status 400: instantiateStreaming] - expected: FAIL - - [Response with status 404: instantiateStreaming] - expected: FAIL - - [Response with status 500: instantiateStreaming] - expected: FAIL - - [Response with status 600: instantiateStreaming] - expected: FAIL - - [Response with status 700: instantiateStreaming] - expected: FAIL - - [Response with status 999: instantiateStreaming] - expected: FAIL - - [status.any.sharedworker.html] expected: ERROR [status.any.serviceworker.html] expected: ERROR - -[status.any.worker.html] - [Response with status 300: compileStreaming] - expected: FAIL - - [Response with status 400: compileStreaming] - expected: FAIL - - [Response with status 404: compileStreaming] - expected: FAIL - - [Response with status 500: compileStreaming] - expected: FAIL - - [Response with status 600: compileStreaming] - expected: FAIL - - [Response with status 700: compileStreaming] - expected: FAIL - - [Response with status 999: compileStreaming] - expected: FAIL - - [Response with status 300: instantiateStreaming] - expected: FAIL - - [Response with status 400: instantiateStreaming] - expected: FAIL - - [Response with status 404: instantiateStreaming] - expected: FAIL - - [Response with status 500: instantiateStreaming] - expected: FAIL - - [Response with status 600: instantiateStreaming] - expected: FAIL - - [Response with status 700: instantiateStreaming] - expected: FAIL - - [Response with status 999: instantiateStreaming] - expected: FAIL |