From 2eaac7e3f9fd712d6802f380b77b86c860bd43e7 Mon Sep 17 00:00:00 2001 From: Sam Gibson Date: Sun, 16 Aug 2015 17:16:10 +1000 Subject: [PATCH] Adds tests for gzip/deflate response decoding --- components/net/http_loader.rs | 84 ++++++++++++++++++----------------- components/servo/Cargo.lock | 1 + tests/unit/net/Cargo.toml | 1 + tests/unit/net/http_loader.rs | 84 +++++++++++++++++++++++++++-------- tests/unit/net/lib.rs | 1 + 5 files changed, 112 insertions(+), 59 deletions(-) diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index ee93ef2acdb..d4a6b10f7b0 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -110,9 +110,6 @@ fn load_for_consumer(load_data: LoadData, Err(LoadError::MaxRedirects(url)) => { send_error(url, "too many redirects".to_string(), start_chan) } - Err(LoadError::UnsupportedContentEncodings(url)) => { - send_error(url, "no valid content encodings supported".to_string(), start_chan) - } Err(LoadError::Cors(url, msg)) | Err(LoadError::InvalidRedirect(url, msg)) | Err(LoadError::Decoding(url, msg)) => { @@ -139,6 +136,23 @@ pub trait HttpResponse: Read { fn headers(&self) -> &Headers; fn status(&self) -> StatusCode; fn status_raw(&self) -> &RawStatus; + + fn content_encoding(&self) -> Option { + match self.headers().get::() { + Some(&ContentEncoding(ref encodings)) => { + if encodings.contains(&Encoding::Gzip) { + Some(Encoding::Gzip) + } else if encodings.contains(&Encoding::Deflate) { + Some(Encoding::Deflate) + } else { + // TODO: Is this the correct behaviour? + None + } + } + + None => None + } + } } struct WrappedHttpResponse { @@ -268,8 +282,7 @@ pub enum LoadError { Ssl(Url, String), InvalidRedirect(Url, String), Decoding(Url, String), - MaxRedirects(Url), - UnsupportedContentEncodings(Url) + MaxRedirects(Url) } #[inline(always)] @@ -357,6 +370,7 @@ pub struct LoadResponse { pub metadata: Metadata } + impl Read for LoadResponse { #[inline] fn read(&mut self, buf: &mut [u8]) -> io::Result { @@ -372,6 +386,29 @@ impl LoadResponse { fn new(m: Metadata, d: Decoders) -> LoadResponse { LoadResponse { metadata: m, decoder: d } } + + fn from_http_response(response: R, m: Metadata) -> Result, LoadError> { + match response.content_encoding() { + Some(Encoding::Gzip) => { + let result = GzDecoder::new(response); + match result { + Ok(response_decoding) => { + return Ok(LoadResponse::new(m, Decoders::Gzip(response_decoding))); + } + Err(err) => { + return Err(LoadError::Decoding(m.final_url, err.to_string())); + } + } + } + Some(Encoding::Deflate) => { + let response_decoding = DeflateDecoder::new(response); + return Ok(LoadResponse::new(m, Decoders::Deflate(response_decoding))); + } + _ => { + return Ok(LoadResponse::new(m, Decoders::Plain(response))); + } + } + } } enum Decoders { @@ -578,42 +615,7 @@ pub fn load(mut load_data: LoadData, net_event_response))).unwrap(); } - let selected_content_encoding = match response.headers().get::() { - Some(&ContentEncoding(ref encodings)) => { - if encodings.contains(&Encoding::Gzip) { - Some(Encoding::Gzip) - } else if encodings.contains(&Encoding::Deflate) { - Some(Encoding::Deflate) - } else { - return Err(LoadError::UnsupportedContentEncodings(url.clone())) - } - } - None => { - None - } - }; - - match selected_content_encoding { - Some(Encoding::Gzip) => { - let result = GzDecoder::new(response); - match result { - Ok(response_decoding) => { - return Ok(LoadResponse::new(metadata, Decoders::Gzip(response_decoding))); - } - Err(err) => { - return Err(LoadError::Decoding(metadata.final_url, err.to_string())); - } - } - } - Some(Encoding::Deflate) => { - let response_decoding = DeflateDecoder::new(response); - return Ok(LoadResponse::new(metadata, Decoders::Deflate(response_decoding))); - } - None => { - return Ok(LoadResponse::new(metadata, Decoders::Plain(response))); - } - _ => return Err(LoadError::UnsupportedContentEncodings(url.clone())) - } + return LoadResponse::from_http_response(response, metadata) } } diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock index 3e880ad7a2e..f52014d7269 100644 --- a/components/servo/Cargo.lock +++ b/components/servo/Cargo.lock @@ -1053,6 +1053,7 @@ name = "net_tests" version = "0.0.1" dependencies = [ "cookie 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", + "flate2 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", "ipc-channel 0.1.0 (git+https://github.com/pcwalton/ipc-channel)", "net 0.0.1", diff --git a/tests/unit/net/Cargo.toml b/tests/unit/net/Cargo.toml index 62c72b975bc..fdd82f7fb6d 100644 --- a/tests/unit/net/Cargo.toml +++ b/tests/unit/net/Cargo.toml @@ -25,3 +25,4 @@ cookie = "0.1" hyper = "0.6" url = "0.2" time = "0.1" +flate2 = "0.2.0" diff --git a/tests/unit/net/http_loader.rs b/tests/unit/net/http_loader.rs index 3e61ec71ed0..956a9b073e5 100644 --- a/tests/unit/net/http_loader.rs +++ b/tests/unit/net/http_loader.rs @@ -12,18 +12,19 @@ use hyper::method::Method; use hyper::http::RawStatus; use hyper::status::StatusCode; use hyper::header::{Headers, Location, ContentLength}; -use std::io::{self, Read}; -use std::cmp::{self}; +use std::io::{self, Write, Read, Cursor}; +use flate2::write::{GzEncoder, DeflateEncoder}; +use flate2::Compression; use std::borrow::Cow; fn respond_with(body: Vec) -> MockResponse { let mut headers = Headers::new(); - headers.set(ContentLength(body.len() as u64)); - - respond_with_headers(body, headers) + respond_with_headers(body, &mut headers) } -fn respond_with_headers(body: Vec, headers: Headers) -> MockResponse { +fn respond_with_headers(body: Vec, headers: &mut Headers) -> MockResponse { + headers.set(ContentLength(body.len() as u64)); + MockResponse::new( headers.clone(), StatusCode::Ok, @@ -40,7 +41,7 @@ fn read_response(reader: &mut Read) -> String { String::from_utf8(buf).unwrap() }, Ok(_) => "".to_string(), - _ => panic!("problem reading response") + Err(e) => panic!("problem reading response {}", e) } } @@ -48,23 +49,18 @@ struct MockResponse { h: Headers, sc: StatusCode, sr: RawStatus, - msg: Vec + msg: Cursor> } impl MockResponse { fn new(h: Headers, sc: StatusCode, sr: RawStatus, msg: Vec) -> MockResponse { - MockResponse { h: h, sc: sc, sr: sr, msg: msg } + MockResponse { h: h, sc: sc, sr: sr, msg: Cursor::new(msg) } } } impl Read for MockResponse { fn read(&mut self, buf: &mut [u8]) -> io::Result { - let buf_len = buf.len(); - for (a, b) in buf.iter_mut().zip(&self.msg[0 .. cmp::min(buf_len, self.msg.len())]) { - *a = *b - } - - Ok(cmp::min(buf.len(), self.msg.len())) + self.msg.read(buf) } } @@ -112,8 +108,8 @@ fn response_for_request_type(t: RequestType) -> Result RequestType::Text(b) => { Ok(respond_with(b)) }, - RequestType::WithHeaders(b, h) => { - Ok(respond_with_headers(b, h)) + RequestType::WithHeaders(b, mut h) => { + Ok(respond_with_headers(b, &mut h)) } } } @@ -212,6 +208,58 @@ impl HttpRequest for AssertMustHaveBodyRequest { } } +#[test] +fn test_load_should_decode_the_response_as_deflate_when_response_headers_have_content_encoding_deflate() { + struct Factory; + + impl HttpRequestFactory for Factory { + type R=MockRequest; + + fn create(&self, _: Url, _: Method) -> Result { + let mut e = DeflateEncoder::new(Vec::new(), Compression::Default); + e.write(b"Yay!").unwrap(); + let encoded_content = e.finish().unwrap(); + + let mut headers = Headers::new(); + headers.set_raw("Content-Encoding", vec![b"deflate".to_vec()]); + Ok(MockRequest::new(RequestType::WithHeaders(encoded_content, headers))) + } + } + + let url = Url::parse("http://mozilla.com").unwrap(); + let resource_mgr = new_resource_task(None, None); + let load_data = LoadData::new(url.clone(), None); + let mut response = load::(load_data, resource_mgr.clone(), None, &Factory).unwrap(); + + assert_eq!(read_response(&mut response), "Yay!"); +} + +#[test] +fn test_load_should_decode_the_response_as_gzip_when_response_headers_have_content_encoding_gzip() { + struct Factory; + + impl HttpRequestFactory for Factory { + type R=MockRequest; + + fn create(&self, _: Url, _: Method) -> Result { + let mut e = GzEncoder::new(Vec::new(), Compression::Default); + e.write(b"Yay!").unwrap(); + let encoded_content = e.finish().unwrap(); + + let mut headers = Headers::new(); + headers.set_raw("Content-Encoding", vec![b"gzip".to_vec()]); + Ok(MockRequest::new(RequestType::WithHeaders(encoded_content, headers))) + } + } + + let url = Url::parse("http://mozilla.com").unwrap(); + let resource_mgr = new_resource_task(None, None); + let load_data = LoadData::new(url.clone(), None); + let mut response = load::(load_data, resource_mgr.clone(), None, &Factory).unwrap(); + + assert_eq!(read_response(&mut response), "Yay!"); +} + #[test] fn test_load_doesnt_send_request_body_on_any_redirect() { struct Factory; @@ -494,7 +542,7 @@ fn test_load_follows_a_redirect() { let load_data = LoadData::new(url.clone(), None); match load::(load_data, resource_mgr, None, &Factory) { - Err(_) => panic!("expected to follow a redirect"), + Err(e) => panic!("expected to follow a redirect {:?}", e), Ok(mut lr) => { let response = read_response(&mut lr); assert_eq!(response, "Yay!".to_string()); diff --git a/tests/unit/net/lib.rs b/tests/unit/net/lib.rs index 203a554a5eb..8f7b0d1511f 100644 --- a/tests/unit/net/lib.rs +++ b/tests/unit/net/lib.rs @@ -11,6 +11,7 @@ extern crate url; extern crate util; extern crate time; extern crate hyper; +extern crate flate2; #[cfg(test)] mod cookie; #[cfg(test)] mod data_loader;