mirror of
https://github.com/servo/servo.git
synced 2025-06-14 11:24:33 +00:00
Add simple implementation of content-security-policy on scripts / styles
This needs a lot more hooks before it'll actually be a good implementation, but for a start it can help get some feedback on if this is the right way to go about it. Part of servo/servo#4577
This commit is contained in:
parent
6d488f1be2
commit
b8f3e8bb2e
16 changed files with 175 additions and 41 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -736,6 +736,20 @@ dependencies = [
|
||||||
"webxr-api",
|
"webxr-api",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "content-security-policy"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f30ee9967a875968e66f6690e299f06781ed109cb82d10e0d60a126a38d61947"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"lazy_static",
|
||||||
|
"percent-encoding",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cookie"
|
name = "cookie"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
@ -2835,6 +2849,7 @@ name = "malloc_size_of"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"app_units",
|
"app_units",
|
||||||
|
"content-security-policy",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"cssparser",
|
"cssparser",
|
||||||
"euclid",
|
"euclid",
|
||||||
|
@ -3156,6 +3171,7 @@ dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"brotli",
|
"brotli",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"content-security-policy",
|
||||||
"cookie",
|
"cookie",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"devtools_traits",
|
"devtools_traits",
|
||||||
|
@ -3215,6 +3231,7 @@ dependencies = [
|
||||||
name = "net_traits"
|
name = "net_traits"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"content-security-policy",
|
||||||
"cookie",
|
"cookie",
|
||||||
"embedder_traits",
|
"embedder_traits",
|
||||||
"headers",
|
"headers",
|
||||||
|
@ -4122,6 +4139,7 @@ dependencies = [
|
||||||
"canvas_traits",
|
"canvas_traits",
|
||||||
"caseless",
|
"caseless",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"content-security-policy",
|
||||||
"cookie",
|
"cookie",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"cssparser",
|
"cssparser",
|
||||||
|
|
|
@ -21,10 +21,12 @@ servo = [
|
||||||
"url",
|
"url",
|
||||||
"webrender_api",
|
"webrender_api",
|
||||||
"xml5ever",
|
"xml5ever",
|
||||||
|
"content-security-policy",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
app_units = "0.7"
|
app_units = "0.7"
|
||||||
|
content-security-policy = {version = "0.3.0", features = ["serde"], optional = true}
|
||||||
crossbeam-channel = { version = "0.3", optional = true }
|
crossbeam-channel = { version = "0.3", optional = true }
|
||||||
cssparser = "0.25"
|
cssparser = "0.25"
|
||||||
euclid = "0.20"
|
euclid = "0.20"
|
||||||
|
|
|
@ -48,6 +48,8 @@
|
||||||
|
|
||||||
extern crate app_units;
|
extern crate app_units;
|
||||||
#[cfg(feature = "servo")]
|
#[cfg(feature = "servo")]
|
||||||
|
extern crate content_security_policy;
|
||||||
|
#[cfg(feature = "servo")]
|
||||||
extern crate crossbeam_channel;
|
extern crate crossbeam_channel;
|
||||||
extern crate cssparser;
|
extern crate cssparser;
|
||||||
extern crate euclid;
|
extern crate euclid;
|
||||||
|
@ -79,6 +81,8 @@ extern crate webrender_api;
|
||||||
#[cfg(feature = "servo")]
|
#[cfg(feature = "servo")]
|
||||||
extern crate xml5ever;
|
extern crate xml5ever;
|
||||||
|
|
||||||
|
#[cfg(feature = "servo")]
|
||||||
|
use content_security_policy as csp;
|
||||||
#[cfg(feature = "servo")]
|
#[cfg(feature = "servo")]
|
||||||
use serde_bytes::ByteBuf;
|
use serde_bytes::ByteBuf;
|
||||||
use std::hash::{BuildHasher, Hash};
|
use std::hash::{BuildHasher, Hash};
|
||||||
|
@ -833,6 +837,8 @@ malloc_size_of_is_0!(app_units::Au);
|
||||||
|
|
||||||
malloc_size_of_is_0!(cssparser::RGBA, cssparser::TokenSerializationType);
|
malloc_size_of_is_0!(cssparser::RGBA, cssparser::TokenSerializationType);
|
||||||
|
|
||||||
|
malloc_size_of_is_0!(csp::Destination);
|
||||||
|
|
||||||
#[cfg(feature = "url")]
|
#[cfg(feature = "url")]
|
||||||
impl MallocSizeOf for url::Host {
|
impl MallocSizeOf for url::Host {
|
||||||
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
|
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
|
||||||
|
|
|
@ -17,6 +17,7 @@ doctest = false
|
||||||
base64 = "0.10.1"
|
base64 = "0.10.1"
|
||||||
brotli = "3"
|
brotli = "3"
|
||||||
bytes = "0.4"
|
bytes = "0.4"
|
||||||
|
content-security-policy = {version = "0.3.0", features = ["serde"]}
|
||||||
cookie_rs = {package = "cookie", version = "0.11"}
|
cookie_rs = {package = "cookie", version = "0.11"}
|
||||||
crossbeam-channel = "0.3"
|
crossbeam-channel = "0.3"
|
||||||
devtools_traits = {path = "../devtools_traits"}
|
devtools_traits = {path = "../devtools_traits"}
|
||||||
|
|
|
@ -8,6 +8,7 @@ use crate::filemanager_thread::{fetch_file_in_chunks, FileManager, FILE_CHUNK_SI
|
||||||
use crate::http_loader::{determine_request_referrer, http_fetch, HttpState};
|
use crate::http_loader::{determine_request_referrer, http_fetch, HttpState};
|
||||||
use crate::http_loader::{set_default_accept, set_default_accept_language};
|
use crate::http_loader::{set_default_accept, set_default_accept_language};
|
||||||
use crate::subresource_integrity::is_response_integrity_valid;
|
use crate::subresource_integrity::is_response_integrity_valid;
|
||||||
|
use content_security_policy as csp;
|
||||||
use crossbeam_channel::{unbounded, Receiver, Sender};
|
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||||
use devtools_traits::DevtoolsControlMsg;
|
use devtools_traits::DevtoolsControlMsg;
|
||||||
use headers::{AccessControlExposeHeaders, ContentType, HeaderMapExt, Range};
|
use headers::{AccessControlExposeHeaders, ContentType, HeaderMapExt, Range};
|
||||||
|
@ -138,6 +139,30 @@ pub fn fetch_with_cors_cache(
|
||||||
main_fetch(request, cache, false, false, target, &mut None, &context);
|
main_fetch(request, cache, false, false, target, &mut None, &context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// https://www.w3.org/TR/CSP/#should-block-request
|
||||||
|
pub fn should_request_be_blocked_by_csp(request: &Request) -> csp::CheckResult {
|
||||||
|
let origin = match &request.origin {
|
||||||
|
Origin::Client => return csp::CheckResult::Allowed,
|
||||||
|
Origin::Origin(origin) => origin,
|
||||||
|
};
|
||||||
|
let csp_request = csp::Request {
|
||||||
|
url: request.url().into_url(),
|
||||||
|
origin: origin.clone().into_url_origin(),
|
||||||
|
redirect_count: request.redirect_count,
|
||||||
|
destination: request.destination,
|
||||||
|
initiator: csp::Initiator::None,
|
||||||
|
nonce: String::new(),
|
||||||
|
integrity_metadata: request.integrity_metadata.clone(),
|
||||||
|
parser_metadata: csp::ParserMetadata::None,
|
||||||
|
};
|
||||||
|
// TODO: Instead of ignoring violations, report them.
|
||||||
|
request
|
||||||
|
.csp_list
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| c.should_request_be_blocked(&csp_request).0)
|
||||||
|
.unwrap_or(csp::CheckResult::Allowed)
|
||||||
|
}
|
||||||
|
|
||||||
/// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch)
|
/// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch)
|
||||||
pub fn main_fetch(
|
pub fn main_fetch(
|
||||||
request: &mut Request,
|
request: &mut Request,
|
||||||
|
@ -163,8 +188,18 @@ pub fn main_fetch(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 2.2.
|
||||||
|
// TODO: Report violations.
|
||||||
|
|
||||||
|
// Step 2.4.
|
||||||
|
if should_request_be_blocked_by_csp(request) == csp::CheckResult::Blocked {
|
||||||
|
response = Some(Response::network_error(NetworkError::Internal(
|
||||||
|
"Blocked by Content-Security-Policy".into(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
// Step 3.
|
// Step 3.
|
||||||
// TODO: handle content security policy violations.
|
// TODO: handle request abort.
|
||||||
|
|
||||||
// Step 4.
|
// Step 4.
|
||||||
// TODO: handle upgrade to a potentially secure URL.
|
// TODO: handle upgrade to a potentially secure URL.
|
||||||
|
|
|
@ -13,6 +13,7 @@ test = false
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
content-security-policy = {version = "0.3.0", features = ["serde"]}
|
||||||
cookie = "0.11"
|
cookie = "0.11"
|
||||||
embedder_traits = { path = "../embedder_traits" }
|
embedder_traits = { path = "../embedder_traits" }
|
||||||
headers = "0.2"
|
headers = "0.2"
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
use crate::ReferrerPolicy;
|
use crate::ReferrerPolicy;
|
||||||
use crate::ResourceTimingType;
|
use crate::ResourceTimingType;
|
||||||
|
use content_security_policy::{self as csp, CspList};
|
||||||
use http::HeaderMap;
|
use http::HeaderMap;
|
||||||
use hyper::Method;
|
use hyper::Method;
|
||||||
use msg::constellation_msg::PipelineId;
|
use msg::constellation_msg::PipelineId;
|
||||||
|
@ -20,37 +21,7 @@ pub enum Initiator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A request [destination](https://fetch.spec.whatwg.org/#concept-request-destination)
|
/// A request [destination](https://fetch.spec.whatwg.org/#concept-request-destination)
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
|
pub use csp::Destination;
|
||||||
pub enum Destination {
|
|
||||||
None,
|
|
||||||
Audio,
|
|
||||||
Document,
|
|
||||||
Embed,
|
|
||||||
Font,
|
|
||||||
Image,
|
|
||||||
Manifest,
|
|
||||||
Object,
|
|
||||||
Report,
|
|
||||||
Script,
|
|
||||||
ServiceWorker,
|
|
||||||
SharedWorker,
|
|
||||||
Style,
|
|
||||||
Track,
|
|
||||||
Video,
|
|
||||||
Worker,
|
|
||||||
Xslt,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Destination {
|
|
||||||
/// https://fetch.spec.whatwg.org/#request-destination-script-like
|
|
||||||
#[inline]
|
|
||||||
pub fn is_script_like(&self) -> bool {
|
|
||||||
*self == Destination::Script ||
|
|
||||||
*self == Destination::ServiceWorker ||
|
|
||||||
*self == Destination::SharedWorker ||
|
|
||||||
*self == Destination::Worker
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A request [origin](https://fetch.spec.whatwg.org/#concept-request-origin)
|
/// A request [origin](https://fetch.spec.whatwg.org/#concept-request-origin)
|
||||||
#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
|
#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
|
||||||
|
@ -175,6 +146,11 @@ pub struct RequestBuilder {
|
||||||
pub pipeline_id: Option<PipelineId>,
|
pub pipeline_id: Option<PipelineId>,
|
||||||
pub redirect_mode: RedirectMode,
|
pub redirect_mode: RedirectMode,
|
||||||
pub integrity_metadata: String,
|
pub integrity_metadata: String,
|
||||||
|
// This is nominally a part of the client's global object.
|
||||||
|
// It is copied here to avoid having to reach across the thread
|
||||||
|
// boundary every time a redirect occurs.
|
||||||
|
#[ignore_malloc_size_of = "Defined in rust-content-security-policy"]
|
||||||
|
pub csp_list: Option<CspList>,
|
||||||
// to keep track of redirects
|
// to keep track of redirects
|
||||||
pub url_list: Vec<ServoUrl>,
|
pub url_list: Vec<ServoUrl>,
|
||||||
pub parser_metadata: ParserMetadata,
|
pub parser_metadata: ParserMetadata,
|
||||||
|
@ -206,6 +182,7 @@ impl RequestBuilder {
|
||||||
url_list: vec![],
|
url_list: vec![],
|
||||||
parser_metadata: ParserMetadata::Default,
|
parser_metadata: ParserMetadata::Default,
|
||||||
initiator: Initiator::None,
|
initiator: Initiator::None,
|
||||||
|
csp_list: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,6 +306,7 @@ impl RequestBuilder {
|
||||||
request.url_list = url_list;
|
request.url_list = url_list;
|
||||||
request.integrity_metadata = self.integrity_metadata;
|
request.integrity_metadata = self.integrity_metadata;
|
||||||
request.parser_metadata = self.parser_metadata;
|
request.parser_metadata = self.parser_metadata;
|
||||||
|
request.csp_list = self.csp_list;
|
||||||
request
|
request
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -396,6 +374,11 @@ pub struct Request {
|
||||||
pub response_tainting: ResponseTainting,
|
pub response_tainting: ResponseTainting,
|
||||||
/// <https://fetch.spec.whatwg.org/#concept-request-parser-metadata>
|
/// <https://fetch.spec.whatwg.org/#concept-request-parser-metadata>
|
||||||
pub parser_metadata: ParserMetadata,
|
pub parser_metadata: ParserMetadata,
|
||||||
|
// This is nominally a part of the client's global object.
|
||||||
|
// It is copied here to avoid having to reach across the thread
|
||||||
|
// boundary every time a redirect occurs.
|
||||||
|
#[ignore_malloc_size_of = "Defined in rust-content-security-policy"]
|
||||||
|
pub csp_list: Option<CspList>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Request {
|
impl Request {
|
||||||
|
@ -428,6 +411,7 @@ impl Request {
|
||||||
parser_metadata: ParserMetadata::Default,
|
parser_metadata: ParserMetadata::Default,
|
||||||
redirect_count: 0,
|
redirect_count: 0,
|
||||||
response_tainting: ResponseTainting::Basic,
|
response_tainting: ResponseTainting::Basic,
|
||||||
|
csp_list: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ bitflags = "1.0"
|
||||||
bluetooth_traits = {path = "../bluetooth_traits"}
|
bluetooth_traits = {path = "../bluetooth_traits"}
|
||||||
canvas_traits = {path = "../canvas_traits"}
|
canvas_traits = {path = "../canvas_traits"}
|
||||||
caseless = "0.2"
|
caseless = "0.2"
|
||||||
|
content-security-policy = {version = "0.3.0", features = ["serde"]}
|
||||||
cookie = "0.11"
|
cookie = "0.11"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
crossbeam-channel = "0.3"
|
crossbeam-channel = "0.3"
|
||||||
|
|
|
@ -54,6 +54,7 @@ use canvas_traits::webgl::{
|
||||||
use canvas_traits::webgl::{WebGLFramebufferId, WebGLMsgSender, WebGLPipeline, WebGLProgramId};
|
use canvas_traits::webgl::{WebGLFramebufferId, WebGLMsgSender, WebGLPipeline, WebGLProgramId};
|
||||||
use canvas_traits::webgl::{WebGLReceiver, WebGLRenderbufferId, WebGLSLVersion, WebGLSender};
|
use canvas_traits::webgl::{WebGLReceiver, WebGLRenderbufferId, WebGLSLVersion, WebGLSender};
|
||||||
use canvas_traits::webgl::{WebGLShaderId, WebGLSyncId, WebGLTextureId, WebGLVersion};
|
use canvas_traits::webgl::{WebGLShaderId, WebGLSyncId, WebGLTextureId, WebGLVersion};
|
||||||
|
use content_security_policy::CspList;
|
||||||
use crossbeam_channel::{Receiver, Sender};
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
use cssparser::RGBA;
|
use cssparser::RGBA;
|
||||||
use devtools_traits::{CSSError, TimelineMarkerType, WorkerId};
|
use devtools_traits::{CSSError, TimelineMarkerType, WorkerId};
|
||||||
|
@ -170,6 +171,8 @@ unsafe_no_jsmanaged_fields!(*mut JobQueue);
|
||||||
|
|
||||||
unsafe_no_jsmanaged_fields!(Cow<'static, str>);
|
unsafe_no_jsmanaged_fields!(Cow<'static, str>);
|
||||||
|
|
||||||
|
unsafe_no_jsmanaged_fields!(CspList);
|
||||||
|
|
||||||
/// Trace a `JSVal`.
|
/// Trace a `JSVal`.
|
||||||
pub fn trace_jsval(tracer: *mut JSTracer, description: &str, val: &Heap<JSVal>) {
|
pub fn trace_jsval(tracer: *mut JSTracer, description: &str, val: &Heap<JSVal>) {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
|
@ -110,6 +110,7 @@ use crate::task::TaskBox;
|
||||||
use crate::task_source::{TaskSource, TaskSourceName};
|
use crate::task_source::{TaskSource, TaskSourceName};
|
||||||
use crate::timers::OneshotTimerCallback;
|
use crate::timers::OneshotTimerCallback;
|
||||||
use canvas_traits::webgl::{self, WebGLContextId, WebGLMsg};
|
use canvas_traits::webgl::{self, WebGLContextId, WebGLMsg};
|
||||||
|
use content_security_policy::{self as csp, CspList};
|
||||||
use cookie::Cookie;
|
use cookie::Cookie;
|
||||||
use devtools_traits::ScriptToDevtoolsControlMsg;
|
use devtools_traits::ScriptToDevtoolsControlMsg;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
|
@ -137,6 +138,7 @@ use num_traits::ToPrimitive;
|
||||||
use percent_encoding::percent_decode;
|
use percent_encoding::percent_decode;
|
||||||
use profile_traits::ipc as profile_ipc;
|
use profile_traits::ipc as profile_ipc;
|
||||||
use profile_traits::time::{TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType};
|
use profile_traits::time::{TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType};
|
||||||
|
use ref_filter_map::ref_filter_map;
|
||||||
use ref_slice::ref_slice;
|
use ref_slice::ref_slice;
|
||||||
use script_layout_interface::message::{Msg, ReflowGoal};
|
use script_layout_interface::message::{Msg, ReflowGoal};
|
||||||
use script_traits::{AnimationState, DocumentActivity, MouseButton, MouseEventType};
|
use script_traits::{AnimationState, DocumentActivity, MouseButton, MouseEventType};
|
||||||
|
@ -148,7 +150,7 @@ use servo_atoms::Atom;
|
||||||
use servo_config::pref;
|
use servo_config::pref;
|
||||||
use servo_media::{ClientContextId, ServoMedia};
|
use servo_media::{ClientContextId, ServoMedia};
|
||||||
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
|
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::Cow;
|
||||||
use std::cell::{Cell, Ref, RefMut};
|
use std::cell::{Cell, Ref, RefMut};
|
||||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||||
use std::collections::{HashMap, HashSet, VecDeque};
|
use std::collections::{HashMap, HashSet, VecDeque};
|
||||||
|
@ -398,6 +400,9 @@ pub struct Document {
|
||||||
media_controls: DomRefCell<HashMap<String, Dom<ShadowRoot>>>,
|
media_controls: DomRefCell<HashMap<String, Dom<ShadowRoot>>>,
|
||||||
/// List of all WebGL context IDs that need flushing.
|
/// List of all WebGL context IDs that need flushing.
|
||||||
dirty_webgl_contexts: DomRefCell<HashSet<WebGLContextId>>,
|
dirty_webgl_contexts: DomRefCell<HashSet<WebGLContextId>>,
|
||||||
|
/// https://html.spec.whatwg.org/multipage/#concept-document-csp-list
|
||||||
|
#[ignore_malloc_size_of = "Defined in rust-content-security-policy"]
|
||||||
|
csp_list: DomRefCell<Option<CspList>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(JSTraceable, MallocSizeOf)]
|
#[derive(JSTraceable, MallocSizeOf)]
|
||||||
|
@ -1734,9 +1739,10 @@ impl Document {
|
||||||
pub fn fetch_async(
|
pub fn fetch_async(
|
||||||
&self,
|
&self,
|
||||||
load: LoadType,
|
load: LoadType,
|
||||||
request: RequestBuilder,
|
mut request: RequestBuilder,
|
||||||
fetch_target: IpcSender<FetchResponseMsg>,
|
fetch_target: IpcSender<FetchResponseMsg>,
|
||||||
) {
|
) {
|
||||||
|
request.csp_list = self.get_csp_list().map(|x| x.clone());
|
||||||
let mut loader = self.loader.borrow_mut();
|
let mut loader = self.loader.borrow_mut();
|
||||||
loader.fetch_async(load, request, fetch_target);
|
loader.fetch_async(load, request, fetch_target);
|
||||||
}
|
}
|
||||||
|
@ -2806,9 +2812,39 @@ impl Document {
|
||||||
shadow_roots_styles_changed: Cell::new(false),
|
shadow_roots_styles_changed: Cell::new(false),
|
||||||
media_controls: DomRefCell::new(HashMap::new()),
|
media_controls: DomRefCell::new(HashMap::new()),
|
||||||
dirty_webgl_contexts: DomRefCell::new(HashSet::new()),
|
dirty_webgl_contexts: DomRefCell::new(HashSet::new()),
|
||||||
|
csp_list: DomRefCell::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_csp_list(&self, csp_list: Option<CspList>) {
|
||||||
|
*self.csp_list.borrow_mut() = csp_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_csp_list(&self) -> Option<Ref<CspList>> {
|
||||||
|
ref_filter_map(self.csp_list.borrow(), Option::as_ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// https://www.w3.org/TR/CSP/#should-block-inline
|
||||||
|
pub fn should_elements_inline_type_behavior_be_blocked(
|
||||||
|
&self,
|
||||||
|
el: &Element,
|
||||||
|
type_: csp::InlineCheckType,
|
||||||
|
source: &str,
|
||||||
|
) -> csp::CheckResult {
|
||||||
|
let element = csp::Element {
|
||||||
|
nonce: el
|
||||||
|
.get_attribute(&ns!(), &local_name!("nonce"))
|
||||||
|
.map(|attr| Cow::Owned(attr.value().to_string())),
|
||||||
|
};
|
||||||
|
// TODO: Instead of ignoring violations, report them.
|
||||||
|
self.get_csp_list()
|
||||||
|
.map(|c| {
|
||||||
|
c.should_elements_inline_type_behavior_be_blocked(&element, type_, source)
|
||||||
|
.0
|
||||||
|
})
|
||||||
|
.unwrap_or(csp::CheckResult::Allowed)
|
||||||
|
}
|
||||||
|
|
||||||
/// Prevent any JS or layout from running until the corresponding call to
|
/// Prevent any JS or layout from running until the corresponding call to
|
||||||
/// `remove_script_and_layout_blocker`. Used to isolate periods in which
|
/// `remove_script_and_layout_blocker`. Used to isolate periods in which
|
||||||
/// the DOM is in an unstable state and should not be exposed to arbitrary
|
/// the DOM is in an unstable state and should not be exposed to arbitrary
|
||||||
|
|
|
@ -38,6 +38,7 @@ use crate::task_source::websocket::WebsocketTaskSource;
|
||||||
use crate::task_source::TaskSourceName;
|
use crate::task_source::TaskSourceName;
|
||||||
use crate::timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle};
|
use crate::timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle};
|
||||||
use crate::timers::{OneshotTimers, TimerCallback};
|
use crate::timers::{OneshotTimers, TimerCallback};
|
||||||
|
use content_security_policy::CspList;
|
||||||
use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId};
|
use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId};
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use ipc_channel::ipc::IpcSender;
|
use ipc_channel::ipc::IpcSender;
|
||||||
|
@ -812,6 +813,15 @@ impl GlobalScope {
|
||||||
pub fn get_user_agent(&self) -> Cow<'static, str> {
|
pub fn get_user_agent(&self) -> Cow<'static, str> {
|
||||||
self.user_agent.clone()
|
self.user_agent.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// https://www.w3.org/TR/CSP/#get-csp-of-object
|
||||||
|
pub fn get_csp_list(&self) -> Option<CspList> {
|
||||||
|
if let Some(window) = self.downcast::<Window>() {
|
||||||
|
return window.Document().get_csp_list().map(|c| c.clone());
|
||||||
|
}
|
||||||
|
// TODO: Worker and Worklet global scopes.
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn timestamp_in_ms(time: Timespec) -> u64 {
|
fn timestamp_in_ms(time: Timespec) -> u64 {
|
||||||
|
|
|
@ -27,6 +27,7 @@ use crate::dom::performanceresourcetiming::InitiatorType;
|
||||||
use crate::dom::virtualmethods::VirtualMethods;
|
use crate::dom::virtualmethods::VirtualMethods;
|
||||||
use crate::fetch::create_a_potential_CORS_request;
|
use crate::fetch::create_a_potential_CORS_request;
|
||||||
use crate::network_listener::{self, NetworkListener, PreInvoke, ResourceTimingListener};
|
use crate::network_listener::{self, NetworkListener, PreInvoke, ResourceTimingListener};
|
||||||
|
use content_security_policy as csp;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use encoding_rs::Encoding;
|
use encoding_rs::Encoding;
|
||||||
use html5ever::{LocalName, Prefix};
|
use html5ever::{LocalName, Prefix};
|
||||||
|
@ -428,7 +429,16 @@ impl HTMLScriptElement {
|
||||||
|
|
||||||
// TODO: Step 12: nomodule content attribute
|
// TODO: Step 12: nomodule content attribute
|
||||||
|
|
||||||
// TODO(#4577): Step 13: CSP.
|
// Step 13.
|
||||||
|
if !element.has_attribute(&local_name!("src")) &&
|
||||||
|
doc.should_elements_inline_type_behavior_be_blocked(
|
||||||
|
&element,
|
||||||
|
csp::InlineCheckType::Script,
|
||||||
|
&text,
|
||||||
|
) == csp::CheckResult::Blocked
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Step 14.
|
// Step 14.
|
||||||
let for_attribute = element.get_attribute(&ns!(), &local_name!("for"));
|
let for_attribute = element.get_attribute(&ns!(), &local_name!("for"));
|
||||||
|
|
|
@ -755,7 +755,9 @@ impl Into<RequestDestination> for NetTraitsRequestDestination {
|
||||||
NetTraitsRequestDestination::Object => RequestDestination::Object,
|
NetTraitsRequestDestination::Object => RequestDestination::Object,
|
||||||
NetTraitsRequestDestination::Report => RequestDestination::Report,
|
NetTraitsRequestDestination::Report => RequestDestination::Report,
|
||||||
NetTraitsRequestDestination::Script => RequestDestination::Script,
|
NetTraitsRequestDestination::Script => RequestDestination::Script,
|
||||||
NetTraitsRequestDestination::ServiceWorker => {
|
NetTraitsRequestDestination::ServiceWorker |
|
||||||
|
NetTraitsRequestDestination::AudioWorklet |
|
||||||
|
NetTraitsRequestDestination::PaintWorklet => {
|
||||||
panic!("ServiceWorker request destination should not be exposed to DOM")
|
panic!("ServiceWorker request destination should not be exposed to DOM")
|
||||||
},
|
},
|
||||||
NetTraitsRequestDestination::SharedWorker => RequestDestination::Sharedworker,
|
NetTraitsRequestDestination::SharedWorker => RequestDestination::Sharedworker,
|
||||||
|
|
|
@ -35,6 +35,7 @@ use crate::dom::text::Text;
|
||||||
use crate::dom::virtualmethods::vtable_for;
|
use crate::dom::virtualmethods::vtable_for;
|
||||||
use crate::network_listener::PreInvoke;
|
use crate::network_listener::PreInvoke;
|
||||||
use crate::script_thread::ScriptThread;
|
use crate::script_thread::ScriptThread;
|
||||||
|
use content_security_policy::{self as csp, CspList};
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use embedder_traits::resources::{self, Resource};
|
use embedder_traits::resources::{self, Resource};
|
||||||
use encoding_rs::Encoding;
|
use encoding_rs::Encoding;
|
||||||
|
@ -736,6 +737,31 @@ impl FetchResponseListener for ParserContext {
|
||||||
.and_then(|meta| meta.content_type)
|
.and_then(|meta| meta.content_type)
|
||||||
.map(Serde::into_inner)
|
.map(Serde::into_inner)
|
||||||
.map(Into::into);
|
.map(Into::into);
|
||||||
|
|
||||||
|
// https://www.w3.org/TR/CSP/#initialize-document-csp
|
||||||
|
// TODO: Implement step 1 (local scheme special case)
|
||||||
|
let csp_list = metadata.as_ref().and_then(|m| {
|
||||||
|
let h = m.headers.as_ref()?;
|
||||||
|
let mut csp = h.get_all("content-security-policy").iter();
|
||||||
|
// This silently ignores the CSP if it contains invalid Unicode.
|
||||||
|
// We should probably report an error somewhere.
|
||||||
|
let c = csp.next().and_then(|c| c.to_str().ok())?;
|
||||||
|
let mut csp_list = CspList::parse(
|
||||||
|
c,
|
||||||
|
csp::PolicySource::Header,
|
||||||
|
csp::PolicyDisposition::Enforce,
|
||||||
|
);
|
||||||
|
for c in csp {
|
||||||
|
let c = c.to_str().ok()?;
|
||||||
|
csp_list.append(CspList::parse(
|
||||||
|
c,
|
||||||
|
csp::PolicySource::Header,
|
||||||
|
csp::PolicyDisposition::Enforce,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Some(csp_list)
|
||||||
|
});
|
||||||
|
|
||||||
let parser = match ScriptThread::page_headers_available(&self.id, metadata) {
|
let parser = match ScriptThread::page_headers_available(&self.id, metadata) {
|
||||||
Some(parser) => parser,
|
Some(parser) => parser,
|
||||||
None => return,
|
None => return,
|
||||||
|
@ -744,6 +770,8 @@ impl FetchResponseListener for ParserContext {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parser.document.set_csp_list(csp_list);
|
||||||
|
|
||||||
self.parser = Some(Trusted::new(&*parser));
|
self.parser = Some(Trusted::new(&*parser));
|
||||||
|
|
||||||
match content_type {
|
match content_type {
|
||||||
|
|
|
@ -127,6 +127,7 @@ fn request_init_from_request(request: NetTraitsRequest) -> RequestBuilder {
|
||||||
url_list: vec![],
|
url_list: vec![],
|
||||||
parser_metadata: request.parser_metadata,
|
parser_metadata: request.parser_metadata,
|
||||||
initiator: request.initiator,
|
initiator: request.initiator,
|
||||||
|
csp_list: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,6 +156,7 @@ pub fn Fetch(
|
||||||
let timing_type = request.timing_type();
|
let timing_type = request.timing_type();
|
||||||
|
|
||||||
let mut request_init = request_init_from_request(request);
|
let mut request_init = request_init_from_request(request);
|
||||||
|
request_init.csp_list = global.get_csp_list().clone();
|
||||||
|
|
||||||
// Step 3
|
// Step 3
|
||||||
if global.downcast::<ServiceWorkerGlobalScope>().is_some() {
|
if global.downcast::<ServiceWorkerGlobalScope>().is_some() {
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[csp-blocked.html]
|
|
||||||
type: testharness
|
|
||||||
[Fetch is blocked by CSP, got a TypeError]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue