servo: Merge #14865 - Implement Subresource Integrity (from mrnayak:sri-fetch); r=jdm
authormrnayak <rmuddur@gmail.com>
Sat, 07 Jan 2017 23:14:37 -0800
changeset 340509 be74efd41f605c7e9c78fdc810f309f4c52a2956
parent 340508 73ae6d5540ed47b99557a811e34622b3757731dd
child 340510 b148913a2abe738161b14ae0be1cbca1fe34a6e1
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
servo: Merge #14865 - Implement Subresource Integrity (from mrnayak:sri-fetch); r=jdm Implemented response validation part of https://w3c.github.io/webappsec-subresource-integrity/. Implemented step eighteen of the main fetch. If a request has integrity metadata, then following steps are performed 1) Wait for response body 2) If the response does not have a termination reason and response does not match request’s integrity metadata, set response and internalResponse to a network error. Dependency updated: html5ever-atoms from 0.1.2 to 0.1.3. This will not completely fix #14523, It will implement changes related to response validation. Request validation algorithm implementation needs CSP. I did not update any WPT-Test. In my local system, I found some assertion issue dependent on the order of execution of test-case. It would be helpful if someone could do "try" build on these changes to get wpt results. r? @jdm <!-- Please describe your changes on the following line: --> --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors <!-- Either: --> - [X] There are tests for these changes <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: f958dafcaeed643f1232a23f5c2d4f6ba141bfea
servo/Cargo.lock
servo/components/net/fetch/methods.rs
servo/components/net/http_loader.rs
servo/components/net/lib.rs
servo/components/net/subresource_integrity.rs
servo/components/net_traits/request.rs
servo/components/script/dom/htmllinkelement.rs
servo/components/script/dom/htmlscriptelement.rs
servo/components/script/dom/webidls/HTMLLinkElement.webidl
servo/components/script/dom/webidls/HTMLScriptElement.webidl
servo/components/script/stylesheet_loader.rs
servo/tests/unit/net/fetch.rs
servo/tests/unit/net/lib.rs
servo/tests/unit/net/subresource_integrity.rs
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -1103,30 +1103,30 @@ dependencies = [
 
 [[package]]
 name = "html5ever"
 version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "html5ever-atoms 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "mac 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf_codegen 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "syn 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "tendril 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "html5ever-atoms"
-version = "0.1.2"
+version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "string_cache 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "string_cache_codegen 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1301,17 +1301,17 @@ dependencies = [
  "canvas_traits 0.0.1",
  "cssparser 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "gfx 0.0.1",
  "gfx_traits 0.0.1",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "html5ever-atoms 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "ordered-float 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
@@ -2259,17 +2259,17 @@ dependencies = [
  "devtools_traits 0.0.1",
  "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "gfx_traits 0.0.1",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "html5ever 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "html5ever-atoms 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "hyper_serde 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "image 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "js 0.1.3 (git+https://github.com/servo/rust-mozjs)",
  "jstraceable_derive 0.0.1",
  "libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2319,17 +2319,17 @@ dependencies = [
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "canvas_traits 0.0.1",
  "cssparser 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "gfx_traits 0.0.1",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "html5ever-atoms 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "plugins 0.0.1",
  "profile_traits 0.0.1",
  "range 0.0.1",
@@ -2735,17 +2735,17 @@ dependencies = [
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "html5ever-atoms 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libbindgen 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "nsstring_vendor 0.1.0",
  "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2774,17 +2774,17 @@ dependencies = [
 
 [[package]]
 name = "style_tests"
 version = "0.0.1"
 dependencies = [
  "app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "html5ever-atoms 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_atoms 0.0.1",
  "servo_config 0.0.1",
@@ -3307,17 +3307,17 @@ dependencies = [
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "xml5ever"
 version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "html5ever-atoms 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "mac 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf_codegen 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "tendril 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -3409,17 +3409,17 @@ dependencies = [
 "checksum glx 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b280007fa9c7442cfd1e0b1addb8d1a59240267110e8705f8f7e2c7bfb7e2f72"
 "checksum harfbuzz-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6b76113246f5c089dcf272cf89c3f61168a4d77b50ec5b2c1fab8c628c9ea762"
 "checksum heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "5a376f7402b85be6e0ba504243ecbc0709c48019ecc6286d0540c2e359050c88"
 "checksum heapsize_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b6876925b6c3de6f9073f016f425de0076ab68cf30522107fa586ae6524abfe"
 "checksum heartbeats-simple 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "78c0810722eacd0bdd3f1f691524bd9900bf8fed1947f6b883c10ddecd2560b1"
 "checksum heartbeats-simple-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53c4b67617665d7f4172f381f9843c1bec6a4fccc9a9226529e5b1be40dc1301"
 "checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58"
 "checksum html5ever 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a358fef34c3334e92cd34d83ce870a386334e605e7abe987a69a7078a4142c69"
-"checksum html5ever-atoms 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fd3fc831590ee7fcf693c673e4e3cbe14fbda44dc0f26d9bdc79cfc9f551dc05"
+"checksum html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4109e35fec157307b918eb9d5b7018e2fa771aea0c04831e22003ac4722fbd1b"
 "checksum httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e7a63e511f9edffbab707141fbb8707d1a3098615fb2adbd5769cdfcc9b17d"
 "checksum hyper 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)" = "bcb3fc65554155980167fb821d05c7c66177f92464976c0b676a19d9e03387a7"
 "checksum hyper_serde 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "572d2168173019de312a050a24f2ad33ac2ac7895a2139fbf21ee6b6f470a24e"
 "checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11"
 "checksum image 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)" = "76df2dce95fef56fd35dbc41c36e37b19aede703c6be7739e8b65d5788ffc728"
 "checksum immeta 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3e76ecb1d64979a91c7fc5b7c0495ef1467e3cbff759044f2b88878a5a845ef7"
 "checksum inflate 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e7e0062d2dc2f17d2f13750d95316ae8a2ff909af0fda957084f5defd87c43bb"
 "checksum io-surface 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c93eb4952ee5b903c4193391779f90209e1b75ba55911097fa494f35e975846"
--- a/servo/components/net/fetch/methods.rs
+++ b/servo/components/net/fetch/methods.rs
@@ -19,16 +19,17 @@ use net_traits::request::{RedirectMode, 
 use net_traits::request::{Type, Origin, Window};
 use net_traits::response::{Response, ResponseBody, ResponseType};
 use std::borrow::Cow;
 use std::fs::File;
 use std::io::Read;
 use std::mem;
 use std::rc::Rc;
 use std::sync::mpsc::{Sender, Receiver};
+use subresource_integrity::is_response_integrity_valid;
 
 pub type Target<'a> = &'a mut (FetchTaskTarget + Send);
 
 pub enum Data {
     Payload(Vec<u8>),
     Done,
 }
 
@@ -263,16 +264,17 @@ pub fn main_fetch(request: Rc<Request>,
             ResponseTainting::CorsTainting => ResponseType::Cors,
             ResponseTainting::Opaque => ResponseType::Opaque,
         };
         response.to_filtered(response_type)
     } else {
         response
     };
 
+    let mut response_loaded = false;
     {
         // Step 14
         let network_error_res;
         let internal_response = if let Some(error) = response.get_network_error() {
             network_error_res = Response::network_error(error.clone());
             &network_error_res
         } else {
             response.actual_response()
@@ -292,60 +294,43 @@ pub fn main_fetch(request: Rc<Request>,
                 Method::Head | Method::Connect => true,
                 _ => false })
             {
             // when Fetch is used only asynchronously, we will need to make sure
             // that nothing tries to write to the body at this point
             let mut body = internal_response.body.lock().unwrap();
             *body = ResponseBody::Empty;
         }
-
-        // Step 18
-        // TODO be able to compare response integrity against request integrity metadata
-        // if !response.is_network_error() {
-
-        //     // Substep 1
-        //     response.wait_until_done();
+    }
+     // Step 18
+    let response = if !response.is_network_error() && *request.integrity_metadata.borrow() != "" {
+        // Substep 1
+        wait_for_response(&response, target, done_chan);
+        response_loaded = true;
 
-        //     // Substep 2
-        //     if response.termination_reason.is_none() {
-        //         response = Response::network_error();
-        //         internal_response = Response::network_error();
-        //     }
-        // }
-    }
+        // Substep 2
+        let ref integrity_metadata = *request.integrity_metadata.borrow();
+        if response.termination_reason.is_none() &&
+           !is_response_integrity_valid(integrity_metadata, &response) {
+            Response::network_error(NetworkError::Internal("Subresource integrity validation failed".into()))
+        } else {
+            response
+        }
+    } else {
+        response
+    };
 
     // Step 19
     if request.synchronous {
         // process_response is not supposed to be used
         // by sync fetch, but we overload it here for simplicity
         target.process_response(&response);
-
-        if let Some(ref ch) = *done_chan {
-            loop {
-                match ch.1.recv()
-                        .expect("fetch worker should always send Done before terminating") {
-                    Data::Payload(vec) => {
-                        target.process_response_chunk(vec);
-                    }
-                    Data::Done => break,
-                }
-            }
-        } else {
-            let body = response.body.lock().unwrap();
-            if let ResponseBody::Done(ref vec) = *body {
-                // in case there was no channel to wait for, the body was
-                // obtained synchronously via basic_fetch for data/file/about/etc
-                // We should still send the body across as a chunk
-                target.process_response_chunk(vec.clone());
-            } else {
-                assert!(*body == ResponseBody::Empty)
-            }
+        if !response_loaded {
+            wait_for_response(&response, target, done_chan);
         }
-
         // overloaded similarly to process_response
         target.process_response_eof(&response);
         return response;
     }
 
     // Step 20
     if request.body.borrow().is_some() && matches!(request.current_url().scheme(), "http" | "https") {
         // XXXManishearth: We actually should be calling process_request
@@ -355,43 +340,49 @@ pub fn main_fetch(request: Rc<Request>,
         target.process_request_body(&request);
         target.process_request_eof(&request);
     }
 
     // Step 21
     target.process_response(&response);
 
     // Step 22
+    if !response_loaded {
+       wait_for_response(&response, target, done_chan);
+    }
+
+    // Step 24
+    target.process_response_eof(&response);
+
+    // TODO remove this line when only asynchronous fetches are used
+    return response;
+}
+
+fn wait_for_response(response: &Response, target: Target, done_chan: &mut DoneChannel) {
     if let Some(ref ch) = *done_chan {
         loop {
             match ch.1.recv()
                     .expect("fetch worker should always send Done before terminating") {
                 Data::Payload(vec) => {
                     target.process_response_chunk(vec);
-                }
+                },
                 Data::Done => break,
             }
         }
     } else {
         let body = response.body.lock().unwrap();
         if let ResponseBody::Done(ref vec) = *body {
             // in case there was no channel to wait for, the body was
             // obtained synchronously via basic_fetch for data/file/about/etc
             // We should still send the body across as a chunk
             target.process_response_chunk(vec.clone());
         } else {
             assert!(*body == ResponseBody::Empty)
         }
     }
-
-    // Step 24
-    target.process_response_eof(&response);
-
-    // TODO remove this line when only asynchronous fetches are used
-    return response;
 }
 
 /// [Basic fetch](https://fetch.spec.whatwg.org#basic-fetch)
 fn basic_fetch(request: Rc<Request>,
                cache: &mut CorsCache,
                target: Target,
                done_chan: &mut DoneChannel,
                context: &FetchContext)
@@ -513,8 +504,9 @@ fn is_null_body_status(status: &Option<S
         Some(status) => match status {
             StatusCode::SwitchingProtocols | StatusCode::NoContent |
                 StatusCode::ResetContent | StatusCode::NotModified => true,
             _ => false
         },
         _ => false
     }
 }
+
--- a/servo/components/net/http_loader.rs
+++ b/servo/components/net/http_loader.rs
@@ -1373,17 +1373,17 @@ fn cors_check(request: Rc<Request>, resp
     }
 
     // Step 5
     if request.credentials_mode != CredentialsMode::Include {
         return Ok(());
     }
 
     // Step 6
-    let credentials = request.headers.borrow().get::<AccessControlAllowCredentials>().cloned();
+    let credentials = response.headers.get::<AccessControlAllowCredentials>().cloned();
 
     // Step 7
     if credentials.is_some() {
         return Ok(());
     }
 
     // Step 8
     Err(())
--- a/servo/components/net/lib.rs
+++ b/servo/components/net/lib.rs
@@ -55,18 +55,18 @@ pub mod cookie_storage;
 mod data_loader;
 pub mod filemanager_thread;
 pub mod hsts;
 mod http_loader;
 pub mod image_cache_thread;
 pub mod mime_classifier;
 pub mod resource_thread;
 mod storage_thread;
+pub mod subresource_integrity;
 mod websocket_loader;
-
 /// An implementation of the [Fetch specification](https://fetch.spec.whatwg.org/)
 pub mod fetch {
     pub mod cors_cache;
     pub mod methods;
 }
 
 /// A module for re-exports of items used in unit tests.
 pub mod test {
new file mode 100644
--- /dev/null
+++ b/servo/components/net/subresource_integrity.rs
@@ -0,0 +1,177 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use net_traits::response::{Response, ResponseBody, ResponseType};
+use openssl::crypto::hash::{hash, Type as MessageDigest};
+use rustc_serialize::base64::{STANDARD, ToBase64};
+use std::iter::Filter;
+use std::str::Split;
+use std::sync::MutexGuard;
+const SUPPORTED_ALGORITHM: &'static [&'static str] = &[
+    "sha256",
+    "sha384",
+    "sha512",
+];
+pub type StaticCharVec = &'static [char];
+/// A "space character" according to:
+///
+/// https://html.spec.whatwg.org/multipage/#space-character
+pub static HTML_SPACE_CHARACTERS: StaticCharVec = &[
+    '\u{0020}',
+    '\u{0009}',
+    '\u{000a}',
+    '\u{000c}',
+    '\u{000d}',
+];
+#[derive(Clone)]
+pub struct SriEntry {
+    pub alg: String,
+    pub val: String,
+    // TODO : Current version of spec does not define any option.
+    // Can be refactored into appropriate datastructure when future
+    // spec has more details.
+    pub opt: Option<String>,
+}
+
+impl SriEntry {
+    pub fn new(alg: &str, val: &str, opt: Option<String>) -> SriEntry {
+        SriEntry {
+            alg: alg.to_owned(),
+            val: val.to_owned(),
+            opt: opt,
+        }
+    }
+}
+
+/// https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
+pub fn parsed_metadata(integrity_metadata: &str) -> Vec<SriEntry> {
+    // Step 1
+    let mut result = vec![];
+
+    // Step 3
+    let tokens = split_html_space_chars(integrity_metadata);
+    for token in tokens {
+        let parsed_data: Vec<&str> = token.split("-").collect();
+
+        if parsed_data.len() > 1 {
+            let alg = parsed_data[0];
+
+            if !SUPPORTED_ALGORITHM.contains(&alg) {
+                continue;
+            }
+
+            let data: Vec<&str> = parsed_data[1].split("?").collect();
+            let digest = data[0];
+
+            let opt = if data.len() > 1 {
+                Some(data[1].to_owned())
+            } else {
+                None
+            };
+
+            result.push(SriEntry::new(alg, digest, opt));
+        }
+    }
+
+    return result;
+}
+
+/// https://w3c.github.io/webappsec-subresource-integrity/#getprioritizedhashfunction
+pub fn get_prioritized_hash_function(hash_func_left: &str, hash_func_right: &str) -> Option<String> {
+    let left_priority = SUPPORTED_ALGORITHM.iter().position(|s| s.to_owned() == hash_func_left).unwrap();
+    let right_priority = SUPPORTED_ALGORITHM.iter().position(|s| s.to_owned() == hash_func_right).unwrap();
+
+    if left_priority == right_priority {
+        return None;
+    }
+    if left_priority > right_priority {
+        Some(hash_func_left.to_owned())
+    } else {
+        Some(hash_func_right.to_owned())
+    }
+
+}
+
+/// https://w3c.github.io/webappsec-subresource-integrity/#get-the-strongest-metadata
+pub fn get_strongest_metadata(integrity_metadata_list: Vec<SriEntry>) -> Vec<SriEntry> {
+    let mut result: Vec<SriEntry> = vec![integrity_metadata_list[0].clone()];
+    let mut current_algorithm = result[0].alg.clone();
+
+    for integrity_metadata in &integrity_metadata_list[1..] {
+        let prioritized_hash = get_prioritized_hash_function(&integrity_metadata.alg,
+                                                                 &*current_algorithm);
+        if prioritized_hash.is_none() {
+            result.push(integrity_metadata.clone());
+        } else if let Some(algorithm) = prioritized_hash {
+            if algorithm != current_algorithm {
+                result = vec![integrity_metadata.clone()];
+                current_algorithm = algorithm;
+            }
+        }
+    }
+
+    result
+}
+
+/// https://w3c.github.io/webappsec-subresource-integrity/#apply-algorithm-to-response
+fn apply_algorithm_to_response(body: MutexGuard<ResponseBody>,
+                               message_digest: MessageDigest)
+                               -> String {
+    if let ResponseBody::Done(ref vec) = *body {
+        let response_digest = hash(message_digest, vec);
+        response_digest.to_base64(STANDARD)
+    } else {
+        unreachable!("Tried to calculate digest of incomplete response body")
+    }
+}
+
+/// https://w3c.github.io/webappsec-subresource-integrity/#is-response-eligible
+fn is_eligible_for_integrity_validation(response: &Response) -> bool {
+    match response.response_type {
+        ResponseType::Basic | ResponseType::Default | ResponseType::Cors => true,
+        _ => false,
+    }
+}
+
+/// https://w3c.github.io/webappsec-subresource-integrity/#does-response-match-metadatalist
+pub fn is_response_integrity_valid(integrity_metadata: &str, response: &Response) -> bool {
+    let parsed_metadata_list: Vec<SriEntry> = parsed_metadata(integrity_metadata);
+
+    // Step 2 & 4
+    if parsed_metadata_list.is_empty() {
+        return true;
+    }
+
+    // Step 3
+    if !is_eligible_for_integrity_validation(response) {
+        return false;
+    }
+
+    // Step 5
+    let metadata: Vec<SriEntry> = get_strongest_metadata(parsed_metadata_list);
+    for item in metadata {
+        let body = response.body.lock().unwrap();
+        let algorithm = item.alg;
+        let digest = item.val;
+
+        let message_digest = match &*algorithm {
+            "sha256" => MessageDigest::SHA256,
+            "sha384" => MessageDigest::SHA384,
+            "sha512" => MessageDigest::SHA512,
+            _ => continue,
+        };
+
+        if apply_algorithm_to_response(body, message_digest) == digest {
+            return true;
+        }
+    }
+
+    false
+}
+
+pub fn split_html_space_chars<'a>(s: &'a str) ->
+                                  Filter<Split<'a, StaticCharVec>, fn(&&str) -> bool> {
+    fn not_empty(&split: &&str) -> bool { !split.is_empty() }
+    s.split(HTML_SPACE_CHARACTERS).filter(not_empty as fn(&&str) -> bool)
+}
--- a/servo/components/net_traits/request.rs
+++ b/servo/components/net_traits/request.rs
@@ -153,16 +153,17 @@ pub struct RequestInit {
     // this should actually be set by fetch, but fetch
     // doesn't have info about the client right now
     pub origin: ServoUrl,
     // XXXManishearth these should be part of the client object
     pub referrer_url: Option<ServoUrl>,
     pub referrer_policy: Option<ReferrerPolicy>,
     pub pipeline_id: Option<PipelineId>,
     pub redirect_mode: RedirectMode,
+    pub integrity_metadata: String,
 }
 
 impl Default for RequestInit {
     fn default() -> RequestInit {
         RequestInit {
             method: Method::Get,
             url: ServoUrl::parse("about:blank").unwrap(),
             headers: Headers::new(),
@@ -176,16 +177,17 @@ impl Default for RequestInit {
             use_cors_preflight: false,
             credentials_mode: CredentialsMode::Omit,
             use_url_credentials: false,
             origin: ServoUrl::parse("about:blank").unwrap(),
             referrer_url: None,
             referrer_policy: None,
             pipeline_id: None,
             redirect_mode: RedirectMode::Follow,
+            integrity_metadata: "".to_owned(),
         }
     }
 }
 
 /// A [Request](https://fetch.spec.whatwg.org/#requests) as defined by the Fetch spec
 #[derive(Clone, HeapSizeOf)]
 pub struct Request {
     #[ignore_heap_size_of = "Defined in hyper"]
@@ -286,16 +288,17 @@ impl Request {
         *req.referrer.borrow_mut() = if let Some(url) = init.referrer_url {
             Referrer::ReferrerUrl(url)
         } else {
             Referrer::NoReferrer
         };
         req.referrer_policy.set(init.referrer_policy);
         req.pipeline_id.set(init.pipeline_id);
         req.redirect_mode.set(init.redirect_mode);
+        *req.integrity_metadata.borrow_mut() = init.integrity_metadata;
         req
     }
 
     pub fn url(&self) -> ServoUrl {
         self.url_list.borrow().first().unwrap().clone()
     }
 
     pub fn current_url(&self) -> ServoUrl {
--- a/servo/components/script/dom/htmllinkelement.rs
+++ b/servo/components/script/dom/htmllinkelement.rs
@@ -238,26 +238,34 @@ impl HTMLLinkElement {
         let element = self.upcast::<Element>();
 
         let mq_attribute = element.get_attribute(&ns!(), &local_name!("media"));
         let value = mq_attribute.r().map(|a| a.value());
         let mq_str = match value {
             Some(ref value) => &***value,
             None => "",
         };
+
         let mut css_parser = CssParser::new(&mq_str);
         let media = parse_media_query_list(&mut css_parser);
 
+        let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity"));
+        let integrity_val = im_attribute.r().map(|a| a.value());
+        let integrity_metadata = match integrity_val {
+            Some(ref value) => &***value,
+            None => "",
+        };
+
         // TODO: #8085 - Don't load external stylesheets if the node's mq
         // doesn't match.
         let loader = StylesheetLoader::for_element(self.upcast());
         loader.load(StylesheetContextSource::LinkElement {
             url: url,
             media: Some(media),
-        });
+        }, integrity_metadata.to_owned());
     }
 
     fn handle_favicon_url(&self, rel: &str, href: &str, sizes: &Option<String>) {
         let document = document_from_node(self);
         match document.base_url().join(href) {
             Ok(url) => {
                 let event = ConstellationMsg::NewFavicon(url.clone());
                 document.window().upcast::<GlobalScope>().constellation_chan().send(event).unwrap();
@@ -323,16 +331,22 @@ impl HTMLLinkElementMethods for HTMLLink
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-link-media
     make_getter!(Media, "media");
 
     // https://html.spec.whatwg.org/multipage/#dom-link-media
     make_setter!(SetMedia, "media");
 
+    // https://html.spec.whatwg.org/multipage/#dom-link-integrity
+    make_getter!(Integrity, "integrity");
+
+    // https://html.spec.whatwg.org/multipage/#dom-link-integrity
+    make_setter!(SetIntegrity, "integrity");
+
     // https://html.spec.whatwg.org/multipage/#dom-link-hreflang
     make_getter!(Hreflang, "hreflang");
 
     // https://html.spec.whatwg.org/multipage/#dom-link-hreflang
     make_setter!(SetHreflang, "hreflang");
 
     // https://html.spec.whatwg.org/multipage/#dom-link-type
     make_getter!(Type, "type");
--- a/servo/components/script/dom/htmlscriptelement.rs
+++ b/servo/components/script/dom/htmlscriptelement.rs
@@ -35,17 +35,16 @@ use net_traits::{FetchMetadata, FetchRes
 use net_traits::request::{CorsSettings, CredentialsMode, Destination, RequestInit, RequestMode, Type as RequestType};
 use network_listener::{NetworkListener, PreInvoke};
 use servo_atoms::Atom;
 use servo_url::ServoUrl;
 use std::ascii::AsciiExt;
 use std::cell::Cell;
 use std::sync::{Arc, Mutex};
 use style::str::{HTML_SPACE_CHARACTERS, StaticStringVec};
-
 #[dom_struct]
 pub struct HTMLScriptElement {
     htmlelement: HTMLElement,
 
     /// https://html.spec.whatwg.org/multipage/#already-started
     already_started: Cell<bool>,
 
     /// https://html.spec.whatwg.org/multipage/#parser-inserted
@@ -216,16 +215,17 @@ impl FetchResponseListener for ScriptCon
 }
 
 impl PreInvoke for ScriptContext {}
 
 /// https://html.spec.whatwg.org/multipage/#fetch-a-classic-script
 fn fetch_a_classic_script(script: &HTMLScriptElement,
                           url: ServoUrl,
                           cors_setting: Option<CorsSettings>,
+                          integrity_metadata: String,
                           character_encoding: EncodingRef) {
     let doc = document_from_node(script);
 
     // Step 1, 2.
     let request = RequestInit {
         url: url.clone(),
         type_: RequestType::Script,
         destination: Destination::Script,
@@ -240,16 +240,17 @@ fn fetch_a_classic_script(script: &HTMLS
         credentials_mode: match cors_setting {
             Some(CorsSettings::Anonymous) => CredentialsMode::CredentialsSameOrigin,
             _ => CredentialsMode::Include,
         },
         origin: doc.url(),
         pipeline_id: Some(script.global().pipeline_id()),
         referrer_url: Some(doc.url()),
         referrer_policy: doc.get_referrer_policy(),
+        integrity_metadata: integrity_metadata,
         .. RequestInit::default()
     };
 
     // TODO: Step 3, Add custom steps to perform fetch
 
     let context = Arc::new(Mutex::new(ScriptContext {
         elem: Trusted::new(script),
         character_encoding: character_encoding,
@@ -360,17 +361,23 @@ impl HTMLScriptElement {
             Some(ref s) if *s == "anonymous" => Some(CorsSettings::Anonymous),
             Some(ref s) if *s == "use-credentials" => Some(CorsSettings::UseCredentials),
             None => None,
             _ => unreachable!()
         };
 
         // TODO: Step 15: Nonce.
 
-        // TODO: Step 16: Parser state.
+        // Step 16: Integrity Metadata
+        let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity"));
+        let integrity_val = im_attribute.r().map(|a| a.value());
+        let integrity_metadata = match integrity_val {
+            Some(ref value) => &***value,
+            None => "",
+        };
 
         // TODO: Step 17: environment settings object.
 
         let base_url = doc.base_url();
         let is_external = match element.get_attribute(&ns!(), &local_name!("src")) {
             // Step 18.
             Some(ref src) => {
                 // Step 18.1.
@@ -388,17 +395,17 @@ impl HTMLScriptElement {
                         warn!("error parsing URL for script {}", &**src);
                         self.queue_error_event();
                         return;
                     }
                     Ok(url) => url,
                 };
 
                 // Step 18.6.
-                fetch_a_classic_script(self, url, cors_setting, encoding);
+                fetch_a_classic_script(self, url, cors_setting, integrity_metadata.to_owned(), encoding);
 
                 true
             },
             // TODO: Step 19.
             None => false,
         };
 
         // Step 20.
@@ -670,16 +677,21 @@ impl HTMLScriptElementMethods for HTMLSc
     // https://html.spec.whatwg.org/multipage/#dom-script-charset
     make_setter!(SetCharset, "charset");
 
     // https://html.spec.whatwg.org/multipage/#dom-script-defer
     make_bool_getter!(Defer, "defer");
     // https://html.spec.whatwg.org/multipage/#dom-script-defer
     make_bool_setter!(SetDefer, "defer");
 
+    // https://html.spec.whatwg.org/multipage/#dom-script-integrity
+    make_getter!(Integrity, "integrity");
+    // https://html.spec.whatwg.org/multipage/#dom-script-integrity
+    make_setter!(SetIntegrity, "integrity");
+
     // https://html.spec.whatwg.org/multipage/#dom-script-event
     make_getter!(Event, "event");
     // https://html.spec.whatwg.org/multipage/#dom-script-event
     make_setter!(SetEvent, "event");
 
     // https://html.spec.whatwg.org/multipage/#dom-script-htmlfor
     make_getter!(HtmlFor, "for");
     // https://html.spec.whatwg.org/multipage/#dom-script-htmlfor
--- a/servo/components/script/dom/webidls/HTMLLinkElement.webidl
+++ b/servo/components/script/dom/webidls/HTMLLinkElement.webidl
@@ -6,16 +6,17 @@
 interface HTMLLinkElement : HTMLElement {
            attribute DOMString href;
   //         attribute DOMString crossOrigin;
            attribute DOMString rel;
   readonly attribute DOMTokenList relList;
            attribute DOMString media;
            attribute DOMString hreflang;
            attribute DOMString type;
+           attribute DOMString integrity;
   // [SameObject, PutForwards=value] readonly attribute DOMTokenList sizes;
 
   // also has obsolete members
 };
 HTMLLinkElement implements LinkStyle;
 
 // https://html.spec.whatwg.org/multipage/#HTMLLinkElement-partial
 partial interface HTMLLinkElement {
--- a/servo/components/script/dom/webidls/HTMLScriptElement.webidl
+++ b/servo/components/script/dom/webidls/HTMLScriptElement.webidl
@@ -7,16 +7,17 @@ interface HTMLScriptElement : HTMLElemen
            attribute DOMString src;
            attribute DOMString type;
            attribute DOMString charset;
   //         attribute boolean async;
            attribute boolean defer;
            attribute DOMString? crossOrigin;
            [Pure]
            attribute DOMString text;
+           attribute DOMString integrity;
 
   // also has obsolete members
 };
 
 // https://html.spec.whatwg.org/multipage/#HTMLScriptElement-partial
 partial interface HTMLScriptElement {
            attribute DOMString event;
            attribute DOMString htmlFor;
--- a/servo/components/script/stylesheet_loader.rs
+++ b/servo/components/script/stylesheet_loader.rs
@@ -188,17 +188,17 @@ impl<'a> StylesheetLoader<'a> {
     pub fn for_element(element: &'a HTMLElement) -> Self {
         StylesheetLoader {
             elem: element,
         }
     }
 }
 
 impl<'a> StylesheetLoader<'a> {
-    pub fn load(&self, source: StylesheetContextSource) {
+    pub fn load(&self, source: StylesheetContextSource, integrity_metadata: String) {
         let url = source.url();
         let document = document_from_node(self.elem);
         let context = Arc::new(Mutex::new(StylesheetContext {
             elem: Trusted::new(&*self.elem),
             source: source,
             metadata: None,
             data: vec![],
             document: Trusted::new(&*document),
@@ -229,20 +229,21 @@ impl<'a> StylesheetLoader<'a> {
             type_: RequestType::Style,
             destination: Destination::Style,
             credentials_mode: CredentialsMode::Include,
             use_url_credentials: true,
             origin: document.url(),
             pipeline_id: Some(self.elem.global().pipeline_id()),
             referrer_url: Some(document.url()),
             referrer_policy: referrer_policy,
+            integrity_metadata: integrity_metadata,
             .. RequestInit::default()
         };
 
         document.fetch_async(LoadType::Stylesheet(url), request, action_sender);
     }
 }
 
 impl<'a> StyleStylesheetLoader for StylesheetLoader<'a> {
     fn request_stylesheet(&self, import: &Arc<RwLock<ImportRule>>) {
-        self.load(StylesheetContextSource::Import(import.clone()))
+        self.load(StylesheetContextSource::Import(import.clone()), "".to_owned())
     }
 }
--- a/servo/tests/unit/net/fetch.rs
+++ b/servo/tests/unit/net/fetch.rs
@@ -228,17 +228,16 @@ fn test_cors_preflight_fetch() {
     *request.referrer.borrow_mut() = Referrer::ReferrerUrl(target_url);
     *request.referrer_policy.get_mut() = Some(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() {
@@ -551,16 +550,63 @@ fn test_fetch_with_hsts() {
     // Set the flag.
     request.local_urls_only = false;
     let response = fetch_with_context(request, &context);
     let _ = server.close();
     assert_eq!(response.internal_response.unwrap().url().unwrap().scheme(),
                "https");
 }
 
+#[test]
+fn test_fetch_with_sri_network_error() {
+    static MESSAGE: &'static [u8] = b"alert('Hello, Network Error');";
+    let handler = move |_: HyperRequest, response: HyperResponse| {
+        response.send(MESSAGE).unwrap();
+    };
+    let (mut server, url) = make_server(handler);
+
+    let origin = Origin::Origin(url.origin());
+    let mut request = Request::new(url, Some(origin), false, None);
+    *request.referrer.borrow_mut() = Referrer::NoReferrer;
+    // To calulate hash use :
+    // echo -n "alert('Hello, Network Error');" | openssl dgst -sha384 -binary | openssl base64 -A
+    *request.integrity_metadata.borrow_mut() =
+           "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, response: HyperResponse| {
+        response.send(MESSAGE).unwrap();
+    };
+    let (mut server, url) = make_server(handler);
+
+    let origin = Origin::Origin(url.origin());
+    let mut request = Request::new(url, Some(origin), false, None);
+    *request.referrer.borrow_mut() = Referrer::NoReferrer;
+    // To calulate hash use :
+    // echo -n "alert('Hello, Network Error');" | openssl dgst -sha384 -binary | openssl base64 -A
+    *request.integrity_metadata.borrow_mut() =
+            "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);
+}
 fn setup_server_and_fetch(message: &'static [u8], redirect_cap: u32) -> Response {
     let handler = move |request: HyperRequest, mut response: HyperResponse| {
         let redirects = match request.uri {
             RequestUri::AbsolutePath(url) =>
                 url.split("/").collect::<String>().parse::<u32>().unwrap_or(0),
             RequestUri::AbsoluteUri(url) =>
                 url.path_segments().unwrap().next_back().unwrap().parse::<u32>().unwrap_or(0),
             _ => panic!()
@@ -737,17 +783,16 @@ fn test_fetch_async_returns_complete_res
 
     let origin = Origin::Origin(url.origin());
     let request = Request::new(url, Some(origin), false, None);
     *request.referrer.borrow_mut() = Referrer::NoReferrer;
 
     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, response: HyperResponse| {
         response.send(MESSAGE).unwrap();
--- a/servo/tests/unit/net/lib.rs
+++ b/servo/tests/unit/net/lib.rs
@@ -28,16 +28,17 @@ extern crate url;
 #[cfg(test)] mod data_loader;
 #[cfg(test)] mod file_loader;
 #[cfg(test)] mod fetch;
 #[cfg(test)] mod mime_classifier;
 #[cfg(test)] mod resource_thread;
 #[cfg(test)] mod hsts;
 #[cfg(test)] mod http_loader;
 #[cfg(test)] mod filemanager_thread;
+#[cfg(test)] mod subresource_integrity;
 
 use devtools_traits::DevtoolsControlMsg;
 use hyper::server::{Handler, Listening, Server};
 use net::fetch::cors_cache::CorsCache;
 use net::fetch::methods::{self, FetchContext};
 use net::filemanager_thread::FileManager;
 use net::test::HttpState;
 use net_traits::FetchTaskTarget;
new file mode 100644
--- /dev/null
+++ b/servo/tests/unit/net/subresource_integrity.rs
@@ -0,0 +1,92 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use net::subresource_integrity::{SriEntry, get_prioritized_hash_function, get_strongest_metadata};
+use net::subresource_integrity::{is_response_integrity_valid, parsed_metadata};
+use net_traits::response::{Response, ResponseBody};
+use servo_url::ServoUrl;
+
+#[test]
+fn test_get_prioritized_hash_function() {
+    let mut algorithm = get_prioritized_hash_function("sha256", "sha256");
+    assert_eq!(algorithm, None);
+
+    algorithm = get_prioritized_hash_function("sha256", "sha384");
+    assert_eq!(algorithm.unwrap(), "sha384");
+
+    algorithm = get_prioritized_hash_function("sha384", "sha512");
+    assert_eq!(algorithm.unwrap(), "sha512");
+}
+
+#[test]
+fn test_parsed_metadata_without_options() {
+    let integrity_metadata = "sha384-Hash1";
+    let ref parsed_metadata: SriEntry = parsed_metadata(integrity_metadata)[0];
+
+    assert_eq!(parsed_metadata.alg, "sha384");
+    assert_eq!(parsed_metadata.val, "Hash1");
+    assert!(parsed_metadata.opt.is_none());
+}
+
+#[test]
+fn test_parsed_metadata_with_options() {
+    let integrity_metadata = "sha384-Hash1?opt=23";
+    let ref parsed_metadata: SriEntry = parsed_metadata(integrity_metadata)[0];
+
+    assert_eq!(parsed_metadata.alg, "sha384");
+    assert_eq!(parsed_metadata.val, "Hash1");
+    assert!(parsed_metadata.opt.is_some());
+}
+
+#[test]
+fn test_parsed_metadata_with_malformed_integrity() {
+    let integrity_metadata = "Not a valid integrity";
+    let ref parsed_metadata_list: Vec<SriEntry> = parsed_metadata(integrity_metadata);
+
+    assert!(parsed_metadata_list.is_empty());
+}
+
+#[test]
+fn test_get_strongest_metadata_two_same_algorithm() {
+    let integrity_metadata = "sha512-Hash1 sha512-Hash2?opt=23";
+    let parsed_metadata_list: Vec<SriEntry> = parsed_metadata(integrity_metadata);
+
+    let strong_metadata: Vec<SriEntry> = get_strongest_metadata(parsed_metadata_list);
+    assert_eq!(strong_metadata.len(), 2);
+    assert_eq!(strong_metadata[0].alg, strong_metadata[1].alg);
+}
+
+#[test]
+fn test_get_strongest_metadata_different_algorithm() {
+    let integrity_metadata = "sha256-Hash0 sha384-Hash1 sha512-Hash2?opt=23";
+    let parsed_metadata_list: Vec<SriEntry> = parsed_metadata(integrity_metadata);
+
+    let strong_metadata: Vec<SriEntry> = get_strongest_metadata(parsed_metadata_list);
+    assert_eq!(strong_metadata.len(), 1);
+    assert_eq!(strong_metadata[0].alg, "sha512");
+}
+
+#[test]
+fn test_response_integrity_valid() {
+    let url: ServoUrl = ServoUrl::parse("http://servo.org").unwrap();
+    let response: Response = Response::new(url);
+
+    let integrity_metadata = "sha384-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO";
+    let response_body = "alert('Hello, world.');".to_owned().into_bytes();
+
+    *response.body.lock().unwrap() = ResponseBody::Done(response_body);
+    assert!(is_response_integrity_valid(integrity_metadata, &response));
+}
+
+#[test]
+fn test_response_integrity_invalid() {
+    let url: ServoUrl = ServoUrl::parse("http://servo.org").unwrap();
+    let response: Response = Response::new(url);
+
+    let integrity_metadata = "sha256-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO";
+    let response_body = "alert('Hello, world.');".to_owned().into_bytes();
+
+    *response.body.lock().unwrap() = ResponseBody::Done(response_body);
+    assert!(!is_response_integrity_valid(integrity_metadata, &response));
+}