diff --git a/Cargo.toml b/Cargo.toml index ab2dc81..42e7e53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,10 +5,9 @@ authors = ["Carl Lerche "] [dependencies] futures = "0.1" -tokio-io = { git = "https://github.com/alexcrichton/tokio-io" } +tokio-io = "0.1" tokio-timer = { git = "https://github.com/tokio-rs/tokio-timer" } -bytes = { git = "https://github.com/carllerche/bytes" } -tower = { path = "/Users/carllerche/Code/Oss/Tokio/tower/tower-http" } - -[replace] -"futures:0.1.10" = { git = "https://github.com/alexcrichton/futures-rs", branch = "close" } +bytes = "0.4" +http = { path = "/Users/carllerche/Code/Oss/Tokio/tower/http" } +log = "0.3.8" +# tower = { path = "/Users/carllerche/Code/Oss/Tokio/tower/tower-http" } diff --git a/src/hpack/decoder.rs b/src/hpack/decoder.rs index d121ca7..150d45a 100644 --- a/src/hpack/decoder.rs +++ b/src/hpack/decoder.rs @@ -1,6 +1,7 @@ use super::{huffman, Entry, Key}; +use util::byte_str::FromUtf8Error; -use tower::http::{status, HeaderName, StatusCode, Method, Str, FromUtf8Error}; +use http::{method, header, StatusCode, Method}; use bytes::{Buf, Bytes}; use std::io::Cursor; @@ -23,6 +24,8 @@ pub enum DecoderError { InvalidHuffmanCode, InvalidUtf8, InvalidStatusCode, + InvalidPseudoheader, + InvalidMaxDynamicSize, IntegerUnderflow, IntegerOverflow, StringUnderflow, @@ -143,6 +146,7 @@ impl Decoder { use self::Representation::*; let mut buf = Cursor::new(src); + let mut can_resize = true; while buf.has_remaining() { // At this point we are always at the beginning of the next block @@ -150,23 +154,43 @@ impl Decoder { // determined from the first byte. match try!(Representation::load(peek_u8(&mut buf))) { Indexed => { + can_resize = false; f(try!(self.decode_indexed(&mut buf))); } LiteralWithIndexing => { + can_resize = false; let entry = try!(self.decode_literal(&mut buf, true)); - // TODO: Add entry to the table + // Insert the header into the table + self.table.insert(entry.clone()); f(entry); } LiteralWithoutIndexing => { - unimplemented!(); + can_resize = false; + let entry = try!(self.decode_literal(&mut buf, false)); + f(entry); } LiteralNeverIndexed => { - unimplemented!(); + can_resize = false; + let entry = try!(self.decode_literal(&mut buf, false)); + + // TODO: Track that this should never be indexed + + f(entry); } SizeUpdate => { - unimplemented!(); + let max = match self.max_size_update { + Some(max) if can_resize => max, + _ => { + // Resize is too big or other frames have been read + // before the resize. + return Err(DecoderError::InvalidMaxDynamicSize); + } + }; + + // Handle the dynamic table size update... + try!(self.process_size_update(&mut buf, max)) } } } @@ -174,6 +198,23 @@ impl Decoder { Ok(()) } + fn process_size_update(&mut self, buf: &mut Cursor<&Bytes>, max: usize) + -> Result<(), DecoderError> + { + let new_size = try!(decode_int(buf, 5)); + + if new_size > max { + return Err(DecoderError::InvalidMaxDynamicSize); + } + + debug!("Decoder changed max table size from {} to {}", + self.table.size(), new_size); + + self.table.set_max_size(new_size); + + Ok(()) + } + fn decode_indexed(&self, buf: &mut Cursor<&Bytes>) -> Result { @@ -197,8 +238,9 @@ impl Decoder { if table_idx == 0 { // Read the name as a literal let name = try!(decode_string(buf)); + let value = try!(decode_string(buf)); - unimplemented!(); + Entry::new(name, value) } else { let e = try!(self.table.get(table_idx)); let value = try!(decode_string(buf)); @@ -338,6 +380,9 @@ impl Table { self.max_size } + fn size(&self) -> usize { + self.size + } /// Returns the entry located at the given index. /// @@ -374,6 +419,12 @@ impl Table { self.entries.push_front(entry); } + fn set_max_size(&mut self, size: usize) { + self.max_size = size; + // Make the table size fit within the new constraints. + self.consolidate(); + } + fn reserve(&mut self, size: usize) { debug_assert!(size <= self.max_size); @@ -384,6 +435,25 @@ impl Table { self.size -= last.len(); } } + + fn consolidate(&mut self) { + while self.size > self.max_size { + { + let last = match self.entries.back() { + Some(x) => x, + None => { + // Can never happen as the size of the table must reach + // 0 by the time we've exhausted all elements. + panic!("Size of table != 0, but no headers left!"); + } + }; + + self.size -= last.len(); + } + + self.entries.pop_back(); + } + } } // ===== impl DecoderError ===== @@ -394,212 +464,234 @@ impl From for DecoderError { } } +impl From for DecoderError { + fn from(src: header::InvalidValueError) -> DecoderError { + // TODO: Better error? + DecoderError::InvalidUtf8 + } +} + +impl From for DecoderError { + fn from(src: method::FromBytesError) -> DecoderError { + // TODO: Better error + DecoderError::InvalidUtf8 + } +} + +impl From for DecoderError { + fn from(src: header::FromBytesError) -> DecoderError { + DecoderError::InvalidUtf8 + } +} + /// Get an entry from the static table pub fn get_static(idx: usize) -> Entry { - use tower::http::StandardHeader::*; + use http::{status, method, header}; + use http::header::HeaderValue; + use util::byte_str::ByteStr; match idx { - 1 => Entry::Authority(Str::new()), - 2 => Entry::Method(Method::Get), - 3 => Entry::Method(Method::Post), - 4 => Entry::Path(Str::from_static("/")), - 5 => Entry::Path(Str::from_static("/index.html")), - 6 => Entry::Scheme(Str::from_static("http")), - 7 => Entry::Scheme(Str::from_static("https")), - 8 => Entry::Status(status::Ok), - 9 => Entry::Status(status::NoContent), - 10 => Entry::Status(status::PartialContent), - 11 => Entry::Status(status::NotModified), - 12 => Entry::Status(status::BadRequest), - 13 => Entry::Status(status::NotFound), - 14 => Entry::Status(status::InternalServerError), + 1 => Entry::Authority(ByteStr::from_static("")), + 2 => Entry::Method(method::GET), + 3 => Entry::Method(method::POST), + 4 => Entry::Path(ByteStr::from_static("/")), + 5 => Entry::Path(ByteStr::from_static("/index.html")), + 6 => Entry::Scheme(ByteStr::from_static("http")), + 7 => Entry::Scheme(ByteStr::from_static("https")), + 8 => Entry::Status(status::OK), + 9 => Entry::Status(status::NO_CONTENT), + 10 => Entry::Status(status::PARTIAL_CONTENT), + 11 => Entry::Status(status::NOT_MODIFIED), + 12 => Entry::Status(status::BAD_REQUEST), + 13 => Entry::Status(status::NOT_FOUND), + 14 => Entry::Status(status::INTERNAL_SERVER_ERROR), 15 => Entry::Header { - name: AcceptCharset.into(), - value: Str::new(), + name: header::ACCEPT_CHARSET, + value: HeaderValue::from_static(""), }, 16 => Entry::Header { - name: AcceptEncoding.into(), - value: Str::from_static("gzip, deflate"), + name: header::ACCEPT_ENCODING, + value: HeaderValue::from_static("gzip, deflate"), }, 17 => Entry::Header { - name: AcceptLanguage.into(), - value: Str::new(), + name: header::ACCEPT_LANGUAGE, + value: HeaderValue::from_static(""), }, 18 => Entry::Header { - name: AcceptRanges.into(), - value: Str::new(), + name: header::ACCEPT_RANGES, + value: HeaderValue::from_static(""), }, 19 => Entry::Header { - name: Accept.into(), - value: Str::new(), + name: header::ACCEPT, + value: HeaderValue::from_static(""), }, 20 => Entry::Header { - name: AccessControlAllowOrigin.into(), - value: Str::new(), + name: header::ACCESS_CONTROL_ALLOW_ORIGIN, + value: HeaderValue::from_static(""), }, 21 => Entry::Header { - name: Age.into(), - value: Str::new(), + name: header::AGE, + value: HeaderValue::from_static(""), }, 22 => Entry::Header { - name: Allow.into(), - value: Str::new(), + name: header::ALLOW, + value: HeaderValue::from_static(""), }, 23 => Entry::Header { - name: Authorization.into(), - value: Str::new(), + name: header::AUTHORIZATION, + value: HeaderValue::from_static(""), }, 24 => Entry::Header { - name: CacheControl.into(), - value: Str::new(), + name: header::CACHE_CONTROL, + value: HeaderValue::from_static(""), }, 25 => Entry::Header { - name: ContentDisposition.into(), - value: Str::new(), + name: header::CONTENT_DISPOSITION, + value: HeaderValue::from_static(""), }, 26 => Entry::Header { - name: ContentEncoding.into(), - value: Str::new(), + name: header::CONTENT_ENCODING, + value: HeaderValue::from_static(""), }, 27 => Entry::Header { - name: ContentLanguage.into(), - value: Str::new(), + name: header::CONTENT_LANGUAGE, + value: HeaderValue::from_static(""), }, 28 => Entry::Header { - name: ContentLength.into(), - value: Str::new(), + name: header::CONTENT_LENGTH, + value: HeaderValue::from_static(""), }, 29 => Entry::Header { - name: ContentLocation.into(), - value: Str::new(), + name: header::CONTENT_LOCATION, + value: HeaderValue::from_static(""), }, 30 => Entry::Header { - name: ContentRange.into(), - value: Str::new(), + name: header::CONTENT_RANGE, + value: HeaderValue::from_static(""), }, 31 => Entry::Header { - name: ContentType.into(), - value: Str::new(), + name: header::CONTENT_TYPE, + value: HeaderValue::from_static(""), }, 32 => Entry::Header { - name: Cookie.into(), - value: Str::new(), + name: header::COOKIE, + value: HeaderValue::from_static(""), }, 33 => Entry::Header { - name: Date.into(), - value: Str::new(), + name: header::DATE, + value: HeaderValue::from_static(""), }, 34 => Entry::Header { - name: Etag.into(), - value: Str::new(), + name: header::ETAG, + value: HeaderValue::from_static(""), }, 35 => Entry::Header { - name: Expect.into(), - value: Str::new(), + name: header::EXPECT, + value: HeaderValue::from_static(""), }, 36 => Entry::Header { - name: Expires.into(), - value: Str::new(), + name: header::EXPIRES, + value: HeaderValue::from_static(""), }, 37 => Entry::Header { - name: From.into(), - value: Str::new(), + name: header::FROM, + value: HeaderValue::from_static(""), }, 38 => Entry::Header { - name: Host.into(), - value: Str::new(), + name: header::HOST, + value: HeaderValue::from_static(""), }, 39 => Entry::Header { - name: IfMatch.into(), - value: Str::new(), + name: header::IF_MATCH, + value: HeaderValue::from_static(""), }, 40 => Entry::Header { - name: IfModifiedSince.into(), - value: Str::new(), + name: header::IF_MODIFIED_SINCE, + value: HeaderValue::from_static(""), }, 41 => Entry::Header { - name: IfNoneMatch.into(), - value: Str::new(), + name: header::IF_NONE_MATCH, + value: HeaderValue::from_static(""), }, 42 => Entry::Header { - name: IfRange.into(), - value: Str::new(), + name: header::IF_RANGE, + value: HeaderValue::from_static(""), }, 43 => Entry::Header { - name: IfUnmodifiedSince.into(), - value: Str::new(), + name: header::IF_UNMODIFIED_SINCE, + value: HeaderValue::from_static(""), }, 44 => Entry::Header { - name: LastModified.into(), - value: Str::new(), + name: header::LAST_MODIFIED, + value: HeaderValue::from_static(""), }, 45 => Entry::Header { - name: Link.into(), - value: Str::new(), + name: header::LINK, + value: HeaderValue::from_static(""), }, 46 => Entry::Header { - name: Location.into(), - value: Str::new(), + name: header::LOCATION, + value: HeaderValue::from_static(""), }, 47 => Entry::Header { - name: MaxForwards.into(), - value: Str::new(), + name: header::MAX_FORWARDS, + value: HeaderValue::from_static(""), }, 48 => Entry::Header { - name: ProxyAuthenticate.into(), - value: Str::new(), + name: header::PROXY_AUTHENTICATE, + value: HeaderValue::from_static(""), }, 49 => Entry::Header { - name: ProxyAuthorization.into(), - value: Str::new(), + name: header::PROXY_AUTHORIZATION, + value: HeaderValue::from_static(""), }, 50 => Entry::Header { - name: Range.into(), - value: Str::new(), + name: header::RANGE, + value: HeaderValue::from_static(""), }, 51 => Entry::Header { - name: Referer.into(), - value: Str::new(), + name: header::REFERER, + value: HeaderValue::from_static(""), }, 52 => Entry::Header { - name: Refresh.into(), - value: Str::new(), + name: header::REFRESH, + value: HeaderValue::from_static(""), }, 53 => Entry::Header { - name: RetryAfter.into(), - value: Str::new(), + name: header::RETRY_AFTER, + value: HeaderValue::from_static(""), }, 54 => Entry::Header { - name: Server.into(), - value: Str::new(), + name: header::SERVER, + value: HeaderValue::from_static(""), }, 55 => Entry::Header { - name: SetCookie.into(), - value: Str::new(), + name: header::SET_COOKIE, + value: HeaderValue::from_static(""), }, 56 => Entry::Header { - name: StrictTransportSecurity.into(), - value: Str::new(), + name: header::STRICT_TRANSPORT_SECURITY, + value: HeaderValue::from_static(""), }, 57 => Entry::Header { - name: TransferEncoding.into(), - value: Str::new(), + name: header::TRANSFER_ENCODING, + value: HeaderValue::from_static(""), }, 58 => Entry::Header { - name: UserAgent.into(), - value: Str::new(), + name: header::USER_AGENT, + value: HeaderValue::from_static(""), }, 59 => Entry::Header { - name: Vary.into(), - value: Str::new(), + name: header::VARY, + value: HeaderValue::from_static(""), }, 60 => Entry::Header { - name: Via.into(), - value: Str::new(), + name: header::VIA, + value: HeaderValue::from_static(""), }, 61 => Entry::Header { - name: WwwAuthenticate.into(), - value: Str::new(), + name: header::WWW_AUTHENTICATE, + value: HeaderValue::from_static(""), }, _ => unreachable!(), } diff --git a/src/hpack/entry.rs b/src/hpack/entry.rs index 4b98957..940b2da 100644 --- a/src/hpack/entry.rs +++ b/src/hpack/entry.rs @@ -1,6 +1,8 @@ use super::DecoderError; +use util::byte_str::{ByteStr, FromUtf8Error}; -use tower::http::{HeaderName, Method, StatusCode, Str}; +use http::{Method, StatusCode}; +use http::header::{HeaderName, HeaderValue}; use bytes::Bytes; /// HPack table entry @@ -8,12 +10,12 @@ use bytes::Bytes; pub enum Entry { Header { name: HeaderName, - value: Str, + value: HeaderValue, }, - Authority(Str), + Authority(ByteStr), Method(Method), - Scheme(Str), - Path(Str), + Scheme(ByteStr), + Path(ByteStr), Status(StatusCode), } @@ -28,6 +30,38 @@ pub enum Key<'a> { } impl Entry { + pub fn new(name: Bytes, value: Bytes) -> Result { + if name[0] == b':' { + match &name[1..] { + b"authority" => { + let value = try!(ByteStr::from_utf8(value)); + Ok(Entry::Authority(value)) + } + b"method" => { + let method = try!(Method::from_bytes(&value)); + Ok(Entry::Method(method)) + } + b"scheme" => { + unimplemented!(); + } + b"path" => { + unimplemented!(); + } + b"status" => { + unimplemented!(); + } + _ => { + Err(DecoderError::InvalidPseudoheader) + } + } + } else { + let name = try!(HeaderName::from_bytes(&name)); + let value = try!(HeaderValue::try_from_slice(&value)); + + Ok(Entry::Header { name: name, value: value }) + } + } + pub fn len(&self) -> usize { match *self { Entry::Header { ref name, ref value } => { @@ -70,25 +104,26 @@ impl<'a> Key<'a> { Key::Header(name) => { Ok(Entry::Header { name: name.clone(), - value: try!(Str::from_utf8(value)), + value: try!(HeaderValue::try_from_slice(&*value)), }) } Key::Authority => { - Ok(Entry::Authority(try!(Str::from_utf8(value)))) + Ok(Entry::Authority(try!(ByteStr::from_utf8(value)))) } Key::Method => { - Ok(Entry::Scheme(try!(Str::from_utf8(value)))) + Ok(Entry::Scheme(try!(ByteStr::from_utf8(value)))) } Key::Scheme => { - Ok(Entry::Scheme(try!(Str::from_utf8(value)))) + Ok(Entry::Scheme(try!(ByteStr::from_utf8(value)))) } Key::Path => { - Ok(Entry::Path(try!(Str::from_utf8(value)))) + Ok(Entry::Path(try!(ByteStr::from_utf8(value)))) } Key::Status => { - match StatusCode::parse(&value) { - Some(status) => Ok(Entry::Status(status)), - None => Err(DecoderError::InvalidStatusCode), + match StatusCode::from_slice(&value) { + Ok(status) => Ok(Entry::Status(status)), + // TODO: better error handling + Err(_) => Err(DecoderError::InvalidStatusCode), } } } diff --git a/src/lib.rs b/src/lib.rs index a3633c1..8d9766d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,15 +8,20 @@ extern crate tokio_io; extern crate tokio_timer; // HTTP types -extern crate tower; +extern crate http; // Buffer utilities extern crate bytes; +#[macro_use] +extern crate log; + pub mod error; pub mod hpack; pub mod proto; pub mod frame; +mod util; + pub use error::{ConnectionError, StreamError, Reason}; pub use proto::Connection;