mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Embed user agent stylesheets and media control resouces in libservo as decided in https://github.com/servo/servo/pull/36788#issuecomment-2845332210 Signed-off-by: webbeef <me@webbeef.org>
1453 lines
50 KiB
Rust
1453 lines
50 KiB
Rust
/* 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/. */
|
|
|
|
#![cfg(not(target_os = "windows"))]
|
|
|
|
use std::fs;
|
|
use std::iter::FromIterator;
|
|
use std::path::Path;
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
use std::sync::{Arc, Mutex, Weak};
|
|
use std::time::{Duration, SystemTime};
|
|
|
|
use base::id::TEST_PIPELINE_ID;
|
|
use content_security_policy as csp;
|
|
use crossbeam_channel::{Sender, unbounded};
|
|
use devtools_traits::{HttpRequest as DevtoolsHttpRequest, HttpResponse as DevtoolsHttpResponse};
|
|
use headers::{
|
|
AccessControlAllowCredentials, AccessControlAllowHeaders, AccessControlAllowMethods,
|
|
AccessControlAllowOrigin, AccessControlMaxAge, CacheControl, ContentLength, ContentType,
|
|
Expires, HeaderMapExt, LastModified, Pragma, StrictTransportSecurity, UserAgent,
|
|
};
|
|
use http::header::{self, HeaderMap, HeaderName, HeaderValue};
|
|
use http::{Method, StatusCode};
|
|
use http_body_util::combinators::BoxBody;
|
|
use hyper::body::{Bytes, Incoming};
|
|
use hyper::{Request as HyperRequest, Response as HyperResponse};
|
|
use mime::{self, Mime};
|
|
use net::fetch::cors_cache::CorsCache;
|
|
use net::fetch::methods::{self, FetchContext};
|
|
use net::filemanager_thread::FileManager;
|
|
use net::hsts::HstsEntry;
|
|
use net::protocols::ProtocolRegistry;
|
|
use net::request_interceptor::RequestInterceptor;
|
|
use net::resource_thread::CoreResourceThreadPool;
|
|
use net_traits::filemanager_thread::FileTokenCheck;
|
|
use net_traits::http_status::HttpStatus;
|
|
use net_traits::request::{
|
|
Destination, RedirectMode, Referrer, Request, RequestBuilder, RequestMode,
|
|
};
|
|
use net_traits::response::{CacheState, Response, ResponseBody, ResponseType};
|
|
use net_traits::{
|
|
FetchTaskTarget, IncludeSubdomains, NetworkError, ReferrerPolicy, ResourceFetchTiming,
|
|
ResourceTimingType,
|
|
};
|
|
use servo_arc::Arc as ServoArc;
|
|
use servo_url::ServoUrl;
|
|
use uuid::Uuid;
|
|
|
|
use crate::http_loader::{expect_devtools_http_request, expect_devtools_http_response};
|
|
use crate::{
|
|
DEFAULT_USER_AGENT, create_embedder_proxy, create_embedder_proxy_and_receiver,
|
|
create_http_state, fetch, fetch_with_context, fetch_with_cors_cache, make_body, make_server,
|
|
make_ssl_server, new_fetch_context,
|
|
};
|
|
|
|
// TODO write a struct that impls Handler for storing test values
|
|
|
|
#[test]
|
|
fn test_fetch_response_is_not_network_error() {
|
|
static MESSAGE: &'static [u8] = b"";
|
|
let handler =
|
|
move |_: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
*response.body_mut() = make_body(MESSAGE.to_vec());
|
|
};
|
|
let (server, url) = make_server(handler);
|
|
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.build();
|
|
let fetch_response = fetch(request, None);
|
|
let _ = server.close();
|
|
|
|
if fetch_response.is_network_error() {
|
|
panic!("fetch response shouldn't be a network error");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_on_bad_port_is_network_error() {
|
|
let url = ServoUrl::parse("http://www.example.org:6667").unwrap();
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.build();
|
|
let fetch_response = fetch(request, None);
|
|
assert!(fetch_response.is_network_error());
|
|
let fetch_error = fetch_response.get_network_error().unwrap();
|
|
assert_eq!(
|
|
fetch_error,
|
|
&NetworkError::Internal("Request attempted on bad port".into())
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_response_body_matches_const_message() {
|
|
static MESSAGE: &'static [u8] = b"Hello World!";
|
|
let handler =
|
|
move |_: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
*response.body_mut() = make_body(MESSAGE.to_vec());
|
|
};
|
|
let (server, url) = make_server(handler);
|
|
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.build();
|
|
let fetch_response = fetch(request, None);
|
|
let _ = server.close();
|
|
|
|
assert!(!fetch_response.is_network_error());
|
|
assert_eq!(fetch_response.response_type, ResponseType::Basic);
|
|
|
|
match *fetch_response.body.lock().unwrap() {
|
|
ResponseBody::Done(ref body) => {
|
|
assert_eq!(&**body, MESSAGE);
|
|
},
|
|
_ => panic!(),
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_aboutblank() {
|
|
let url = ServoUrl::parse("about:blank").unwrap();
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.build();
|
|
|
|
let fetch_response = fetch(request, None);
|
|
// We should see an opaque-filtered response.
|
|
assert_eq!(fetch_response.response_type, ResponseType::Opaque);
|
|
assert!(!fetch_response.is_network_error());
|
|
assert_eq!(fetch_response.headers.len(), 0);
|
|
let resp_body = fetch_response.body.lock().unwrap();
|
|
assert_eq!(*resp_body, ResponseBody::Empty);
|
|
|
|
// The underlying response behind the filter should
|
|
// have a 0-byte body.
|
|
let actual_response = fetch_response.actual_response();
|
|
assert!(!actual_response.is_network_error());
|
|
let resp_body = actual_response.body.lock().unwrap();
|
|
assert_eq!(*resp_body, ResponseBody::Done(vec![]));
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_blob() {
|
|
use net_traits::blob_url_store::BlobBuf;
|
|
|
|
struct FetchResponseCollector {
|
|
sender: Sender<Response>,
|
|
buffer: Vec<u8>,
|
|
expected: Vec<u8>,
|
|
}
|
|
|
|
impl FetchTaskTarget for FetchResponseCollector {
|
|
fn process_request_body(&mut self, _: &Request) {}
|
|
fn process_request_eof(&mut self, _: &Request) {}
|
|
fn process_response(&mut self, _: &Request, _: &Response) {}
|
|
fn process_response_chunk(&mut self, _: &Request, chunk: Vec<u8>) {
|
|
self.buffer.extend_from_slice(chunk.as_slice());
|
|
}
|
|
/// Fired when the response is fully fetched
|
|
fn process_response_eof(&mut self, _: &Request, response: &Response) {
|
|
assert_eq!(self.buffer, self.expected);
|
|
let _ = self.sender.send(response.clone());
|
|
}
|
|
fn process_csp_violations(&mut self, _: &Request, _: Vec<csp::Violation>) {}
|
|
}
|
|
|
|
let context = new_fetch_context(None, None, None);
|
|
|
|
let bytes = b"content";
|
|
let blob_buf = BlobBuf {
|
|
filename: Some("test.txt".into()),
|
|
type_string: "text/plain".into(),
|
|
size: bytes.len() as u64,
|
|
bytes: bytes.to_vec(),
|
|
};
|
|
|
|
let origin = ServoUrl::parse("http://www.example.org/").unwrap();
|
|
|
|
let id = Uuid::new_v4();
|
|
context.filemanager.lock().unwrap().promote_memory(
|
|
id.clone(),
|
|
blob_buf,
|
|
true,
|
|
"http://www.example.org".into(),
|
|
);
|
|
let url = ServoUrl::parse(&format!("blob:{}{}", origin.as_str(), id.simple())).unwrap();
|
|
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(origin.origin())
|
|
.build();
|
|
|
|
let (sender, receiver) = unbounded();
|
|
|
|
let mut target = FetchResponseCollector {
|
|
sender,
|
|
buffer: vec![],
|
|
expected: bytes.to_vec(),
|
|
};
|
|
|
|
crate::HANDLE.block_on(methods::fetch(request, &mut target, &context));
|
|
|
|
let fetch_response = receiver.recv().unwrap();
|
|
assert!(!fetch_response.is_network_error());
|
|
|
|
assert_eq!(fetch_response.headers.len(), 2);
|
|
|
|
let content_type: Mime = fetch_response
|
|
.headers
|
|
.typed_get::<ContentType>()
|
|
.unwrap()
|
|
.into();
|
|
assert_eq!(content_type, mime::TEXT_PLAIN);
|
|
|
|
let content_length: ContentLength = fetch_response.headers.typed_get().unwrap();
|
|
assert_eq!(content_length.0, bytes.len() as u64);
|
|
|
|
assert_eq!(
|
|
*fetch_response.body.lock().unwrap(),
|
|
ResponseBody::Receiving(vec![])
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_file() {
|
|
let path = Path::new("../../resources/ahem.css")
|
|
.canonicalize()
|
|
.unwrap();
|
|
let url = ServoUrl::from_file_path(path.clone()).unwrap();
|
|
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.build();
|
|
|
|
let pool = CoreResourceThreadPool::new(1, "CoreResourceTestPool".to_string());
|
|
let pool_handle = Arc::new(pool);
|
|
let mut context = new_fetch_context(None, None, Some(Arc::downgrade(&pool_handle)));
|
|
let fetch_response = fetch_with_context(request, &mut context);
|
|
|
|
// We should see an opaque-filtered response.
|
|
assert_eq!(fetch_response.response_type, ResponseType::Opaque);
|
|
|
|
assert!(!fetch_response.is_network_error());
|
|
assert_eq!(fetch_response.headers.len(), 0);
|
|
let resp_body = fetch_response.body.lock().unwrap();
|
|
assert_eq!(*resp_body, ResponseBody::Empty);
|
|
|
|
// The underlying response behind the filter should
|
|
// have the file's MIME type and contents.
|
|
let actual_response = fetch_response.actual_response();
|
|
assert!(!actual_response.is_network_error());
|
|
assert_eq!(actual_response.headers.len(), 1);
|
|
let content_type: Mime = actual_response
|
|
.headers
|
|
.typed_get::<ContentType>()
|
|
.unwrap()
|
|
.into();
|
|
assert_eq!(content_type, mime::TEXT_CSS);
|
|
|
|
let resp_body = actual_response.body.lock().unwrap();
|
|
let file = fs::read(path).unwrap();
|
|
|
|
match *resp_body {
|
|
ResponseBody::Done(ref val) => {
|
|
assert_eq!(val, &file);
|
|
},
|
|
_ => panic!(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_ftp() {
|
|
let url = ServoUrl::parse("ftp://not-supported").unwrap();
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.build();
|
|
let fetch_response = fetch(request, None);
|
|
assert!(fetch_response.is_network_error());
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_bogus_scheme() {
|
|
let url = ServoUrl::parse("bogus://whatever").unwrap();
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.build();
|
|
let fetch_response = fetch(request, None);
|
|
assert!(fetch_response.is_network_error());
|
|
}
|
|
|
|
#[test]
|
|
fn test_cors_preflight_fetch() {
|
|
static ACK: &'static [u8] = b"ACK";
|
|
let state = Arc::new(AtomicUsize::new(0));
|
|
let handler =
|
|
move |request: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
if request.method() == Method::OPTIONS &&
|
|
state.clone().fetch_add(1, Ordering::SeqCst) == 0
|
|
{
|
|
assert!(
|
|
request
|
|
.headers()
|
|
.contains_key(header::ACCESS_CONTROL_REQUEST_METHOD)
|
|
);
|
|
assert!(
|
|
!request
|
|
.headers()
|
|
.contains_key(header::ACCESS_CONTROL_REQUEST_HEADERS)
|
|
);
|
|
assert!(
|
|
!request
|
|
.headers()
|
|
.get(header::REFERER)
|
|
.unwrap()
|
|
.to_str()
|
|
.unwrap()
|
|
.contains("a.html")
|
|
);
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(AccessControlAllowOrigin::ANY);
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(AccessControlAllowCredentials);
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(AccessControlAllowMethods::from_iter(vec![Method::GET]));
|
|
} else {
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(AccessControlAllowOrigin::ANY);
|
|
*response.body_mut() = make_body(ACK.to_vec());
|
|
}
|
|
};
|
|
let (server, url) = make_server(handler);
|
|
|
|
let target_url = url.clone().join("a.html").unwrap();
|
|
let mut request = RequestBuilder::new(None, url, Referrer::ReferrerUrl(target_url)).build();
|
|
request.referrer_policy = ReferrerPolicy::Origin;
|
|
request.use_cors_preflight = true;
|
|
request.mode = RequestMode::CorsMode;
|
|
let fetch_response = fetch(request, None);
|
|
let _ = server.close();
|
|
|
|
assert!(!fetch_response.is_network_error());
|
|
match *fetch_response.body.lock().unwrap() {
|
|
ResponseBody::Done(ref body) => assert_eq!(&**body, ACK),
|
|
_ => panic!(),
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn test_cors_preflight_cache_fetch() {
|
|
static ACK: &'static [u8] = b"ACK";
|
|
let state = Arc::new(AtomicUsize::new(0));
|
|
let counter = state.clone();
|
|
let mut cache = CorsCache::default();
|
|
let handler =
|
|
move |request: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
if request.method() == Method::OPTIONS &&
|
|
state.clone().fetch_add(1, Ordering::SeqCst) == 0
|
|
{
|
|
assert!(
|
|
request
|
|
.headers()
|
|
.contains_key(header::ACCESS_CONTROL_REQUEST_METHOD)
|
|
);
|
|
assert!(
|
|
!request
|
|
.headers()
|
|
.contains_key(header::ACCESS_CONTROL_REQUEST_HEADERS)
|
|
);
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(AccessControlAllowOrigin::ANY);
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(AccessControlAllowCredentials);
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(AccessControlAllowMethods::from_iter(vec![Method::GET]));
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(AccessControlMaxAge::from(Duration::new(6000, 0)));
|
|
} else {
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(AccessControlAllowOrigin::ANY);
|
|
*response.body_mut() = make_body(ACK.to_vec());
|
|
}
|
|
};
|
|
let (server, url) = make_server(handler);
|
|
|
|
let mut request = RequestBuilder::new(None, url, Referrer::NoReferrer).build();
|
|
request.use_cors_preflight = true;
|
|
request.mode = RequestMode::CorsMode;
|
|
let wrapped_request0 = request.clone();
|
|
let wrapped_request1 = request.clone();
|
|
let wrapped_request2 = request.clone();
|
|
let wrapped_request3 = request;
|
|
|
|
let fetch_response0 = fetch_with_cors_cache(wrapped_request0, &mut cache);
|
|
let fetch_response1 = fetch_with_cors_cache(wrapped_request1, &mut cache);
|
|
let _ = server.close();
|
|
|
|
assert!(!fetch_response0.is_network_error() && !fetch_response1.is_network_error());
|
|
|
|
// The response from the CORS-preflight cache was used
|
|
assert_eq!(1, counter.load(Ordering::SeqCst));
|
|
|
|
// The entry exists in the CORS-preflight cache
|
|
assert_eq!(true, cache.match_method(&wrapped_request2, Method::GET));
|
|
assert_eq!(true, cache.match_method(&wrapped_request3, Method::GET));
|
|
|
|
match *fetch_response0.body.lock().unwrap() {
|
|
ResponseBody::Done(ref body) => assert_eq!(&**body, ACK),
|
|
_ => panic!(),
|
|
};
|
|
match *fetch_response1.body.lock().unwrap() {
|
|
ResponseBody::Done(ref body) => assert_eq!(&**body, ACK),
|
|
_ => panic!(),
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn test_cors_preflight_fetch_network_error() {
|
|
static ACK: &'static [u8] = b"ACK";
|
|
let state = Arc::new(AtomicUsize::new(0));
|
|
let handler =
|
|
move |request: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
if request.method() == Method::OPTIONS &&
|
|
state.clone().fetch_add(1, Ordering::SeqCst) == 0
|
|
{
|
|
assert!(
|
|
request
|
|
.headers()
|
|
.contains_key(header::ACCESS_CONTROL_REQUEST_METHOD)
|
|
);
|
|
assert!(
|
|
!request
|
|
.headers()
|
|
.contains_key(header::ACCESS_CONTROL_REQUEST_HEADERS)
|
|
);
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(AccessControlAllowOrigin::ANY);
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(AccessControlAllowCredentials);
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(AccessControlAllowMethods::from_iter(vec![Method::GET]));
|
|
} else {
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(AccessControlAllowOrigin::ANY);
|
|
*response.body_mut() = make_body(ACK.to_vec());
|
|
}
|
|
};
|
|
let (server, url) = make_server(handler);
|
|
|
|
let mut request = RequestBuilder::new(None, url, Referrer::NoReferrer).build();
|
|
request.method = Method::from_bytes(b"CHICKEN").unwrap();
|
|
request.use_cors_preflight = true;
|
|
request.mode = RequestMode::CorsMode;
|
|
let fetch_response = fetch(request, None);
|
|
let _ = server.close();
|
|
|
|
assert!(fetch_response.is_network_error());
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_response_is_basic_filtered() {
|
|
static MESSAGE: &'static [u8] = b"";
|
|
let handler =
|
|
move |_: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
response
|
|
.headers_mut()
|
|
.insert(header::SET_COOKIE, HeaderValue::from_static(""));
|
|
// this header is obsoleted, so hyper doesn't implement it, but it's still covered by the spec
|
|
response.headers_mut().insert(
|
|
HeaderName::from_static("set-cookie2"),
|
|
HeaderValue::from_bytes(&vec![]).unwrap(),
|
|
);
|
|
|
|
*response.body_mut() = make_body(MESSAGE.to_vec());
|
|
};
|
|
let (server, url) = make_server(handler);
|
|
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.build();
|
|
let fetch_response = fetch(request, None);
|
|
let _ = server.close();
|
|
|
|
assert!(!fetch_response.is_network_error());
|
|
assert_eq!(fetch_response.response_type, ResponseType::Basic);
|
|
|
|
let headers = fetch_response.headers;
|
|
assert!(!headers.contains_key(header::SET_COOKIE));
|
|
assert!(
|
|
headers
|
|
.get(HeaderName::from_static("set-cookie2"))
|
|
.is_none()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_response_is_cors_filtered() {
|
|
static MESSAGE: &'static [u8] = b"";
|
|
let handler =
|
|
move |_: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
// this is mandatory for the Cors Check to pass
|
|
// TODO test using different url encodings with this value ie. punycode
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(AccessControlAllowOrigin::ANY);
|
|
|
|
// these are the headers that should be kept after filtering
|
|
response.headers_mut().typed_insert(CacheControl::new());
|
|
response.headers_mut().insert(
|
|
header::CONTENT_LANGUAGE,
|
|
HeaderValue::from_bytes(&vec![]).unwrap(),
|
|
);
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(ContentType::from(mime::TEXT_HTML));
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(Expires::from(SystemTime::now() + Duration::new(86400, 0)));
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(LastModified::from(SystemTime::now()));
|
|
response.headers_mut().typed_insert(Pragma::no_cache());
|
|
|
|
// these headers should not be kept after filtering, even though they are given a pass
|
|
response
|
|
.headers_mut()
|
|
.insert(header::SET_COOKIE, HeaderValue::from_static(""));
|
|
response.headers_mut().insert(
|
|
HeaderName::from_static("set-cookie2"),
|
|
HeaderValue::from_bytes(&vec![]).unwrap(),
|
|
);
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(AccessControlAllowHeaders::from_iter(vec![
|
|
HeaderName::from_static("set-cookie"),
|
|
HeaderName::from_static("set-cookie2"),
|
|
]));
|
|
|
|
*response.body_mut() = make_body(MESSAGE.to_vec());
|
|
};
|
|
let (server, url) = make_server(handler);
|
|
|
|
// an origin mis-match will stop it from defaulting to a basic filtered response
|
|
let mut request = RequestBuilder::new(None, url, Referrer::NoReferrer).build();
|
|
request.mode = RequestMode::CorsMode;
|
|
let fetch_response = fetch(request, None);
|
|
let _ = server.close();
|
|
|
|
assert!(!fetch_response.is_network_error());
|
|
assert_eq!(fetch_response.response_type, ResponseType::Cors);
|
|
|
|
let headers = fetch_response.headers;
|
|
assert!(headers.contains_key(header::CACHE_CONTROL));
|
|
assert!(headers.contains_key(header::CONTENT_LANGUAGE));
|
|
assert!(headers.contains_key(header::CONTENT_TYPE));
|
|
assert!(headers.contains_key(header::EXPIRES));
|
|
assert!(headers.contains_key(header::LAST_MODIFIED));
|
|
assert!(headers.contains_key(header::PRAGMA));
|
|
|
|
assert!(!headers.contains_key(header::ACCESS_CONTROL_ALLOW_ORIGIN));
|
|
assert!(!headers.contains_key(header::SET_COOKIE));
|
|
assert!(
|
|
headers
|
|
.get(HeaderName::from_static("set-cookie2"))
|
|
.is_none()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_response_is_opaque_filtered() {
|
|
static MESSAGE: &'static [u8] = b"";
|
|
let handler =
|
|
move |_: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
*response.body_mut() = make_body(MESSAGE.to_vec());
|
|
};
|
|
let (server, url) = make_server(handler);
|
|
|
|
// an origin mis-match will fall through to an Opaque filtered response
|
|
let request = RequestBuilder::new(None, url, Referrer::NoReferrer).build();
|
|
let fetch_response = fetch(request, None);
|
|
let _ = server.close();
|
|
|
|
assert!(!fetch_response.is_network_error());
|
|
assert_eq!(fetch_response.response_type, ResponseType::Opaque);
|
|
|
|
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_error());
|
|
assert_eq!(fetch_response.headers, HeaderMap::new());
|
|
match *fetch_response.body.lock().unwrap() {
|
|
ResponseBody::Empty => {},
|
|
_ => panic!(),
|
|
}
|
|
match fetch_response.cache_state {
|
|
CacheState::None => {},
|
|
_ => panic!(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_response_is_opaque_redirect_filtered() {
|
|
static MESSAGE: &'static [u8] = b"";
|
|
let handler =
|
|
move |request: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
let redirects = request
|
|
.uri()
|
|
.path()
|
|
.split("/")
|
|
.collect::<String>()
|
|
.parse::<u32>()
|
|
.unwrap_or(0);
|
|
|
|
if redirects == 1 {
|
|
*response.body_mut() = make_body(MESSAGE.to_vec());
|
|
} else {
|
|
*response.status_mut() = StatusCode::FOUND;
|
|
response
|
|
.headers_mut()
|
|
.insert(header::LOCATION, HeaderValue::from_static("1"));
|
|
}
|
|
};
|
|
|
|
let (server, url) = make_server(handler);
|
|
|
|
let mut request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.build();
|
|
request.redirect_mode = RedirectMode::Manual;
|
|
let fetch_response = fetch(request, None);
|
|
let _ = server.close();
|
|
|
|
assert!(!fetch_response.is_network_error());
|
|
assert_eq!(fetch_response.response_type, ResponseType::OpaqueRedirect);
|
|
|
|
// this also asserts that status message is "the empty byte sequence"
|
|
assert!(fetch_response.status.is_error());
|
|
assert_eq!(fetch_response.headers, HeaderMap::new());
|
|
match *fetch_response.body.lock().unwrap() {
|
|
ResponseBody::Empty => {},
|
|
_ => panic!(),
|
|
}
|
|
match fetch_response.cache_state {
|
|
CacheState::None => {},
|
|
_ => panic!(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_with_local_urls_only() {
|
|
// If flag `local_urls_only` is set, fetching a non-local URL must result in network error.
|
|
|
|
static MESSAGE: &'static [u8] = b"";
|
|
let handler =
|
|
move |_: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
*response.body_mut() = make_body(MESSAGE.to_vec());
|
|
};
|
|
let (server, server_url) = make_server(handler);
|
|
|
|
let do_fetch = |url: ServoUrl| {
|
|
let mut request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.build();
|
|
|
|
// Set the flag.
|
|
request.local_urls_only = true;
|
|
|
|
fetch(request, None)
|
|
};
|
|
|
|
let local_url = ServoUrl::parse("about:blank").unwrap();
|
|
let local_response = do_fetch(local_url);
|
|
let server_response = do_fetch(server_url);
|
|
|
|
let _ = server.close();
|
|
|
|
assert!(!local_response.is_network_error());
|
|
assert!(server_response.is_network_error());
|
|
}
|
|
// NOTE(emilio): If this test starts failing:
|
|
//
|
|
// openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
|
|
// -keyout resources/privatekey_for_testing.key \
|
|
// -out resources/self_signed_certificate_for_testing.crt
|
|
//
|
|
// And make sure to specify `localhost` as the server name.
|
|
#[test]
|
|
fn test_fetch_with_hsts() {
|
|
static MESSAGE: &'static [u8] = b"";
|
|
let handler =
|
|
move |_: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
*response.body_mut() = make_body(MESSAGE.to_vec());
|
|
};
|
|
|
|
let (server, url) = make_ssl_server(handler);
|
|
|
|
let embedder_proxy = create_embedder_proxy();
|
|
|
|
let mut context = FetchContext {
|
|
state: Arc::new(create_http_state(None)),
|
|
user_agent: DEFAULT_USER_AGENT.into(),
|
|
devtools_chan: None,
|
|
filemanager: Arc::new(Mutex::new(FileManager::new(
|
|
embedder_proxy.clone(),
|
|
Weak::new(),
|
|
))),
|
|
file_token: FileTokenCheck::NotRequired,
|
|
request_interceptor: Arc::new(Mutex::new(RequestInterceptor::new(embedder_proxy))),
|
|
cancellation_listener: Arc::new(Default::default()),
|
|
timing: ServoArc::new(Mutex::new(ResourceFetchTiming::new(
|
|
ResourceTimingType::Navigation,
|
|
))),
|
|
protocols: Arc::new(ProtocolRegistry::default()),
|
|
};
|
|
|
|
// The server certificate is self-signed, so we need to add an override
|
|
// so that the connection works properly.
|
|
for certificate in server.certificates.as_ref().unwrap().iter() {
|
|
context.state.override_manager.add_override(certificate);
|
|
}
|
|
|
|
{
|
|
let mut list = context.state.hsts_list.write().unwrap();
|
|
list.push(
|
|
HstsEntry::new("localhost".to_owned(), IncludeSubdomains::NotIncluded, None).unwrap(),
|
|
);
|
|
}
|
|
let mut request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.build();
|
|
// Set the flag.
|
|
request.local_urls_only = false;
|
|
let response = fetch_with_context(request, &mut context);
|
|
server.close();
|
|
assert_eq!(
|
|
response.internal_response.unwrap().url().unwrap().scheme(),
|
|
"https"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_load_adds_host_to_hsts_list_when_url_is_https() {
|
|
let handler =
|
|
move |_: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
response
|
|
.headers_mut()
|
|
.typed_insert(StrictTransportSecurity::excluding_subdomains(
|
|
Duration::from_secs(31536000),
|
|
));
|
|
*response.body_mut() = make_body(b"Yay!".to_vec());
|
|
};
|
|
|
|
let (server, mut url) = make_ssl_server(handler);
|
|
url.as_mut_url().set_scheme("https").unwrap();
|
|
|
|
let embedder_proxy = create_embedder_proxy();
|
|
|
|
let mut context = FetchContext {
|
|
state: Arc::new(create_http_state(None)),
|
|
user_agent: DEFAULT_USER_AGENT.into(),
|
|
devtools_chan: None,
|
|
filemanager: Arc::new(Mutex::new(FileManager::new(
|
|
embedder_proxy.clone(),
|
|
Weak::new(),
|
|
))),
|
|
file_token: FileTokenCheck::NotRequired,
|
|
request_interceptor: Arc::new(Mutex::new(RequestInterceptor::new(embedder_proxy))),
|
|
cancellation_listener: Arc::new(Default::default()),
|
|
timing: ServoArc::new(Mutex::new(ResourceFetchTiming::new(
|
|
ResourceTimingType::Navigation,
|
|
))),
|
|
protocols: Arc::new(ProtocolRegistry::default()),
|
|
};
|
|
|
|
// The server certificate is self-signed, so we need to add an override
|
|
// so that the connection works properly.
|
|
for certificate in server.certificates.as_ref().unwrap().iter() {
|
|
context.state.override_manager.add_override(certificate);
|
|
}
|
|
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.method(Method::GET)
|
|
.body(None)
|
|
.destination(Destination::Document)
|
|
.origin(url.clone().origin())
|
|
.pipeline_id(Some(TEST_PIPELINE_ID))
|
|
.build();
|
|
|
|
let response = fetch_with_context(request, &mut context);
|
|
|
|
let _ = server.close();
|
|
|
|
assert!(
|
|
response
|
|
.internal_response
|
|
.unwrap()
|
|
.status
|
|
.code()
|
|
.is_success()
|
|
);
|
|
assert!(
|
|
context
|
|
.state
|
|
.hsts_list
|
|
.read()
|
|
.unwrap()
|
|
.is_host_secure(url.host_str().unwrap())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_self_signed() {
|
|
let handler =
|
|
move |_: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
*response.body_mut() = make_body(b"Yay!".to_vec());
|
|
};
|
|
|
|
let (server, mut url) = make_ssl_server(handler);
|
|
url.as_mut_url().set_scheme("https").unwrap();
|
|
|
|
let embedder_proxy = create_embedder_proxy();
|
|
|
|
let mut context = FetchContext {
|
|
state: Arc::new(create_http_state(None)),
|
|
user_agent: DEFAULT_USER_AGENT.into(),
|
|
devtools_chan: None,
|
|
filemanager: Arc::new(Mutex::new(FileManager::new(
|
|
embedder_proxy.clone(),
|
|
Weak::new(),
|
|
))),
|
|
file_token: FileTokenCheck::NotRequired,
|
|
request_interceptor: Arc::new(Mutex::new(RequestInterceptor::new(embedder_proxy))),
|
|
cancellation_listener: Arc::new(Default::default()),
|
|
timing: ServoArc::new(Mutex::new(ResourceFetchTiming::new(
|
|
ResourceTimingType::Navigation,
|
|
))),
|
|
protocols: Arc::new(ProtocolRegistry::default()),
|
|
};
|
|
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.method(Method::GET)
|
|
.body(None)
|
|
.destination(Destination::Document)
|
|
.origin(url.clone().origin())
|
|
.pipeline_id(Some(TEST_PIPELINE_ID))
|
|
.build();
|
|
|
|
let response = fetch_with_context(request, &mut context);
|
|
|
|
assert!(matches!(
|
|
response.get_network_error(),
|
|
Some(NetworkError::SslValidation(..))
|
|
));
|
|
|
|
// The server certificate is self-signed, so we need to add an override
|
|
// so that the connection works properly.
|
|
for certificate in server.certificates.as_ref().unwrap().iter() {
|
|
context.state.override_manager.add_override(certificate);
|
|
}
|
|
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.method(Method::GET)
|
|
.body(None)
|
|
.destination(Destination::Document)
|
|
.origin(url.clone().origin())
|
|
.pipeline_id(Some(TEST_PIPELINE_ID))
|
|
.build();
|
|
|
|
let response = fetch_with_context(request, &mut context);
|
|
|
|
assert!(response.status.code().is_success());
|
|
|
|
let _ = server.close();
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_with_sri_network_error() {
|
|
static MESSAGE: &'static [u8] = b"alert('Hello, Network Error');";
|
|
let handler =
|
|
move |_: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
*response.body_mut() = make_body(MESSAGE.to_vec());
|
|
};
|
|
let (server, url) = make_server(handler);
|
|
|
|
let mut request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.build();
|
|
// To calulate hash use :
|
|
// echo -n "alert('Hello, Network Error');" | openssl dgst -sha384 -binary | openssl base64 -A
|
|
request.integrity_metadata =
|
|
"sha384-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO".to_owned();
|
|
// Set the flag.
|
|
request.local_urls_only = false;
|
|
|
|
let response = fetch(request, None);
|
|
|
|
let _ = server.close();
|
|
assert!(response.is_network_error());
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_with_sri_sucess() {
|
|
static MESSAGE: &'static [u8] = b"alert('Hello, world.');";
|
|
let handler =
|
|
move |_: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
*response.body_mut() = make_body(MESSAGE.to_vec());
|
|
};
|
|
let (server, url) = make_server(handler);
|
|
|
|
let mut request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.build();
|
|
// To calulate hash use :
|
|
// echo -n "alert('Hello, Network Error');" | openssl dgst -sha384 -binary | openssl base64 -A
|
|
request.integrity_metadata =
|
|
"sha384-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO".to_owned();
|
|
// Set the flag.
|
|
request.local_urls_only = false;
|
|
|
|
let response = fetch(request, None);
|
|
|
|
let _ = server.close();
|
|
assert_eq!(response_is_done(&response), true);
|
|
}
|
|
|
|
/// `fetch` should return a network error if there is a header `X-Content-Type-Options: nosniff`
|
|
#[test]
|
|
fn test_fetch_blocked_nosniff() {
|
|
#[inline]
|
|
fn test_nosniff_request(destination: Destination, mime: Mime, should_error: bool) {
|
|
const MESSAGE: &'static [u8] = b"";
|
|
const HEADER: &'static str = "x-content-type-options";
|
|
const VALUE: &'static [u8] = b"nosniff";
|
|
|
|
let handler =
|
|
move |_: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
let mime_header = ContentType::from(mime.clone());
|
|
response.headers_mut().typed_insert(mime_header);
|
|
assert!(response.headers().contains_key(header::CONTENT_TYPE));
|
|
// Add the nosniff header
|
|
response.headers_mut().insert(
|
|
HeaderName::from_static(HEADER),
|
|
HeaderValue::from_bytes(VALUE).unwrap(),
|
|
);
|
|
|
|
*response.body_mut() = make_body(MESSAGE.to_vec());
|
|
};
|
|
|
|
let (server, url) = make_server(handler);
|
|
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.destination(destination)
|
|
.build();
|
|
let fetch_response = fetch(request, None);
|
|
let _ = server.close();
|
|
|
|
assert_eq!(fetch_response.is_network_error(), should_error);
|
|
}
|
|
|
|
let tests = vec![
|
|
(Destination::Script, mime::TEXT_JAVASCRIPT, false),
|
|
(Destination::Script, mime::TEXT_CSS, true),
|
|
(Destination::Style, mime::TEXT_CSS, false),
|
|
];
|
|
|
|
for test in tests {
|
|
let (destination, mime, should_error) = test;
|
|
test_nosniff_request(destination, mime, should_error);
|
|
}
|
|
}
|
|
|
|
fn setup_server_and_fetch(message: &'static [u8], redirect_cap: u32) -> Response {
|
|
let handler =
|
|
move |request: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
let redirects = request
|
|
.uri()
|
|
.path()
|
|
.split("/")
|
|
.collect::<String>()
|
|
.parse::<u32>()
|
|
.unwrap_or(0);
|
|
|
|
if redirects >= redirect_cap {
|
|
*response.body_mut() = make_body(message.to_vec());
|
|
} else {
|
|
*response.status_mut() = StatusCode::FOUND;
|
|
let url = format!("{redirects}", redirects = redirects + 1);
|
|
response
|
|
.headers_mut()
|
|
.insert(header::LOCATION, HeaderValue::from_str(&url).unwrap());
|
|
}
|
|
};
|
|
|
|
let (server, url) = make_server(handler);
|
|
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.build();
|
|
let fetch_response = fetch(request, None);
|
|
let _ = server.close();
|
|
fetch_response
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_redirect_count_ceiling() {
|
|
static MESSAGE: &'static [u8] = b"no more redirects";
|
|
// how many redirects to cause
|
|
let redirect_cap = 20;
|
|
|
|
let fetch_response = setup_server_and_fetch(MESSAGE, redirect_cap);
|
|
|
|
assert!(!fetch_response.is_network_error());
|
|
assert_eq!(fetch_response.response_type, ResponseType::Basic);
|
|
|
|
match *fetch_response.body.lock().unwrap() {
|
|
ResponseBody::Done(ref body) => {
|
|
assert_eq!(&**body, MESSAGE);
|
|
},
|
|
_ => panic!(),
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_redirect_count_failure() {
|
|
static MESSAGE: &'static [u8] = b"this message shouldn't be reachable";
|
|
// how many redirects to cause
|
|
let redirect_cap = 21;
|
|
|
|
let fetch_response = setup_server_and_fetch(MESSAGE, redirect_cap);
|
|
|
|
assert!(fetch_response.is_network_error());
|
|
|
|
match *fetch_response.body.lock().unwrap() {
|
|
ResponseBody::Done(_) | ResponseBody::Receiving(_) => panic!(),
|
|
_ => {},
|
|
};
|
|
}
|
|
|
|
fn test_fetch_redirect_updates_method_runner(
|
|
tx: Sender<bool>,
|
|
status_code: StatusCode,
|
|
method: Method,
|
|
) {
|
|
let handler_method = method.clone();
|
|
let handler_tx = Arc::new(tx);
|
|
|
|
let handler =
|
|
move |request: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
let redirects = request
|
|
.uri()
|
|
.path()
|
|
.split("/")
|
|
.collect::<String>()
|
|
.parse::<u32>()
|
|
.unwrap_or(0);
|
|
|
|
let mut test_pass = true;
|
|
|
|
if redirects == 0 {
|
|
*response.status_mut() = StatusCode::TEMPORARY_REDIRECT;
|
|
response
|
|
.headers_mut()
|
|
.insert(header::LOCATION, HeaderValue::from_static("1"));
|
|
} else if redirects == 1 {
|
|
// this makes sure that the request method does't change from the wrong status code
|
|
if handler_method != Method::GET && request.method() == Method::GET {
|
|
test_pass = false;
|
|
}
|
|
*response.status_mut() = status_code;
|
|
response
|
|
.headers_mut()
|
|
.insert(header::LOCATION, HeaderValue::from_static("2"));
|
|
} else if request.method() != Method::GET {
|
|
test_pass = false;
|
|
}
|
|
|
|
// the first time this handler is reached, nothing is being tested, so don't send anything
|
|
if redirects > 0 {
|
|
handler_tx.send(test_pass).unwrap();
|
|
}
|
|
};
|
|
|
|
let (server, url) = crate::make_server(handler);
|
|
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.method(method)
|
|
.build();
|
|
|
|
let _ = fetch(request, None);
|
|
let _ = server.close();
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_redirect_updates_method() {
|
|
let (tx, rx) = unbounded();
|
|
|
|
test_fetch_redirect_updates_method_runner(
|
|
tx.clone(),
|
|
StatusCode::MOVED_PERMANENTLY,
|
|
Method::POST,
|
|
);
|
|
assert_eq!(rx.recv().unwrap(), true);
|
|
assert_eq!(rx.recv().unwrap(), true);
|
|
// make sure the test doesn't send more data than expected
|
|
assert_eq!(rx.try_recv().is_err(), true);
|
|
|
|
test_fetch_redirect_updates_method_runner(tx.clone(), StatusCode::FOUND, Method::POST);
|
|
assert_eq!(rx.recv().unwrap(), true);
|
|
assert_eq!(rx.recv().unwrap(), true);
|
|
assert_eq!(rx.try_recv().is_err(), true);
|
|
|
|
test_fetch_redirect_updates_method_runner(tx.clone(), StatusCode::SEE_OTHER, Method::GET);
|
|
assert_eq!(rx.recv().unwrap(), true);
|
|
assert_eq!(rx.recv().unwrap(), true);
|
|
assert_eq!(rx.try_recv().is_err(), true);
|
|
|
|
let extension = Method::from_bytes(b"FOO").unwrap();
|
|
|
|
test_fetch_redirect_updates_method_runner(
|
|
tx.clone(),
|
|
StatusCode::MOVED_PERMANENTLY,
|
|
extension.clone(),
|
|
);
|
|
assert_eq!(rx.recv().unwrap(), true);
|
|
// for MovedPermanently and Found, Method should only be changed if it was Post
|
|
assert_eq!(rx.recv().unwrap(), false);
|
|
assert_eq!(rx.try_recv().is_err(), true);
|
|
|
|
test_fetch_redirect_updates_method_runner(tx.clone(), StatusCode::FOUND, extension.clone());
|
|
assert_eq!(rx.recv().unwrap(), true);
|
|
assert_eq!(rx.recv().unwrap(), false);
|
|
assert_eq!(rx.try_recv().is_err(), true);
|
|
|
|
test_fetch_redirect_updates_method_runner(tx.clone(), StatusCode::SEE_OTHER, extension.clone());
|
|
assert_eq!(rx.recv().unwrap(), true);
|
|
// for SeeOther, Method should always be changed, so this should be true
|
|
assert_eq!(rx.recv().unwrap(), true);
|
|
assert_eq!(rx.try_recv().is_err(), true);
|
|
}
|
|
|
|
fn response_is_done(response: &Response) -> bool {
|
|
let response_complete = match response.response_type {
|
|
ResponseType::Default | ResponseType::Basic | ResponseType::Cors => {
|
|
(*response.body.lock().unwrap()).is_done()
|
|
},
|
|
// if the internal response cannot have a body, it shouldn't block the "done" state
|
|
ResponseType::Opaque | ResponseType::OpaqueRedirect | ResponseType::Error(..) => true,
|
|
};
|
|
|
|
let internal_complete = if let Some(ref res) = response.internal_response {
|
|
res.body.lock().unwrap().is_done()
|
|
} else {
|
|
true
|
|
};
|
|
|
|
response_complete && internal_complete
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_async_returns_complete_response() {
|
|
static MESSAGE: &'static [u8] = b"this message should be retrieved in full";
|
|
let handler =
|
|
move |_: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
*response.body_mut() = make_body(MESSAGE.to_vec());
|
|
};
|
|
let (server, url) = make_server(handler);
|
|
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.build();
|
|
let fetch_response = fetch(request, None);
|
|
|
|
let _ = server.close();
|
|
assert_eq!(response_is_done(&fetch_response), true);
|
|
}
|
|
|
|
#[test]
|
|
fn test_opaque_filtered_fetch_async_returns_complete_response() {
|
|
static MESSAGE: &'static [u8] = b"";
|
|
let handler =
|
|
move |_: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
*response.body_mut() = make_body(MESSAGE.to_vec());
|
|
};
|
|
let (server, url) = make_server(handler);
|
|
|
|
// an origin mis-match will fall through to an Opaque filtered response
|
|
let request = RequestBuilder::new(None, url, Referrer::NoReferrer).build();
|
|
let fetch_response = fetch(request, None);
|
|
|
|
let _ = server.close();
|
|
|
|
assert_eq!(fetch_response.response_type, ResponseType::Opaque);
|
|
assert_eq!(response_is_done(&fetch_response), true);
|
|
}
|
|
|
|
#[test]
|
|
fn test_opaque_redirect_filtered_fetch_async_returns_complete_response() {
|
|
static MESSAGE: &'static [u8] = b"";
|
|
let handler =
|
|
move |request: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
let redirects = request
|
|
.uri()
|
|
.path()
|
|
.split("/")
|
|
.collect::<String>()
|
|
.parse::<u32>()
|
|
.unwrap_or(0);
|
|
|
|
if redirects == 1 {
|
|
*response.body_mut() = make_body(MESSAGE.to_vec());
|
|
} else {
|
|
*response.status_mut() = StatusCode::FOUND;
|
|
response
|
|
.headers_mut()
|
|
.insert(header::LOCATION, HeaderValue::from_static("1"));
|
|
}
|
|
};
|
|
|
|
let (server, url) = make_server(handler);
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.redirect_mode(RedirectMode::Manual)
|
|
.build();
|
|
|
|
let fetch_response = fetch(request, None);
|
|
|
|
let _ = server.close();
|
|
|
|
assert_eq!(fetch_response.response_type, ResponseType::OpaqueRedirect);
|
|
assert_eq!(response_is_done(&fetch_response), true);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(not(target_os = "windows"))]
|
|
fn test_fetch_with_devtools() {
|
|
static MESSAGE: &'static [u8] = b"Yay!";
|
|
let handler =
|
|
move |_: HyperRequest<Incoming>,
|
|
response: &mut HyperResponse<BoxBody<Bytes, hyper::Error>>| {
|
|
*response.body_mut() = make_body(MESSAGE.to_vec());
|
|
};
|
|
|
|
let (server, url) = make_server(handler);
|
|
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.redirect_mode(RedirectMode::Manual)
|
|
.pipeline_id(Some(TEST_PIPELINE_ID))
|
|
.build();
|
|
|
|
let (devtools_chan, devtools_port) = unbounded();
|
|
|
|
let _ = fetch(request, Some(devtools_chan));
|
|
let _ = server.close();
|
|
|
|
// notification received from devtools
|
|
let devhttprequest = expect_devtools_http_request(&devtools_port);
|
|
let mut devhttpresponse = expect_devtools_http_response(&devtools_port);
|
|
|
|
//Creating default headers for request
|
|
let mut headers = HeaderMap::new();
|
|
|
|
headers.insert(header::ACCEPT, HeaderValue::from_static("*/*"));
|
|
|
|
headers.insert(
|
|
header::ACCEPT_LANGUAGE,
|
|
HeaderValue::from_static("en-US,en;q=0.5"),
|
|
);
|
|
|
|
headers.typed_insert::<UserAgent>(DEFAULT_USER_AGENT.parse().unwrap());
|
|
|
|
headers.insert(
|
|
header::ACCEPT_ENCODING,
|
|
HeaderValue::from_static("gzip, deflate, br"),
|
|
);
|
|
|
|
// Append fetch metadata headers
|
|
headers.insert(
|
|
HeaderName::from_static("sec-fetch-dest"),
|
|
HeaderValue::from_static("empty"),
|
|
);
|
|
headers.insert(
|
|
HeaderName::from_static("sec-fetch-mode"),
|
|
HeaderValue::from_static("no-cors"),
|
|
);
|
|
headers.insert(
|
|
HeaderName::from_static("sec-fetch-site"),
|
|
HeaderValue::from_static("same-origin"),
|
|
);
|
|
|
|
let httprequest = DevtoolsHttpRequest {
|
|
url: url,
|
|
method: Method::GET,
|
|
headers: headers,
|
|
body: Some(vec![]),
|
|
pipeline_id: TEST_PIPELINE_ID,
|
|
started_date_time: devhttprequest.started_date_time,
|
|
time_stamp: devhttprequest.time_stamp,
|
|
connect_time: devhttprequest.connect_time,
|
|
send_time: devhttprequest.send_time,
|
|
is_xhr: true,
|
|
};
|
|
|
|
let content = "Yay!";
|
|
let mut response_headers = HeaderMap::new();
|
|
response_headers.typed_insert(ContentLength(content.len() as u64));
|
|
devhttpresponse
|
|
.headers
|
|
.as_mut()
|
|
.unwrap()
|
|
.remove(header::DATE);
|
|
|
|
let httpresponse = DevtoolsHttpResponse {
|
|
headers: Some(response_headers),
|
|
status: HttpStatus::default(),
|
|
body: None,
|
|
pipeline_id: TEST_PIPELINE_ID,
|
|
};
|
|
|
|
assert_eq!(devhttprequest, httprequest);
|
|
assert_eq!(devhttpresponse, httpresponse);
|
|
}
|
|
|
|
#[test]
|
|
fn test_fetch_request_intercepted() {
|
|
static BODY_PART1: &[u8] = b"Request is";
|
|
static BODY_PART2: &[u8] = b" intercepted";
|
|
static EXPECTED_BODY: &[u8] = b"Request is intercepted";
|
|
static HEADERNAME: &str = "custom-header";
|
|
static HEADERVALUE: &str = "custom-value";
|
|
static STATUS_MESSAGE: &[u8] = b"custom status message";
|
|
|
|
let (embedder_proxy, embedder_receiver) = create_embedder_proxy_and_receiver();
|
|
|
|
std::thread::spawn(move || {
|
|
let embedder_msg = embedder_receiver.recv().unwrap();
|
|
match embedder_msg {
|
|
embedder_traits::EmbedderMsg::WebResourceRequested(
|
|
_,
|
|
web_resource_request,
|
|
response_sender,
|
|
) => {
|
|
let mut headers = HeaderMap::new();
|
|
headers.insert(
|
|
HeaderName::from_static(HEADERNAME),
|
|
HeaderValue::from_static(HEADERVALUE),
|
|
);
|
|
let response =
|
|
embedder_traits::WebResourceResponse::new(web_resource_request.url.clone())
|
|
.headers(headers)
|
|
.status_code(StatusCode::FOUND)
|
|
.status_message(STATUS_MESSAGE.to_vec());
|
|
let msg = embedder_traits::WebResourceResponseMsg::Start(response);
|
|
let _ = response_sender.send(msg);
|
|
let msg2 =
|
|
embedder_traits::WebResourceResponseMsg::SendBodyData(BODY_PART1.to_vec());
|
|
let _ = response_sender.send(msg2);
|
|
let msg3 =
|
|
embedder_traits::WebResourceResponseMsg::SendBodyData(BODY_PART2.to_vec());
|
|
let _ = response_sender.send(msg3);
|
|
let _ = response_sender.send(embedder_traits::WebResourceResponseMsg::FinishLoad);
|
|
},
|
|
_ => unreachable!(),
|
|
}
|
|
});
|
|
|
|
let mut context = FetchContext {
|
|
state: Arc::new(create_http_state(None)),
|
|
user_agent: DEFAULT_USER_AGENT.into(),
|
|
devtools_chan: None,
|
|
filemanager: Arc::new(Mutex::new(FileManager::new(
|
|
embedder_proxy.clone(),
|
|
Weak::new(),
|
|
))),
|
|
file_token: FileTokenCheck::NotRequired,
|
|
request_interceptor: Arc::new(Mutex::new(RequestInterceptor::new(embedder_proxy))),
|
|
cancellation_listener: Arc::new(Default::default()),
|
|
timing: ServoArc::new(Mutex::new(ResourceFetchTiming::new(
|
|
ResourceTimingType::Navigation,
|
|
))),
|
|
protocols: Arc::new(ProtocolRegistry::default()),
|
|
};
|
|
|
|
let url = ServoUrl::parse("http://www.example.org").unwrap();
|
|
let request = RequestBuilder::new(None, url.clone(), Referrer::NoReferrer)
|
|
.origin(url.origin())
|
|
.build();
|
|
let response = fetch_with_context(request, &mut context);
|
|
|
|
assert!(
|
|
response
|
|
.headers
|
|
.get(HEADERNAME)
|
|
.map(|v| v == HEADERVALUE)
|
|
.unwrap_or(false),
|
|
"The custom header does not exist or has an incorrect value!"
|
|
);
|
|
|
|
let body = response.body.lock().unwrap();
|
|
match &*body {
|
|
ResponseBody::Done(data) => {
|
|
assert_eq!(data, &EXPECTED_BODY, "Body content does not match");
|
|
},
|
|
_ => panic!("Expected ResponseBody::Done, but got {:?}", *body),
|
|
}
|
|
|
|
assert_eq!(
|
|
response.status.code(),
|
|
StatusCode::FOUND,
|
|
"Status code does not match!"
|
|
);
|
|
|
|
assert_eq!(
|
|
response.status.message(),
|
|
STATUS_MESSAGE,
|
|
"The status_message was not set correctly!"
|
|
);
|
|
}
|