aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwebbeef <me@webbeef.org>2024-09-29 11:23:48 -0700
committerGitHub <noreply@github.com>2024-09-29 18:23:48 +0000
commit58f34ad7a3db8c633871d568bfcef8b094217e3e (patch)
tree43c3f128c54f5da5d80d5feeaca495950e2de144
parent013473f1d5a18f7d99183593ef370045dc58c978 (diff)
downloadservo-58f34ad7a3db8c633871d568bfcef8b094217e3e.tar.gz
servo-58f34ad7a3db8c633871d568bfcef8b094217e3e.zip
Create HttpStatus to safely deal with HTTP responses status. (#33581)
Signed-off-by: webbeef <me@webbeef.org>
-rw-r--r--Cargo.lock2
-rw-r--r--components/constellation/network_listener.rs4
-rw-r--r--components/devtools/Cargo.toml1
-rw-r--r--components/devtools/actors/network_event.rs24
-rw-r--r--components/net/fetch/methods.rs16
-rw-r--r--components/net/http_cache.rs53
-rw-r--r--components/net/http_loader.rs73
-rw-r--r--components/net/protocols/blob.rs6
-rw-r--r--components/net/protocols/data.rs5
-rw-r--r--components/net/protocols/mod.rs8
-rw-r--r--components/net/tests/fetch.rs12
-rw-r--r--components/net/tests/http_cache.rs2
-rw-r--r--components/net/tests/http_loader.rs84
-rw-r--r--components/script/dom/htmlimageelement.rs31
-rw-r--r--components/script/dom/htmlmediaelement.rs16
-rw-r--r--components/script/dom/htmlscriptelement.rs34
-rw-r--r--components/script/dom/htmlvideoelement.rs3
-rw-r--r--components/script/dom/response.rs58
-rw-r--r--components/script/dom/xmlhttprequest.rs22
-rw-r--r--components/script/fetch.rs2
-rw-r--r--components/script/script_module.rs34
-rw-r--r--components/script/script_thread.rs17
-rw-r--r--components/script/stylesheet_loader.rs2
-rw-r--r--components/shared/devtools/Cargo.toml1
-rw-r--r--components/shared/devtools/lib.rs3
-rw-r--r--components/shared/net/http_status.rs117
-rw-r--r--components/shared/net/lib.rs7
-rw-r--r--components/shared/net/response.rs24
-rw-r--r--ports/servoshell/desktop/protocols/urlinfo.rs5
-rw-r--r--tests/wpt/meta/wasm/webapi/status.any.js.ini87
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