diff --git a/Cargo.lock b/Cargo.lock index 25d1dd886b9d8..aa691a4d094a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -305,18 +305,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "brotli" -version = "3.0.2" +version = "3.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "alloc-no-stdlib 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "alloc-stdlib 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "brotli-decompressor 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "brotli-decompressor 2.1.1 (git+https://github.com/servo/rust-brotli-decompressor?branch=async)", ] [[package]] name = "brotli-decompressor" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" +version = "2.1.1" +source = "git+https://github.com/servo/rust-brotli-decompressor?branch=async#309cacd2f15f4ce1cd611e68f7ad6f6d9f370ca4" dependencies = [ "alloc-no-stdlib 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "alloc-stdlib 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -675,6 +675,14 @@ dependencies = [ "build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crc32fast" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-channel" version = "0.3.0" @@ -2119,6 +2127,16 @@ dependencies = [ "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "libflate" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crc32fast 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "libloading" version = "0.5.0" @@ -2545,7 +2563,7 @@ name = "net" version = "0.0.1" dependencies = [ "base64 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", - "brotli 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "brotli 3.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", "cookie 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-channel 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2562,6 +2580,7 @@ dependencies = [ "immeta 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "ipc-channel 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libflate 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "malloc_size_of 0.0.1", "malloc_size_of_derive 0.0.1", @@ -4849,8 +4868,8 @@ dependencies = [ "checksum blurmock 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9c150fd617830fd121919bbd500a784507e8af1bae744efcf587591c65c375d4" "checksum blurz 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f6dae8337ff67fe8ead29a28a0115605753e6a5205d4b6017e9f42f198c3c50a" "checksum boxfnonce 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cbec60c560f322d8e3cd403f91d8908cfd965fff53ba97154bd1b9d90149d98e" -"checksum brotli 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "866aa77580d9501b6d87e0bcbb6605403df764d356b8ee65e1510bcba6a45fc0" -"checksum brotli-decompressor 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71867b8c3aaff6c493969d2166a9a9ff5321fcc6c6346115dc66b3dd3ff8bd5b" +"checksum brotli 3.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "43b92d759a5f8532e5b0bc06dc31593af01447db9e141c3b67bdb132e58c2844" +"checksum brotli-decompressor 2.1.1 (git+https://github.com/servo/rust-brotli-decompressor?branch=async)" = "" "checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" "checksum byte-slice-cast 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "28346c117b50270785fbc123bd6e4ecad20d0c6d5f43d081dc80a3abcc62be64" "checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" @@ -4880,6 +4899,7 @@ dependencies = [ "checksum core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)" = "56790968ab1c8a1202a102e6de05fc6e1ec87da99e4e93e9a7d13efbfc1e95a9" "checksum core-text 13.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a6705907bc253cee90b8551017836ce50904be305c99fec5ea74a8d72d799e58" "checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" +"checksum crc32fast 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e91d5240c6975ef33aeb5f148f35275c25eda8e8a5f95abe421978b05b8bf192" "checksum crossbeam-channel 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "548dfc2fa4933009668d4a51f9ef074b8af8f2d6a6410225d76d6b1c3c56ca6f" "checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3" "checksum crossbeam-deque 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3486aefc4c0487b9cb52372c97df0a48b8c249514af1ee99703bf70d2f2ceda1" @@ -5000,6 +5020,7 @@ dependencies = [ "checksum leaky-cow 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40a8225d44241fd324a8af2806ba635fc7c8a7e9a7de4d5cf3ef54e71f5926fc" "checksum libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)" = "10923947f84a519a45c8fefb7dd1b3e8c08747993381adee176d7a82b4195311" "checksum libdbus-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "99c78106156a964aadc1c59f7798276967be6705243b60f3ab7e131e3841db88" +"checksum libflate 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "bff3ac7d6f23730d3b533c35ed75eef638167634476a499feef16c428d74b57b" "checksum libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3ad660d7cb8c5822cd83d10897b0f1f1526792737a179e73896152f85b88c2" "checksum libz-sys 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "87f737ad6cc6fd6eefe3d9dc5412f1573865bded441300904d2f42269e140f16" "checksum line_drawing 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5cc7ad3d82c845bdb5dde34ffdcc7a5fb4d2996e1e1ee0f19c33bc80e15196b9" diff --git a/Cargo.toml b/Cargo.toml index 0cfd8e3105370..7422eef7b163d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,4 @@ opt-level = 3 # # [patch."https://github.com/servo/"] # = { path = "/path/to/local/checkout" } +brotli-decompressor = { git = "https://github.com/servo/rust-brotli-decompressor", branch = "async" } diff --git a/components/net/Cargo.toml b/components/net/Cargo.toml index f094e81747329..5095697053442 100644 --- a/components/net/Cargo.toml +++ b/components/net/Cargo.toml @@ -22,6 +22,7 @@ crossbeam-channel = "0.3" devtools_traits = {path = "../devtools_traits"} embedder_traits = { path = "../embedder_traits" } flate2 = "1" +futures = "0.1" headers-core = "0.0.1" headers-ext = "0.0.3" http = "0.1" @@ -31,6 +32,7 @@ hyper-openssl = "0.7" immeta = "0.4" ipc-channel = "0.11" lazy_static = "1" +libflate = "0.1" log = "0.4" malloc_size_of = { path = "../malloc_size_of" } malloc_size_of_derive = { path = "../malloc_size_of_derive" } diff --git a/components/net/connector.rs b/components/net/connector.rs index c93f82df5e281..1306c2d560939 100644 --- a/components/net/connector.rs +++ b/components/net/connector.rs @@ -3,9 +3,6 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::hosts::replace_host; -use crate::http_loader::Decoder; -use flate2::read::GzDecoder; -use hyper::body::Payload; use hyper::client::connect::{Connect, Destination}; use hyper::client::HttpConnector as HyperHttpConnector; use hyper::rt::Future; @@ -13,9 +10,7 @@ use hyper::{Body, Client}; use hyper_openssl::HttpsConnector; use openssl::ssl::{SslConnector, SslConnectorBuilder, SslMethod, SslOptions}; use openssl::x509; -use std::io::{Cursor, Read}; use tokio::prelude::future::Executor; -use tokio::prelude::{Async, Stream}; pub const BUF_SIZE: usize = 32768; @@ -47,105 +42,6 @@ impl Connect for HttpConnector { } pub type Connector = HttpsConnector; -pub struct WrappedBody { - pub body: Body, - pub decoder: Decoder, -} - -impl WrappedBody { - pub fn new(body: Body) -> Self { - Self::new_with_decoder(body, Decoder::Plain) - } - - pub fn new_with_decoder(body: Body, decoder: Decoder) -> Self { - WrappedBody { body, decoder } - } -} - -impl Payload for WrappedBody { - type Data = ::Data; - type Error = ::Error; - fn poll_data(&mut self) -> Result>, Self::Error> { - self.body.poll_data() - } -} - -impl Stream for WrappedBody { - type Item = ::Item; - type Error = ::Error; - fn poll(&mut self) -> Result>, Self::Error> { - self.body.poll().map(|res| { - res.map(|maybe_chunk| { - if let Some(chunk) = maybe_chunk { - match self.decoder { - Decoder::Plain => Some(chunk), - Decoder::Gzip(Some(ref mut decoder)) => { - let mut buf = vec![0; BUF_SIZE]; - decoder.get_mut().get_mut().extend(chunk.as_ref()); - let len = decoder.read(&mut buf).ok()?; - buf.truncate(len); - Some(buf.into()) - }, - Decoder::Gzip(None) => { - let mut buf = vec![0; BUF_SIZE]; - let mut decoder = GzDecoder::new(Cursor::new(chunk.into_bytes())); - let len = decoder.read(&mut buf).ok()?; - buf.truncate(len); - self.decoder = Decoder::Gzip(Some(decoder)); - Some(buf.into()) - }, - Decoder::Deflate(ref mut decoder) => { - let mut buf = vec![0; BUF_SIZE]; - decoder.get_mut().get_mut().extend(chunk.as_ref()); - let len = decoder.read(&mut buf).ok()?; - buf.truncate(len); - Some(buf.into()) - }, - Decoder::Brotli(ref mut decoder) => { - let mut buf = vec![0; BUF_SIZE]; - decoder.get_mut().get_mut().extend(chunk.as_ref()); - let len = decoder.read(&mut buf).ok()?; - buf.truncate(len); - Some(buf.into()) - }, - } - } else { - // Hyper is done downloading but we still have uncompressed data - match self.decoder { - Decoder::Gzip(Some(ref mut decoder)) => { - let mut buf = vec![0; BUF_SIZE]; - let len = decoder.read(&mut buf).ok()?; - if len == 0 { - return None; - } - buf.truncate(len); - Some(buf.into()) - }, - Decoder::Deflate(ref mut decoder) => { - let mut buf = vec![0; BUF_SIZE]; - let len = decoder.read(&mut buf).ok()?; - if len == 0 { - return None; - } - buf.truncate(len); - Some(buf.into()) - }, - Decoder::Brotli(ref mut decoder) => { - let mut buf = vec![0; BUF_SIZE]; - let len = decoder.read(&mut buf).ok()?; - if len == 0 { - return None; - } - buf.truncate(len); - Some(buf.into()) - }, - _ => None, - } - } - }) - }) - } -} pub fn create_ssl_connector_builder(certs: &str) -> SslConnectorBuilder { // certs include multiple certificates. We could add all of them at once, @@ -189,7 +85,7 @@ pub fn create_ssl_connector_builder(certs: &str) -> SslConnectorBuilder { pub fn create_http_client( ssl_connector_builder: SslConnectorBuilder, executor: E, -) -> Client +) -> Client where E: Executor + Send + 'static>> + Sync + Send + 'static, { diff --git a/components/net/decoder.rs b/components/net/decoder.rs new file mode 100644 index 0000000000000..bad25eb10cd91 --- /dev/null +++ b/components/net/decoder.rs @@ -0,0 +1,483 @@ +/* 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/. */ + +//! Adapted from an implementation in reqwest. + +/*! +A potentially non-blocking response decoder. + +The decoder wraps a stream of chunks and produces a new stream of decompressed chunks. +The decompressed chunks aren't guaranteed to align to the compressed ones. + +If the response is plaintext then no additional work is carried out. +Chunks are just passed along. + +If the response is gzip, then the chunks are decompressed into a buffer. +Slices of that buffer are emitted as new chunks. + +This module consists of a few main types: + +- `ReadableChunks` is a `Read`-like wrapper around a stream +- `Decoder` is a layer over `ReadableChunks` that applies the right decompression + +The following types directly support the gzip compression case: + +- `Pending` is a non-blocking constructor for a `Decoder` in case the body needs to be checked for EOF +- `Peeked` is a buffer that keeps a few bytes available so `libflate`s `read_exact` calls won't fail +*/ + +use crate::connector::BUF_SIZE; +use brotli::Decompressor; +use bytes::{Buf, BufMut, BytesMut}; +use flate2::read::DeflateDecoder; +use futures::{Async, Future, Poll, Stream}; +use hyper::header::{HeaderValue, CONTENT_ENCODING, TRANSFER_ENCODING}; +use hyper::{self, Body, Chunk, Response}; +use libflate::non_blocking::gzip; +use std::cmp; +use std::fmt; +use std::io::{self, Read}; +use std::mem; + +pub enum Error { + Io(io::Error), + Hyper(hyper::error::Error), +} + +impl From for Error { + fn from(err: io::Error) -> Error { + Error::Io(err) + } +} + +impl From for Error { + fn from(err: hyper::error::Error) -> Error { + Error::Hyper(err) + } +} + +const INIT_BUFFER_SIZE: usize = 8192; + +/// A response decompressor over a non-blocking stream of chunks. +/// +/// The inner decoder may be constructed asynchronously. +pub struct Decoder { + inner: Inner, +} + +#[derive(PartialEq)] +enum DecoderType { + Gzip, + Brotli, + Deflate, +} + +enum Inner { + /// A `PlainText` decoder just returns the response content as is. + PlainText(Body), + /// A `Gzip` decoder will uncompress the gzipped response content before returning it. + Gzip(Gzip), + /// A `Delfate` decoder will uncompress the inflated response content before returning it. + Deflate(Deflate), + /// A `Brotli` decoder will uncompress the brotli-encoded response content before returning it. + Brotli(Brotli), + /// A decoder that doesn't have a value yet. + Pending(Pending), +} + +/// A future attempt to poll the response body for EOF so we know whether to use gzip or not. +struct Pending { + body: ReadableChunks, + type_: DecoderType, +} + +/// A gzip decoder that reads from a `libflate::gzip::Decoder` into a `BytesMut` and emits the results +/// as a `Chunk`. +struct Gzip { + inner: Box>>>, + buf: BytesMut, +} + +impl fmt::Debug for Decoder { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Decoder").finish() + } +} + +impl Decoder { + /// A plain text decoder. + /// + /// This decoder will emit the underlying chunks as-is. + #[inline] + fn plain_text(body: Body) -> Decoder { + Decoder { + inner: Inner::PlainText(body), + } + } + + /// A pending decoder. + /// + /// This decoder will buffer and decompress chunks that are encoded in the expected format. + #[inline] + fn pending(body: Body, type_: DecoderType) -> Decoder { + Decoder { + inner: Inner::Pending(Pending { + body: ReadableChunks::new(body), + type_: type_, + }), + } + } + + /// Constructs a Decoder from a hyper request. + /// + /// A decoder is just a wrapper around the hyper request that knows + /// how to decode the content body of the request. + /// + /// Uses the correct variant by inspecting the Content-Encoding header. + pub fn detect(response: Response) -> Response { + let values = response + .headers() + .get_all(CONTENT_ENCODING) + .iter() + .chain(response.headers().get_all(TRANSFER_ENCODING).iter()); + let decoder = values.fold(None, |acc, enc| { + acc.or_else(|| { + if enc == HeaderValue::from_static("gzip") { + Some(DecoderType::Gzip) + } else if enc == HeaderValue::from_static("br") { + Some(DecoderType::Brotli) + } else if enc == HeaderValue::from_static("deflate") { + Some(DecoderType::Deflate) + } else { + None + } + }) + }); + match decoder { + Some(type_) => response.map(|r| Decoder::pending(r, type_)), + None => response.map(Decoder::plain_text), + } + } +} + +impl Stream for Decoder { + type Item = Chunk; + type Error = Error; + + fn poll(&mut self) -> Poll, Self::Error> { + // Do a read or poll for a pending decoder value. + let new_value = match self.inner { + Inner::Pending(ref mut future) => match future.poll() { + Ok(Async::Ready(inner)) => inner, + Ok(Async::NotReady) => return Ok(Async::NotReady), + Err(e) => return Err(e.into()), + }, + Inner::PlainText(ref mut body) => return body.poll().map_err(|e| e.into()), + Inner::Gzip(ref mut decoder) => return decoder.poll(), + Inner::Brotli(ref mut decoder) => return decoder.poll(), + Inner::Deflate(ref mut decoder) => return decoder.poll(), + }; + + self.inner = new_value; + self.poll() + } +} + +impl Future for Pending { + type Item = Inner; + type Error = hyper::error::Error; + + fn poll(&mut self) -> Poll { + let body_state = match self.body.poll_stream() { + Ok(Async::Ready(state)) => state, + Ok(Async::NotReady) => return Ok(Async::NotReady), + Err(e) => return Err(e), + }; + + let body = mem::replace(&mut self.body, ReadableChunks::new(Body::empty())); + // libflate does a read_exact([0; 2]), so its impossible to tell + // if the stream was empty, or truly had an UnexpectedEof. + // Therefore, we need to check for EOF first. + match body_state { + StreamState::Eof => Ok(Async::Ready(Inner::PlainText(Body::empty()))), + StreamState::HasMore => Ok(Async::Ready(match self.type_ { + DecoderType::Gzip => Inner::Gzip(Gzip::new(body)), + DecoderType::Brotli => Inner::Brotli(Brotli::new(body)), + DecoderType::Deflate => Inner::Deflate(Deflate::new(body)), + })), + } + } +} + +impl Gzip { + fn new(stream: ReadableChunks) -> Self { + Gzip { + buf: BytesMut::with_capacity(INIT_BUFFER_SIZE), + inner: Box::new(gzip::Decoder::new(Peeked::new(stream))), + } + } +} + +#[allow(unsafe_code)] +fn poll_with_read(reader: &mut Read, buf: &mut BytesMut) -> Poll, Error> { + if buf.remaining_mut() == 0 { + buf.reserve(INIT_BUFFER_SIZE); + } + + // The buffer contains uninitialised memory so getting a readable slice is unsafe. + // We trust the reader not to read from the memory given. + // + // To be safe, this memory could be zeroed before passing to the reader. + // Otherwise we might need to deal with the case where the reader panics. + let read = { + let mut buf = unsafe { buf.bytes_mut() }; + reader.read(&mut buf) + }; + + match read { + Ok(read) if read == 0 => Ok(Async::Ready(None)), + Ok(read) => { + unsafe { buf.advance_mut(read) }; + let chunk = Chunk::from(buf.split_to(read).freeze()); + + Ok(Async::Ready(Some(chunk))) + }, + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Ok(Async::NotReady), + Err(e) => Err(e.into()), + } +} + +impl Stream for Gzip { + type Item = Chunk; + type Error = Error; + + fn poll(&mut self) -> Poll, Self::Error> { + poll_with_read(&mut self.inner, &mut self.buf) + } +} + +/// A brotli decoder that reads from a `brotli::Decompressor` into a `BytesMut` and emits the results +/// as a `Chunk`. +struct Brotli { + inner: Box>>>, + buf: BytesMut, +} + +impl Brotli { + fn new(stream: ReadableChunks) -> Self { + Self { + buf: BytesMut::with_capacity(INIT_BUFFER_SIZE), + inner: Box::new(Decompressor::new(Peeked::new(stream), BUF_SIZE)), + } + } +} + +impl Stream for Brotli { + type Item = Chunk; + type Error = Error; + + fn poll(&mut self) -> Poll, Self::Error> { + poll_with_read(&mut self.inner, &mut self.buf) + } +} + +/// A deflate decoder that reads from a `deflate::Decoder` into a `BytesMut` and emits the results +/// as a `Chunk`. +struct Deflate { + inner: Box>>>, + buf: BytesMut, +} + +impl Deflate { + fn new(stream: ReadableChunks) -> Self { + Self { + buf: BytesMut::with_capacity(INIT_BUFFER_SIZE), + inner: Box::new(DeflateDecoder::new(Peeked::new(stream))), + } + } +} + +impl Stream for Deflate { + type Item = Chunk; + type Error = Error; + + fn poll(&mut self) -> Poll, Self::Error> { + poll_with_read(&mut self.inner, &mut self.buf) + } +} + +/// A `Read`able wrapper over a stream of chunks. +pub struct ReadableChunks { + state: ReadState, + stream: S, +} + +enum ReadState { + /// A chunk is ready to be read from. + Ready(Chunk), + /// The next chunk isn't ready yet. + NotReady, + /// The stream has finished. + Eof, +} + +enum StreamState { + /// More bytes can be read from the stream. + HasMore, + /// No more bytes can be read from the stream. + Eof, +} + +/// A buffering reader that ensures `Read`s return at least a few bytes. +struct Peeked { + state: PeekedState, + peeked_buf: [u8; 10], + pos: usize, + inner: R, +} + +enum PeekedState { + /// The internal buffer hasn't filled yet. + NotReady, + /// The internal buffer can be read. + Ready(usize), +} + +impl Peeked { + #[inline] + fn new(inner: R) -> Self { + Peeked { + state: PeekedState::NotReady, + peeked_buf: [0; 10], + inner: inner, + pos: 0, + } + } + + #[inline] + fn ready(&mut self) { + self.state = PeekedState::Ready(self.pos); + self.pos = 0; + } + + #[inline] + fn not_ready(&mut self) { + self.state = PeekedState::NotReady; + self.pos = 0; + } +} + +impl Read for Peeked { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + loop { + match self.state { + PeekedState::Ready(peeked_buf_len) => { + let len = cmp::min(buf.len(), peeked_buf_len - self.pos); + let start = self.pos; + let end = self.pos + len; + + buf[..len].copy_from_slice(&self.peeked_buf[start..end]); + self.pos += len; + if self.pos == peeked_buf_len { + self.not_ready(); + } + + return Ok(len); + }, + PeekedState::NotReady => { + let read = self.inner.read(&mut self.peeked_buf[self.pos..]); + + match read { + Ok(0) => self.ready(), + Ok(read) => { + self.pos += read; + if self.pos == self.peeked_buf.len() { + self.ready(); + } + }, + Err(e) => return Err(e), + } + }, + }; + } + } +} + +impl ReadableChunks { + #[inline] + fn new(stream: S) -> Self { + ReadableChunks { + state: ReadState::NotReady, + stream: stream, + } + } +} + +impl fmt::Debug for ReadableChunks { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("ReadableChunks").finish() + } +} + +impl Read for ReadableChunks +where + S: Stream, +{ + fn read(&mut self, buf: &mut [u8]) -> io::Result { + loop { + let ret; + match self.state { + ReadState::Ready(ref mut chunk) => { + let len = cmp::min(buf.len(), chunk.remaining()); + + buf[..len].copy_from_slice(&chunk[..len]); + chunk.advance(len); + if chunk.is_empty() { + ret = len; + } else { + return Ok(len); + } + }, + ReadState::NotReady => match self.poll_stream() { + Ok(Async::Ready(StreamState::HasMore)) => continue, + Ok(Async::Ready(StreamState::Eof)) => return Ok(0), + Ok(Async::NotReady) => return Err(io::ErrorKind::WouldBlock.into()), + Err(e) => { + return Err(io::Error::new(io::ErrorKind::Other, e)); + }, + }, + ReadState::Eof => return Ok(0), + } + self.state = ReadState::NotReady; + return Ok(ret); + } + } +} + +impl ReadableChunks +where + S: Stream, +{ + /// Poll the readiness of the inner reader. + /// + /// This function will update the internal state and return a simplified + /// version of the `ReadState`. + fn poll_stream(&mut self) -> Poll { + match self.stream.poll() { + Ok(Async::Ready(Some(chunk))) => { + self.state = ReadState::Ready(chunk); + + Ok(Async::Ready(StreamState::HasMore)) + }, + Ok(Async::Ready(None)) => { + self.state = ReadState::Eof; + + Ok(Async::Ready(StreamState::Eof)) + }, + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(e) => Err(e), + } + } +} diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index 1290d7754b30e..c94f747f8b69c 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -2,9 +2,10 @@ * 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/. */ -use crate::connector::{create_http_client, Connector, WrappedBody, BUF_SIZE}; +use crate::connector::{create_http_client, Connector}; use crate::cookie; use crate::cookie_storage::CookieStorage; +use crate::decoder::Decoder; use crate::fetch::cors_cache::CorsCache; use crate::fetch::methods::{ is_cors_safelisted_method, is_cors_safelisted_request_header, main_fetch, @@ -20,7 +21,6 @@ use devtools_traits::{ ChromeToDevtoolsControlMsg, DevtoolsControlMsg, HttpRequest as DevtoolsHttpRequest, }; use devtools_traits::{HttpResponse as DevtoolsHttpResponse, NetworkEvent}; -use flate2::read::{DeflateDecoder, GzDecoder}; use headers_core::HeaderMapExt; use headers_ext::{AccessControlAllowCredentials, AccessControlAllowHeaders}; use headers_ext::{ @@ -49,7 +49,6 @@ use openssl::ssl::SslConnectorBuilder; use servo_url::{ImmutableOrigin, ServoUrl}; use std::collections::{HashMap, HashSet}; use std::error::Error; -use std::io::Cursor; use std::iter::FromIterator; use std::mem; use std::ops::Deref; @@ -71,7 +70,7 @@ pub struct HttpState { pub http_cache: RwLock, pub auth_cache: RwLock, pub history_states: RwLock>>, - pub client: Client, + pub client: Client, } impl HttpState { @@ -266,31 +265,6 @@ fn set_cookies_from_headers( } } -impl Decoder { - fn from_http_response(response: &HyperResponse) -> Decoder { - if let Some(encoding) = response.headers().typed_get::() { - if encoding.contains("gzip") { - Decoder::Gzip(None) - } else if encoding.contains("deflate") { - Decoder::Deflate(DeflateDecoder::new(Cursor::new(Bytes::new()))) - } else if encoding.contains("br") { - Decoder::Brotli(Decompressor::new(Cursor::new(Bytes::new()), BUF_SIZE)) - } else { - Decoder::Plain - } - } else { - Decoder::Plain - } - } -} - -pub enum Decoder { - Gzip(Option>>), - Deflate(DeflateDecoder>), - Brotli(Decompressor>), - Plain, -} - fn prepare_devtools_request( request_id: String, url: ServoUrl, @@ -367,7 +341,7 @@ fn auth_from_cache( } fn obtain_response( - client: &Client, + client: &Client, url: &ServoUrl, method: &Method, request_headers: &HeaderMap, @@ -379,10 +353,7 @@ fn obtain_response( is_xhr: bool, ) -> Box< dyn Future< - Item = ( - HyperResponse, - Option, - ), + Item = (HyperResponse, Option), Error = NetworkError, >, > { @@ -423,7 +394,7 @@ fn obtain_response( .replace("{", "%7B") .replace("}", "%7D"), ) - .body(WrappedBody::new(request_body.clone().into())); + .body(request_body.clone().into()); let mut request = match request { Ok(request) => request, @@ -474,11 +445,7 @@ fn obtain_response( debug!("Not notifying devtools (no request_id)"); None }; - let decoder = Decoder::from_http_response(&res); - Ok(( - res.map(move |r| WrappedBody::new_with_decoder(r, decoder)), - msg, - )) + Ok((Decoder::detect(res), msg)) }) .map_err(move |e| NetworkError::from_hyper_error(&e)), ) @@ -1265,6 +1232,7 @@ fn http_network_fetch( } *res_body.lock().unwrap() = ResponseBody::Receiving(vec![]); + let res_body2 = res_body.clone(); if let Some(ref sender) = devtools_sender { if let Some(m) = msg { @@ -1285,6 +1253,7 @@ fn http_network_fetch( } let done_sender2 = done_sender.clone(); + let done_sender3 = done_sender.clone(); HANDLE.lock().unwrap().spawn( res.into_body() .map_err(|_| ()) @@ -1311,7 +1280,15 @@ fn http_network_fetch( let _ = done_sender2.send(Data::Done); future::ok(()) }) - .map_err(|_| ()), + .map_err(move |_| { + let mut body = res_body2.lock().unwrap(); + let completed_body = match *body { + ResponseBody::Receiving(ref mut body) => mem::replace(body, vec![]), + _ => vec![], + }; + *body = ResponseBody::Done(completed_body); + let _ = done_sender3.send(Data::Done); + }), ); // TODO these substeps aren't possible yet diff --git a/components/net/lib.rs b/components/net/lib.rs index d5943fa380c1e..fb3c6fff78b5e 100644 --- a/components/net/lib.rs +++ b/components/net/lib.rs @@ -21,6 +21,7 @@ pub mod connector; pub mod cookie; pub mod cookie_storage; mod data_loader; +mod decoder; pub mod filemanager_thread; mod hosts; pub mod hsts; diff --git a/servo-tidy.toml b/servo-tidy.toml index b6f2992b0ac5e..bd4687f6cb511 100644 --- a/servo-tidy.toml +++ b/servo-tidy.toml @@ -51,6 +51,8 @@ files = [ "./resources/hsts_preload.json", "./tests/wpt/metadata/MANIFEST.json", "./tests/wpt/mozilla/meta/MANIFEST.json", + # Long encoded string + "./tests/wpt/mozilla/tests/mozilla/resources/brotli.py", "./tests/wpt/webgl/meta/MANIFEST.json", "./support/android/openssl.sh", "./support/linux/gstreamer/gstreamer.sh", diff --git a/tests/wpt/mozilla/tests/mozilla/resources/brotli.py b/tests/wpt/mozilla/tests/mozilla/resources/brotli.py index ca49df23367e8..a24b4771e31ab 100644 --- a/tests/wpt/mozilla/tests/mozilla/resources/brotli.py +++ b/tests/wpt/mozilla/tests/mozilla/resources/brotli.py @@ -2,11 +2,37 @@ # 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/. +decoded = """\ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut viverra neque in massa rutrum, non rutrum nunc pellentesque. Phasellus et nulla metus. Nam aliquet felis nec iaculis eleifend. Donec pretium tellus non aliquam tristique. Mauris feugiat eu velit sed maximus. Praesent fringilla lorem vel orci maximus accumsan. Fusce vel sapien ipsum. Nulla ac lectus non arcu semper laoreet. + +Aliquam et massa at ex elementum dictum vitae ac purus. Sed a nunc sed dui pulvinar mollis eu sed eros. Mauris vitae ullamcorper dolor. Ut sed nisl sem. Mauris pulvinar vitae orci nec tincidunt. Integer fringilla quam in lobortis vehicula. In aliquam egestas dapibus. Suspendisse est enim, maximus non massa eget, finibus finibus lorem. Phasellus a varius ante. Pellentesque tempor dignissim nunc ut malesuada. + +Ut rutrum massa vitae pellentesque lobortis. Vivamus gravida cursus venenatis. Sed metus lorem, commodo vitae pulvinar sagittis, scelerisque quis orci. Vivamus viverra lorem nibh, quis commodo orci luctus pulvinar. Morbi aliquam urna sed magna luctus, quis vulputate tellus pulvinar. Mauris congue velit sed volutpat sodales. Aenean faucibus sapien erat, ac vestibulum leo aliquam at. In magna lorem, hendrerit sed ultrices a, maximus a turpis. Sed sit amet magna eget velit luctus rutrum ac eu est. Donec tristique augue quis luctus auctor. Pellentesque eu mi viverra, lobortis orci et, semper justo. Nunc non viverra purus. Phasellus a purus id elit dictum ullamcorper at quis urna. Nullam semper porttitor diam, in feugiat mi imperdiet eget. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; + +Fusce sed mattis ligula, non viverra ante. Proin viverra maximus enim, non facilisis ante rhoncus eget. Praesent molestie justo ac diam luctus aliquam. Nunc vitae justo posuere ex convallis varius. Suspendisse potenti. Vivamus elementum lacus vel tortor varius, ut blandit nisl laoreet. Suspendisse potenti. Proin gravida facilisis dui, id maximus est porttitor rhoncus. + +Mauris ut quam purus. Cras maximus risus sit amet pretium facilisis. Vivamus ullamcorper arcu et est semper, nec malesuada metus interdum. Nulla tincidunt orci suscipit nisi congue aliquet. In ultrices pulvinar ipsum a egestas. Proin felis diam, malesuada vel suscipit quis, auctor vehicula augue. Vestibulum eget ipsum in neque volutpat aliquam porta ac augue. + +Etiam tincidunt nulla at tellus sodales, sit amet facilisis elit maximus. Nulla et lorem aliquam arcu molestie suscipit. Donec convallis tellus nibh, vel vulputate velit ultrices luctus. Nam et diam sit amet dolor suscipit auctor. Nam blandit felis quis ex pretium consectetur. Etiam vitae ultrices arcu. Nullam et urna pellentesque, ornare mi mattis, fringilla dolor. Vestibulum augue tortor, imperdiet in porta nec, vestibulum a sem. Phasellus nec mattis eros. Maecenas tristique enim egestas libero laoreet posuere. Sed eros nunc, auctor a molestie egestas, dictum a ante. In hac habitasse platea dictumst. Nullam luctus aliquam pellentesque. Morbi non diam ligula. Proin vulputate tortor dui, ac lacinia nisi luctus at. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec dignissim urna sed ligula semper, id mattis enim malesuada. Nullam ac magna et justo commodo viverra ac ac erat. Praesent mi mi, aliquet sit amet augue eu, blandit tempus nisi. Donec dapibus ex et sodales malesuada. Vivamus venenatis aliquet nunc, eget egestas ipsum. Nunc venenatis iaculis congue. Sed iaculis rhoncus diam, at rhoncus arcu imperdiet vel. + +Suspendisse potenti. Praesent ut iaculis elit, a venenatis metus. Sed vitae neque ut neque vehicula placerat id eget erat. Aliquam erat volutpat. Sed dictum molestie nisi eget rhoncus. Nulla et laoreet nisl, a consectetur mauris. In ornare ut mauris quis scelerisque. Pellentesque ultrices nibh in lacus feugiat, id tempus lacus condimentum. + +Aenean tincidunt consequat augue, in bibendum nisl placerat in. Nulla non dignissim est. Nam ac mauris eleifend, elementum ex in, luctus justo. Aliquam ac leo facilisis, elementum libero et, tristique lacus. Aliquam commodo elementum consequat. Vestibulum mollis, lacus a porttitor lacinia, est ipsum mollis ante, non cursus elit tortor eget lacus. Quisque suscipit quam massa, sit amet tempus nulla varius sed. Ut maximus velit quis nulla blandit mollis eu a libero. Aliquam sed dignissim elit. Vivamus quis erat vitae turpis tincidunt ultrices. + +Nulla facilisis dui odio, at scelerisque erat cursus a. Ut arcu nunc, ullamcorper vitae orci eget, sollicitudin maximus sapien. Fusce eu arcu nunc. Integer vitae eros turpis. Fusce ac elit in nisi rutrum suscipit id consectetur lacus. Suspendisse rutrum ligula auctor fringilla cursus. Sed consequat erat in nunc lacinia, et ullamcorper velit vestibulum. Mauris sed est a tellus feugiat sagittis non nec neque. Sed eu bibendum orci. Donec diam diam, viverra sed dolor non, posuere sagittis mi. Duis rhoncus, risus sit amet luctus sodales, risus arcu faucibus enim, eu cras amet.\ +""" + +encoded = '\x1b\x99\x13 ,\x0elw\x08\x95t\xd2\xe6\xf2(\xcd\'5M\x9dg\xd7\xc9)uG\xf4P\x8c;\x92_(\xb1\x14@L\x9d$\xa2\x16\x8f\x06\xc8\x18J\xed\xac\xabd_\xfb\xbe\xcb\xab\x90]VTu\xbc\xe1\xc3\x11\x96\x81T\r\xabj\x07\xbd\xe0\xb2\xd7y\x89\x1c\x81\xfd8\x9f\x15\xcf\xf8\xf6\xe6\x84\x8d\x90Ta<\xef\xcf\xce\xcc\x95\xa4\xff;\xcaO\xe9e\x97z/\xeap\t\x0e\x17\xd9\x14\xb6\xa9V\x151\n\xd0\xe0Fh\xd8\xbd\xd2\xdcB@\xed\xfa\xbe\xea\xb1V\xa1\xe7I\xd5\xfa\x7fTV\xa0\xa4\xda\x86G>R\x1a\x84\x1fs\x1b/\x98\xd6\xfa#\xaa_\xf5\xb7-\xf8d\x99\x80\x87O\xdb\xa1\xbe\xa9\x1a\xc3\xfb\xaf!\xc4f#oa\xc4\xffY?\x8f\xf3\xfc{\xe6\x1dB\x11\xe6\xdd\xe6(\\_S \xfd\xc9\x12\x9d4\t\x1cMO\x9a0u\xfd\t\xd7z\x1d\xe4l\'y\x17\x83\xbcCb\x18\xbfs\x16\xe3\xcc\xf1\x82\x97y\xc9n\x93\xce>=\x05\xb7\x15i\x91\xc4\xe3\xceek_\xfe\xfdzQ\x18\xfa\x19/\xe7\xddfk\x15E\xb2\xf6\xf6\xddo\x05\xbeSOc\xbd\xcb\xad{Ve\x1e/\xa0Y\xac\xaf\x87Z\x0f\xc7\xf0\xd9\xda\x17\xf4\x8e%\xf5Qc\xb9[K\xd2\xe1\x86k\x14\x84k \xf8\x12\xe8,2\x7fE}RT\xd5\xcb\xe0lv\xb8~\x11\xc0Bl\x92`\xf1\xb2\xcd\xfc3\xba\xf1\xe5m\xc2mI\xc0>D\x813e\x1b\\]\xfb\xf4G\x1d\xf9,\xa6\xb8\xff@\x947I\x8d\xd1\xbc\x1c\x0c(\xde\x138\xa3\xd8\x8e`\xd6\x7f\x81 \x82\x0e\x87\xfa"\x01\xdbqzL\x8a\x7f{\xb2\xefw\x8c^\xcdS\x9c&K\x1e\x1f\xc7\xaaj\xad\x1f0\x1f\x199\x10\xaez\n\x18\x81R6v\x99j/^\xf9\xbb\x88WB\xae\x97\xc2*\xedM\x80a]\xcc\xc1\x0e{\xf8\x81\xbd,=\xdf\xe6c\x9a\xbe\x7f\nO\x8a\x99\xd1?\xfc\x88\xc4\\\x1a(\xa4\\\xf6!\x7f}\xfd\xed\xb7+\xe4\xff\xfa\xebhk\xf6\x13R@h9j\xfd\x8ev\x9b\x89l\xbe\xfe\x9d8S\x0b\xec\xb7gNk\xcc\x9a\x9fR\xed\xc5Fv/F\xc0\xef)B1u6z\xfc\xd6\x9d\x9a\x1b\x01;a\xfa$\x96\x1b\xd7\x97\xf5\x8f\x0316\xfb\xddZ\xe8;\xdf=\x80S\xed-\xf3\x13\xb5$1\x7f2CNm\xc3+KQ\x97\xafe\xf4i\x91\x8bNq=-h\x82\x9e\xed>B\xb1\xfc,\xbbU\xe1\x14\x1c\x1f\xc9\x14\xc6\xbd\xb5*\xc8\xc5\x0f\xc4l\xed\x13\\_\xf5j\xff0s\xbev\x11\xf0d\x1dl\xd8\x1f\xc0\xe5g\x98(P\x87\xbb%.\x8c\xf0~8\xdcF\x8e\xb3\xd8>\xc6\x0c\xfb\xc4_\xc3\xce\x85\xeds\x9aR\xf3\xdc\xe6\x8dI\xc7`F\x08B?U\xda\xcf\x0c\xb8r,\xa2\x07\x9b\xd3\x1c$aG\xfc\xe5\xd5\x02\x85\xe9\xca\t\x12\xf1\xf6@[C\x10\xe9:\xed\xb5T\x96\xca\x8a\xb1X\xbeaV\x15\x0cp\xd8k\xbam\xe4\xf2\x12*\x03\xebo\x14 \x17\xe6\x04\xff_\x80\x8f\x10\x85/\xe5T\x13\x15\x84o\xde\xc6\xac\x965\x0f\xa7\xa7]\xec^\xbfXd \xd8\x7fiL\xacg\xb2\xc7\xf1\xa5\xd0\x81;\xd7e\x87\x14.\x80\x01z\xe0\xd1\x9cV\xf4\x1e;\xfe\x83\x1d\x9e\xc1\xf6\xbd\xcb\x97\xe2xa]\x18\x1c\x02\xeeZ\xf4b\x08\xa0<\xde\xab3\xec\xe0K\x1b\xfe\xdaC\xe1 \xf7\xb3&?\xae\xa6u\x18\x9buaq\xcd\xefI\xc6zNO\xf1\xca:\xc5\xdfk\\\x96\xc5:\x01\x95S\x99\'\xc9\xa6o\x1a\xd7~w\xcb\xbc~\xd1XE\x056\x97\x06\ra\xa0\xd8\x1a\xcb\xd4jB\xa8\x9e\x0b\xbc\xf2\xcb3`Y"\xf4\x05\xbe\x98\xcb\xa4S\x97\x0b\xcd\x0cp\x84p\xad\xa2\x07\x8ej?\n\x96m \xdb`\x12\xd4\x11&\\\x07b\xa79\xda\xcb\xc8\x83\xed\x81\xefw\x9f\xf1oF\x0e\xab}`\xee\xb54\xef\xcc\x9f\xc1\xec\xfe\x16\x96B\xa7\x94^\xc4\x10P\xba,eb.\x08-8\t\x8a\xd3Uq\xc3S\x88Z"+J\x93\xd4\xc6\xdde\xde\x8au"l\xc6\x13(\x13\xe4\xc1\xf7c\x1d\xee\xe9)\x11xF\x16\x08"\xafK/W \xdc\xb9\xbd\xa5CY\x03Q\xf0\xe4F\xa5\x0eO\xec\xad\xb2q\x17>N-\x15\xff\xfa8\xbbs\xc4|\xcd;,\xc7\xec\'\xa3\xfa\xb9\x07\xd9Q%\xf6\x84\x10q\xe7*VQ\xa3\xbb\xc8\x89\xb7g\xe7t\xe1\xe7\xb5\xc0\x0e8\x8d\x19\xe5v\xa1\t{\x8c\x9b\x1dx#\xf9\xc5\xcb\xf4y\xb9^\x1d\xba\x06\x81\xc52\xb8p\x91\x8b\x04c,\x9a\xa7\xfa\xaa\x93V\xc5>\xe0\xe5X>H\x99\xa6X\x9b\xfa\xbe\xcd\x14\xfd\xe4\x8an\xa18\x1f\x11gc\x83\x0b\xb6RLIz[\x1e\x80\x18\xa3\x9d\xc5\xec\x87\x12\x1b\x12\xe7\xf1\x8a\xae\xb4\xea\x99\x0e2\xa2w\xe4S\xd7\xe9Pq\xfd\x9c\xd6k\xf6\xa5`\x99}\x08\xc9\x9b5\x12\xe8\x17\xe2\xcf\x9f\x9bm\xc3\xe5<\x9f5m\xa1\xa4\xb5\xf1\x87\x8d\xf5}2yte\x14V\xf6\x10\xae\xd4\xeec\xa0\xdaq@(\xd6B\xa8R\xee"v\xf3\xeef\xb7\xb1\x8a\x8cu|\x11J\xb0 \xbe\xe1\x0e\rg\xc3\x9dd\xe2\xb12\xaf\xa3T\xa9\x18\xe7\xf3\x14V\x90\x07\xfali\x91\xc8\x06\xb3\xad\xe0i@\x19"W\x19\x1b\xc9|\xca\xfb\xe1x\xa8\xe4\xd8\x19\x81u4%\xc4_\xfb\xe9\xf90fI\x0eo\x9b\x1d\x98\x13\xa9\xd5\x89\x8c\xab>\xafH\xa2\x91eVe\xea\x03\x19p\xab\xa5\xed\x06\xb9f\x80\xc60\xc0\x8b\x1c\x18\xec\xd3\xb2\xc6l\xe44TAs3\x15\xc4\xac\xac\x0c\x0baN\xcb\xb7\x17\xd9\x1a\xbeG\x88\x9b\x98R\xb0Tp\x04\xa8\x99\x86\x11\xd5_I\x07\xce\x0e\xb8\x92\'Y\xefV\xc287\xdb+\xfd\xd2D\x13\xf7\x84\xec\xd45\x19R\x16O\xa1\x119<2\xb9\xa0K\xf6G\x8e\xc6S\n\r*h\xb1\xd1p\x10\xdd\\\xa9\xd0y\x1cG\x95\xb3D\xba\xa16\xb0\xd1\x98E\x87\x08\x01l.J\xe8\xeaA\x11\xb4Yr@\x19d!\xbb\x91\x06\xf1\x8a\xc0\xcdK\xf9\xback\x14\xa8F\x99)\x9f\xe5\xaf\xce#}ITF\x131T\xab\xe0\x05*>\xbeA{>\xac\xeak\xea\x95\xf9Bw 4\xec\xac\xdc\xe8\xac\xe4\xb6v\xcd\x91\x95\x05' def main(request, response): - output = '\x1b\x03)\x00\xa4\xcc\xde\xe2\xb3 vA\x00\x0c' + if 'raw' in request.GET: + headers = [("Content-type", "text/plain"), + ("Content-Length", len(decoded))] + return headers, decoded + headers = [("Content-type", "text/plain"), ("Content-Encoding", "br"), - ("Content-Length", len(output))] - - return headers, output + ("Content-Length", len(encoded))] + return headers, encoded diff --git a/tests/wpt/mozilla/tests/mozilla/response-data-brotli.htm b/tests/wpt/mozilla/tests/mozilla/response-data-brotli.htm index e46a0e5fb0428..d6f2f8f71d8be 100644 --- a/tests/wpt/mozilla/tests/mozilla/response-data-brotli.htm +++ b/tests/wpt/mozilla/tests/mozilla/response-data-brotli.htm @@ -9,34 +9,27 @@