diff --git a/.travis.yml b/.travis.yml index fb8b93a08f..0a493525a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,10 +9,6 @@ matrix: - rust: beta - rust: stable env: HYPER_DOCS=1 - - rust: stable - env: FEATURES="--no-default-features" - - rust: stable - env: FEATURES="--features compat" - rust: 1.21.0 cache: diff --git a/Cargo.toml b/Cargo.toml index a317b2d55e..18f9662587 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ base64 = "0.9" bytes = "0.4.4" futures = "0.1.17" futures-cpupool = "0.1.6" -http = { version = "0.1", optional = true } +http = "0.1.5" httparse = "1.0" iovec = "0.1" language-tags = "0.2" @@ -46,7 +46,4 @@ spmc = "0.2" url = "1.0" [features] -default = [] nightly = [] -raw_status = [] -compat = [ "http" ] diff --git a/benches/end_to_end.rs b/benches/end_to_end.rs index 3b68543790..fc2992acd5 100644 --- a/benches/end_to_end.rs +++ b/benches/end_to_end.rs @@ -12,9 +12,7 @@ use futures::{Future, Stream}; use tokio_core::reactor::{Core, Handle}; use tokio_core::net::TcpListener; -use hyper::client; -use hyper::header::{ContentLength, ContentType}; -use hyper::Method; +use hyper::{Body, Method, Request, Response}; #[bench] @@ -30,7 +28,7 @@ fn get_one_at_a_time(b: &mut test::Bencher) { b.bytes = 160 * 2 + PHRASE.len() as u64; b.iter(move || { let work = client.get(url.clone()).and_then(|res| { - res.body().for_each(|_chunk| { + res.into_body().into_stream().for_each(|_chunk| { Ok(()) }) }); @@ -54,12 +52,11 @@ fn post_one_at_a_time(b: &mut test::Bencher) { let post = "foo bar baz quux"; b.bytes = 180 * 2 + post.len() as u64 + PHRASE.len() as u64; b.iter(move || { - let mut req = client::Request::new(Method::Post, url.clone()); - req.headers_mut().set(ContentLength(post.len() as u64)); - req.set_body(post); - + let mut req = Request::new(post.into()); + *req.method_mut() = Method::POST; + *req.uri_mut() = url.clone(); let work = client.request(req).and_then(|res| { - res.body().for_each(|_chunk| { + res.into_body().into_stream().for_each(|_chunk| { Ok(()) }) }); @@ -71,7 +68,7 @@ fn post_one_at_a_time(b: &mut test::Bencher) { static PHRASE: &'static [u8] = include_bytes!("../CHANGELOG.md"); //b"Hello, World!"; fn spawn_hello(handle: &Handle) -> SocketAddr { - use hyper::server::{const_service, service_fn, NewService, Request, Response}; + use hyper::server::{const_service, service_fn, NewService}; let addr = "127.0.0.1:0".parse().unwrap(); let listener = TcpListener::bind(&addr, handle).unwrap(); let addr = listener.local_addr().unwrap(); @@ -79,14 +76,12 @@ fn spawn_hello(handle: &Handle) -> SocketAddr { let handle2 = handle.clone(); let http = hyper::server::Http::::new(); - let service = const_service(service_fn(|req: Request| { - req.body() + let service = const_service(service_fn(|req: Request| { + req.into_body() + .into_stream() .concat2() .map(|_| { - Response::::new() - .with_header(ContentLength(PHRASE.len() as u64)) - .with_header(ContentType::plaintext()) - .with_body(PHRASE) + Response::new(Body::from(PHRASE)) }) })); diff --git a/benches/server.rs b/benches/server.rs index 1542ff7071..7ddc43b3b1 100644 --- a/benches/server.rs +++ b/benches/server.rs @@ -10,11 +10,11 @@ use std::io::{Read, Write}; use std::net::{TcpListener, TcpStream}; use std::sync::mpsc; -use futures::{future, stream, Future}; +use futures::{future, stream, Future, Stream}; use futures::sync::oneshot; -use hyper::header::{ContentLength, ContentType, TransferEncoding}; -use hyper::server::{self, Service}; +use hyper::{Body, Request, Response}; +use hyper::server::Service; macro_rules! bench_server { ($b:ident, $header:expr, $body:expr) => ({ @@ -65,37 +65,37 @@ fn body(b: &'static [u8]) -> hyper::Body { #[bench] fn throughput_fixedsize_small_payload(b: &mut test::Bencher) { - bench_server!(b, ContentLength(13), || body(b"Hello, World!")) + bench_server!(b, ("content-length", "13"), || body(b"Hello, World!")) } #[bench] fn throughput_fixedsize_large_payload(b: &mut test::Bencher) { - bench_server!(b, ContentLength(1_000_000), || body(&[b'x'; 1_000_000])) + bench_server!(b, ("content-length", "1000000"), || body(&[b'x'; 1_000_000])) } #[bench] fn throughput_fixedsize_many_chunks(b: &mut test::Bencher) { - bench_server!(b, ContentLength(1_000_000), || { + bench_server!(b, ("content-length", "1000000"), || { static S: &'static [&'static [u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _; - stream::iter_ok(S.iter()) + Body::wrap_stream(stream::iter_ok(S.iter()).map(|&s| s)) }) } #[bench] fn throughput_chunked_small_payload(b: &mut test::Bencher) { - bench_server!(b, TransferEncoding::chunked(), || body(b"Hello, World!")) + bench_server!(b, ("transfer-encoding", "chunked"), || body(b"Hello, World!")) } #[bench] fn throughput_chunked_large_payload(b: &mut test::Bencher) { - bench_server!(b, TransferEncoding::chunked(), || body(&[b'x'; 1_000_000])) + bench_server!(b, ("transfer-encoding", "chunked"), || body(&[b'x'; 1_000_000])) } #[bench] fn throughput_chunked_many_chunks(b: &mut test::Bencher) { - bench_server!(b, TransferEncoding::chunked(), || { + bench_server!(b, ("transfer-encoding", "chunked"), || { static S: &'static [&'static [u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _; - stream::iter_ok(S.iter()) + Body::wrap_stream(stream::iter_ok(S.iter()).map(|&s| s)) }) } @@ -177,26 +177,26 @@ fn raw_tcp_throughput_large_payload(b: &mut test::Bencher) { tx.send(()).unwrap(); } -struct BenchPayload { - header: H, +struct BenchPayload { + header: (&'static str, &'static str), body: F, } -impl Service for BenchPayload +impl Service for BenchPayload where - H: hyper::header::Header + Clone, F: Fn() -> B, { - type Request = server::Request; - type Response = server::Response; + type Request = Request; + type Response = Response; type Error = hyper::Error; type Future = future::FutureResult; fn call(&self, _req: Self::Request) -> Self::Future { future::ok( - server::Response::new() - .with_header(self.header.clone()) - .with_header(ContentType::plaintext()) - .with_body((self.body)()) + Response::builder() + .header(self.header.0, self.header.1) + .header("content-type", "text/plain") + .body((self.body)()) + .unwrap() ) } } diff --git a/examples/client.rs b/examples/client.rs index c2a79c0278..bafb6ba7fe 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -11,7 +11,7 @@ use std::io::{self, Write}; use futures::Future; use futures::stream::Stream; -use hyper::Client; +use hyper::{Body, Client, Request}; fn main() { pretty_env_logger::init(); @@ -25,7 +25,7 @@ fn main() { }; let url = url.parse::().unwrap(); - if url.scheme() != Some("http") { + if url.scheme_part().map(|s| s.as_ref()) != Some("http") { println!("This example only works with 'http' URLs."); return; } @@ -34,11 +34,13 @@ fn main() { let handle = core.handle(); let client = Client::new(&handle); - let work = client.get(url).and_then(|res| { + let mut req = Request::new(Body::empty()); + *req.uri_mut() = url; + let work = client.request(req).and_then(|res| { println!("Response: {}", res.status()); - println!("Headers: \n{}", res.headers()); + println!("Headers: {:#?}", res.headers()); - res.body().for_each(|chunk| { + res.into_parts().1.for_each(|chunk| { io::stdout().write_all(&chunk).map_err(From::from) }) }).map(|_| { diff --git a/examples/hello.rs b/examples/hello.rs index 8636bdbad0..f62200f4cf 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -3,8 +3,8 @@ extern crate hyper; extern crate futures; extern crate pretty_env_logger; -use hyper::header::{ContentLength, ContentType}; -use hyper::server::{Http, Response, const_service, service_fn}; +use hyper::{Body, Response}; +use hyper::server::{Http, const_service, service_fn}; static PHRASE: &'static [u8] = b"Hello World!"; @@ -13,10 +13,7 @@ fn main() { let addr = ([127, 0, 0, 1], 3000).into(); let new_service = const_service(service_fn(|_| { - Ok(Response::::new() - .with_header(ContentLength(PHRASE.len() as u64)) - .with_header(ContentType::plaintext()) - .with_body(PHRASE)) + Ok(Response::new(Body::from(PHRASE))) })); let server = Http::new() diff --git a/examples/multi_server.rs b/examples/multi_server.rs index 39be36b916..cb55f64055 100644 --- a/examples/multi_server.rs +++ b/examples/multi_server.rs @@ -7,10 +7,9 @@ extern crate pretty_env_logger; use futures::{Future, Stream}; use futures::future::FutureResult; -use hyper::{Get, StatusCode}; +use hyper::{Body, Method, Request, Response, StatusCode}; use tokio_core::reactor::Core; -use hyper::header::ContentLength; -use hyper::server::{Http, Service, Request, Response}; +use hyper::server::{Http, Service}; static INDEX1: &'static [u8] = b"The 1st service!"; static INDEX2: &'static [u8] = b"The 2nd service!"; @@ -18,21 +17,21 @@ static INDEX2: &'static [u8] = b"The 2nd service!"; struct Srv(&'static [u8]); impl Service for Srv { - type Request = Request; - type Response = Response; + type Request = Request; + type Response = Response; type Error = hyper::Error; - type Future = FutureResult; + type Future = FutureResult, hyper::Error>; - fn call(&self, req: Request) -> Self::Future { - futures::future::ok(match (req.method(), req.path()) { - (&Get, "/") => { - Response::new() - .with_header(ContentLength(self.0.len() as u64)) - .with_body(self.0) + fn call(&self, req: Request) -> Self::Future { + futures::future::ok(match (req.method(), req.uri().path()) { + (&Method::GET, "/") => { + Response::new(self.0.into()) }, _ => { - Response::new() - .with_status(StatusCode::NotFound) + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::empty()) + .unwrap() } }) } diff --git a/examples/params.rs b/examples/params.rs index 5eefbc3e88..ff51b91e3c 100644 --- a/examples/params.rs +++ b/examples/params.rs @@ -6,9 +6,8 @@ extern crate url; use futures::{Future, Stream}; -use hyper::{Get, Post, StatusCode}; -use hyper::header::ContentLength; -use hyper::server::{Http, Service, Request, Response}; +use hyper::{Body, Method, Request, Response, StatusCode}; +use hyper::server::{Http, Service}; use std::collections::HashMap; use url::form_urlencoded; @@ -20,20 +19,18 @@ static NOTNUMERIC: &[u8] = b"Number field is not numeric"; struct ParamExample; impl Service for ParamExample { - type Request = Request; - type Response = Response; + type Request = Request; + type Response = Response; type Error = hyper::Error; type Future = Box>; - fn call(&self, req: Request) -> Self::Future { - match (req.method(), req.path()) { - (&Get, "/") | (&Get, "/post") => { - Box::new(futures::future::ok(Response::new() - .with_header(ContentLength(INDEX.len() as u64)) - .with_body(INDEX))) + fn call(&self, req: Request) -> Self::Future { + match (req.method(), req.uri().path()) { + (&Method::GET, "/") | (&Method::GET, "/post") => { + Box::new(futures::future::ok(Response::new(INDEX.into()))) }, - (&Post, "/post") => { - Box::new(req.body().concat2().map(|b| { + (&Method::POST, "/post") => { + Box::new(req.into_parts().1.concat2().map(|b| { // Parse the request body. form_urlencoded::parse // always succeeds, but in general parsing may // fail (for example, an invalid post of json), so @@ -52,25 +49,25 @@ impl Service for ParamExample { let name = if let Some(n) = params.get("name") { n } else { - return Response::new() - .with_status(StatusCode::UnprocessableEntity) - .with_header(ContentLength(MISSING.len() as u64)) - .with_body(MISSING); + return Response::builder() + .status(StatusCode::UNPROCESSABLE_ENTITY) + .body(MISSING.into()) + .unwrap(); }; let number = if let Some(n) = params.get("number") { if let Ok(v) = n.parse::() { v } else { - return Response::new() - .with_status(StatusCode::UnprocessableEntity) - .with_header(ContentLength(NOTNUMERIC.len() as u64)) - .with_body(NOTNUMERIC); + return Response::builder() + .status(StatusCode::UNPROCESSABLE_ENTITY) + .body(NOTNUMERIC.into()) + .unwrap(); } } else { - return Response::new() - .with_status(StatusCode::UnprocessableEntity) - .with_header(ContentLength(MISSING.len() as u64)) - .with_body(MISSING); + return Response::builder() + .status(StatusCode::UNPROCESSABLE_ENTITY) + .body(MISSING.into()) + .unwrap(); }; // Render the response. This will often involve @@ -80,14 +77,14 @@ impl Service for ParamExample { // responses such as InternalServiceError may be // needed here, too. let body = format!("Hello {}, your number is {}", name, number); - Response::new() - .with_header(ContentLength(body.len() as u64)) - .with_body(body) + Response::new(body.into()) })) }, _ => { - Box::new(futures::future::ok(Response::new() - .with_status(StatusCode::NotFound))) + Box::new(futures::future::ok(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::empty()) + .unwrap())) } } } diff --git a/examples/send_file.rs b/examples/send_file.rs index c085ec106b..71351ef975 100644 --- a/examples/send_file.rs +++ b/examples/send_file.rs @@ -4,12 +4,11 @@ extern crate hyper; extern crate pretty_env_logger; use futures::{Future, Sink}; -use futures::sync::{mpsc, oneshot}; +use futures::sync::oneshot; -use hyper::{Chunk, Get, StatusCode}; +use hyper::{Body, Chunk, Method, Request, Response, StatusCode}; use hyper::error::Error; -use hyper::header::ContentLength; -use hyper::server::{Http, Service, Request, Response}; +use hyper::server::{Http, Service}; use std::fs::File; use std::io::{self, copy, Read}; @@ -18,7 +17,7 @@ use std::thread; static NOTFOUND: &[u8] = b"Not Found"; static INDEX: &str = "examples/send_file_index.html"; -fn simple_file_send(f: &str) -> Box> { +fn simple_file_send(f: &str) -> Box, Error = hyper::Error>> { // Serve a file by reading it entirely into memory. As a result // this is limited to serving small files, but it is somewhat // simpler with a little less overhead. @@ -31,10 +30,10 @@ fn simple_file_send(f: &str) -> Box f, Err(_) => { - tx.send(Response::new() - .with_status(StatusCode::NotFound) - .with_header(ContentLength(NOTFOUND.len() as u64)) - .with_body(NOTFOUND)) + tx.send(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(NOTFOUND.into()) + .unwrap()) .expect("Send error on open"); return; }, @@ -42,14 +41,15 @@ fn simple_file_send(f: &str) -> Box = Vec::new(); match copy(&mut file, &mut buf) { Ok(_) => { - let res = Response::new() - .with_header(ContentLength(buf.len() as u64)) - .with_body(buf); + let res = Response::new(buf.into()); tx.send(res).expect("Send error on successful file read"); }, Err(_) => { - tx.send(Response::new().with_status(StatusCode::InternalServerError)). - expect("Send error on error reading file"); + tx.send(Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::empty()) + .unwrap()) + .expect("Send error on error reading file"); }, }; }); @@ -60,17 +60,17 @@ fn simple_file_send(f: &str) -> Box; + type Response = Response; type Error = hyper::Error; type Future = Box>; - fn call(&self, req: Request) -> Self::Future { - match (req.method(), req.path()) { - (&Get, "/") | (&Get, "/index.html") => { + fn call(&self, req: Request) -> Self::Future { + match (req.method(), req.uri().path()) { + (&Method::GET, "/") | (&Method::GET, "/index.html") => { simple_file_send(INDEX) }, - (&Get, "/big_file.html") => { + (&Method::GET, "/big_file.html") => { // Stream a large file in chunks. This requires a // little more overhead with two channels, (one for // the response future, and a second for the response @@ -83,16 +83,16 @@ impl Service for ResponseExamples { let mut file = match File::open(INDEX) { Ok(f) => f, Err(_) => { - tx.send(Response::new() - .with_status(StatusCode::NotFound) - .with_header(ContentLength(NOTFOUND.len() as u64)) - .with_body(NOTFOUND)) + tx.send(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(NOTFOUND.into()) + .unwrap()) .expect("Send error on open"); return; }, }; - let (mut tx_body, rx_body) = mpsc::channel(1); - let res = Response::new().with_body(rx_body); + let (mut tx_body, rx_body) = Body::pair(); + let res = Response::new(rx_body.into()); tx.send(res).expect("Send error on successful file read"); let mut buf = [0u8; 16]; loop { @@ -114,16 +114,18 @@ impl Service for ResponseExamples { } } }); - + Box::new(rx.map_err(|e| Error::from(io::Error::new(io::ErrorKind::Other, e)))) }, - (&Get, "/no_file.html") => { + (&Method::GET, "/no_file.html") => { // Test what happens when file cannot be be found simple_file_send("this_file_should_not_exist.html") }, _ => { - Box::new(futures::future::ok(Response::new() - .with_status(StatusCode::NotFound))) + Box::new(futures::future::ok(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::empty()) + .unwrap())) } } } diff --git a/examples/server.rs b/examples/server.rs index 9b246cf608..7c7b3c510b 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -5,37 +5,31 @@ extern crate pretty_env_logger; use futures::future::FutureResult; -use hyper::{Get, Post, StatusCode}; -use hyper::header::ContentLength; -use hyper::server::{Http, Service, Request, Response}; +use hyper::{Body, Method, Request, Response, StatusCode}; +use hyper::server::{Http, Service}; static INDEX: &'static [u8] = b"Try POST /echo"; struct Echo; impl Service for Echo { - type Request = Request; - type Response = Response; + type Request = Request; + type Response = Response; type Error = hyper::Error; - type Future = FutureResult; - - fn call(&self, req: Request) -> Self::Future { - futures::future::ok(match (req.method(), req.path()) { - (&Get, "/") | (&Get, "/echo") => { - Response::new() - .with_header(ContentLength(INDEX.len() as u64)) - .with_body(INDEX) + type Future = FutureResult; + + fn call(&self, req: Self::Request) -> Self::Future { + futures::future::ok(match (req.method(), req.uri().path()) { + (&Method::GET, "/") | (&Method::POST, "/") => { + Response::new(INDEX.into()) }, - (&Post, "/echo") => { - let mut res = Response::new(); - if let Some(len) = req.headers().get::() { - res.headers_mut().set(len.clone()); - } - res.with_body(req.body()) + (&Method::POST, "/echo") => { + Response::new(req.into_parts().1) }, _ => { - Response::new() - .with_status(StatusCode::NotFound) + let mut res = Response::new(Body::empty()); + *res.status_mut() = StatusCode::NOT_FOUND; + res } }) } diff --git a/examples/web_api.rs b/examples/web_api.rs index 341847602e..d7fdb46e7b 100644 --- a/examples/web_api.rs +++ b/examples/web_api.rs @@ -6,10 +6,9 @@ extern crate tokio_core; use futures::{Future, Stream}; -use hyper::{Body, Chunk, Client, Get, Post, StatusCode}; +use hyper::{Body, Chunk, Client, Method, Request, Response, StatusCode}; use hyper::error::Error; -use hyper::header::ContentLength; -use hyper::server::{Http, Service, Request, Response}; +use hyper::server::{Http, Service}; #[allow(unused)] use std::ascii::AsciiExt; @@ -24,50 +23,51 @@ pub type ResponseStream = Box>; struct ResponseExamples(tokio_core::reactor::Handle); impl Service for ResponseExamples { - type Request = Request; + type Request = Request; type Response = Response; type Error = hyper::Error; type Future = Box>; - fn call(&self, req: Request) -> Self::Future { - match (req.method(), req.path()) { - (&Get, "/") | (&Get, "/index.html") => { + fn call(&self, req: Self::Request) -> Self::Future { + match (req.method(), req.uri().path()) { + (&Method::GET, "/") | (&Method::GET, "/index.html") => { let body: ResponseStream = Box::new(Body::from(INDEX)); - Box::new(futures::future::ok(Response::new() - .with_header(ContentLength(INDEX.len() as u64)) - .with_body(body))) + Box::new(futures::future::ok(Response::new(body))) }, - (&Get, "/test.html") => { + (&Method::GET, "/test.html") => { // Run a web query against the web api below let client = Client::configure().build(&self.0); - let mut req = Request::new(Post, URL.parse().unwrap()); - req.set_body(LOWERCASE); + let req = Request::builder() + .method(Method::POST) + .uri(URL) + .body(LOWERCASE.into()) + .unwrap(); let web_res_future = client.request(req); Box::new(web_res_future.map(|web_res| { - let body: ResponseStream = Box::new(web_res.body().map(|b| { + let body: ResponseStream = Box::new(web_res.into_parts().1.map(|b| { Chunk::from(format!("before: '{:?}'
after: '{:?}'", std::str::from_utf8(LOWERCASE).unwrap(), std::str::from_utf8(&b).unwrap())) })); - Response::new().with_body(body) + Response::new(body) })) }, - (&Post, "/web_api") => { + (&Method::POST, "/web_api") => { // A web api to run against. Simple upcasing of the body. - let body: ResponseStream = Box::new(req.body().map(|chunk| { + let body: ResponseStream = Box::new(req.into_parts().1.map(|chunk| { let upper = chunk.iter().map(|byte| byte.to_ascii_uppercase()) .collect::>(); Chunk::from(upper) })); - Box::new(futures::future::ok(Response::new().with_body(body))) + Box::new(futures::future::ok(Response::new(body))) }, _ => { let body: ResponseStream = Box::new(Body::from(NOTFOUND)); - Box::new(futures::future::ok(Response::new() - .with_status(StatusCode::NotFound) - .with_header(ContentLength(NOTFOUND.len() as u64)) - .with_body(body))) + Box::new(futures::future::ok(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(body) + .unwrap())) } } } diff --git a/src/client/compat.rs b/src/client/compat.rs deleted file mode 100644 index 26399e82aa..0000000000 --- a/src/client/compat.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Wrappers to build compatibility with the `http` crate. - -use futures::{Future, Poll, Stream}; -use http; -use tokio_service::Service; - -use client::{Connect, Client, FutureResponse}; -use error::Error; -use proto::Body; - -/// A Client to make outgoing HTTP requests. -#[derive(Debug)] -pub struct CompatClient { - inner: Client -} - -pub(super) fn client(client: Client) -> CompatClient { - CompatClient { inner: client } -} - -impl Service for CompatClient -where C: Connect, - B: Stream + 'static, - B::Item: AsRef<[u8]>, -{ - type Request = http::Request; - type Response = http::Response; - type Error = Error; - type Future = CompatFutureResponse; - - fn call(&self, req: Self::Request) -> Self::Future { - future(self.inner.call(req.into())) - } -} - -/// A `Future` that will resolve to an `http::Response`. -#[must_use = "futures do nothing unless polled"] -#[derive(Debug)] -pub struct CompatFutureResponse { - inner: FutureResponse -} - -pub(super) fn future(fut: FutureResponse) -> CompatFutureResponse { - CompatFutureResponse { inner: fut } -} - -impl Future for CompatFutureResponse { - type Item = http::Response; - type Error = Error; - - fn poll(&mut self) -> Poll { - self.inner.poll() - .map(|a| a.map(|r| r.into())) - } -} diff --git a/src/client/conn.rs b/src/client/conn.rs index d35b74cf71..91fdeeaa36 100644 --- a/src/client/conn.rs +++ b/src/client/conn.rs @@ -16,7 +16,8 @@ use futures::future::{self, Either}; use tokio_io::{AsyncRead, AsyncWrite}; use proto; -use super::{dispatch, Request, Response}; +use super::dispatch; +use {Body, Request, Response, StatusCode}; /// Returns a `Handshake` future over some IO. /// @@ -31,7 +32,7 @@ where /// The sender side of an established connection. pub struct SendRequest { - dispatch: dispatch::Sender, ::Response>, + dispatch: dispatch::Sender, Response>, } @@ -79,7 +80,7 @@ pub struct Handshake { pub struct ResponseFuture { // for now, a Box is used to hide away the internal `B` // that can be returned if canceled - inner: Box + Send>, + inner: Box, Error=::Error> + Send>, } /// Deconstructed parts of a `Connection`. @@ -158,17 +159,20 @@ where /// ``` /// # extern crate futures; /// # extern crate hyper; + /// # extern crate http; + /// # use http::header::HOST; /// # use hyper::client::conn::SendRequest; /// # use hyper::Body; /// use futures::Future; - /// use hyper::{Method, Request}; - /// use hyper::header::Host; + /// use hyper::Request; /// /// # fn doc(mut tx: SendRequest) { /// // build a Request - /// let path = "/foo/bar".parse().expect("valid path"); - /// let mut req = Request::new(Method::Get, path); - /// req.headers_mut().set(Host::new("hyper.rs", None)); + /// let req = Request::builder() + /// .uri("/foo/bar") + /// .header(HOST, "hyper.rs") + /// .body(Body::empty()) + /// .unwrap(); /// /// // send it and get a future back /// let fut = tx.send_request(req) @@ -180,7 +184,8 @@ where /// # } /// # fn main() {} /// ``` - pub fn send_request(&mut self, mut req: Request) -> ResponseFuture { + pub fn send_request(&mut self, req: Request) -> ResponseFuture { + /* TODO? // The Connection API does less things automatically than the Client // API does. For instance, right here, we always assume set_proxy, so // that if an absolute-form URI is provided, it is serialized as-is. @@ -191,9 +196,9 @@ where // It's important that this method isn't called directly from the // `Client`, so that `set_proxy` there is still respected. req.set_proxy(true); + */ - let (head, body) = proto::request::split(req); - let inner = match self.dispatch.send((head, body)) { + let inner = match self.dispatch.send(req) { Ok(rx) => { Either::A(rx.then(move |res| { match res { @@ -210,15 +215,15 @@ where Either::B(future::err(err)) } }; + ResponseFuture { inner: Box::new(inner), } } //TODO: replace with `impl Future` when stable - pub(crate) fn send_request_retryable(&mut self, req: Request) -> Box)>)>> { - let (head, body) = proto::request::split(req); - let inner = match self.dispatch.try_send((head, body)) { + pub(crate) fn send_request_retryable(&mut self, req: Request) -> Box, Error=(::Error, Option>)>> { + let inner = match self.dispatch.try_send(req) { Ok(rx) => { Either::A(rx.then(move |res| { match res { @@ -418,7 +423,7 @@ where B: Stream + 'static, B::Item: AsRef<[u8]>, R: proto::Http1Transaction< - Incoming=proto::RawStatus, + Incoming=StatusCode, Outgoing=proto::RequestLine, >, { @@ -451,7 +456,7 @@ where // ===== impl ResponseFuture impl Future for ResponseFuture { - type Item = Response; + type Item = Response; type Error = ::Error; #[inline] @@ -497,3 +502,4 @@ impl AssertSendSync for Builder {} #[doc(hidden)] impl AssertSend for ResponseFuture {} + diff --git a/src/client/connect.rs b/src/client/connect.rs index 4e6131ea8e..cfe6fd96a5 100644 --- a/src/client/connect.rs +++ b/src/client/connect.rs @@ -9,11 +9,12 @@ use futures::{Future, Poll, Async}; use futures::future::{Executor, ExecuteError}; use futures::sync::oneshot; use futures_cpupool::{Builder as CpuPoolBuilder}; +use http::Uri; +use http::uri::Scheme; use tokio_io::{AsyncRead, AsyncWrite}; use tokio::reactor::Handle; use tokio::net::{TcpStream, TcpStreamNew}; use tokio_service::Service; -use Uri; use super::dns; @@ -118,10 +119,10 @@ impl Service for HttpConnector { trace!("Http::connect({:?})", uri); if self.enforce_http { - if uri.scheme() != Some("http") { + if uri.scheme_part() != Some(&Scheme::HTTP) { return invalid_url(InvalidUrl::NotHttp, &self.handle); } - } else if uri.scheme().is_none() { + } else if uri.scheme_part().is_none() { return invalid_url(InvalidUrl::MissingScheme, &self.handle); } @@ -131,10 +132,7 @@ impl Service for HttpConnector { }; let port = match uri.port() { Some(port) => port, - None => match uri.scheme() { - Some("https") => 443, - _ => 80, - }, + None => if uri.scheme_part() == Some(&Scheme::HTTPS) { 443 } else { 80 }, }; HttpConnecting { diff --git a/src/client/mod.rs b/src/client/mod.rs index 697d8b509d..e2245de403 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -9,21 +9,14 @@ use std::time::Duration; use futures::{Async, Future, Poll, Stream}; use futures::future::{self, Executor}; -#[cfg(feature = "compat")] -use http; +use http::{Method, Request, Response, Uri, Version}; +use http::header::{Entry, HeaderValue, HOST}; use tokio::reactor::Handle; pub use tokio_service::Service; -use header::{Host}; -use proto; -use proto::request; -use method::Method; +use proto::{self, Body}; use self::pool::Pool; -use uri::{self, Uri}; -use version::HttpVersion; -pub use proto::response::Response; -pub use proto::request::Request; pub use self::connect::{HttpConnector, Connect}; use self::background::{bg, Background}; @@ -34,8 +27,6 @@ mod connect; pub(crate) mod dispatch; mod dns; mod pool; -#[cfg(feature = "compat")] -pub mod compat; mod signal; #[cfg(test)] mod tests; @@ -113,14 +104,29 @@ where C: Connect, B: Stream + 'static, B::Item: AsRef<[u8]>, { - /// Send a GET Request using this Client. - #[inline] - pub fn get(&self, url: Uri) -> FutureResponse { - self.request(Request::new(Method::Get, url)) + + /// Send a `GET` request to the supplied `Uri`. + /// + /// # Note + /// + /// This requires that the `Entity` type have a `Default` implementation. + /// It *should* return an "empty" version of itself, such that + /// `Entity::is_end_stream` is `true`. + pub fn get(&self, uri: Uri) -> FutureResponse + where + B: Default, + { + let body = B::default(); + if !body.is_end_stream() { + warn!("default Entity used for get() does not return true for is_end_stream"); + } + + let mut req = Request::new(body); + *req.uri_mut() = uri; + self.request(req) } /// Send a constructed Request using this Client. - #[inline] pub fn request(&self, mut req: Request) -> FutureResponse { // TODO(0.12): do this at construction time. // @@ -131,23 +137,25 @@ where C: Connect, self.schedule_pool_timer(); match req.version() { - HttpVersion::Http10 | - HttpVersion::Http11 => (), + Version::HTTP_10 | + Version::HTTP_11 => (), other => { - error!("Request has unsupported version \"{}\"", other); + error!("Request has unsupported version \"{:?}\"", other); return FutureResponse(Box::new(future::err(::Error::Version))); } } - if req.method() == &Method::Connect { + if req.method() == &Method::CONNECT { debug!("Client does not support CONNECT requests"); return FutureResponse(Box::new(future::err(::Error::Method))); } - let domain = match uri::scheme_and_authority(req.uri()) { - Some(uri) => uri, - None => { - debug!("request uri does not include scheme and authority"); + let uri = req.uri().clone(); + let domain = match (uri.scheme_part(), uri.authority_part()) { + (Some(scheme), Some(auth)) => { + format!("{}://{}", scheme, auth) + } + _ => { return FutureResponse(Box::new(future::err(::Error::Io( io::Error::new( io::ErrorKind::InvalidInput, @@ -156,45 +164,51 @@ where C: Connect, )))); } }; - if self.set_host && !req.headers().has::() { - let host = Host::new( - domain.host().expect("authority implies host").to_owned(), - domain.port(), - ); - req.headers_mut().set_pos(0, host); + + if self.set_host { + if let Entry::Vacant(entry) = req.headers_mut().entry(HOST).expect("HOST is always valid header name") { + let hostname = uri.host().expect("authority implies host"); + let host = if let Some(port) = uri.port() { + let s = format!("{}:{}", hostname, port); + HeaderValue::from_str(&s) + } else { + HeaderValue::from_str(hostname) + }.expect("uri host is valid header value"); + entry.insert(host); + } } + let client = self.clone(); - let is_proxy = req.is_proxy(); - let uri = req.uri().clone(); + //TODO: let is_proxy = req.is_proxy(); + //let uri = req.uri().clone(); let fut = RetryableSendRequest { client: client, future: self.send_request(req, &domain), domain: domain, - is_proxy: is_proxy, - uri: uri, + //is_proxy: is_proxy, + //uri: uri, }; FutureResponse(Box::new(fut)) } - /// Send an `http::Request` using this Client. - #[inline] - #[cfg(feature = "compat")] - pub fn request_compat(&self, req: http::Request) -> compat::CompatFutureResponse { - self::compat::future(self.call(req.into())) - } - - /// Convert into a client accepting `http::Request`. - #[cfg(feature = "compat")] - pub fn into_compat(self) -> compat::CompatClient { - self::compat::client(self) - } - //TODO: replace with `impl Future` when stable - fn send_request(&self, req: Request, domain: &Uri) -> Box>> { - //fn send_request(&self, req: Request, domain: &Uri) -> Box> { + fn send_request(&self, mut req: Request, domain: &str) -> Box, Error=ClientError>> { let url = req.uri().clone(); - let checkout = self.pool.checkout(domain.as_ref()); + + let path = match url.path_and_query() { + Some(path) => { + let mut parts = ::http::uri::Parts::default(); + parts.path_and_query = Some(path.clone()); + Uri::from_parts(parts).expect("path is valid uri") + }, + None => { + "/".parse().expect("/ is valid path") + } + }; + *req.uri_mut() = path; + + let checkout = self.pool.checkout(domain); let connect = { let executor = self.executor.clone(); let pool = self.pool.clone(); @@ -228,7 +242,6 @@ where C: Connect, ClientError::Normal(e) }); - let executor = self.executor.clone(); let resp = race.and_then(move |mut pooled| { let conn_reused = pooled.is_reused(); @@ -284,7 +297,7 @@ where C: Connect, B::Item: AsRef<[u8]>, { type Request = Request; - type Response = Response; + type Response = Response; type Error = ::Error; type Future = FutureResponse; @@ -315,7 +328,7 @@ impl fmt::Debug for Client { /// A `Future` that will resolve to an HTTP Response. #[must_use = "futures do nothing unless polled"] -pub struct FutureResponse(Box + 'static>); +pub struct FutureResponse(Box, Error=::Error> + 'static>); impl fmt::Debug for FutureResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -324,7 +337,7 @@ impl fmt::Debug for FutureResponse { } impl Future for FutureResponse { - type Item = Response; + type Item = Response; type Error = ::Error; fn poll(&mut self) -> Poll { @@ -334,10 +347,10 @@ impl Future for FutureResponse { struct RetryableSendRequest { client: Client, - domain: Uri, - future: Box>>, - is_proxy: bool, - uri: Uri, + domain: String, + future: Box, Error=ClientError>>, + //is_proxy: bool, + //uri: Uri, } impl Future for RetryableSendRequest @@ -346,7 +359,7 @@ where B: Stream + 'static, B::Item: AsRef<[u8]>, { - type Item = Response; + type Item = Response; type Error = ::Error; fn poll(&mut self) -> Poll { @@ -367,9 +380,6 @@ where } trace!("unstarted request canceled, trying again (reason={:?})", reason); - let mut req = request::join(req); - req.set_proxy(self.is_proxy); - req.set_uri(self.uri.clone()); self.future = self.client.send_request(req, &self.domain); } } @@ -394,7 +404,7 @@ pub(crate) enum ClientError { Normal(::Error), Canceled { connection_reused: bool, - req: (::proto::RequestHead, Option), + req: Request, reason: ::Error, } } diff --git a/src/client/tests.rs b/src/client/tests.rs index 7d2157ddf9..1f40e81671 100644 --- a/src/client/tests.rs +++ b/src/client/tests.rs @@ -23,7 +23,12 @@ fn retryable_request() { { - let res1 = client.get("http://mock.local/a".parse().unwrap()); + + let req = Request::builder() + .uri("http://mock.local/a") + .body(Default::default()) + .unwrap(); + let res1 = client.request(req); let srv1 = poll_fn(|| { try_ready!(sock1.read(&mut [0u8; 512])); try_ready!(sock1.write(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")); @@ -33,7 +38,11 @@ fn retryable_request() { } drop(sock1); - let res2 = client.get("http://mock.local/b".parse().unwrap()) + let req = Request::builder() + .uri("http://mock.local/b") + .body(Default::default()) + .unwrap(); + let res2 = client.request(req) .map(|res| { assert_eq!(res.status().as_u16(), 222); }); @@ -61,7 +70,13 @@ fn conn_reset_after_write() { { - let res1 = client.get("http://mock.local/a".parse().unwrap()); + let req = Request::builder() + .uri("http://mock.local/a") + //TODO: remove this header when auto lengths are fixed + .header("content-length", "0") + .body(Default::default()) + .unwrap(); + let res1 = client.request(req); let srv1 = poll_fn(|| { try_ready!(sock1.read(&mut [0u8; 512])); try_ready!(sock1.write(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")); @@ -70,7 +85,11 @@ fn conn_reset_after_write() { core.run(res1.join(srv1)).expect("res1"); } - let res2 = client.get("http://mock.local/a".parse().unwrap()); + let req = Request::builder() + .uri("http://mock.local/a") + .body(Default::default()) + .unwrap(); + let res2 = client.request(req); let mut sock1 = Some(sock1); let srv2 = poll_fn(|| { // We purposefully keep the socket open until the client diff --git a/src/common/mod.rs b/src/common/mod.rs index bc2304bb49..a627be7d3f 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,6 +1,2 @@ -pub use self::str::ByteStr; - -mod str; - #[derive(Debug)] pub enum Never {} diff --git a/src/common/str.rs b/src/common/str.rs deleted file mode 100644 index 3f370d8549..0000000000 --- a/src/common/str.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::ops::Deref; -use std::str; - -use bytes::Bytes; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ByteStr(Bytes); - -impl ByteStr { - pub unsafe fn from_utf8_unchecked(slice: Bytes) -> ByteStr { - ByteStr(slice) - } - - pub fn from_static(s: &'static str) -> ByteStr { - ByteStr(Bytes::from_static(s.as_bytes())) - } - - pub fn slice(&self, from: usize, to: usize) -> ByteStr { - assert!(self.as_str().is_char_boundary(from)); - assert!(self.as_str().is_char_boundary(to)); - ByteStr(self.0.slice(from, to)) - } - - pub fn slice_to(&self, idx: usize) -> ByteStr { - assert!(self.as_str().is_char_boundary(idx)); - ByteStr(self.0.slice_to(idx)) - } - - pub fn as_str(&self) -> &str { - unsafe { str::from_utf8_unchecked(self.0.as_ref()) } - } - - pub fn insert(&mut self, idx: usize, ch: char) { - let mut s = self.as_str().to_owned(); - s.insert(idx, ch); - let bytes = Bytes::from(s); - self.0 = bytes; - } - - #[cfg(feature = "compat")] - pub fn into_bytes(self) -> Bytes { - self.0 - } -} - -impl Deref for ByteStr { - type Target = str; - fn deref(&self) -> &str { - self.as_str() - } -} - -impl<'a> From<&'a str> for ByteStr { - fn from(s: &'a str) -> ByteStr { - ByteStr(Bytes::from(s)) - } -} diff --git a/src/error.rs b/src/error.rs index b6f846a585..e3f14afaf7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,13 +6,12 @@ use std::str::Utf8Error; use std::string::FromUtf8Error; use httparse; - -pub use uri::UriError; +use http; use self::Error::{ Method, - Uri, Version, + Uri, Header, Status, Timeout, @@ -33,10 +32,10 @@ pub type Result = ::std::result::Result; pub enum Error { /// An invalid `Method`, such as `GE,T`. Method, - /// An invalid `Uri`, such as `exam ple.domain`. - Uri(UriError), /// An invalid `HttpVersion`, such as `HTP/1.1` Version, + /// Uri Errors + Uri, /// An invalid `Header`. Header, /// A message head is too large to be reasonable. @@ -105,7 +104,6 @@ impl fmt::Debug for Void { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Uri(ref e) => fmt::Display::fmt(e, f), Io(ref e) => fmt::Display::fmt(e, f), Utf8(ref e) => fmt::Display::fmt(e, f), ref e => f.write_str(e.description()), @@ -118,6 +116,7 @@ impl StdError for Error { match *self { Method => "invalid Method specified", Version => "invalid HTTP version specified", + Uri => "invalid URI", Header => "invalid Header provided", TooLarge => "message head is too large", Status => "invalid Status provided", @@ -126,7 +125,6 @@ impl StdError for Error { Upgrade => "unsupported protocol upgrade", Closed => "connection is closed", Cancel(ref e) => e.description(), - Uri(ref e) => e.description(), Io(ref e) => e.description(), Utf8(ref e) => e.description(), Error::__Nonexhaustive(..) => unreachable!(), @@ -136,7 +134,6 @@ impl StdError for Error { fn cause(&self) -> Option<&StdError> { match *self { Io(ref error) => Some(error), - Uri(ref error) => Some(error), Utf8(ref error) => Some(error), Cancel(ref e) => e.cause.as_ref().map(|e| &**e as &StdError), Error::__Nonexhaustive(..) => unreachable!(), @@ -145,12 +142,6 @@ impl StdError for Error { } } -impl From for Error { - fn from(err: UriError) -> Error { - Uri(err) - } -} - impl From for Error { fn from(err: IoError) -> Error { Io(err) @@ -183,6 +174,18 @@ impl From for Error { } } +impl From for Error { + fn from(_: http::method::InvalidMethod) -> Error { + Error::Method + } +} + +impl From for Error { + fn from(_: http::uri::InvalidUriBytes) -> Error { + Error::Uri + } +} + #[doc(hidden)] trait AssertSendSync: Send + Sync + 'static {} #[doc(hidden)] diff --git a/src/header/common/accept.rs b/src/header/common/accept.rs deleted file mode 100644 index ed6ee69d12..0000000000 --- a/src/header/common/accept.rs +++ /dev/null @@ -1,147 +0,0 @@ -use mime::{self, Mime}; - -use header::{QualityItem, qitem}; - -header! { - /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) - /// - /// The `Accept` header field can be used by user agents to specify - /// response media types that are acceptable. Accept header fields can - /// be used to indicate that the request is specifically limited to a - /// small set of desired types, as in the case of a request for an - /// in-line image - /// - /// # ABNF - /// - /// ```text - /// Accept = #( media-range [ accept-params ] ) - /// - /// media-range = ( "*/*" - /// / ( type "/" "*" ) - /// / ( type "/" subtype ) - /// ) *( OWS ";" OWS parameter ) - /// accept-params = weight *( accept-ext ) - /// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ] - /// ``` - /// - /// # Example values - /// * `audio/*; q=0.2, audio/basic` - /// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c` - /// - /// # Examples - /// ``` - /// use hyper::header::{Headers, Accept, qitem}; - /// use hyper::mime; - /// - /// let mut headers = Headers::new(); - /// - /// headers.set( - /// Accept(vec![ - /// qitem(mime::TEXT_HTML), - /// ]) - /// ); - /// ``` - /// - /// ``` - /// use hyper::header::{Headers, Accept, qitem}; - /// use hyper::mime; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// Accept(vec![ - /// qitem(mime::APPLICATION_JSON), - /// ]) - /// ); - /// ``` - /// ``` - /// use hyper::header::{Headers, Accept, QualityItem, q, qitem}; - /// use hyper::mime; - /// - /// let mut headers = Headers::new(); - /// - /// headers.set( - /// Accept(vec![ - /// qitem(mime::TEXT_HTML), - /// qitem("application/xhtml+xml".parse().unwrap()), - /// QualityItem::new( - /// mime::TEXT_XML, - /// q(900) - /// ), - /// qitem("image/webp".parse().unwrap()), - /// QualityItem::new( - /// mime::STAR_STAR, - /// q(800) - /// ), - /// ]) - /// ); - /// ``` - (Accept, "Accept") => (QualityItem)+ - - test_accept { - // Tests from the RFC - test_header!( - test1, - vec![b"audio/*; q=0.2, audio/basic"], - Some(HeaderField(vec![ - QualityItem::new("audio/*".parse().unwrap(), q(200)), - qitem("audio/basic".parse().unwrap()), - ]))); - test_header!( - test2, - vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], - Some(HeaderField(vec![ - QualityItem::new(TEXT_PLAIN, q(500)), - qitem(TEXT_HTML), - QualityItem::new( - "text/x-dvi".parse().unwrap(), - q(800)), - qitem("text/x-c".parse().unwrap()), - ]))); - // Custom tests - test_header!( - test3, - vec![b"text/plain; charset=utf-8"], - Some(Accept(vec![ - qitem(TEXT_PLAIN_UTF_8), - ]))); - test_header!( - test4, - vec![b"text/plain; charset=utf-8; q=0.5"], - Some(Accept(vec![ - QualityItem::new(TEXT_PLAIN_UTF_8, - q(500)), - ]))); - - #[test] - fn test_fuzzing1() { - let raw: Raw = "chunk#;e".into(); - let header = Accept::parse_header(&raw); - assert!(header.is_ok()); - } - } -} - -impl Accept { - /// A constructor to easily create `Accept: */*`. - pub fn star() -> Accept { - Accept(vec![qitem(mime::STAR_STAR)]) - } - - /// A constructor to easily create `Accept: application/json`. - pub fn json() -> Accept { - Accept(vec![qitem(mime::APPLICATION_JSON)]) - } - - /// A constructor to easily create `Accept: text/*`. - pub fn text() -> Accept { - Accept(vec![qitem(mime::TEXT_STAR)]) - } - - /// A constructor to easily create `Accept: image/*`. - pub fn image() -> Accept { - Accept(vec![qitem(mime::IMAGE_STAR)]) - } -} - - -bench_header!(bench, Accept, { vec![b"text/plain; q=0.5, text/html".to_vec()] }); diff --git a/src/header/common/accept_charset.rs b/src/header/common/accept_charset.rs deleted file mode 100644 index 9aa22a7cb1..0000000000 --- a/src/header/common/accept_charset.rs +++ /dev/null @@ -1,57 +0,0 @@ -use header::{Charset, QualityItem}; - -header! { - /// `Accept-Charset` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) - /// - /// The `Accept-Charset` header field can be sent by a user agent to - /// indicate what charsets are acceptable in textual response content. - /// This field allows user agents capable of understanding more - /// comprehensive or special-purpose charsets to signal that capability - /// to an origin server that is capable of representing information in - /// those charsets. - /// - /// # ABNF - /// - /// ```text - /// Accept-Charset = 1#( ( charset / "*" ) [ weight ] ) - /// ``` - /// - /// # Example values - /// * `iso-8859-5, unicode-1-1;q=0.8` - /// - /// # Examples - /// ``` - /// use hyper::header::{Headers, AcceptCharset, Charset, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) - /// ); - /// ``` - /// ``` - /// use hyper::header::{Headers, AcceptCharset, Charset, q, QualityItem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptCharset(vec![ - /// QualityItem::new(Charset::Us_Ascii, q(900)), - /// QualityItem::new(Charset::Iso_8859_10, q(200)), - /// ]) - /// ); - /// ``` - /// ``` - /// use hyper::header::{Headers, AcceptCharset, Charset, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) - /// ); - /// ``` - (AcceptCharset, "Accept-Charset") => (QualityItem)+ - - test_accept_charset { - /// Testcase from RFC - test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); - } -} diff --git a/src/header/common/accept_encoding.rs b/src/header/common/accept_encoding.rs deleted file mode 100644 index c90f529bca..0000000000 --- a/src/header/common/accept_encoding.rs +++ /dev/null @@ -1,72 +0,0 @@ -use header::{Encoding, QualityItem}; - -header! { - /// `Accept-Encoding` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.4) - /// - /// The `Accept-Encoding` header field can be used by user agents to - /// indicate what response content-codings are - /// acceptable in the response. An `identity` token is used as a synonym - /// for "no encoding" in order to communicate when no encoding is - /// preferred. - /// - /// # ABNF - /// - /// ```text - /// Accept-Encoding = #( codings [ weight ] ) - /// codings = content-coding / "identity" / "*" - /// ``` - /// - /// # Example values - /// * `compress, gzip` - /// * `` - /// * `*` - /// * `compress;q=0.5, gzip;q=1` - /// * `gzip;q=1.0, identity; q=0.5, *;q=0` - /// - /// # Examples - /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptEncoding(vec![qitem(Encoding::Chunked)]) - /// ); - /// ``` - /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptEncoding(vec![ - /// qitem(Encoding::Chunked), - /// qitem(Encoding::Gzip), - /// qitem(Encoding::Deflate), - /// ]) - /// ); - /// ``` - /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, QualityItem, q, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptEncoding(vec![ - /// qitem(Encoding::Chunked), - /// QualityItem::new(Encoding::Gzip, q(600)), - /// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)), - /// ]) - /// ); - /// ``` - (AcceptEncoding, "Accept-Encoding") => (QualityItem)* - - test_accept_encoding { - // From the RFC - test_header!(test1, vec![b"compress, gzip"]); - test_header!(test2, vec![b""], Some(AcceptEncoding(vec![]))); - test_header!(test3, vec![b"*"]); - // Note: Removed quality 1 from gzip - test_header!(test4, vec![b"compress;q=0.5, gzip"]); - // Note: Removed quality 1 from gzip - test_header!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); - } -} diff --git a/src/header/common/accept_language.rs b/src/header/common/accept_language.rs deleted file mode 100644 index b8e47b6a78..0000000000 --- a/src/header/common/accept_language.rs +++ /dev/null @@ -1,72 +0,0 @@ -use language_tags::LanguageTag; -use header::QualityItem; - -header! { - /// `Accept-Language` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) - /// - /// The `Accept-Language` header field can be used by user agents to - /// indicate the set of natural languages that are preferred in the - /// response. - /// - /// # ABNF - /// - /// ```text - /// Accept-Language = 1#( language-range [ weight ] ) - /// language-range = - /// ``` - /// - /// # Example values - /// * `da, en-gb;q=0.8, en;q=0.7` - /// * `en-us;q=1.0, en;q=0.5, fr` - /// - /// # Examples - /// - /// ``` - /// use hyper::header::{Headers, AcceptLanguage, LanguageTag, qitem}; - /// - /// let mut headers = Headers::new(); - /// let mut langtag: LanguageTag = Default::default(); - /// langtag.language = Some("en".to_owned()); - /// langtag.region = Some("US".to_owned()); - /// headers.set( - /// AcceptLanguage(vec![ - /// qitem(langtag), - /// ]) - /// ); - /// ``` - /// - /// ``` - /// # extern crate hyper; - /// # #[macro_use] extern crate language_tags; - /// # use hyper::header::{Headers, AcceptLanguage, QualityItem, q, qitem}; - /// # - /// # fn main() { - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptLanguage(vec![ - /// qitem(langtag!(da)), - /// QualityItem::new(langtag!(en;;;GB), q(800)), - /// QualityItem::new(langtag!(en), q(700)), - /// ]) - /// ); - /// # } - /// ``` - (AcceptLanguage, "Accept-Language") => (QualityItem)+ - - test_accept_language { - // From the RFC - test_header!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); - // Own test - test_header!( - test2, vec![b"en-US, en; q=0.5, fr"], - Some(AcceptLanguage(vec![ - qitem("en-US".parse().unwrap()), - QualityItem::new("en".parse().unwrap(), q(500)), - qitem("fr".parse().unwrap()), - ]))); - } -} - -bench_header!(bench, AcceptLanguage, - { vec![b"en-us;q=1.0, en;q=0.5, fr".to_vec()] }); diff --git a/src/header/common/accept_ranges.rs b/src/header/common/accept_ranges.rs deleted file mode 100644 index bd79584f9d..0000000000 --- a/src/header/common/accept_ranges.rs +++ /dev/null @@ -1,105 +0,0 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; - -header! { - /// `Accept-Ranges` header, defined in - /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-2.3) - /// - /// The `Accept-Ranges` header field allows a server to indicate that it - /// supports range requests for the target resource. - /// - /// # ABNF - /// - /// ```text - /// Accept-Ranges = acceptable-ranges - /// acceptable-ranges = 1#range-unit / \"none\" - /// - /// # Example values - /// * `bytes` - /// * `none` - /// * `unknown-unit` - /// ``` - /// - /// # Examples - /// ``` - /// use hyper::header::{Headers, AcceptRanges, RangeUnit}; - /// - /// let mut headers = Headers::new(); - /// headers.set(AcceptRanges(vec![RangeUnit::Bytes])); - /// ``` - /// - /// ``` - /// use hyper::header::{Headers, AcceptRanges, RangeUnit}; - /// - /// let mut headers = Headers::new(); - /// headers.set(AcceptRanges(vec![RangeUnit::None])); - /// ``` - /// - /// ``` - /// use hyper::header::{Headers, AcceptRanges, RangeUnit}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptRanges(vec![ - /// RangeUnit::Unregistered("nibbles".to_owned()), - /// RangeUnit::Bytes, - /// RangeUnit::Unregistered("doublets".to_owned()), - /// RangeUnit::Unregistered("quadlets".to_owned()), - /// ]) - /// ); - /// ``` - (AcceptRanges, "Accept-Ranges") => (RangeUnit)+ - - test_acccept_ranges { - test_header!(test1, vec![b"bytes"]); - test_header!(test2, vec![b"none"]); - test_header!(test3, vec![b"unknown-unit"]); - test_header!(test4, vec![b"bytes, unknown-unit"]); - } -} - -/// Range Units, described in [RFC7233](http://tools.ietf.org/html/rfc7233#section-2) -/// -/// A representation can be partitioned into subranges according to -/// various structural units, depending on the structure inherent in the -/// representation's media type. -/// -/// # ABNF -/// -/// ```text -/// range-unit = bytes-unit / other-range-unit -/// bytes-unit = "bytes" -/// other-range-unit = token -/// ``` -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum RangeUnit { - /// Indicating byte-range requests are supported. - Bytes, - /// Reserved as keyword, indicating no ranges are supported. - None, - /// The given range unit is not registered at IANA. - Unregistered(String), -} - - -impl FromStr for RangeUnit { - type Err = ::Error; - fn from_str(s: &str) -> ::Result { - match s { - "bytes" => Ok(RangeUnit::Bytes), - "none" => Ok(RangeUnit::None), - // FIXME: Check if s is really a Token - _ => Ok(RangeUnit::Unregistered(s.to_owned())), - } - } -} - -impl Display for RangeUnit { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - RangeUnit::Bytes => f.write_str("bytes"), - RangeUnit::None => f.write_str("none"), - RangeUnit::Unregistered(ref x) => f.write_str(x), - } - } -} diff --git a/src/header/common/access_control_allow_credentials.rs b/src/header/common/access_control_allow_credentials.rs deleted file mode 100644 index a4e688df51..0000000000 --- a/src/header/common/access_control_allow_credentials.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::fmt::{self, Display}; -use std::str; -use unicase; -use header::{Header, Raw}; - -/// `Access-Control-Allow-Credentials` header, part of -/// [CORS](http://www.w3.org/TR/cors/#access-control-allow-headers-response-header) -/// -/// > The Access-Control-Allow-Credentials HTTP response header indicates whether the -/// > response to request can be exposed when the credentials flag is true. When part -/// > of the response to an preflight request it indicates that the actual request can -/// > be made with credentials. The Access-Control-Allow-Credentials HTTP header must -/// > match the following ABNF: -/// -/// # ABNF -/// -/// ```text -/// Access-Control-Allow-Credentials: "Access-Control-Allow-Credentials" ":" "true" -/// ``` -/// -/// Since there is only one acceptable field value, the header struct does not accept -/// any values at all. Setting an empty `AccessControlAllowCredentials` header is -/// sufficient. See the examples below. -/// -/// # Example values -/// * "true" -/// -/// # Examples -/// -/// ``` -/// # extern crate hyper; -/// # fn main() { -/// -/// use hyper::header::{Headers, AccessControlAllowCredentials}; -/// -/// let mut headers = Headers::new(); -/// headers.set(AccessControlAllowCredentials); -/// # } -/// ``` -#[derive(Clone, PartialEq, Debug)] -pub struct AccessControlAllowCredentials; - -const ACCESS_CONTROL_ALLOW_CREDENTIALS_TRUE: &'static str = "true"; - -impl Header for AccessControlAllowCredentials { - fn header_name() -> &'static str { - static NAME: &'static str = "Access-Control-Allow-Credentials"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - if let Some(line) = raw.one() { - let text = unsafe { - // safe because: - // 1. we don't actually care if it's utf8, we just want to - // compare the bytes with the "case" normalized. If it's not - // utf8, then the byte comparison will fail, and we'll return - // None. No big deal. - str::from_utf8_unchecked(line) - }; - if unicase::eq_ascii(text, ACCESS_CONTROL_ALLOW_CREDENTIALS_TRUE) { - return Ok(AccessControlAllowCredentials); - } - } - Err(::Error::Header) - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -impl Display for AccessControlAllowCredentials { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - f.write_str("true") - } -} - -#[cfg(test)] -mod test_access_control_allow_credentials { - use std::str; - use header::*; - use super::AccessControlAllowCredentials as HeaderField; - test_header!(works, vec![b"true"], Some(HeaderField)); - test_header!(ignores_case, vec![b"True"]); - test_header!(not_bool, vec![b"false"], None); - test_header!(only_single, vec![b"true", b"true"], None); - test_header!(no_gibberish, vec!["\u{645}\u{631}\u{62d}\u{628}\u{627}".as_bytes()], None); -} diff --git a/src/header/common/access_control_allow_headers.rs b/src/header/common/access_control_allow_headers.rs deleted file mode 100644 index 83bc2da0ae..0000000000 --- a/src/header/common/access_control_allow_headers.rs +++ /dev/null @@ -1,61 +0,0 @@ -use unicase::Ascii; - -header! { - /// `Access-Control-Allow-Headers` header, part of - /// [CORS](http://www.w3.org/TR/cors/#access-control-allow-headers-response-header) - /// - /// The `Access-Control-Allow-Headers` header indicates, as part of the - /// response to a preflight request, which header field names can be used - /// during the actual request. - /// - /// # ABNF - /// - /// ```text - /// Access-Control-Allow-Headers: "Access-Control-Allow-Headers" ":" #field-name - /// ``` - /// - /// # Example values - /// * `accept-language, date` - /// - /// # Examples - /// - /// ``` - /// # extern crate hyper; - /// # extern crate unicase; - /// # fn main() { - /// // extern crate unicase; - /// - /// use hyper::header::{Headers, AccessControlAllowHeaders}; - /// use unicase::Ascii; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AccessControlAllowHeaders(vec![Ascii::new("date".to_owned())]) - /// ); - /// # } - /// ``` - /// - /// ``` - /// # extern crate hyper; - /// # extern crate unicase; - /// # fn main() { - /// // extern crate unicase; - /// - /// use hyper::header::{Headers, AccessControlAllowHeaders}; - /// use unicase::Ascii; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AccessControlAllowHeaders(vec![ - /// Ascii::new("accept-language".to_owned()), - /// Ascii::new("date".to_owned()), - /// ]) - /// ); - /// # } - /// ``` - (AccessControlAllowHeaders, "Access-Control-Allow-Headers") => (Ascii)* - - test_access_control_allow_headers { - test_header!(test1, vec![b"accept-language, date"]); - } -} diff --git a/src/header/common/access_control_allow_methods.rs b/src/header/common/access_control_allow_methods.rs deleted file mode 100644 index f96c078bea..0000000000 --- a/src/header/common/access_control_allow_methods.rs +++ /dev/null @@ -1,51 +0,0 @@ -use method::Method; - -header! { - /// `Access-Control-Allow-Methods` header, part of - /// [CORS](http://www.w3.org/TR/cors/#access-control-allow-methods-response-header) - /// - /// The `Access-Control-Allow-Methods` header indicates, as part of the - /// response to a preflight request, which methods can be used during the - /// actual request. - /// - /// # ABNF - /// - /// ```text - /// Access-Control-Allow-Methods: "Access-Control-Allow-Methods" ":" #Method - /// ``` - /// - /// # Example values - /// * `PUT, DELETE, XMODIFY` - /// - /// # Examples - /// - /// ``` - /// use hyper::header::{Headers, AccessControlAllowMethods}; - /// use hyper::Method; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AccessControlAllowMethods(vec![Method::Get]) - /// ); - /// ``` - /// - /// ``` - /// use hyper::header::{Headers, AccessControlAllowMethods}; - /// use hyper::Method; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AccessControlAllowMethods(vec![ - /// Method::Get, - /// Method::Post, - /// Method::Patch, - /// Method::Extension("COPY".to_owned()), - /// ]) - /// ); - /// ``` - (AccessControlAllowMethods, "Access-Control-Allow-Methods") => (Method)* - - test_access_control_allow_methods { - test_header!(test1, vec![b"PUT, DELETE, XMODIFY"]); - } -} diff --git a/src/header/common/access_control_allow_origin.rs b/src/header/common/access_control_allow_origin.rs deleted file mode 100644 index 5812ee90a7..0000000000 --- a/src/header/common/access_control_allow_origin.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::fmt::{self, Display}; -use std::str; - -use header::{Header, Raw}; - -/// The `Access-Control-Allow-Origin` response header, -/// part of [CORS](http://www.w3.org/TR/cors/#access-control-allow-origin-response-header) -/// -/// The `Access-Control-Allow-Origin` header indicates whether a resource -/// can be shared based by returning the value of the Origin request header, -/// `*`, or `null` in the response. -/// -/// # ABNF -/// -/// ```text -/// Access-Control-Allow-Origin = "Access-Control-Allow-Origin" ":" origin-list-or-null | "*" -/// ``` -/// -/// # Example values -/// * `null` -/// * `*` -/// * `http://google.com/` -/// -/// # Examples -/// ``` -/// use hyper::header::{Headers, AccessControlAllowOrigin}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// AccessControlAllowOrigin::Any -/// ); -/// ``` -/// ``` -/// use hyper::header::{Headers, AccessControlAllowOrigin}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// AccessControlAllowOrigin::Null, -/// ); -/// ``` -/// ``` -/// use hyper::header::{Headers, AccessControlAllowOrigin}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// AccessControlAllowOrigin::Value("http://hyper.rs".to_owned()) -/// ); -/// ``` -#[derive(Clone, PartialEq, Debug)] -pub enum AccessControlAllowOrigin { - /// Allow all origins - Any, - /// A hidden origin - Null, - /// Allow one particular origin - Value(String), -} - -impl Header for AccessControlAllowOrigin { - fn header_name() -> &'static str { - "Access-Control-Allow-Origin" - } - - fn parse_header(raw: &Raw) -> ::Result { - if let Some(line) = raw.one() { - Ok(match line { - b"*" => AccessControlAllowOrigin::Any, - b"null" => AccessControlAllowOrigin::Null, - _ => AccessControlAllowOrigin::Value(try!(str::from_utf8(line)).into()) - }) - } else { - Err(::Error::Header) - } - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -impl Display for AccessControlAllowOrigin { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - match *self { - AccessControlAllowOrigin::Any => f.write_str("*"), - AccessControlAllowOrigin::Null => f.write_str("null"), - AccessControlAllowOrigin::Value(ref url) => Display::fmt(url, f), - } - } -} - -#[cfg(test)] -mod test_access_control_allow_origin { - use header::*; - use super::AccessControlAllowOrigin as HeaderField; - test_header!(test1, vec![b"null"]); - test_header!(test2, vec![b"*"]); - test_header!(test3, vec![b"http://google.com/"]); -} diff --git a/src/header/common/access_control_expose_headers.rs b/src/header/common/access_control_expose_headers.rs deleted file mode 100644 index 205431a8d4..0000000000 --- a/src/header/common/access_control_expose_headers.rs +++ /dev/null @@ -1,63 +0,0 @@ -use unicase::Ascii; - -header! { - /// `Access-Control-Expose-Headers` header, part of - /// [CORS](http://www.w3.org/TR/cors/#access-control-expose-headers-response-header) - /// - /// The Access-Control-Expose-Headers header indicates which headers are safe to expose to the - /// API of a CORS API specification. - /// - /// # ABNF - /// - /// ```text - /// Access-Control-Expose-Headers = "Access-Control-Expose-Headers" ":" #field-name - /// ``` - /// - /// # Example values - /// * `ETag, Content-Length` - /// - /// # Examples - /// - /// ``` - /// # extern crate hyper; - /// # extern crate unicase; - /// # fn main() { - /// // extern crate unicase; - /// - /// use hyper::header::{Headers, AccessControlExposeHeaders}; - /// use unicase::Ascii; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AccessControlExposeHeaders(vec![ - /// Ascii::new("etag".to_owned()), - /// Ascii::new("content-length".to_owned()) - /// ]) - /// ); - /// # } - /// ``` - /// - /// ``` - /// # extern crate hyper; - /// # extern crate unicase; - /// # fn main() { - /// // extern crate unicase; - /// - /// use hyper::header::{Headers, AccessControlExposeHeaders}; - /// use unicase::Ascii; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AccessControlExposeHeaders(vec![ - /// Ascii::new("etag".to_owned()), - /// Ascii::new("content-length".to_owned()) - /// ]) - /// ); - /// # } - /// ``` - (AccessControlExposeHeaders, "Access-Control-Expose-Headers") => (Ascii)* - - test_access_control_expose_headers { - test_header!(test1, vec![b"etag, content-length"]); - } -} diff --git a/src/header/common/access_control_max_age.rs b/src/header/common/access_control_max_age.rs deleted file mode 100644 index 57952e201e..0000000000 --- a/src/header/common/access_control_max_age.rs +++ /dev/null @@ -1,31 +0,0 @@ -header! { - /// `Access-Control-Max-Age` header, part of - /// [CORS](http://www.w3.org/TR/cors/#access-control-max-age-response-header) - /// - /// The `Access-Control-Max-Age` header indicates how long the results of a - /// preflight request can be cached in a preflight result cache. - /// - /// # ABNF - /// - /// ```text - /// Access-Control-Max-Age = \"Access-Control-Max-Age\" \":\" delta-seconds - /// ``` - /// - /// # Example values - /// - /// * `531` - /// - /// # Examples - /// - /// ``` - /// use hyper::header::{Headers, AccessControlMaxAge}; - /// - /// let mut headers = Headers::new(); - /// headers.set(AccessControlMaxAge(1728000u32)); - /// ``` - (AccessControlMaxAge, "Access-Control-Max-Age") => [u32] - - test_access_control_max_age { - test_header!(test1, vec![b"531"]); - } -} diff --git a/src/header/common/access_control_request_headers.rs b/src/header/common/access_control_request_headers.rs deleted file mode 100644 index 048136d497..0000000000 --- a/src/header/common/access_control_request_headers.rs +++ /dev/null @@ -1,61 +0,0 @@ -use unicase::Ascii; - -header! { - /// `Access-Control-Request-Headers` header, part of - /// [CORS](http://www.w3.org/TR/cors/#access-control-request-headers-request-header) - /// - /// The `Access-Control-Request-Headers` header indicates which headers will - /// be used in the actual request as part of the preflight request. - /// during the actual request. - /// - /// # ABNF - /// - /// ```text - /// Access-Control-Allow-Headers: "Access-Control-Allow-Headers" ":" #field-name - /// ``` - /// - /// # Example values - /// * `accept-language, date` - /// - /// # Examples - /// - /// ``` - /// # extern crate hyper; - /// # extern crate unicase; - /// # fn main() { - /// // extern crate unicase; - /// - /// use hyper::header::{Headers, AccessControlRequestHeaders}; - /// use unicase::Ascii; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AccessControlRequestHeaders(vec![Ascii::new("date".to_owned())]) - /// ); - /// # } - /// ``` - /// - /// ``` - /// # extern crate hyper; - /// # extern crate unicase; - /// # fn main() { - /// // extern crate unicase; - /// - /// use hyper::header::{Headers, AccessControlRequestHeaders}; - /// use unicase::Ascii; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AccessControlRequestHeaders(vec![ - /// Ascii::new("accept-language".to_owned()), - /// Ascii::new("date".to_owned()), - /// ]) - /// ); - /// # } - /// ``` - (AccessControlRequestHeaders, "Access-Control-Request-Headers") => (Ascii)* - - test_access_control_request_headers { - test_header!(test1, vec![b"accept-language, date"]); - } -} diff --git a/src/header/common/access_control_request_method.rs b/src/header/common/access_control_request_method.rs deleted file mode 100644 index 32be1e8549..0000000000 --- a/src/header/common/access_control_request_method.rs +++ /dev/null @@ -1,32 +0,0 @@ -use method::Method; - -header! { - /// `Access-Control-Request-Method` header, part of - /// [CORS](http://www.w3.org/TR/cors/#access-control-request-method-request-header) - /// - /// The `Access-Control-Request-Method` header indicates which method will be - /// used in the actual request as part of the preflight request. - /// # ABNF - /// - /// ```text - /// Access-Control-Request-Method: \"Access-Control-Request-Method\" \":\" Method - /// ``` - /// - /// # Example values - /// * `GET` - /// - /// # Examples - /// - /// ``` - /// use hyper::header::{Headers, AccessControlRequestMethod}; - /// use hyper::Method; - /// - /// let mut headers = Headers::new(); - /// headers.set(AccessControlRequestMethod(Method::Get)); - /// ``` - (AccessControlRequestMethod, "Access-Control-Request-Method") => [Method] - - test_access_control_request_method { - test_header!(test1, vec![b"GET"]); - } -} diff --git a/src/header/common/allow.rs b/src/header/common/allow.rs deleted file mode 100644 index ecc69e1cbe..0000000000 --- a/src/header/common/allow.rs +++ /dev/null @@ -1,79 +0,0 @@ -use method::Method; - -header! { - /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) - /// - /// The `Allow` header field lists the set of methods advertised as - /// supported by the target resource. The purpose of this field is - /// strictly to inform the recipient of valid request methods associated - /// with the resource. - /// - /// # ABNF - /// - /// ```text - /// Allow = #method - /// ``` - /// - /// # Example values - /// * `GET, HEAD, PUT` - /// * `OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr` - /// * `` - /// - /// # Examples - /// - /// ``` - /// use hyper::header::{Headers, Allow}; - /// use hyper::Method; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// Allow(vec![Method::Get]) - /// ); - /// ``` - /// - /// ``` - /// use hyper::header::{Headers, Allow}; - /// use hyper::Method; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// Allow(vec![ - /// Method::Get, - /// Method::Post, - /// Method::Patch, - /// Method::Extension("COPY".to_owned()), - /// ]) - /// ); - /// ``` - (Allow, "Allow") => (Method)* - - test_allow { - // From the RFC - test_header!( - test1, - vec![b"GET, HEAD, PUT"], - Some(HeaderField(vec![Method::Get, Method::Head, Method::Put]))); - // Own tests - test_header!( - test2, - vec![b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr"], - Some(HeaderField(vec![ - Method::Options, - Method::Get, - Method::Put, - Method::Post, - Method::Delete, - Method::Head, - Method::Trace, - Method::Connect, - Method::Patch, - Method::Extension("fOObAr".to_owned())]))); - test_header!( - test3, - vec![b""], - Some(HeaderField(Vec::::new()))); - } -} - -bench_header!(bench, - Allow, { vec![b"OPTIONS,GET,PUT,POST,DELETE,HEAD,TRACE,CONNECT,PATCH,fOObAr".to_vec()] }); diff --git a/src/header/common/authorization.rs b/src/header/common/authorization.rs deleted file mode 100644 index 97b310d7bb..0000000000 --- a/src/header/common/authorization.rs +++ /dev/null @@ -1,299 +0,0 @@ -use std::any::Any; -use std::fmt::{self, Display}; -use std::str::{FromStr, from_utf8}; -use std::ops::{Deref, DerefMut}; -use base64::{encode, decode}; -use header::{Header, Raw}; - -/// `Authorization` header, defined in [RFC7235](https://tools.ietf.org/html/rfc7235#section-4.2) -/// -/// The `Authorization` header field allows a user agent to authenticate -/// itself with an origin server -- usually, but not necessarily, after -/// receiving a 401 (Unauthorized) response. Its value consists of -/// credentials containing the authentication information of the user -/// agent for the realm of the resource being requested. -/// -/// # ABNF -/// -/// ```text -/// Authorization = credentials -/// ``` -/// -/// # Example values -/// * `Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==` -/// * `Bearer fpKL54jvWmEGVoRdCNjG` -/// -/// # Examples -/// -/// ``` -/// use hyper::header::{Headers, Authorization}; -/// -/// let mut headers = Headers::new(); -/// headers.set(Authorization("let me in".to_owned())); -/// ``` -/// ``` -/// use hyper::header::{Headers, Authorization, Basic}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// Authorization( -/// Basic { -/// username: "Aladdin".to_owned(), -/// password: Some("open sesame".to_owned()) -/// } -/// ) -/// ); -/// ``` -/// -/// ``` -/// use hyper::header::{Headers, Authorization, Bearer}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// Authorization( -/// Bearer { -/// token: "QWxhZGRpbjpvcGVuIHNlc2FtZQ".to_owned() -/// } -/// ) -/// ); -/// ``` -#[derive(Clone, PartialEq, Debug)] -pub struct Authorization(pub S); - -impl Deref for Authorization { - type Target = S; - - fn deref(&self) -> &S { - &self.0 - } -} - -impl DerefMut for Authorization { - fn deref_mut(&mut self) -> &mut S { - &mut self.0 - } -} - -impl Header for Authorization where ::Err: 'static { - fn header_name() -> &'static str { - static NAME: &'static str = "Authorization"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result> { - if let Some(line) = raw.one() { - let header = try!(from_utf8(line)); - if let Some(scheme) = ::scheme() { - if header.starts_with(scheme) && header.len() > scheme.len() + 1 { - match header[scheme.len() + 1..].parse::().map(Authorization) { - Ok(h) => Ok(h), - Err(_) => Err(::Error::Header) - } - } else { - Err(::Error::Header) - } - } else { - match header.parse::().map(Authorization) { - Ok(h) => Ok(h), - Err(_) => Err(::Error::Header) - } - } - } else { - Err(::Error::Header) - } - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -impl fmt::Display for Authorization { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(scheme) = ::scheme() { - try!(write!(f, "{} ", scheme)) - }; - self.0.fmt_scheme(f) - } -} - -/// An Authorization scheme to be used in the header. -pub trait Scheme: FromStr + fmt::Debug + Clone + Send + Sync { - /// An optional Scheme name. - /// - /// Will be replaced with an associated constant once available. - fn scheme() -> Option<&'static str>; - /// Format the Scheme data into a header value. - fn fmt_scheme(&self, &mut fmt::Formatter) -> fmt::Result; -} - -impl Scheme for String { - fn scheme() -> Option<&'static str> { - None - } - - fn fmt_scheme(&self, f: &mut fmt::Formatter) -> fmt::Result { - Display::fmt(self, f) - } -} - -/// Credential holder for Basic Authentication -#[derive(Clone, PartialEq, Debug)] -pub struct Basic { - /// The username as a possibly empty string - pub username: String, - /// The password. `None` if the `:` delimiter character was not - /// part of the parsed input. Note: A compliant client MUST - /// always send a password (which may be the empty string). - pub password: Option -} - -impl Scheme for Basic { - fn scheme() -> Option<&'static str> { - Some("Basic") - } - - fn fmt_scheme(&self, f: &mut fmt::Formatter) -> fmt::Result { - //FIXME: serialize::base64 could use some Debug implementation, so - //that we don't have to allocate a new string here just to write it - //to the formatter. - let mut text = self.username.clone(); - text.push(':'); - if let Some(ref pass) = self.password { - text.push_str(&pass[..]); - } - - f.write_str(&encode(&text)) - } -} - -/// creates a Basic from a base-64 encoded, `:`-delimited utf-8 string -impl FromStr for Basic { - type Err = ::Error; - fn from_str(s: &str) -> ::Result { - match decode(s) { - Ok(decoded) => match String::from_utf8(decoded) { - Ok(text) => { - let parts = &mut text.split(':'); - let user = match parts.next() { - Some(part) => part.to_owned(), - None => return Err(::Error::Header) - }; - let password = match parts.next() { - Some(part) => Some(part.to_owned()), - None => None - }; - Ok(Basic { - username: user, - password: password - }) - }, - Err(_) => { - debug!("Basic::from_str utf8 error"); - Err(::Error::Header) - } - }, - Err(_) => { - debug!("Basic::from_str base64 error"); - Err(::Error::Header) - } - } - } -} - -#[derive(Clone, PartialEq, Debug)] -///Token holder for Bearer Authentication, most often seen with oauth -pub struct Bearer { - ///Actual bearer token as a string - pub token: String -} - -impl Scheme for Bearer { - fn scheme() -> Option<&'static str> { - Some("Bearer") - } - - fn fmt_scheme(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.token) - } -} - -impl FromStr for Bearer { - type Err = ::Error; - fn from_str(s: &str) -> ::Result { - Ok(Bearer { token: s.to_owned()}) - } -} - -#[cfg(test)] -mod tests { - use super::{Authorization, Basic, Bearer}; - use super::super::super::{Headers, Header}; - - #[test] - fn test_raw_auth() { - let mut headers = Headers::new(); - headers.set(Authorization("foo bar baz".to_owned())); - assert_eq!(headers.to_string(), "Authorization: foo bar baz\r\n".to_owned()); - } - - #[test] - fn test_raw_auth_parse() { - let header: Authorization = Header::parse_header(&b"foo bar baz".as_ref().into()).unwrap(); - assert_eq!(header.0, "foo bar baz"); - } - - #[test] - fn test_basic_auth() { - let mut headers = Headers::new(); - headers.set(Authorization( - Basic { username: "Aladdin".to_owned(), password: Some("open sesame".to_owned()) })); - assert_eq!( - headers.to_string(), - "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n".to_owned()); - } - - #[test] - fn test_basic_auth_no_password() { - let mut headers = Headers::new(); - headers.set(Authorization(Basic { username: "Aladdin".to_owned(), password: None })); - assert_eq!(headers.to_string(), "Authorization: Basic QWxhZGRpbjo=\r\n".to_owned()); - } - - #[test] - fn test_basic_auth_parse() { - let auth: Authorization = Header::parse_header( - &b"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==".as_ref().into()).unwrap(); - assert_eq!(auth.0.username, "Aladdin"); - assert_eq!(auth.0.password, Some("open sesame".to_owned())); - } - - #[test] - fn test_basic_auth_parse_no_password() { - let auth: Authorization = Header::parse_header( - &b"Basic QWxhZGRpbjo=".as_ref().into()).unwrap(); - assert_eq!(auth.0.username, "Aladdin"); - assert_eq!(auth.0.password, Some("".to_owned())); - } - - #[test] - fn test_bearer_auth() { - let mut headers = Headers::new(); - headers.set(Authorization( - Bearer { token: "fpKL54jvWmEGVoRdCNjG".to_owned() })); - assert_eq!( - headers.to_string(), - "Authorization: Bearer fpKL54jvWmEGVoRdCNjG\r\n".to_owned()); - } - - #[test] - fn test_bearer_auth_parse() { - let auth: Authorization = Header::parse_header( - &b"Bearer fpKL54jvWmEGVoRdCNjG".as_ref().into()).unwrap(); - assert_eq!(auth.0.token, "fpKL54jvWmEGVoRdCNjG"); - } -} - -bench_header!(raw, Authorization, { vec![b"foo bar baz".to_vec()] }); -bench_header!(basic, Authorization, { vec![b"Basic QWxhZGRpbjpuIHNlc2FtZQ==".to_vec()] }); -bench_header!(bearer, Authorization, { vec![b"Bearer fpKL54jvWmEGVoRdCNjG".to_vec()] }); diff --git a/src/header/common/cache_control.rs b/src/header/common/cache_control.rs deleted file mode 100644 index fc92936683..0000000000 --- a/src/header/common/cache_control.rs +++ /dev/null @@ -1,214 +0,0 @@ -use std::fmt; -use std::str::FromStr; -use header::{Header, Raw}; -use header::parsing::{from_comma_delimited, fmt_comma_delimited}; - -/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) -/// -/// The `Cache-Control` header field is used to specify directives for -/// caches along the request/response chain. Such cache directives are -/// unidirectional in that the presence of a directive in a request does -/// not imply that the same directive is to be given in the response. -/// -/// # ABNF -/// -/// ```text -/// Cache-Control = 1#cache-directive -/// cache-directive = token [ "=" ( token / quoted-string ) ] -/// ``` -/// -/// # Example values -/// -/// * `no-cache` -/// * `private, community="UCI"` -/// * `max-age=30` -/// -/// # Examples -/// ``` -/// use hyper::header::{Headers, CacheControl, CacheDirective}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// CacheControl(vec![CacheDirective::MaxAge(86400u32)]) -/// ); -/// ``` -/// -/// ``` -/// use hyper::header::{Headers, CacheControl, CacheDirective}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// CacheControl(vec![ -/// CacheDirective::NoCache, -/// CacheDirective::Private, -/// CacheDirective::MaxAge(360u32), -/// CacheDirective::Extension("foo".to_owned(), -/// Some("bar".to_owned())), -/// ]) -/// ); -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub struct CacheControl(pub Vec); - -__hyper__deref!(CacheControl => Vec); - -//TODO: this could just be the header! macro -impl Header for CacheControl { - fn header_name() -> &'static str { - static NAME: &'static str = "Cache-Control"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - let directives = try!(from_comma_delimited(raw)); - if !directives.is_empty() { - Ok(CacheControl(directives)) - } else { - Err(::Error::Header) - } - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -impl fmt::Display for CacheControl { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt_comma_delimited(f, &self[..]) - } -} - -/// `CacheControl` contains a list of these directives. -#[derive(PartialEq, Clone, Debug)] -pub enum CacheDirective { - /// "no-cache" - NoCache, - /// "no-store" - NoStore, - /// "no-transform" - NoTransform, - /// "only-if-cached" - OnlyIfCached, - - // request directives - /// "max-age=delta" - MaxAge(u32), - /// "max-stale=delta" - MaxStale(u32), - /// "min-fresh=delta" - MinFresh(u32), - - // response directives - /// "must-revalidate" - MustRevalidate, - /// "public" - Public, - /// "private" - Private, - /// "proxy-revalidate" - ProxyRevalidate, - /// "s-maxage=delta" - SMaxAge(u32), - - /// Extension directives. Optionally include an argument. - Extension(String, Option) -} - -impl fmt::Display for CacheDirective { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use self::CacheDirective::*; - fmt::Display::fmt(match *self { - NoCache => "no-cache", - NoStore => "no-store", - NoTransform => "no-transform", - OnlyIfCached => "only-if-cached", - - MaxAge(secs) => return write!(f, "max-age={}", secs), - MaxStale(secs) => return write!(f, "max-stale={}", secs), - MinFresh(secs) => return write!(f, "min-fresh={}", secs), - - MustRevalidate => "must-revalidate", - Public => "public", - Private => "private", - ProxyRevalidate => "proxy-revalidate", - SMaxAge(secs) => return write!(f, "s-maxage={}", secs), - - Extension(ref name, None) => &name[..], - Extension(ref name, Some(ref arg)) => return write!(f, "{}={}", name, arg), - - }, f) - } -} - -impl FromStr for CacheDirective { - type Err = Option<::Err>; - fn from_str(s: &str) -> Result::Err>> { - use self::CacheDirective::*; - match s { - "no-cache" => Ok(NoCache), - "no-store" => Ok(NoStore), - "no-transform" => Ok(NoTransform), - "only-if-cached" => Ok(OnlyIfCached), - "must-revalidate" => Ok(MustRevalidate), - "public" => Ok(Public), - "private" => Ok(Private), - "proxy-revalidate" => Ok(ProxyRevalidate), - "" => Err(None), - _ => match s.find('=') { - Some(idx) if idx+1 < s.len() => match (&s[..idx], (&s[idx+1..]).trim_matches('"')) { - ("max-age" , secs) => secs.parse().map(MaxAge).map_err(Some), - ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some), - ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some), - ("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some), - (left, right) => Ok(Extension(left.to_owned(), Some(right.to_owned()))) - }, - Some(_) => Err(None), - None => Ok(Extension(s.to_owned(), None)) - } - } - } -} - -#[cfg(test)] -mod tests { - use header::Header; - use super::*; - - #[test] - fn test_parse_multiple_headers() { - let cache = Header::parse_header(&vec![b"no-cache".to_vec(), b"private".to_vec()].into()); - assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::NoCache, - CacheDirective::Private]))) - } - - #[test] - fn test_parse_argument() { - let cache = Header::parse_header(&vec![b"max-age=100, private".to_vec()].into()); - assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::MaxAge(100), - CacheDirective::Private]))) - } - - #[test] - fn test_parse_quote_form() { - let cache = Header::parse_header(&vec![b"max-age=\"200\"".to_vec()].into()); - assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::MaxAge(200)]))) - } - - #[test] - fn test_parse_extension() { - let cache = Header::parse_header(&vec![b"foo, bar=baz".to_vec()].into()); - assert_eq!(cache.ok(), Some(CacheControl(vec![ - CacheDirective::Extension("foo".to_owned(), None), - CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned()))]))) - } - - #[test] - fn test_parse_bad_syntax() { - let cache: ::Result = Header::parse_header(&vec![b"foo=".to_vec()].into()); - assert_eq!(cache.ok(), None) - } -} - -bench_header!(normal, - CacheControl, { vec![b"no-cache, private".to_vec(), b"max-age=100".to_vec()] }); diff --git a/src/header/common/connection.rs b/src/header/common/connection.rs deleted file mode 100644 index 8601c12d23..0000000000 --- a/src/header/common/connection.rs +++ /dev/null @@ -1,146 +0,0 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; -use unicase::Ascii; - -pub use self::ConnectionOption::{KeepAlive, Close, ConnectionHeader}; - -static KEEP_ALIVE: &'static str = "keep-alive"; -static CLOSE: &'static str = "close"; - -/// Values that can be in the `Connection` header. -#[derive(Clone, PartialEq, Debug)] -pub enum ConnectionOption { - /// The `keep-alive` connection value. - KeepAlive, - /// The `close` connection value. - Close, - /// Values in the Connection header that are supposed to be names of other Headers. - /// - /// > When a header field aside from Connection is used to supply control - /// > information for or about the current connection, the sender MUST list - /// > the corresponding field-name within the Connection header field. - // TODO: it would be nice if these "Strings" could be stronger types, since - // they are supposed to relate to other Header fields (which we have strong - // types for). - ConnectionHeader(Ascii), -} - -impl FromStr for ConnectionOption { - type Err = (); - fn from_str(s: &str) -> Result { - if Ascii::new(s) == KEEP_ALIVE { - Ok(KeepAlive) - } else if Ascii::new(s) == CLOSE { - Ok(Close) - } else { - Ok(ConnectionHeader(Ascii::new(s.to_owned()))) - } - } -} - -impl Display for ConnectionOption { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match *self { - KeepAlive => "keep-alive", - Close => "close", - ConnectionHeader(ref s) => s.as_ref() - }) - } -} - -header! { - /// `Connection` header, defined in - /// [RFC7230](http://tools.ietf.org/html/rfc7230#section-6.1) - /// - /// The `Connection` header field allows the sender to indicate desired - /// control options for the current connection. In order to avoid - /// confusing downstream recipients, a proxy or gateway MUST remove or - /// replace any received connection options before forwarding the - /// message. - /// - /// # ABNF - /// - /// ```text - /// Connection = 1#connection-option - /// connection-option = token - /// - /// # Example values - /// * `close` - /// * `keep-alive` - /// * `upgrade` - /// ``` - /// - /// # Examples - /// - /// ``` - /// use hyper::header::{Headers, Connection}; - /// - /// let mut headers = Headers::new(); - /// headers.set(Connection::keep_alive()); - /// ``` - /// - /// ``` - /// # extern crate hyper; - /// # extern crate unicase; - /// # fn main() { - /// // extern crate unicase; - /// - /// use hyper::header::{Headers, Connection, ConnectionOption}; - /// use unicase::Ascii; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// Connection(vec![ - /// ConnectionOption::ConnectionHeader(Ascii::new("upgrade".to_owned())), - /// ]) - /// ); - /// # } - /// ``` - (Connection, "Connection") => (ConnectionOption)+ - - test_connection { - test_header!(test1, vec![b"close"]); - test_header!(test2, vec![b"keep-alive"]); - test_header!(test3, vec![b"upgrade"]); - } -} - -impl Connection { - /// A constructor to easily create a `Connection: close` header. - #[inline] - pub fn close() -> Connection { - Connection(vec![ConnectionOption::Close]) - } - - /// A constructor to easily create a `Connection: keep-alive` header. - #[inline] - pub fn keep_alive() -> Connection { - Connection(vec![ConnectionOption::KeepAlive]) - } -} - -bench_header!(close, Connection, { vec![b"close".to_vec()] }); -bench_header!(keep_alive, Connection, { vec![b"keep-alive".to_vec()] }); -bench_header!(header, Connection, { vec![b"authorization".to_vec()] }); - -#[cfg(test)] -mod tests { - use super::{Connection,ConnectionHeader}; - use header::Header; - use unicase::Ascii; - - fn parse_option(header: Vec) -> Connection { - let val = header.into(); - let connection: Connection = Header::parse_header(&val).unwrap(); - connection - } - - #[test] - fn test_parse() { - assert_eq!(Connection::close(),parse_option(b"close".to_vec())); - assert_eq!(Connection::keep_alive(),parse_option(b"keep-alive".to_vec())); - assert_eq!(Connection::keep_alive(),parse_option(b"Keep-Alive".to_vec())); - assert_eq!(Connection(vec![ConnectionHeader(Ascii::new("upgrade".to_owned()))]), - parse_option(b"upgrade".to_vec())); - } -} diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs deleted file mode 100644 index 0fcd6ee09f..0000000000 --- a/src/header/common/content_disposition.rs +++ /dev/null @@ -1,264 +0,0 @@ -// # References -// -// "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt -// "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt -// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc2388.txt -// Browser conformance tests at: http://greenbytes.de/tech/tc2231/ -// IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml - -use language_tags::LanguageTag; -use std::fmt; -use unicase; - -use header::{Header, Raw, parsing}; -use header::parsing::{parse_extended_value, http_percent_encode}; -use header::shared::Charset; - -/// The implied disposition of the content of the HTTP body. -#[derive(Clone, Debug, PartialEq)] -pub enum DispositionType { - /// Inline implies default processing - Inline, - /// Attachment implies that the recipient should prompt the user to save the response locally, - /// rather than process it normally (as per its media type). - Attachment, - /// Extension type. Should be handled by recipients the same way as Attachment - Ext(String) -} - -/// A parameter to the disposition type. -#[derive(Clone, Debug, PartialEq)] -pub enum DispositionParam { - /// A Filename consisting of a Charset, an optional LanguageTag, and finally a sequence of - /// bytes representing the filename - Filename(Charset, Option, Vec), - /// Extension type consisting of token and value. Recipients should ignore unrecognized - /// parameters. - Ext(String, String) -} - -/// A `Content-Disposition` header, (re)defined in [RFC6266](https://tools.ietf.org/html/rfc6266). -/// -/// The Content-Disposition response header field is used to convey -/// additional information about how to process the response payload, and -/// also can be used to attach additional metadata, such as the filename -/// to use when saving the response payload locally. -/// -/// # ABNF - -/// ```text -/// content-disposition = "Content-Disposition" ":" -/// disposition-type *( ";" disposition-parm ) -/// -/// disposition-type = "inline" | "attachment" | disp-ext-type -/// ; case-insensitive -/// -/// disp-ext-type = token -/// -/// disposition-parm = filename-parm | disp-ext-parm -/// -/// filename-parm = "filename" "=" value -/// | "filename*" "=" ext-value -/// -/// disp-ext-parm = token "=" value -/// | ext-token "=" ext-value -/// -/// ext-token = -/// ``` -/// -/// # Example -/// -/// ``` -/// use hyper::header::{Headers, ContentDisposition, DispositionType, DispositionParam, Charset}; -/// -/// let mut headers = Headers::new(); -/// headers.set(ContentDisposition { -/// disposition: DispositionType::Attachment, -/// parameters: vec![DispositionParam::Filename( -/// Charset::Iso_8859_1, // The character set for the bytes of the filename -/// None, // The optional language tag (see `language-tag` crate) -/// b"\xa9 Copyright 1989.txt".to_vec() // the actual bytes of the filename -/// )] -/// }); -/// ``` -#[derive(Clone, Debug, PartialEq)] -pub struct ContentDisposition { - /// The disposition - pub disposition: DispositionType, - /// Disposition parameters - pub parameters: Vec, -} - -impl Header for ContentDisposition { - fn header_name() -> &'static str { - static NAME: &'static str = "Content-Disposition"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - parsing::from_one_raw_str(raw).and_then(|s: String| { - let mut sections = s.split(';'); - let disposition = match sections.next() { - Some(s) => s.trim(), - None => return Err(::Error::Header), - }; - - let mut cd = ContentDisposition { - disposition: if unicase::eq_ascii(&*disposition, "inline") { - DispositionType::Inline - } else if unicase::eq_ascii(&*disposition, "attachment") { - DispositionType::Attachment - } else { - DispositionType::Ext(disposition.to_owned()) - }, - parameters: Vec::new(), - }; - - for section in sections { - let mut parts = section.splitn(2, '='); - - let key = if let Some(key) = parts.next() { - key.trim() - } else { - return Err(::Error::Header); - }; - - let val = if let Some(val) = parts.next() { - val.trim() - } else { - return Err(::Error::Header); - }; - - cd.parameters.push( - if unicase::eq_ascii(&*key, "filename") { - DispositionParam::Filename( - Charset::Ext("UTF-8".to_owned()), None, - val.trim_matches('"').as_bytes().to_owned()) - } else if unicase::eq_ascii(&*key, "filename*") { - let extended_value = try!(parse_extended_value(val)); - DispositionParam::Filename(extended_value.charset, extended_value.language_tag, extended_value.value) - } else { - DispositionParam::Ext(key.to_owned(), val.trim_matches('"').to_owned()) - } - ); - } - - Ok(cd) - }) - } - - #[inline] - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -impl fmt::Display for ContentDisposition { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.disposition { - DispositionType::Inline => try!(write!(f, "inline")), - DispositionType::Attachment => try!(write!(f, "attachment")), - DispositionType::Ext(ref s) => try!(write!(f, "{}", s)), - } - for param in &self.parameters { - match *param { - DispositionParam::Filename(ref charset, ref opt_lang, ref bytes) => { - let mut use_simple_format: bool = false; - if opt_lang.is_none() { - if let Charset::Ext(ref ext) = *charset { - if unicase::eq_ascii(&**ext, "utf-8") { - use_simple_format = true; - } - } - } - if use_simple_format { - try!(write!(f, "; filename=\"{}\"", - match String::from_utf8(bytes.clone()) { - Ok(s) => s, - Err(_) => return Err(fmt::Error), - })); - } else { - try!(write!(f, "; filename*={}'", charset)); - if let Some(ref lang) = *opt_lang { - try!(write!(f, "{}", lang)); - }; - try!(write!(f, "'")); - try!(http_percent_encode(f, bytes)) - } - }, - DispositionParam::Ext(ref k, ref v) => try!(write!(f, "; {}=\"{}\"", k, v)), - } - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::{ContentDisposition,DispositionType,DispositionParam}; - use ::header::Header; - use ::header::shared::Charset; - - #[test] - fn test_parse_header() { - assert!(ContentDisposition::parse_header(&"".into()).is_err()); - - let a = "form-data; dummy=3; name=upload;\r\n filename=\"sample.png\"".into(); - let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Ext("form-data".to_owned()), - parameters: vec![ - DispositionParam::Ext("dummy".to_owned(), "3".to_owned()), - DispositionParam::Ext("name".to_owned(), "upload".to_owned()), - DispositionParam::Filename( - Charset::Ext("UTF-8".to_owned()), - None, - "sample.png".bytes().collect()) ] - }; - assert_eq!(a, b); - - let a = "attachment; filename=\"image.jpg\"".into(); - let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![ - DispositionParam::Filename( - Charset::Ext("UTF-8".to_owned()), - None, - "image.jpg".bytes().collect()) ] - }; - assert_eq!(a, b); - - let a = "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates".into(); - let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![ - DispositionParam::Filename( - Charset::Ext("UTF-8".to_owned()), - None, - vec![0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, - 0xe2, 0x82, 0xac, 0x20, b'r', b'a', b't', b'e', b's']) ] - }; - assert_eq!(a, b); - } - - #[test] - fn test_display() { - let as_string = "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; - let a = as_string.into(); - let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); - let display_rendered = format!("{}",a); - assert_eq!(as_string, display_rendered); - - let a = "attachment; filename*=UTF-8''black%20and%20white.csv".into(); - let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); - let display_rendered = format!("{}",a); - assert_eq!("attachment; filename=\"black and white.csv\"".to_owned(), display_rendered); - - let a = "attachment; filename=colourful.csv".into(); - let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); - let display_rendered = format!("{}",a); - assert_eq!("attachment; filename=\"colourful.csv\"".to_owned(), display_rendered); - } -} diff --git a/src/header/common/content_encoding.rs b/src/header/common/content_encoding.rs deleted file mode 100644 index d98b7a22af..0000000000 --- a/src/header/common/content_encoding.rs +++ /dev/null @@ -1,54 +0,0 @@ -use header::Encoding; - -header! { - /// `Content-Encoding` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.2.2) - /// - /// The `Content-Encoding` header field indicates what content codings - /// have been applied to the representation, beyond those inherent in the - /// media type, and thus what decoding mechanisms have to be applied in - /// order to obtain data in the media type referenced by the Content-Type - /// header field. Content-Encoding is primarily used to allow a - /// representation's data to be compressed without losing the identity of - /// its underlying media type. - /// - /// # ABNF - /// - /// ```text - /// Content-Encoding = 1#content-coding - /// ``` - /// - /// # Example values - /// - /// * `gzip` - /// - /// # Examples - /// - /// ``` - /// use hyper::header::{Headers, ContentEncoding, Encoding}; - /// - /// let mut headers = Headers::new(); - /// headers.set(ContentEncoding(vec![Encoding::Chunked])); - /// ``` - /// - /// ``` - /// use hyper::header::{Headers, ContentEncoding, Encoding}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// ContentEncoding(vec![ - /// Encoding::Gzip, - /// Encoding::Chunked, - /// ]) - /// ); - /// ``` - (ContentEncoding, "Content-Encoding") => (Encoding)+ - - test_content_encoding { - /// Testcase from the RFC - test_header!(test1, vec![b"gzip"], Some(ContentEncoding(vec![Encoding::Gzip]))); - } -} - -bench_header!(single, ContentEncoding, { vec![b"gzip".to_vec()] }); -bench_header!(multiple, ContentEncoding, { vec![b"gzip, deflate".to_vec()] }); diff --git a/src/header/common/content_language.rs b/src/header/common/content_language.rs deleted file mode 100644 index bc6dec0c58..0000000000 --- a/src/header/common/content_language.rs +++ /dev/null @@ -1,63 +0,0 @@ -use language_tags::LanguageTag; -use header::QualityItem; - -header! { - /// `Content-Language` header, defined in - /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) - /// - /// The `Content-Language` header field describes the natural language(s) - /// of the intended audience for the representation. Note that this - /// might not be equivalent to all the languages used within the - /// representation. - /// - /// # ABNF - /// - /// ```text - /// Content-Language = 1#language-tag - /// ``` - /// - /// # Example values - /// - /// * `da` - /// * `mi, en` - /// - /// # Examples - /// - /// ``` - /// # extern crate hyper; - /// # #[macro_use] extern crate language_tags; - /// # use hyper::header::{Headers, ContentLanguage, qitem}; - /// # - /// # fn main() { - /// let mut headers = Headers::new(); - /// headers.set( - /// ContentLanguage(vec![ - /// qitem(langtag!(en)), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ``` - /// # extern crate hyper; - /// # #[macro_use] extern crate language_tags; - /// # use hyper::header::{Headers, ContentLanguage, qitem}; - /// # - /// # fn main() { - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// ContentLanguage(vec![ - /// qitem(langtag!(da)), - /// qitem(langtag!(en;;;GB)), - /// ]) - /// ); - /// # } - /// ``` - (ContentLanguage, "Content-Language") => (QualityItem)+ - - test_content_language { - test_header!(test1, vec![b"da"]); - test_header!(test2, vec![b"mi, en"]); - } -} diff --git a/src/header/common/content_length.rs b/src/header/common/content_length.rs deleted file mode 100644 index a596f5ca22..0000000000 --- a/src/header/common/content_length.rs +++ /dev/null @@ -1,103 +0,0 @@ -use std::fmt; - -use header::{Header, Raw, parsing}; - -/// `Content-Length` header, defined in -/// [RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.2) -/// -/// When a message does not have a `Transfer-Encoding` header field, a -/// Content-Length header field can provide the anticipated size, as a -/// decimal number of octets, for a potential payload body. For messages -/// that do include a payload body, the Content-Length field-value -/// provides the framing information necessary for determining where the -/// body (and message) ends. For messages that do not include a payload -/// body, the Content-Length indicates the size of the selected -/// representation. -/// -/// Note that setting this header will *remove* any previously set -/// `Transfer-Encoding` header, in accordance with -/// [RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.2): -/// -/// > A sender MUST NOT send a Content-Length header field in any message -/// > that contains a Transfer-Encoding header field. -/// -/// # ABNF -/// -/// ```text -/// Content-Length = 1*DIGIT -/// ``` -/// -/// # Example values -/// -/// * `3495` -/// -/// # Example -/// -/// ``` -/// use hyper::header::{Headers, ContentLength}; -/// -/// let mut headers = Headers::new(); -/// headers.set(ContentLength(1024u64)); -/// ``` -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct ContentLength(pub u64); - -impl Header for ContentLength { - #[inline] - fn header_name() -> &'static str { - static NAME: &'static str = "Content-Length"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - // If multiple Content-Length headers were sent, everything can still - // be alright if they all contain the same value, and all parse - // correctly. If not, then it's an error. - raw.iter() - .map(parsing::from_raw_str) - .fold(None, |prev, x| { - match (prev, x) { - (None, x) => Some(x), - (e @ Some(Err(_)), _ ) => e, - (Some(Ok(prev)), Ok(x)) if prev == x => Some(Ok(prev)), - _ => Some(Err(::Error::Header)), - } - }) - .unwrap_or(Err(::Error::Header)) - .map(ContentLength) - } - - #[inline] - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.danger_fmt_line_without_newline_replacer(self) - } -} - -impl fmt::Display for ContentLength { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} - -__hyper__deref!(ContentLength => u64); - -__hyper__tm!(ContentLength, tests { - // Testcase from RFC - test_header!(test1, vec![b"3495"], Some(HeaderField(3495))); - - test_header!(test_invalid, vec![b"34v95"], None); - - // Can't use the test_header macro because "5, 5" gets cleaned to "5". - #[test] - fn test_duplicates() { - let parsed = HeaderField::parse_header(&vec![b"5".to_vec(), - b"5".to_vec()].into()).unwrap(); - assert_eq!(parsed, HeaderField(5)); - assert_eq!(format!("{}", parsed), "5"); - } - - test_header!(test_duplicates_vary, vec![b"5", b"6", b"5"], None); -}); - -bench_header!(bench, ContentLength, { vec![b"42349984".to_vec()] }); diff --git a/src/header/common/content_location.rs b/src/header/common/content_location.rs deleted file mode 100644 index 0d73c17b9a..0000000000 --- a/src/header/common/content_location.rs +++ /dev/null @@ -1,47 +0,0 @@ -header! { - /// `Content-Location` header, defined in - /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.4.2) - /// - /// The header can be used by both the client in requests and the server - /// in responses with different semantics. Client sets `Content-Location` - /// to refer to the URI where original representation of the body was - /// obtained. - /// - /// In responses `Content-Location` represents URI for the representation - /// that was content negotiated, created or for the response payload. - /// - /// # ABNF - /// - /// ```text - /// Content-Location = absolute-URI / partial-URI - /// ``` - /// - /// # Example values - /// - /// * `/hypertext/Overview.html` - /// * `http://www.example.org/hypertext/Overview.html` - /// - /// # Examples - /// - /// ``` - /// use hyper::header::{Headers, ContentLocation}; - /// - /// let mut headers = Headers::new(); - /// headers.set(ContentLocation("/hypertext/Overview.html".to_owned())); - /// ``` - /// - /// ``` - /// use hyper::header::{Headers, ContentLocation}; - /// - /// let mut headers = Headers::new(); - /// headers.set(ContentLocation("http://www.example.org/hypertext/Overview.html".to_owned())); - /// ``` - // TODO: use URL - (ContentLocation, "Content-Location") => [String] - - test_content_location { - test_header!(partial_query, vec![b"/hypertext/Overview.html?q=tim"]); - - test_header!(absolute, vec![b"http://www.example.org/hypertext/Overview.html"]); - } -} diff --git a/src/header/common/content_range.rs b/src/header/common/content_range.rs deleted file mode 100644 index 6420c470e0..0000000000 --- a/src/header/common/content_range.rs +++ /dev/null @@ -1,190 +0,0 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; - -header! { - /// `Content-Range` header, defined in - /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) - (ContentRange, "Content-Range") => [ContentRangeSpec] - - test_content_range { - test_header!(test_bytes, - vec![b"bytes 0-499/500"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: Some((0, 499)), - instance_length: Some(500) - }))); - - test_header!(test_bytes_unknown_len, - vec![b"bytes 0-499/*"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: Some((0, 499)), - instance_length: None - }))); - - test_header!(test_bytes_unknown_range, - vec![b"bytes */500"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: None, - instance_length: Some(500) - }))); - - test_header!(test_unregistered, - vec![b"seconds 1-2"], - Some(ContentRange(ContentRangeSpec::Unregistered { - unit: "seconds".to_owned(), - resp: "1-2".to_owned() - }))); - - test_header!(test_no_len, - vec![b"bytes 0-499"], - None::); - - test_header!(test_only_unit, - vec![b"bytes"], - None::); - - test_header!(test_end_less_than_start, - vec![b"bytes 499-0/500"], - None::); - - test_header!(test_blank, - vec![b""], - None::); - - test_header!(test_bytes_many_spaces, - vec![b"bytes 1-2/500 3"], - None::); - - test_header!(test_bytes_many_slashes, - vec![b"bytes 1-2/500/600"], - None::); - - test_header!(test_bytes_many_dashes, - vec![b"bytes 1-2-3/500"], - None::); - - } -} - - -/// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2) -/// -/// # ABNF -/// -/// ```text -/// Content-Range = byte-content-range -/// / other-content-range -/// -/// byte-content-range = bytes-unit SP -/// ( byte-range-resp / unsatisfied-range ) -/// -/// byte-range-resp = byte-range "/" ( complete-length / "*" ) -/// byte-range = first-byte-pos "-" last-byte-pos -/// unsatisfied-range = "*/" complete-length -/// -/// complete-length = 1*DIGIT -/// -/// other-content-range = other-range-unit SP other-range-resp -/// other-range-resp = *CHAR -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub enum ContentRangeSpec { - /// Byte range - Bytes { - /// First and last bytes of the range, omitted if request could not be - /// satisfied - range: Option<(u64, u64)>, - - /// Total length of the instance, can be omitted if unknown - instance_length: Option - }, - - /// Custom range, with unit not registered at IANA - Unregistered { - /// other-range-unit - unit: String, - - /// other-range-resp - resp: String - } -} - -fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> { - let mut iter = s.splitn(2, separator); - match (iter.next(), iter.next()) { - (Some(a), Some(b)) => Some((a, b)), - _ => None - } -} - -impl FromStr for ContentRangeSpec { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - let res = match split_in_two(s, ' ') { - Some(("bytes", resp)) => { - let (range, instance_length) = try!(split_in_two(resp, '/').ok_or(::Error::Header)); - - let instance_length = if instance_length == "*" { - None - } else { - Some(try!(instance_length.parse().map_err(|_| ::Error::Header))) - }; - - let range = if range == "*" { - None - } else { - let (first_byte, last_byte) = try!(split_in_two(range, '-').ok_or(::Error::Header)); - let first_byte = try!(first_byte.parse().map_err(|_| ::Error::Header)); - let last_byte = try!(last_byte.parse().map_err(|_| ::Error::Header)); - if last_byte < first_byte { - return Err(::Error::Header); - } - Some((first_byte, last_byte)) - }; - - ContentRangeSpec::Bytes { - range: range, - instance_length: instance_length - } - } - Some((unit, resp)) => { - ContentRangeSpec::Unregistered { - unit: unit.to_owned(), - resp: resp.to_owned() - } - } - _ => return Err(::Error::Header) - }; - Ok(res) - } -} - -impl Display for ContentRangeSpec { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ContentRangeSpec::Bytes { range, instance_length } => { - try!(f.write_str("bytes ")); - match range { - Some((first_byte, last_byte)) => { - try!(write!(f, "{}-{}", first_byte, last_byte)); - }, - None => { - try!(f.write_str("*")); - } - }; - try!(f.write_str("/")); - if let Some(v) = instance_length { - write!(f, "{}", v) - } else { - f.write_str("*") - } - } - ContentRangeSpec::Unregistered { ref unit, ref resp } => { - try!(f.write_str(&unit)); - try!(f.write_str(" ")); - f.write_str(resp) - } - } - } -} diff --git a/src/header/common/content_type.rs b/src/header/common/content_type.rs deleted file mode 100644 index 1aa1945614..0000000000 --- a/src/header/common/content_type.rs +++ /dev/null @@ -1,113 +0,0 @@ -use mime::{self, Mime}; - -header! { - /// `Content-Type` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) - /// - /// The `Content-Type` header field indicates the media type of the - /// associated representation: either the representation enclosed in the - /// message payload or the selected representation, as determined by the - /// message semantics. The indicated media type defines both the data - /// format and how that data is intended to be processed by a recipient, - /// within the scope of the received message semantics, after any content - /// codings indicated by Content-Encoding are decoded. - /// - /// Although the `mime` crate allows the mime options to be any slice, this crate - /// forces the use of Vec. This is to make sure the same header can't have more than 1 type. If - /// this is an issue, it's possible to implement `Header` on a custom struct. - /// - /// # ABNF - /// - /// ```text - /// Content-Type = media-type - /// ``` - /// - /// # Example values - /// - /// * `text/html; charset=utf-8` - /// * `application/json` - /// - /// # Examples - /// - /// ``` - /// use hyper::header::{Headers, ContentType}; - /// - /// let mut headers = Headers::new(); - /// - /// headers.set( - /// ContentType::json() - /// ); - /// ``` - /// - /// ``` - /// use hyper::header::{Headers, ContentType}; - /// use hyper::mime; - /// - /// let mut headers = Headers::new(); - /// - /// headers.set( - /// ContentType(mime::TEXT_HTML) - /// ); - /// ``` - (ContentType, "Content-Type") => danger [Mime] - - test_content_type { - test_header!( - test1, - vec![b"text/html"], - Some(HeaderField(TEXT_HTML))); - } -} - -impl ContentType { - /// A constructor to easily create a `Content-Type: application/json` header. - #[inline] - pub fn json() -> ContentType { - ContentType(mime::APPLICATION_JSON) - } - - /// A constructor to easily create a `Content-Type: text/plain; charset=utf-8` header. - #[inline] - pub fn plaintext() -> ContentType { - ContentType(mime::TEXT_PLAIN_UTF_8) - } - - /// A constructor to easily create a `Content-Type: text/html` header. - #[inline] - pub fn html() -> ContentType { - ContentType(mime::TEXT_HTML) - } - - /// A constructor to easily create a `Content-Type: text/xml` header. - #[inline] - pub fn xml() -> ContentType { - ContentType(mime::TEXT_XML) - } - - /// A constructor to easily create a `Content-Type: application/www-form-url-encoded` header. - #[inline] - pub fn form_url_encoded() -> ContentType { - ContentType(mime::APPLICATION_WWW_FORM_URLENCODED) - } - /// A constructor to easily create a `Content-Type: image/jpeg` header. - #[inline] - pub fn jpeg() -> ContentType { - ContentType(mime::IMAGE_JPEG) - } - - /// A constructor to easily create a `Content-Type: image/png` header. - #[inline] - pub fn png() -> ContentType { - ContentType(mime::IMAGE_PNG) - } - - /// A constructor to easily create a `Content-Type: application/octet-stream` header. - #[inline] - pub fn octet_stream() -> ContentType { - ContentType(mime::APPLICATION_OCTET_STREAM) - } -} - -impl Eq for ContentType {} - -bench_header!(bench, ContentType, { vec![b"application/json".to_vec()] }); diff --git a/src/header/common/cookie.rs b/src/header/common/cookie.rs deleted file mode 100644 index dbe2d8e2cc..0000000000 --- a/src/header/common/cookie.rs +++ /dev/null @@ -1,292 +0,0 @@ -use std::borrow::Cow; -use std::fmt; -use std::str::from_utf8; - -use header::{Header, Raw}; -use header::internals::VecMap; - -/// `Cookie` header, defined in [RFC6265](http://tools.ietf.org/html/rfc6265#section-5.4) -/// -/// If the user agent does attach a Cookie header field to an HTTP -/// request, the user agent must send the cookie-string -/// as the value of the header field. -/// -/// When the user agent generates an HTTP request, the user agent MUST NOT -/// attach more than one Cookie header field. -/// -/// # Example values -/// * `SID=31d4d96e407aad42` -/// * `SID=31d4d96e407aad42; lang=en-US` -/// -/// # Example -/// ``` -/// use hyper::header::{Headers, Cookie}; -/// -/// let mut headers = Headers::new(); -/// let mut cookie = Cookie::new(); -/// cookie.append("foo", "bar"); -/// -/// assert_eq!(cookie.get("foo"), Some("bar")); -/// -/// headers.set(cookie); -/// ``` -#[derive(Clone)] -pub struct Cookie(VecMap, Cow<'static, str>>); - -impl Cookie { - /// Creates a new `Cookie` header. - pub fn new() -> Cookie { - Cookie(VecMap::with_capacity(0)) - } - - /// Sets a name and value for the `Cookie`. - /// - /// # Note - /// - /// This will remove all other instances with the same name, - /// and insert the new value. - pub fn set(&mut self, key: K, value: V) - where K: Into>, - V: Into> - { - let key = key.into(); - let value = value.into(); - self.0.remove_all(&key); - self.0.append(key, value); - } - - /// Append a name and value for the `Cookie`. - /// - /// # Note - /// - /// Cookies are allowed to set a name with a - /// a value multiple times. For example: - /// - /// ``` - /// use hyper::header::Cookie; - /// let mut cookie = Cookie::new(); - /// cookie.append("foo", "bar"); - /// cookie.append("foo", "quux"); - /// assert_eq!(cookie.to_string(), "foo=bar; foo=quux"); - pub fn append(&mut self, key: K, value: V) - where K: Into>, - V: Into> - { - self.0.append(key.into(), value.into()); - } - - /// Get a value for the name, if it exists. - /// - /// # Note - /// - /// Only returns the first instance found. To access - /// any other values associated with the name, parse - /// the `str` representation. - pub fn get(&self, key: &str) -> Option<&str> { - self.0.get(key).map(AsRef::as_ref) - } - - /// Iterate cookies. - /// - /// Iterate cookie (key, value) in insertion order. - /// - /// ``` - /// use hyper::header::Cookie; - /// let mut cookie = Cookie::new(); - /// cookie.append("foo", "bar"); - /// cookie.append(String::from("dyn"), String::from("amic")); - /// - /// let mut keys = Vec::new(); - /// let mut values = Vec::new(); - /// for (k, v) in cookie.iter() { - /// keys.push(k); - /// values.push(v); - /// } - /// assert_eq!(keys, vec!["foo", "dyn"]); - /// assert_eq!(values, vec!["bar", "amic"]); - /// ``` - pub fn iter(&self) -> CookieIter { - CookieIter(self.0.iter()) - } -} - -impl Header for Cookie { - fn header_name() -> &'static str { - static NAME: &'static str = "Cookie"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - let mut vec_map = VecMap::with_capacity(raw.len()); - for cookies_raw in raw.iter() { - let cookies_str = try!(from_utf8(&cookies_raw[..])); - for cookie_str in cookies_str.split(';') { - let mut key_val = cookie_str.splitn(2, '='); - let key_val = (key_val.next(), key_val.next()); - if let (Some(key), Some(val)) = key_val { - vec_map.insert(key.trim().to_owned().into(), val.trim().to_owned().into()); - } - } - } - - if vec_map.len() != 0 { - Ok(Cookie(vec_map)) - } else { - Err(::Error::Header) - } - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -impl PartialEq for Cookie { - fn eq(&self, other: &Cookie) -> bool { - if self.0.len() == other.0.len() { - for &(ref k, ref v) in self.0.iter() { - if other.get(k) != Some(v) { - return false; - } - } - true - } else { - false - } - } -} - -impl fmt::Debug for Cookie { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_map() - .entries(self.0.iter().map(|&(ref k, ref v)| (k, v))) - .finish() - } -} - -impl fmt::Display for Cookie { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut iter = self.0.iter(); - if let Some(&(ref key, ref val)) = iter.next() { - try!(write!(f, "{}={}", key, val)); - } - for &(ref key, ref val) in iter { - try!(write!(f, "; {}={}", key, val)); - } - Ok(()) - } -} - -/// Iterator for cookie. -#[derive(Debug)] -pub struct CookieIter<'a>(::std::slice::Iter<'a, (Cow<'static, str>, Cow<'static, str>)>); - -impl<'a> Iterator for CookieIter<'a> { - type Item = (&'a str, &'a str); - - fn next(&mut self) -> Option { - self.0.next().map(|kv| (kv.0.as_ref(), kv.1.as_ref())) - } -} - -#[cfg(test)] -mod tests { - use header::Header; - use super::Cookie; - - #[test] - fn test_set_and_get() { - let mut cookie = Cookie::new(); - cookie.append("foo", "bar"); - cookie.append(String::from("dyn"), String::from("amic")); - - assert_eq!(cookie.get("foo"), Some("bar")); - assert_eq!(cookie.get("dyn"), Some("amic")); - assert!(cookie.get("nope").is_none()); - - cookie.append("foo", "notbar"); - assert_eq!(cookie.get("foo"), Some("bar")); - - cookie.set("foo", "hi"); - assert_eq!(cookie.get("foo"), Some("hi")); - assert_eq!(cookie.get("dyn"), Some("amic")); - } - - #[test] - fn test_eq() { - let mut cookie = Cookie::new(); - let mut cookie2 = Cookie::new(); - - // empty is equal - assert_eq!(cookie, cookie2); - - // left has more params - cookie.append("foo", "bar"); - assert_ne!(cookie, cookie2); - - // same len, different params - cookie2.append("bar", "foo"); - assert_ne!(cookie, cookie2); - - - // right has more params, and matching KV - cookie2.append("foo", "bar"); - assert_ne!(cookie, cookie2); - - // same params, different order - cookie.append("bar", "foo"); - assert_eq!(cookie, cookie2); - } - - #[test] - fn test_parse() { - let mut cookie = Cookie::new(); - - let parsed = Cookie::parse_header(&b"foo=bar".to_vec().into()).unwrap(); - cookie.append("foo", "bar"); - assert_eq!(cookie, parsed); - - let parsed = Cookie::parse_header(&b"foo=bar;".to_vec().into()).unwrap(); - assert_eq!(cookie, parsed); - - let parsed = Cookie::parse_header(&b"foo=bar; baz=quux".to_vec().into()).unwrap(); - cookie.append("baz", "quux"); - assert_eq!(cookie, parsed); - - let parsed = Cookie::parse_header(&b"foo=bar;; baz=quux".to_vec().into()).unwrap(); - assert_eq!(cookie, parsed); - - let parsed = Cookie::parse_header(&b"foo=bar; invalid ; bad; ;; baz=quux".to_vec().into()) - .unwrap(); - assert_eq!(cookie, parsed); - - let parsed = Cookie::parse_header(&b" foo = bar;baz= quux ".to_vec().into()).unwrap(); - assert_eq!(cookie, parsed); - - let parsed = - Cookie::parse_header(&vec![b"foo = bar".to_vec(), b"baz= quux ".to_vec()].into()) - .unwrap(); - assert_eq!(cookie, parsed); - - let parsed = Cookie::parse_header(&b"foo=bar; baz=quux ; empty=".to_vec().into()).unwrap(); - cookie.append("empty", ""); - assert_eq!(cookie, parsed); - - - let mut cookie = Cookie::new(); - - let parsed = Cookie::parse_header(&b"middle=equals=in=the=middle".to_vec().into()).unwrap(); - cookie.append("middle", "equals=in=the=middle"); - assert_eq!(cookie, parsed); - - let parsed = - Cookie::parse_header(&b"middle=equals=in=the=middle; double==2".to_vec().into()) - .unwrap(); - cookie.append("double", "=2"); - assert_eq!(cookie, parsed); - } -} - -bench_header!(bench, Cookie, { - vec![b"foo=bar; baz=quux".to_vec()] -}); diff --git a/src/header/common/date.rs b/src/header/common/date.rs deleted file mode 100644 index a86aeaf6d9..0000000000 --- a/src/header/common/date.rs +++ /dev/null @@ -1,37 +0,0 @@ -use header::HttpDate; - -header! { - /// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) - /// - /// The `Date` header field represents the date and time at which the - /// message was originated. - /// - /// # ABNF - /// - /// ```text - /// Date = HTTP-date - /// ``` - /// - /// # Example values - /// - /// * `Tue, 15 Nov 1994 08:12:31 GMT` - /// - /// # Example - /// - /// ``` - /// use hyper::header::{Headers, Date}; - /// use std::time::SystemTime; - /// - /// let mut headers = Headers::new(); - /// headers.set(Date(SystemTime::now().into())); - /// ``` - (Date, "Date") => [HttpDate] - - test_date { - test_header!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); - } -} - -bench_header!(imf_fixdate, Date, { vec![b"Sun, 07 Nov 1994 08:48:37 GMT".to_vec()] }); -bench_header!(rfc_850, Date, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); -bench_header!(asctime, Date, { vec![b"Sun Nov 6 08:49:37 1994".to_vec()] }); diff --git a/src/header/common/etag.rs b/src/header/common/etag.rs deleted file mode 100644 index 5a62253050..0000000000 --- a/src/header/common/etag.rs +++ /dev/null @@ -1,95 +0,0 @@ -use header::EntityTag; - -header! { - /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) - /// - /// The `ETag` header field in a response provides the current entity-tag - /// for the selected representation, as determined at the conclusion of - /// handling the request. An entity-tag is an opaque validator for - /// differentiating between multiple representations of the same - /// resource, regardless of whether those multiple representations are - /// due to resource state changes over time, content negotiation - /// resulting in multiple representations being valid at the same time, - /// or both. An entity-tag consists of an opaque quoted string, possibly - /// prefixed by a weakness indicator. - /// - /// # ABNF - /// - /// ```text - /// ETag = entity-tag - /// ``` - /// - /// # Example values - /// - /// * `"xyzzy"` - /// * `W/"xyzzy"` - /// * `""` - /// - /// # Examples - /// - /// ``` - /// use hyper::header::{Headers, ETag, EntityTag}; - /// - /// let mut headers = Headers::new(); - /// headers.set(ETag(EntityTag::new(false, "xyzzy".to_owned()))); - /// ``` - /// ``` - /// use hyper::header::{Headers, ETag, EntityTag}; - /// - /// let mut headers = Headers::new(); - /// headers.set(ETag(EntityTag::new(true, "xyzzy".to_owned()))); - /// ``` - (ETag, "ETag") => [EntityTag] - - test_etag { - // From the RFC - test_header!(test1, - vec![b"\"xyzzy\""], - Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); - test_header!(test2, - vec![b"W/\"xyzzy\""], - Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); - test_header!(test3, - vec![b"\"\""], - Some(ETag(EntityTag::new(false, "".to_owned())))); - // Own tests - test_header!(test4, - vec![b"\"foobar\""], - Some(ETag(EntityTag::new(false, "foobar".to_owned())))); - test_header!(test5, - vec![b"\"\""], - Some(ETag(EntityTag::new(false, "".to_owned())))); - test_header!(test6, - vec![b"W/\"weak-etag\""], - Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); - test_header!(test7, - vec![b"W/\"\x65\x62\""], - Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); - test_header!(test8, - vec![b"W/\"\""], - Some(ETag(EntityTag::new(true, "".to_owned())))); - test_header!(test9, - vec![b"no-dquotes"], - None::); - test_header!(test10, - vec![b"w/\"the-first-w-is-case-sensitive\""], - None::); - test_header!(test11, - vec![b""], - None::); - test_header!(test12, - vec![b"\"unmatched-dquotes1"], - None::); - test_header!(test13, - vec![b"unmatched-dquotes2\""], - None::); - test_header!(test14, - vec![b"matched-\"dquotes\""], - None::); - test_header!(test15, - vec![b"\""], - None::); - } -} - -bench_header!(bench, ETag, { vec![b"W/\"nonemptytag\"".to_vec()] }); diff --git a/src/header/common/expect.rs b/src/header/common/expect.rs deleted file mode 100644 index f710bbc218..0000000000 --- a/src/header/common/expect.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::fmt; -use std::str; - -use unicase; - -use header::{Header, Raw}; - -/// The `Expect` header. -/// -/// > The "Expect" header field in a request indicates a certain set of -/// > behaviors (expectations) that need to be supported by the server in -/// > order to properly handle this request. The only such expectation -/// > defined by this specification is 100-continue. -/// > -/// > Expect = "100-continue" -/// -/// # Example -/// ``` -/// use hyper::header::{Headers, Expect}; -/// let mut headers = Headers::new(); -/// headers.set(Expect::Continue); -/// ``` -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum Expect { - /// The value `100-continue`. - Continue -} - -impl Header for Expect { - fn header_name() -> &'static str { - static NAME: &'static str = "Expect"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - if let Some(line) = raw.one() { - let text = unsafe { - // safe because: - // 1. we don't actually care if it's utf8, we just want to - // compare the bytes with the "case" normalized. If it's not - // utf8, then the byte comparison will fail, and we'll return - // None. No big deal. - str::from_utf8_unchecked(line) - }; - if unicase::eq_ascii(text, "100-continue") { - Ok(Expect::Continue) - } else { - Err(::Error::Header) - } - } else { - Err(::Error::Header) - } - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -impl fmt::Display for Expect { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("100-continue") - } -} diff --git a/src/header/common/expires.rs b/src/header/common/expires.rs deleted file mode 100644 index 989266e125..0000000000 --- a/src/header/common/expires.rs +++ /dev/null @@ -1,42 +0,0 @@ -use header::HttpDate; - -header! { - /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) - /// - /// The `Expires` header field gives the date/time after which the - /// response is considered stale. - /// - /// The presence of an Expires field does not imply that the original - /// resource will change or cease to exist at, before, or after that - /// time. - /// - /// # ABNF - /// - /// ```text - /// Expires = HTTP-date - /// ``` - /// - /// # Example values - /// * `Thu, 01 Dec 1994 16:00:00 GMT` - /// - /// # Example - /// - /// ``` - /// use hyper::header::{Headers, Expires}; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut headers = Headers::new(); - /// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24); - /// headers.set(Expires(expiration.into())); - /// ``` - (Expires, "Expires") => [HttpDate] - - test_expires { - // Testcase from RFC - test_header!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]); - } -} - -bench_header!(imf_fixdate, Expires, { vec![b"Sun, 07 Nov 1994 08:48:37 GMT".to_vec()] }); -bench_header!(rfc_850, Expires, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); -bench_header!(asctime, Expires, { vec![b"Sun Nov 6 08:49:37 1994".to_vec()] }); diff --git a/src/header/common/from.rs b/src/header/common/from.rs deleted file mode 100644 index 574c30ed6e..0000000000 --- a/src/header/common/from.rs +++ /dev/null @@ -1,29 +0,0 @@ -header! { - /// `From` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.5.1) - /// - /// The `From` header field contains an Internet email address for a - /// human user who controls the requesting user agent. The address ought - /// to be machine-usable. - /// - /// # ABNF - /// - /// ```text - /// From = mailbox - /// mailbox = - /// ``` - /// - /// # Example - /// - /// ``` - /// use hyper::header::{Headers, From}; - /// - /// let mut headers = Headers::new(); - /// headers.set(From("webmaster@example.org".to_owned())); - /// ``` - // FIXME: Maybe use mailbox? - (From, "From") => [String] - - test_from { - test_header!(test1, vec![b"webmaster@example.org"]); - } -} diff --git a/src/header/common/host.rs b/src/header/common/host.rs deleted file mode 100644 index 3a32b6679e..0000000000 --- a/src/header/common/host.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::borrow::Cow; -use std::fmt; -use std::str::FromStr; - -use header::{Header, Raw}; -use header::parsing::from_one_raw_str; - -/// The `Host` header. -/// -/// HTTP/1.1 requires that all requests include a `Host` header, and so hyper -/// client requests add one automatically. -/// -/// # Examples -/// ``` -/// use hyper::header::{Headers, Host}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// Host::new("hyper.rs", None) -/// ); -/// ``` -/// ``` -/// use hyper::header::{Headers, Host}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// // In Rust 1.12+ -/// // Host::new("hyper.rs", 8080) -/// Host::new("hyper.rs", Some(8080)) -/// ); -/// ``` -#[derive(Clone, PartialEq, Debug)] -pub struct Host { - hostname: Cow<'static, str>, - port: Option -} - -impl Host { - /// Create a `Host` header, providing the hostname and optional port. - pub fn new(hostname: H, port: P) -> Host - where H: Into>, - P: Into> - { - Host { - hostname: hostname.into(), - port: port.into(), - } - } - - /// Get the hostname, such as example.domain. - pub fn hostname(&self) -> &str { - self.hostname.as_ref() - } - - /// Get the optional port number. - pub fn port(&self) -> Option { - self.port - } -} - -impl Header for Host { - fn header_name() -> &'static str { - static NAME: &'static str = "Host"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - from_one_raw_str(raw) - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -impl fmt::Display for Host { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.port { - None | Some(80) | Some(443) => f.write_str(&self.hostname[..]), - Some(port) => write!(f, "{}:{}", self.hostname, port) - } - } -} - -impl FromStr for Host { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - let idx = s.rfind(':'); - let port = idx.and_then( - |idx| s[idx + 1..].parse().ok() - ); - let hostname = match port { - None => s, - Some(_) => &s[..idx.unwrap()] - }; - - Ok(Host { - hostname: hostname.to_owned().into(), - port: port, - }) - } -} - -#[cfg(test)] -mod tests { - use super::Host; - use header::Header; - - - #[test] - fn test_host() { - let host = Header::parse_header(&vec![b"foo.com".to_vec()].into()); - assert_eq!(host.ok(), Some(Host::new("foo.com", None))); - - - let host = Header::parse_header(&vec![b"foo.com:8080".to_vec()].into()); - assert_eq!(host.ok(), Some(Host::new("foo.com", Some(8080)))); - - let host = Header::parse_header(&vec![b"foo.com".to_vec()].into()); - assert_eq!(host.ok(), Some(Host::new("foo.com", None))); - - let host = Header::parse_header(&vec![b"[::1]:8080".to_vec()].into()); - assert_eq!(host.ok(), Some(Host::new("[::1]", Some(8080)))); - - let host = Header::parse_header(&vec![b"[::1]".to_vec()].into()); - assert_eq!(host.ok(), Some(Host::new("[::1]", None))); - } -} - -bench_header!(bench, Host, { vec![b"foo.com:3000".to_vec()] }); diff --git a/src/header/common/if_match.rs b/src/header/common/if_match.rs deleted file mode 100644 index 817ba3a6f3..0000000000 --- a/src/header/common/if_match.rs +++ /dev/null @@ -1,73 +0,0 @@ -use header::EntityTag; - -header! { - /// `If-Match` header, defined in - /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) - /// - /// The `If-Match` header field makes the request method conditional on - /// the recipient origin server either having at least one current - /// representation of the target resource, when the field-value is "*", - /// or having a current representation of the target resource that has an - /// entity-tag matching a member of the list of entity-tags provided in - /// the field-value. - /// - /// An origin server MUST use the strong comparison function when - /// comparing entity-tags for `If-Match`, since the client - /// intends this precondition to prevent the method from being applied if - /// there have been any changes to the representation data. - /// - /// # ABNF - /// - /// ```text - /// If-Match = "*" / 1#entity-tag - /// ``` - /// - /// # Example values - /// - /// * `"xyzzy"` - /// * "xyzzy", "r2d2xxxx", "c3piozzzz" - /// - /// # Examples - /// - /// ``` - /// use hyper::header::{Headers, IfMatch}; - /// - /// let mut headers = Headers::new(); - /// headers.set(IfMatch::Any); - /// ``` - /// - /// ``` - /// use hyper::header::{Headers, IfMatch, EntityTag}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// IfMatch::Items(vec![ - /// EntityTag::new(false, "xyzzy".to_owned()), - /// EntityTag::new(false, "foobar".to_owned()), - /// EntityTag::new(false, "bazquux".to_owned()), - /// ]) - /// ); - /// ``` - (IfMatch, "If-Match") => {Any / (EntityTag)+} - - test_if_match { - test_header!( - test1, - vec![b"\"xyzzy\""], - Some(HeaderField::Items( - vec![EntityTag::new(false, "xyzzy".to_owned())]))); - test_header!( - test2, - vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""], - Some(HeaderField::Items( - vec![EntityTag::new(false, "xyzzy".to_owned()), - EntityTag::new(false, "r2d2xxxx".to_owned()), - EntityTag::new(false, "c3piozzzz".to_owned())]))); - test_header!(test3, vec![b"*"], Some(IfMatch::Any)); - } -} - -bench_header!(star, IfMatch, { vec![b"*".to_vec()] }); -bench_header!(single , IfMatch, { vec![b"\"xyzzy\"".to_vec()] }); -bench_header!(multi, IfMatch, - { vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\"".to_vec()] }); diff --git a/src/header/common/if_modified_since.rs b/src/header/common/if_modified_since.rs deleted file mode 100644 index 2610350529..0000000000 --- a/src/header/common/if_modified_since.rs +++ /dev/null @@ -1,42 +0,0 @@ -use header::HttpDate; - -header! { - /// `If-Modified-Since` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) - /// - /// The `If-Modified-Since` header field makes a GET or HEAD request - /// method conditional on the selected representation's modification date - /// being more recent than the date provided in the field-value. - /// Transfer of the selected representation's data is avoided if that - /// data has not changed. - /// - /// # ABNF - /// - /// ```text - /// If-Unmodified-Since = HTTP-date - /// ``` - /// - /// # Example values - /// * `Sat, 29 Oct 1994 19:43:31 GMT` - /// - /// # Example - /// - /// ``` - /// use hyper::header::{Headers, IfModifiedSince}; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut headers = Headers::new(); - /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// headers.set(IfModifiedSince(modified.into())); - /// ``` - (IfModifiedSince, "If-Modified-Since") => [HttpDate] - - test_if_modified_since { - // Testcase from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - } -} - -bench_header!(imf_fixdate, IfModifiedSince, { vec![b"Sun, 07 Nov 1994 08:48:37 GMT".to_vec()] }); -bench_header!(rfc_850, IfModifiedSince, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); -bench_header!(asctime, IfModifiedSince, { vec![b"Sun Nov 6 08:49:37 1994".to_vec()] }); diff --git a/src/header/common/if_none_match.rs b/src/header/common/if_none_match.rs deleted file mode 100644 index 820c30e83e..0000000000 --- a/src/header/common/if_none_match.rs +++ /dev/null @@ -1,87 +0,0 @@ -use header::EntityTag; - -header! { - /// `If-None-Match` header, defined in - /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) - /// - /// The `If-None-Match` header field makes the request method conditional - /// on a recipient cache or origin server either not having any current - /// representation of the target resource, when the field-value is "*", - /// or having a selected representation with an entity-tag that does not - /// match any of those listed in the field-value. - /// - /// A recipient MUST use the weak comparison function when comparing - /// entity-tags for If-None-Match (Section 2.3.2), since weak entity-tags - /// can be used for cache validation even if there have been changes to - /// the representation data. - /// - /// # ABNF - /// - /// ```text - /// If-None-Match = "*" / 1#entity-tag - /// ``` - /// - /// # Example values - /// - /// * `"xyzzy"` - /// * `W/"xyzzy"` - /// * `"xyzzy", "r2d2xxxx", "c3piozzzz"` - /// * `W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"` - /// * `*` - /// - /// # Examples - /// - /// ``` - /// use hyper::header::{Headers, IfNoneMatch}; - /// - /// let mut headers = Headers::new(); - /// headers.set(IfNoneMatch::Any); - /// ``` - /// - /// ``` - /// use hyper::header::{Headers, IfNoneMatch, EntityTag}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// IfNoneMatch::Items(vec![ - /// EntityTag::new(false, "xyzzy".to_owned()), - /// EntityTag::new(false, "foobar".to_owned()), - /// EntityTag::new(false, "bazquux".to_owned()), - /// ]) - /// ); - /// ``` - (IfNoneMatch, "If-None-Match") => {Any / (EntityTag)+} - - test_if_none_match { - test_header!(test1, vec![b"\"xyzzy\""]); - test_header!(test2, vec![b"W/\"xyzzy\""]); - test_header!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); - test_header!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); - test_header!(test5, vec![b"*"]); - } -} - -#[cfg(test)] -mod tests { - use super::IfNoneMatch; - use header::Header; - use header::EntityTag; - - #[test] - fn test_if_none_match() { - let mut if_none_match: ::Result; - - if_none_match = Header::parse_header(&b"*".as_ref().into()); - assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any)); - - if_none_match = Header::parse_header(&b"\"foobar\", W/\"weak-etag\"".as_ref().into()); - let mut entities: Vec = Vec::new(); - let foobar_etag = EntityTag::new(false, "foobar".to_owned()); - let weak_etag = EntityTag::new(true, "weak-etag".to_owned()); - entities.push(foobar_etag); - entities.push(weak_etag); - assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Items(entities))); - } -} - -bench_header!(bench, IfNoneMatch, { vec![b"W/\"nonemptytag\"".to_vec()] }); diff --git a/src/header/common/if_range.rs b/src/header/common/if_range.rs deleted file mode 100644 index 6f0706c831..0000000000 --- a/src/header/common/if_range.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::fmt::{self, Display}; -use header::{self, Header, Raw, EntityTag, HttpDate}; - -/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) -/// -/// If a client has a partial copy of a representation and wishes to have -/// an up-to-date copy of the entire representation, it could use the -/// Range header field with a conditional GET (using either or both of -/// If-Unmodified-Since and If-Match.) However, if the precondition -/// fails because the representation has been modified, the client would -/// then have to make a second request to obtain the entire current -/// representation. -/// -/// The `If-Range` header field allows a client to \"short-circuit\" the -/// second request. Informally, its meaning is as follows: if the -/// representation is unchanged, send me the part(s) that I am requesting -/// in Range; otherwise, send me the entire representation. -/// -/// # ABNF -/// -/// ```text -/// If-Range = entity-tag / HTTP-date -/// ``` -/// -/// # Example values -/// -/// * `Sat, 29 Oct 1994 19:43:31 GMT` -/// * `\"xyzzy\"` -/// -/// # Examples -/// -/// ``` -/// use hyper::header::{Headers, IfRange, EntityTag}; -/// -/// let mut headers = Headers::new(); -/// headers.set(IfRange::EntityTag(EntityTag::new(false, "xyzzy".to_owned()))); -/// ``` -/// -/// ``` -/// use hyper::header::{Headers, IfRange}; -/// use std::time::{SystemTime, Duration}; -/// -/// let mut headers = Headers::new(); -/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); -/// headers.set(IfRange::Date(fetched.into())); -/// ``` -#[derive(Clone, Debug, PartialEq)] -pub enum IfRange { - /// The entity-tag the client has of the resource - EntityTag(EntityTag), - /// The date when the client retrieved the resource - Date(HttpDate), -} - -impl Header for IfRange { - fn header_name() -> &'static str { - static NAME: &'static str = "If-Range"; - NAME - } - fn parse_header(raw: &Raw) -> ::Result { - let etag: ::Result = header::parsing::from_one_raw_str(raw); - if let Ok(etag) = etag { - return Ok(IfRange::EntityTag(etag)); - } - let date: ::Result = header::parsing::from_one_raw_str(raw); - if let Ok(date) = date { - return Ok(IfRange::Date(date)); - } - Err(::Error::Header) - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> ::std::fmt::Result { - f.fmt_line(self) - } -} - -impl Display for IfRange { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - IfRange::EntityTag(ref x) => Display::fmt(x, f), - IfRange::Date(ref x) => Display::fmt(x, f), - } - } -} - -#[cfg(test)] -mod test_if_range { - use std::str; - use header::*; - use super::IfRange as HeaderField; - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - test_header!(test2, vec![b"\"xyzzy\""]); - test_header!(test3, vec![b"this-is-invalid"], None::); -} diff --git a/src/header/common/if_unmodified_since.rs b/src/header/common/if_unmodified_since.rs deleted file mode 100644 index 727ec29535..0000000000 --- a/src/header/common/if_unmodified_since.rs +++ /dev/null @@ -1,43 +0,0 @@ -use header::HttpDate; - -header! { - /// `If-Unmodified-Since` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) - /// - /// The `If-Unmodified-Since` header field makes the request method - /// conditional on the selected representation's last modification date - /// being earlier than or equal to the date provided in the field-value. - /// This field accomplishes the same purpose as If-Match for cases where - /// the user agent does not have an entity-tag for the representation. - /// - /// # ABNF - /// - /// ```text - /// If-Unmodified-Since = HTTP-date - /// ``` - /// - /// # Example values - /// - /// * `Sat, 29 Oct 1994 19:43:31 GMT` - /// - /// # Example - /// - /// ``` - /// use hyper::header::{Headers, IfUnmodifiedSince}; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut headers = Headers::new(); - /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// headers.set(IfUnmodifiedSince(modified.into())); - /// ``` - (IfUnmodifiedSince, "If-Unmodified-Since") => [HttpDate] - - test_if_unmodified_since { - // Testcase from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - } -} - -bench_header!(imf_fixdate, IfUnmodifiedSince, { vec![b"Sun, 07 Nov 1994 08:48:37 GMT".to_vec()] }); -bench_header!(rfc_850, IfUnmodifiedSince, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); -bench_header!(asctime, IfUnmodifiedSince, { vec![b"Sun Nov 6 08:49:37 1994".to_vec()] }); diff --git a/src/header/common/last_event_id.rs b/src/header/common/last_event_id.rs deleted file mode 100644 index 6af16abd22..0000000000 --- a/src/header/common/last_event_id.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::fmt::{self, Display}; -use header::{self, Header, Raw}; - -/// `Last-Event-ID` header, defined in -/// [RFC3864](https://html.spec.whatwg.org/multipage/references.html#refsRFC3864) -/// -/// The `Last-Event-ID` header contains information about -/// the last event in an http interaction so that it's easier to -/// track of event state. This is helpful when working -/// with [Server-Sent-Events](http://www.html5rocks.com/en/tutorials/eventsource/basics/). If the connection were to be dropped, for example, it'd -/// be useful to let the server know what the last event you -/// received was. -/// -/// The spec is a String with the id of the last event, it can be -/// an empty string which acts a sort of "reset". -/// -/// # Example -/// ``` -/// use hyper::header::{Headers, LastEventId}; -/// -/// let mut headers = Headers::new(); -/// headers.set(LastEventId("1".to_owned())); -/// ``` -#[derive(Clone, Debug, PartialEq)] -pub struct LastEventId(pub String); - -impl Header for LastEventId { - #[inline] - fn header_name() -> &'static str { - static NAME: &'static str = "Last-Event-ID"; - NAME - } - - #[inline] - fn parse_header(raw: &Raw) -> ::Result { - match raw.one() { - Some(line) if line.is_empty() => Ok(LastEventId("".to_owned())), - Some(line) => header::parsing::from_raw_str(line).map(LastEventId), - None => Err(::Error::Header), - } - } - - #[inline] - fn fmt_header(&self, f: &mut header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -impl Display for LastEventId { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Display::fmt(&self.0, f) - } -} - -__hyper__deref!(LastEventId => String); - -__hyper__tm!(LastEventId, tests { - // Initial state - test_header!(test1, vec![b""]); - // Own testcase - test_header!(test2, vec![b"1"], Some(LastEventId("1".to_owned()))); -}); diff --git a/src/header/common/last_modified.rs b/src/header/common/last_modified.rs deleted file mode 100644 index 9307fa4e43..0000000000 --- a/src/header/common/last_modified.rs +++ /dev/null @@ -1,41 +0,0 @@ -use header::HttpDate; - -header! { - /// `Last-Modified` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) - /// - /// The `Last-Modified` header field in a response provides a timestamp - /// indicating the date and time at which the origin server believes the - /// selected representation was last modified, as determined at the - /// conclusion of handling the request. - /// - /// # ABNF - /// - /// ```text - /// Expires = HTTP-date - /// ``` - /// - /// # Example values - /// - /// * `Sat, 29 Oct 1994 19:43:31 GMT` - /// - /// # Example - /// - /// ``` - /// use hyper::header::{Headers, LastModified}; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut headers = Headers::new(); - /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// headers.set(LastModified(modified.into())); - /// ``` - (LastModified, "Last-Modified") => [HttpDate] - - test_last_modified { - // Testcase from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);} -} - -bench_header!(imf_fixdate, LastModified, { vec![b"Sun, 07 Nov 1994 08:48:37 GMT".to_vec()] }); -bench_header!(rfc_850, LastModified, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); -bench_header!(asctime, LastModified, { vec![b"Sun Nov 6 08:49:37 1994".to_vec()] }); diff --git a/src/header/common/link.rs b/src/header/common/link.rs deleted file mode 100644 index dbdfdf9ea7..0000000000 --- a/src/header/common/link.rs +++ /dev/null @@ -1,1104 +0,0 @@ -use std::fmt; -use std::borrow::Cow; -use std::str::FromStr; -#[allow(unused)] -use std::ascii::AsciiExt; - -use mime::Mime; -use language_tags::LanguageTag; - -use header::parsing; -use header::{Header, Raw}; - -/// The `Link` header, defined in -/// [RFC5988](http://tools.ietf.org/html/rfc5988#section-5) -/// -/// # ABNF -/// -/// ```text -/// Link = "Link" ":" #link-value -/// link-value = "<" URI-Reference ">" *( ";" link-param ) -/// link-param = ( ( "rel" "=" relation-types ) -/// | ( "anchor" "=" <"> URI-Reference <"> ) -/// | ( "rev" "=" relation-types ) -/// | ( "hreflang" "=" Language-Tag ) -/// | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) ) -/// | ( "title" "=" quoted-string ) -/// | ( "title*" "=" ext-value ) -/// | ( "type" "=" ( media-type | quoted-mt ) ) -/// | ( link-extension ) ) -/// link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] ) -/// | ( ext-name-star "=" ext-value ) -/// ext-name-star = parmname "*" ; reserved for RFC2231-profiled -/// ; extensions. Whitespace NOT -/// ; allowed in between. -/// ptoken = 1*ptokenchar -/// ptokenchar = "!" | "#" | "$" | "%" | "&" | "'" | "(" -/// | ")" | "*" | "+" | "-" | "." | "/" | DIGIT -/// | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA -/// | "[" | "]" | "^" | "_" | "`" | "{" | "|" -/// | "}" | "~" -/// media-type = type-name "/" subtype-name -/// quoted-mt = <"> media-type <"> -/// relation-types = relation-type -/// | <"> relation-type *( 1*SP relation-type ) <"> -/// relation-type = reg-rel-type | ext-rel-type -/// reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" ) -/// ext-rel-type = URI -/// ``` -/// -/// # Example values -/// -/// `Link: ; rel="previous"; -/// title="previous chapter"` -/// -/// `Link: ; rel="previous"; title*=UTF-8'de'letztes%20Kapitel, -/// ; rel="next"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel` -/// -/// # Examples -/// -/// ``` -/// use hyper::header::{Headers, Link, LinkValue, RelationType}; -/// -/// let link_value = LinkValue::new("http://example.com/TheBook/chapter2") -/// .push_rel(RelationType::Previous) -/// .set_title("previous chapter"); -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// Link::new(vec![link_value]) -/// ); -/// ``` -#[derive(Clone, PartialEq, Debug)] -pub struct Link { - /// A list of the `link-value`s of the Link entity-header. - values: Vec -} - -/// A single `link-value` of a `Link` header, based on: -/// [RFC5988](http://tools.ietf.org/html/rfc5988#section-5) -#[derive(Clone, PartialEq, Debug)] -pub struct LinkValue { - /// Target IRI: `link-value`. - link: Cow<'static, str>, - - /// Forward Relation Types: `rel`. - rel: Option>, - - /// Context IRI: `anchor`. - anchor: Option, - - /// Reverse Relation Types: `rev`. - rev: Option>, - - /// Hint on the language of the result of dereferencing - /// the link: `hreflang`. - href_lang: Option>, - - /// Destination medium or media: `media`. - media_desc: Option>, - - /// Label of the destination of a Link: `title`. - title: Option, - - /// The `title` encoded in a different charset: `title*`. - title_star: Option, - - /// Hint on the media type of the result of dereferencing - /// the link: `type`. - media_type: Option, -} - -/// A Media Descriptors Enum based on: -/// [https://www.w3.org/TR/html401/types.html#h-6.13][url] -/// -/// [url]: https://www.w3.org/TR/html401/types.html#h-6.13 -#[derive(Clone, PartialEq, Debug)] -pub enum MediaDesc { - /// screen. - Screen, - /// tty. - Tty, - /// tv. - Tv, - /// projection. - Projection, - /// handheld. - Handheld, - /// print. - Print, - /// braille. - Braille, - /// aural. - Aural, - /// all. - All, - /// Unrecognized media descriptor extension. - Extension(String) -} - -/// A Link Relation Type Enum based on: -/// [RFC5988](https://tools.ietf.org/html/rfc5988#section-6.2.2) -#[derive(Clone, PartialEq, Debug)] -pub enum RelationType { - /// alternate. - Alternate, - /// appendix. - Appendix, - /// bookmark. - Bookmark, - /// chapter. - Chapter, - /// contents. - Contents, - /// copyright. - Copyright, - /// current. - Current, - /// describedby. - DescribedBy, - /// edit. - Edit, - /// edit-media. - EditMedia, - /// enclosure. - Enclosure, - /// first. - First, - /// glossary. - Glossary, - /// help. - Help, - /// hub. - Hub, - /// index. - Index, - /// last. - Last, - /// latest-version. - LatestVersion, - /// license. - License, - /// next. - Next, - /// next-archive. - NextArchive, - /// payment. - Payment, - /// prev. - Prev, - /// predecessor-version. - PredecessorVersion, - /// previous. - Previous, - /// prev-archive. - PrevArchive, - /// related. - Related, - /// replies. - Replies, - /// section. - Section, - /// self. - RelationTypeSelf, - /// service. - Service, - /// start. - Start, - /// stylesheet. - Stylesheet, - /// subsection. - Subsection, - /// successor-version. - SuccessorVersion, - /// up. - Up, - /// versionHistory. - VersionHistory, - /// via. - Via, - /// working-copy. - WorkingCopy, - /// working-copy-of. - WorkingCopyOf, - /// ext-rel-type. - ExtRelType(String) -} - -//////////////////////////////////////////////////////////////////////////////// -// Struct methods -//////////////////////////////////////////////////////////////////////////////// - -impl Link { - /// Create `Link` from a `Vec`. - pub fn new(link_values: Vec) -> Link { - Link { values: link_values } - } - - /// Get the `Link` header's `LinkValue`s. - pub fn values(&self) -> &[LinkValue] { - self.values.as_ref() - } - - /// Add a `LinkValue` instance to the `Link` header's values. - pub fn push_value(&mut self, link_value: LinkValue) { - self.values.push(link_value); - } -} - -impl LinkValue { - /// Create `LinkValue` from URI-Reference. - pub fn new(uri: T) -> LinkValue - where T: Into> { - LinkValue { - link: uri.into(), - rel: None, - anchor: None, - rev: None, - href_lang: None, - media_desc: None, - title: None, - title_star: None, - media_type: None, - } - } - - /// Get the `LinkValue`'s value. - pub fn link(&self) -> &str { - self.link.as_ref() - } - - /// Get the `LinkValue`'s `rel` parameter(s). - pub fn rel(&self) -> Option<&[RelationType]> { - self.rel.as_ref().map(AsRef::as_ref) - } - - /// Get the `LinkValue`'s `anchor` parameter. - pub fn anchor(&self) -> Option<&str> { - self.anchor.as_ref().map(AsRef::as_ref) - } - - /// Get the `LinkValue`'s `rev` parameter(s). - pub fn rev(&self) -> Option<&[RelationType]> { - self.rev.as_ref().map(AsRef::as_ref) - } - - /// Get the `LinkValue`'s `hreflang` parameter(s). - pub fn href_lang(&self) -> Option<&[LanguageTag]> { - self.href_lang.as_ref().map(AsRef::as_ref) - } - - /// Get the `LinkValue`'s `media` parameter(s). - pub fn media_desc(&self) -> Option<&[MediaDesc]> { - self.media_desc.as_ref().map(AsRef::as_ref) - } - - /// Get the `LinkValue`'s `title` parameter. - pub fn title(&self) -> Option<&str> { - self.title.as_ref().map(AsRef::as_ref) - } - - /// Get the `LinkValue`'s `title*` parameter. - pub fn title_star(&self) -> Option<&str> { - self.title_star.as_ref().map(AsRef::as_ref) - } - - /// Get the `LinkValue`'s `type` parameter. - pub fn media_type(&self) -> Option<&Mime> { - self.media_type.as_ref() - } - - /// Add a `RelationType` to the `LinkValue`'s `rel` parameter. - pub fn push_rel(mut self, rel: RelationType) -> LinkValue { - let mut v = self.rel.take().unwrap_or(Vec::new()); - - v.push(rel); - - self.rel = Some(v); - - self - } - - /// Set `LinkValue`'s `anchor` parameter. - pub fn set_anchor>(mut self, anchor: T) -> LinkValue { - self.anchor = Some(anchor.into()); - - self - } - - /// Add a `RelationType` to the `LinkValue`'s `rev` parameter. - pub fn push_rev(mut self, rev: RelationType) -> LinkValue { - let mut v = self.rev.take().unwrap_or(Vec::new()); - - v.push(rev); - - self.rev = Some(v); - - self - } - - /// Add a `LanguageTag` to the `LinkValue`'s `hreflang` parameter. - pub fn push_href_lang(mut self, language_tag: LanguageTag) -> LinkValue { - let mut v = self.href_lang.take().unwrap_or(Vec::new()); - - v.push(language_tag); - - self.href_lang = Some(v); - - self - } - - /// Add a `MediaDesc` to the `LinkValue`'s `media_desc` parameter. - pub fn push_media_desc(mut self, media_desc: MediaDesc) -> LinkValue { - let mut v = self.media_desc.take().unwrap_or(Vec::new()); - - v.push(media_desc); - - self.media_desc = Some(v); - - self - } - - /// Set `LinkValue`'s `title` parameter. - pub fn set_title>(mut self, title: T) -> LinkValue { - self.title = Some(title.into()); - - self - } - - /// Set `LinkValue`'s `title*` parameter. - pub fn set_title_star>(mut self, title_star: T) -> LinkValue { - self.title_star = Some(title_star.into()); - - self - } - - /// Set `LinkValue`'s `type` parameter. - pub fn set_media_type(mut self, media_type: Mime) -> LinkValue { - self.media_type = Some(media_type); - - self - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Trait implementations -//////////////////////////////////////////////////////////////////////////////// - -impl Header for Link { - fn header_name() -> &'static str { - static NAME: &'static str = "Link"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - // If more that one `Link` headers are present in a request's - // headers they are combined in a single `Link` header containing - // all the `link-value`s present in each of those `Link` headers. - raw.iter() - .map(parsing::from_raw_str::) - .fold(None, |p, c| { - match (p, c) { - (None, c) => Some(c), - (e @ Some(Err(_)), _) => e, - (Some(Ok(mut p)), Ok(c)) => { - p.values.extend(c.values); - - Some(Ok(p)) - }, - _ => Some(Err(::Error::Header)), - } - }) - .unwrap_or(Err(::Error::Header)) - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -impl fmt::Display for Link { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt_delimited(f, self.values.as_slice(), ", ", ("", "")) - } -} - -impl fmt::Display for LinkValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - try!(write!(f, "<{}>", self.link)); - - if let Some(ref rel) = self.rel { - try!(fmt_delimited(f, rel.as_slice(), " ", ("; rel=\"", "\""))); - } - if let Some(ref anchor) = self.anchor { - try!(write!(f, "; anchor=\"{}\"", anchor)); - } - if let Some(ref rev) = self.rev { - try!(fmt_delimited(f, rev.as_slice(), " ", ("; rev=\"", "\""))); - } - if let Some(ref href_lang) = self.href_lang { - for tag in href_lang { - try!(write!(f, "; hreflang={}", tag)); - } - } - if let Some(ref media_desc) = self.media_desc { - try!(fmt_delimited(f, media_desc.as_slice(), ", ", ("; media=\"", "\""))); - } - if let Some(ref title) = self.title { - try!(write!(f, "; title=\"{}\"", title)); - } - if let Some(ref title_star) = self.title_star { - try!(write!(f, "; title*={}", title_star)); - } - if let Some(ref media_type) = self.media_type { - try!(write!(f, "; type=\"{}\"", media_type)); - } - - Ok(()) - } -} - -impl FromStr for Link { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - // Create a split iterator with delimiters: `;`, `,` - let link_split = SplitAsciiUnquoted::new(s, ";,"); - - let mut link_values: Vec = Vec::new(); - - // Loop over the splits parsing the Link header into - // a `Vec` - for segment in link_split { - // Parse the `Target IRI` - // https://tools.ietf.org/html/rfc5988#section-5.1 - if segment.trim().starts_with('<') { - link_values.push( - match verify_and_trim(segment.trim(), (b'<', b'>')) { - Err(_) => return Err(::Error::Header), - Ok(s) => { - LinkValue { - link: s.to_owned().into(), - rel: None, - anchor: None, - rev: None, - href_lang: None, - media_desc: None, - title: None, - title_star: None, - media_type: None, - } - }, - } - ); - } else { - // Parse the current link-value's parameters - let mut link_param_split = segment.splitn(2, '='); - - let link_param_name = match link_param_split.next() { - None => return Err(::Error::Header), - Some(p) => p.trim(), - }; - - let link_header = match link_values.last_mut() { - None => return Err(::Error::Header), - Some(l) => l, - }; - - if "rel".eq_ignore_ascii_case(link_param_name) { - // Parse relation type: `rel`. - // https://tools.ietf.org/html/rfc5988#section-5.3 - if link_header.rel.is_none() { - link_header.rel = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => { - s.trim_matches(|c: char| c == '"' || c.is_whitespace()) - .split(' ') - .map(|t| t.trim().parse()) - .collect::, _>>() - .or_else(|_| Err(::Error::Header)) - .ok() - }, - }; - } - } else if "anchor".eq_ignore_ascii_case(link_param_name) { - // Parse the `Context IRI`. - // https://tools.ietf.org/html/rfc5988#section-5.2 - link_header.anchor = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => match verify_and_trim(s.trim(), (b'"', b'"')) { - Err(_) => return Err(::Error::Header), - Ok(a) => Some(String::from(a)), - }, - }; - } else if "rev".eq_ignore_ascii_case(link_param_name) { - // Parse relation type: `rev`. - // https://tools.ietf.org/html/rfc5988#section-5.3 - if link_header.rev.is_none() { - link_header.rev = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => { - s.trim_matches(|c: char| c == '"' || c.is_whitespace()) - .split(' ') - .map(|t| t.trim().parse()) - .collect::, _>>() - .or_else(|_| Err(::Error::Header)) - .ok() - }, - } - } - } else if "hreflang".eq_ignore_ascii_case(link_param_name) { - // Parse target attribute: `hreflang`. - // https://tools.ietf.org/html/rfc5988#section-5.4 - let mut v = link_header.href_lang.take().unwrap_or(Vec::new()); - - v.push( - match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => match s.trim().parse() { - Err(_) => return Err(::Error::Header), - Ok(t) => t, - }, - } - ); - - link_header.href_lang = Some(v); - } else if "media".eq_ignore_ascii_case(link_param_name) { - // Parse target attribute: `media`. - // https://tools.ietf.org/html/rfc5988#section-5.4 - if link_header.media_desc.is_none() { - link_header.media_desc = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => { - s.trim_matches(|c: char| c == '"' || c.is_whitespace()) - .split(',') - .map(|t| t.trim().parse()) - .collect::, _>>() - .or_else(|_| Err(::Error::Header)) - .ok() - }, - }; - } - } else if "title".eq_ignore_ascii_case(link_param_name) { - // Parse target attribute: `title`. - // https://tools.ietf.org/html/rfc5988#section-5.4 - if link_header.title.is_none() { - link_header.title = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => match verify_and_trim(s.trim(), (b'"', b'"')) { - Err(_) => return Err(::Error::Header), - Ok(t) => Some(String::from(t)), - }, - }; - } - } else if "title*".eq_ignore_ascii_case(link_param_name) { - // Parse target attribute: `title*`. - // https://tools.ietf.org/html/rfc5988#section-5.4 - // - // Definition of `ext-value`: - // https://tools.ietf.org/html/rfc5987#section-3.2.1 - if link_header.title_star.is_none() { - link_header.title_star = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => Some(String::from(s.trim())), - }; - } - } else if "type".eq_ignore_ascii_case(link_param_name) { - // Parse target attribute: `type`. - // https://tools.ietf.org/html/rfc5988#section-5.4 - if link_header.media_type.is_none() { - link_header.media_type = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), - Some(s) => match verify_and_trim(s.trim(), (b'"', b'"')) { - Err(_) => return Err(::Error::Header), - Ok(t) => match t.parse() { - Err(_) => return Err(::Error::Header), - Ok(m) => Some(m), - }, - }, - - }; - } - } else { - return Err(::Error::Header); - } - } - } - - Ok(Link::new(link_values)) - } -} - -impl fmt::Display for MediaDesc { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - MediaDesc::Screen => write!(f, "screen"), - MediaDesc::Tty => write!(f, "tty"), - MediaDesc::Tv => write!(f, "tv"), - MediaDesc::Projection => write!(f, "projection"), - MediaDesc::Handheld => write!(f, "handheld"), - MediaDesc::Print => write!(f, "print"), - MediaDesc::Braille => write!(f, "braille"), - MediaDesc::Aural => write!(f, "aural"), - MediaDesc::All => write!(f, "all"), - MediaDesc::Extension(ref other) => write!(f, "{}", other), - } - } -} - -impl FromStr for MediaDesc { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - match s { - "screen" => Ok(MediaDesc::Screen), - "tty" => Ok(MediaDesc::Tty), - "tv" => Ok(MediaDesc::Tv), - "projection" => Ok(MediaDesc::Projection), - "handheld" => Ok(MediaDesc::Handheld), - "print" => Ok(MediaDesc::Print), - "braille" => Ok(MediaDesc::Braille), - "aural" => Ok(MediaDesc::Aural), - "all" => Ok(MediaDesc::All), - _ => Ok(MediaDesc::Extension(String::from(s))), - } - } -} - -impl fmt::Display for RelationType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - RelationType::Alternate => write!(f, "alternate"), - RelationType::Appendix => write!(f, "appendix"), - RelationType::Bookmark => write!(f, "bookmark"), - RelationType::Chapter => write!(f, "chapter"), - RelationType::Contents => write!(f, "contents"), - RelationType::Copyright => write!(f, "copyright"), - RelationType::Current => write!(f, "current"), - RelationType::DescribedBy => write!(f, "describedby"), - RelationType::Edit => write!(f, "edit"), - RelationType::EditMedia => write!(f, "edit-media"), - RelationType::Enclosure => write!(f, "enclosure"), - RelationType::First => write!(f, "first"), - RelationType::Glossary => write!(f, "glossary"), - RelationType::Help => write!(f, "help"), - RelationType::Hub => write!(f, "hub"), - RelationType::Index => write!(f, "index"), - RelationType::Last => write!(f, "last"), - RelationType::LatestVersion => write!(f, "latest-version"), - RelationType::License => write!(f, "license"), - RelationType::Next => write!(f, "next"), - RelationType::NextArchive => write!(f, "next-archive"), - RelationType::Payment => write!(f, "payment"), - RelationType::Prev => write!(f, "prev"), - RelationType::PredecessorVersion => write!(f, "predecessor-version"), - RelationType::Previous => write!(f, "previous"), - RelationType::PrevArchive => write!(f, "prev-archive"), - RelationType::Related => write!(f, "related"), - RelationType::Replies => write!(f, "replies"), - RelationType::Section => write!(f, "section"), - RelationType::RelationTypeSelf => write!(f, "self"), - RelationType::Service => write!(f, "service"), - RelationType::Start => write!(f, "start"), - RelationType::Stylesheet => write!(f, "stylesheet"), - RelationType::Subsection => write!(f, "subsection"), - RelationType::SuccessorVersion => write!(f, "successor-version"), - RelationType::Up => write!(f, "up"), - RelationType::VersionHistory => write!(f, "version-history"), - RelationType::Via => write!(f, "via"), - RelationType::WorkingCopy => write!(f, "working-copy"), - RelationType::WorkingCopyOf => write!(f, "working-copy-of"), - RelationType::ExtRelType(ref uri) => write!(f, "{}", uri), - } - } -} - -impl FromStr for RelationType { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - if "alternate".eq_ignore_ascii_case(s) { - Ok(RelationType::Alternate) - } else if "appendix".eq_ignore_ascii_case(s) { - Ok(RelationType::Appendix) - } else if "bookmark".eq_ignore_ascii_case(s) { - Ok(RelationType::Bookmark) - } else if "chapter".eq_ignore_ascii_case(s) { - Ok(RelationType::Chapter) - } else if "contents".eq_ignore_ascii_case(s) { - Ok(RelationType::Contents) - } else if "copyright".eq_ignore_ascii_case(s) { - Ok(RelationType::Copyright) - } else if "current".eq_ignore_ascii_case(s) { - Ok(RelationType::Current) - } else if "describedby".eq_ignore_ascii_case(s) { - Ok(RelationType::DescribedBy) - } else if "edit".eq_ignore_ascii_case(s) { - Ok(RelationType::Edit) - } else if "edit-media".eq_ignore_ascii_case(s) { - Ok(RelationType::EditMedia) - } else if "enclosure".eq_ignore_ascii_case(s) { - Ok(RelationType::Enclosure) - } else if "first".eq_ignore_ascii_case(s) { - Ok(RelationType::First) - } else if "glossary".eq_ignore_ascii_case(s) { - Ok(RelationType::Glossary) - } else if "help".eq_ignore_ascii_case(s) { - Ok(RelationType::Help) - } else if "hub".eq_ignore_ascii_case(s) { - Ok(RelationType::Hub) - } else if "index".eq_ignore_ascii_case(s) { - Ok(RelationType::Index) - } else if "last".eq_ignore_ascii_case(s) { - Ok(RelationType::Last) - } else if "latest-version".eq_ignore_ascii_case(s) { - Ok(RelationType::LatestVersion) - } else if "license".eq_ignore_ascii_case(s) { - Ok(RelationType::License) - } else if "next".eq_ignore_ascii_case(s) { - Ok(RelationType::Next) - } else if "next-archive".eq_ignore_ascii_case(s) { - Ok(RelationType::NextArchive) - } else if "payment".eq_ignore_ascii_case(s) { - Ok(RelationType::Payment) - } else if "prev".eq_ignore_ascii_case(s) { - Ok(RelationType::Prev) - } else if "predecessor-version".eq_ignore_ascii_case(s) { - Ok(RelationType::PredecessorVersion) - } else if "previous".eq_ignore_ascii_case(s) { - Ok(RelationType::Previous) - } else if "prev-archive".eq_ignore_ascii_case(s) { - Ok(RelationType::PrevArchive) - } else if "related".eq_ignore_ascii_case(s) { - Ok(RelationType::Related) - } else if "replies".eq_ignore_ascii_case(s) { - Ok(RelationType::Replies) - } else if "section".eq_ignore_ascii_case(s) { - Ok(RelationType::Section) - } else if "self".eq_ignore_ascii_case(s) { - Ok(RelationType::RelationTypeSelf) - } else if "service".eq_ignore_ascii_case(s) { - Ok(RelationType::Service) - } else if "start".eq_ignore_ascii_case(s) { - Ok(RelationType::Start) - } else if "stylesheet".eq_ignore_ascii_case(s) { - Ok(RelationType::Stylesheet) - } else if "subsection".eq_ignore_ascii_case(s) { - Ok(RelationType::Subsection) - } else if "successor-version".eq_ignore_ascii_case(s) { - Ok(RelationType::SuccessorVersion) - } else if "up".eq_ignore_ascii_case(s) { - Ok(RelationType::Up) - } else if "version-history".eq_ignore_ascii_case(s) { - Ok(RelationType::VersionHistory) - } else if "via".eq_ignore_ascii_case(s) { - Ok(RelationType::Via) - } else if "working-copy".eq_ignore_ascii_case(s) { - Ok(RelationType::WorkingCopy) - } else if "working-copy-of".eq_ignore_ascii_case(s) { - Ok(RelationType::WorkingCopyOf) - } else { - Ok(RelationType::ExtRelType(String::from(s))) - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Utilities -//////////////////////////////////////////////////////////////////////////////// - -struct SplitAsciiUnquoted<'a> { - src: &'a str, - pos: usize, - del: &'a str -} - -impl<'a> SplitAsciiUnquoted<'a> { - fn new(s: &'a str, d: &'a str) -> SplitAsciiUnquoted<'a> { - SplitAsciiUnquoted{ - src: s, - pos: 0, - del: d, - } - } -} - -impl<'a> Iterator for SplitAsciiUnquoted<'a> { - type Item = &'a str; - - fn next(&mut self) -> Option<&'a str> { - if self.pos < self.src.len() { - let prev_pos = self.pos; - let mut pos = self.pos; - - let mut in_quotes = false; - - for c in self.src[prev_pos..].as_bytes().iter() { - in_quotes ^= *c == b'"'; - - // Ignore `c` if we're `in_quotes`. - if !in_quotes && self.del.as_bytes().contains(c) { - break; - } - - pos += 1; - } - - self.pos = pos + 1; - - Some(&self.src[prev_pos..pos]) - } else { - None - } - } -} - -fn fmt_delimited(f: &mut fmt::Formatter, p: &[T], d: &str, b: (&str, &str)) -> fmt::Result { - if p.len() != 0 { - // Write a starting string `b.0` before the first element - try!(write!(f, "{}{}", b.0, p[0])); - - for i in &p[1..] { - // Write the next element preceded by the delimiter `d` - try!(write!(f, "{}{}", d, i)); - } - - // Write a ending string `b.1` before the first element - try!(write!(f, "{}", b.1)); - } - - Ok(()) -} - -fn verify_and_trim(s: &str, b: (u8, u8)) -> ::Result<&str> { - let length = s.len(); - let byte_array = s.as_bytes(); - - // Verify that `s` starts with `b.0` and ends with `b.1` and return - // the contained substring after trimming whitespace. - if length > 1 && b.0 == byte_array[0] && b.1 == byte_array[length - 1] { - Ok(s.trim_matches( - |c: char| c == b.0 as char || c == b.1 as char || c.is_whitespace()) - ) - } else { - Err(::Error::Header) - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Tests -//////////////////////////////////////////////////////////////////////////////// - -#[cfg(test)] -mod tests { - use std::fmt; - use std::fmt::Write; - - use super::{Link, LinkValue, MediaDesc, RelationType, SplitAsciiUnquoted}; - use super::{fmt_delimited, verify_and_trim}; - - use header::Header; - - use proto::{ServerTransaction, Http1Transaction}; - use bytes::BytesMut; - - use mime; - - #[test] - fn test_link() { - let link_value = LinkValue::new("http://example.com/TheBook/chapter2") - .push_rel(RelationType::Previous) - .push_rev(RelationType::Next) - .set_title("previous chapter"); - - let link_header = b"; \ - rel=\"previous\"; rev=next; title=\"previous chapter\""; - - let expected_link = Link::new(vec![link_value]); - - let link = Header::parse_header(&vec![link_header.to_vec()].into()); - assert_eq!(link.ok(), Some(expected_link)); - } - - #[test] - fn test_link_multiple_values() { - let first_link = LinkValue::new("/TheBook/chapter2") - .push_rel(RelationType::Previous) - .set_title_star("UTF-8'de'letztes%20Kapitel"); - - let second_link = LinkValue::new("/TheBook/chapter4") - .push_rel(RelationType::Next) - .set_title_star("UTF-8'de'n%c3%a4chstes%20Kapitel"); - - let link_header = b"; \ - rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel, \ - ; \ - rel=\"next\"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel"; - - let expected_link = Link::new(vec![first_link, second_link]); - - let link = Header::parse_header(&vec![link_header.to_vec()].into()); - assert_eq!(link.ok(), Some(expected_link)); - } - - #[test] - fn test_link_all_attributes() { - let link_value = LinkValue::new("http://example.com/TheBook/chapter2") - .push_rel(RelationType::Previous) - .set_anchor("../anchor/example/") - .push_rev(RelationType::Next) - .push_href_lang("de".parse().unwrap()) - .push_media_desc(MediaDesc::Screen) - .set_title("previous chapter") - .set_title_star("title* unparsed") - .set_media_type(mime::TEXT_PLAIN); - - let link_header = b"; \ - rel=\"previous\"; anchor=\"../anchor/example/\"; \ - rev=\"next\"; hreflang=de; media=\"screen\"; \ - title=\"previous chapter\"; title*=title* unparsed; \ - type=\"text/plain\""; - - let expected_link = Link::new(vec![link_value]); - - let link = Header::parse_header(&vec![link_header.to_vec()].into()); - assert_eq!(link.ok(), Some(expected_link)); - } - - #[test] - fn test_link_multiple_link_headers() { - let first_link = LinkValue::new("/TheBook/chapter2") - .push_rel(RelationType::Previous) - .set_title_star("UTF-8'de'letztes%20Kapitel"); - - let second_link = LinkValue::new("/TheBook/chapter4") - .push_rel(RelationType::Next) - .set_title_star("UTF-8'de'n%c3%a4chstes%20Kapitel"); - - let third_link = LinkValue::new("http://example.com/TheBook/chapter2") - .push_rel(RelationType::Previous) - .push_rev(RelationType::Next) - .set_title("previous chapter"); - - let expected_link = Link::new(vec![first_link, second_link, third_link]); - - let mut raw = BytesMut::from(b"GET /super_short_uri/and_whatever HTTP/1.1\r\nHost: \ - hyper.rs\r\nAccept: a lot of things\r\nAccept-Charset: \ - utf8\r\nAccept-Encoding: *\r\nLink: ; \ - rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel, \ - ; rel=\"next\"; title*=\ - UTF-8'de'n%c3%a4chstes%20Kapitel\r\n\ - Access-Control-Allow-Credentials: None\r\nLink: \ - ; \ - rel=\"previous\"; rev=next; title=\"previous chapter\"\ - \r\n\r\n".to_vec()); - - let (mut res, _) = ServerTransaction::parse(&mut raw).unwrap().unwrap(); - - let link = res.headers.remove::().unwrap(); - - assert_eq!(link, expected_link); - } - - #[test] - fn test_link_display() { - let link_value = LinkValue::new("http://example.com/TheBook/chapter2") - .push_rel(RelationType::Previous) - .set_anchor("/anchor/example/") - .push_rev(RelationType::Next) - .push_href_lang("de".parse().unwrap()) - .push_media_desc(MediaDesc::Screen) - .set_title("previous chapter") - .set_title_star("title* unparsed") - .set_media_type(mime::TEXT_PLAIN); - - let link = Link::new(vec![link_value]); - - let mut link_header = String::new(); - write!(&mut link_header, "{}", link).unwrap(); - - let expected_link_header = "; \ - rel=\"previous\"; anchor=\"/anchor/example/\"; \ - rev=\"next\"; hreflang=de; media=\"screen\"; \ - title=\"previous chapter\"; title*=title* unparsed; \ - type=\"text/plain\""; - - assert_eq!(link_header, expected_link_header); - } - - #[test] - fn test_link_parsing_errors() { - let link_a = b"http://example.com/TheBook/chapter2; \ - rel=\"previous\"; rev=next; title=\"previous chapter\""; - - let mut err: Result = Header::parse_header(&vec![link_a.to_vec()].into()); - assert_eq!(err.is_err(), true); - - let link_b = b"; \ - =\"previous\"; rev=next; title=\"previous chapter\""; - - err = Header::parse_header(&vec![link_b.to_vec()].into()); - assert_eq!(err.is_err(), true); - - let link_c = b"; \ - rel=; rev=next; title=\"previous chapter\""; - - err = Header::parse_header(&vec![link_c.to_vec()].into()); - assert_eq!(err.is_err(), true); - - let link_d = b"; \ - rel=\"previous\"; rev=next; title="; - - err = Header::parse_header(&vec![link_d.to_vec()].into()); - assert_eq!(err.is_err(), true); - - let link_e = b"; \ - rel=\"previous\"; rev=next; attr=unknown"; - - err = Header::parse_header(&vec![link_e.to_vec()].into()); - assert_eq!(err.is_err(), true); - } - - #[test] - fn test_link_split_ascii_unquoted_iterator() { - let string = "some, text; \"and, more; in quotes\", or not"; - let mut string_split = SplitAsciiUnquoted::new(string, ";,"); - - assert_eq!(Some("some"), string_split.next()); - assert_eq!(Some(" text"), string_split.next()); - assert_eq!(Some(" \"and, more; in quotes\""), string_split.next()); - assert_eq!(Some(" or not"), string_split.next()); - assert_eq!(None, string_split.next()); - } - - #[test] - fn test_link_fmt_delimited() { - struct TestFormatterStruct<'a> { v: Vec<&'a str> }; - - impl<'a> fmt::Display for TestFormatterStruct<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt_delimited(f, self.v.as_slice(), ", ", (">>", "<<")) - } - } - - let test_formatter = TestFormatterStruct { v: vec!["first", "second"] }; - - let mut string = String::new(); - write!(&mut string, "{}", test_formatter).unwrap(); - - let expected_string = ">>first, second<<"; - - assert_eq!(string, expected_string); - } - - #[test] - fn test_link_verify_and_trim() { - let string = verify_and_trim("> some string <", (b'>', b'<')); - assert_eq!(string.ok(), Some("some string")); - - let err = verify_and_trim(" > some string <", (b'>', b'<')); - assert_eq!(err.is_err(), true); - } -} - -bench_header!(bench_link, Link, { vec![b"; rel=\"previous\"; rev=next; title=\"previous chapter\"; type=\"text/html\"; media=\"screen, tty\"".to_vec()] }); diff --git a/src/header/common/location.rs b/src/header/common/location.rs deleted file mode 100644 index 3074844431..0000000000 --- a/src/header/common/location.rs +++ /dev/null @@ -1,46 +0,0 @@ -header! { - /// `Location` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.2) - /// - /// The `Location` header field is used in some responses to refer to a - /// specific resource in relation to the response. The type of - /// relationship is defined by the combination of request method and - /// status code semantics. - /// - /// # ABNF - /// - /// ```text - /// Location = URI-reference - /// ``` - /// - /// # Example values - /// * `/People.html#tim` - /// * `http://www.example.net/index.html` - /// - /// # Examples - /// - /// ``` - /// use hyper::header::{Headers, Location}; - /// - /// let mut headers = Headers::new(); - /// headers.set(Location::new("/People.html#tim")); - /// ``` - /// - /// ``` - /// use hyper::header::{Headers, Location}; - /// - /// let mut headers = Headers::new(); - /// headers.set(Location::new("http://www.example.com/index.html")); - /// ``` - // TODO: Use URL - (Location, "Location") => Cow[str] - - test_location { - // Testcase from RFC - test_header!(test1, vec![b"/People.html#tim"]); - test_header!(test2, vec![b"http://www.example.net/index.html"]); - } - -} - -bench_header!(bench, Location, { vec![b"http://foo.com/hello:3000".to_vec()] }); diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs deleted file mode 100644 index bcedeae7a2..0000000000 --- a/src/header/common/mod.rs +++ /dev/null @@ -1,502 +0,0 @@ -//! A Collection of Header implementations for common HTTP Headers. -//! -//! ## Mime -//! -//! Several header fields use MIME values for their contents. Keeping with the -//! strongly-typed theme, the [mime](https://docs.rs/mime) crate -//! is used, such as `ContentType(pub Mime)`. - -pub use self::accept_charset::AcceptCharset; -pub use self::accept_encoding::AcceptEncoding; -pub use self::accept_language::AcceptLanguage; -pub use self::accept_ranges::{AcceptRanges, RangeUnit}; -pub use self::accept::Accept; -pub use self::access_control_allow_credentials::AccessControlAllowCredentials; -pub use self::access_control_allow_headers::AccessControlAllowHeaders; -pub use self::access_control_allow_methods::AccessControlAllowMethods; -pub use self::access_control_allow_origin::AccessControlAllowOrigin; -pub use self::access_control_expose_headers::AccessControlExposeHeaders; -pub use self::access_control_max_age::AccessControlMaxAge; -pub use self::access_control_request_headers::AccessControlRequestHeaders; -pub use self::access_control_request_method::AccessControlRequestMethod; -pub use self::allow::Allow; -pub use self::authorization::{Authorization, Scheme, Basic, Bearer}; -pub use self::cache_control::{CacheControl, CacheDirective}; -pub use self::connection::{Connection, ConnectionOption}; -pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam}; -pub use self::content_encoding::ContentEncoding; -pub use self::content_language::ContentLanguage; -pub use self::content_length::ContentLength; -pub use self::content_location::ContentLocation; -pub use self::content_range::{ContentRange, ContentRangeSpec}; -pub use self::content_type::ContentType; -pub use self::cookie::{Cookie, CookieIter}; -pub use self::date::Date; -pub use self::etag::ETag; -pub use self::expect::Expect; -pub use self::expires::Expires; -pub use self::from::From; -pub use self::host::Host; -pub use self::if_match::IfMatch; -pub use self::if_modified_since::IfModifiedSince; -pub use self::if_none_match::IfNoneMatch; -pub use self::if_range::IfRange; -pub use self::if_unmodified_since::IfUnmodifiedSince; -pub use self::last_event_id::LastEventId; -pub use self::last_modified::LastModified; -pub use self::link::{Link, LinkValue, RelationType, MediaDesc}; -pub use self::location::Location; -pub use self::origin::Origin; -pub use self::pragma::Pragma; -pub use self::prefer::{Prefer, Preference}; -pub use self::preference_applied::PreferenceApplied; -pub use self::proxy_authorization::ProxyAuthorization; -pub use self::range::{Range, ByteRangeSpec}; -pub use self::referer::Referer; -pub use self::referrer_policy::ReferrerPolicy; -pub use self::retry_after::RetryAfter; -pub use self::server::Server; -pub use self::set_cookie::SetCookie; -pub use self::strict_transport_security::StrictTransportSecurity; -pub use self::te::Te; -pub use self::transfer_encoding::TransferEncoding; -pub use self::upgrade::{Upgrade, Protocol, ProtocolName}; -pub use self::user_agent::UserAgent; -pub use self::vary::Vary; -pub use self::warning::Warning; - -#[doc(hidden)] -#[macro_export] -macro_rules! bench_header( - ($name:ident, $ty:ty, $value:expr) => { - #[cfg(test)] - #[cfg(feature = "nightly")] - mod $name { - use test::Bencher; - use super::*; - - use header::{Header}; - - #[bench] - fn bench_parse(b: &mut Bencher) { - let val = $value.into(); - b.iter(|| { - let _: $ty = Header::parse_header(&val).unwrap(); - }); - } - - #[bench] - fn bench_format(b: &mut Bencher) { - let raw = $value.into(); - let val: $ty = Header::parse_header(&raw).unwrap(); - b.iter(|| { - format!("{}", val); - }); - } - } - } -); - -#[doc(hidden)] -#[macro_export] -macro_rules! __hyper__deref { - ($from:ty => $to:ty) => { - impl ::std::ops::Deref for $from { - type Target = $to; - - #[inline] - fn deref(&self) -> &$to { - &self.0 - } - } - - impl ::std::ops::DerefMut for $from { - #[inline] - fn deref_mut(&mut self) -> &mut $to { - &mut self.0 - } - } - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __hyper__tm { - ($id:ident, $tm:ident{$($tf:item)*}) => { - #[allow(unused_imports)] - #[cfg(test)] - mod $tm{ - use std::str; - use $crate::header::*; - use $crate::mime::*; - use $crate::method::Method; - use super::$id as HeaderField; - $($tf)* - } - - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! test_header { - ($id:ident, $raw:expr) => { - #[test] - fn $id() { - #[allow(unused)] - use std::ascii::AsciiExt; - let raw = $raw; - let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); - let a = a.into(); - let value = HeaderField::parse_header(&a); - let result = format!("{}", value.unwrap()); - let expected = String::from_utf8(raw[0].to_vec()).unwrap(); - let result_cmp: Vec = result - .to_ascii_lowercase() - .split(' ') - .map(|x| x.to_owned()) - .collect(); - let expected_cmp: Vec = expected - .to_ascii_lowercase() - .split(' ') - .map(|x| x.to_owned()) - .collect(); - assert_eq!(result_cmp.concat(), expected_cmp.concat()); - } - }; - ($id:ident, $raw:expr, $typed:expr) => { - #[test] - fn $id() { - let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); - let a = a.into(); - let val = HeaderField::parse_header(&a); - let typed: Option = $typed; - // Test parsing - assert_eq!(val.ok(), typed); - // Test formatting - if typed.is_some() { - let raw = &($raw)[..]; - let mut iter = raw.iter().map(|b|str::from_utf8(&b[..]).unwrap()); - let mut joined = String::new(); - joined.push_str(iter.next().unwrap()); - for s in iter { - joined.push_str(", "); - joined.push_str(s); - } - assert_eq!(format!("{}", typed.unwrap()), joined); - } - } - } -} - -#[macro_export] -macro_rules! header { - // $a:meta: Attributes associated with the header item (usually docs) - // $id:ident: Identifier of the header - // $n:expr: Lowercase name of the header - // $nn:expr: Nice name of the header - - // List header, zero or more items - ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)*) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub Vec<$item>); - __hyper__deref!($id => Vec<$item>); - impl $crate::header::Header for $id { - fn header_name() -> &'static str { - static NAME: &'static str = $n; - NAME - } - #[inline] - fn parse_header(raw: &$crate::header::Raw) -> $crate::Result { - $crate::header::parsing::from_comma_delimited(raw).map($id) - } - #[inline] - fn fmt_header(&self, f: &mut $crate::header::Formatter) -> ::std::fmt::Result { - f.fmt_line(self) - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - $crate::header::parsing::fmt_comma_delimited(f, &self.0[..]) - } - } - }; - // List header, one or more items - ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub Vec<$item>); - __hyper__deref!($id => Vec<$item>); - impl $crate::header::Header for $id { - #[inline] - fn header_name() -> &'static str { - static NAME: &'static str = $n; - NAME - } - #[inline] - fn parse_header(raw: &$crate::header::Raw) -> $crate::Result { - $crate::header::parsing::from_comma_delimited(raw).map($id) - } - #[inline] - fn fmt_header(&self, f: &mut $crate::header::Formatter) -> ::std::fmt::Result { - f.fmt_line(self) - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - $crate::header::parsing::fmt_comma_delimited(f, &self.0[..]) - } - } - }; - // Single value header - ($(#[$a:meta])*($id:ident, $n:expr) => [$value:ty]) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub $value); - __hyper__deref!($id => $value); - impl $crate::header::Header for $id { - #[inline] - fn header_name() -> &'static str { - static NAME: &'static str = $n; - NAME - } - #[inline] - fn parse_header(raw: &$crate::header::Raw) -> $crate::Result { - $crate::header::parsing::from_one_raw_str(raw).map($id) - } - #[inline] - fn fmt_header(&self, f: &mut $crate::header::Formatter) -> ::std::fmt::Result { - f.fmt_line(self) - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - ::std::fmt::Display::fmt(&self.0, f) - } - } - }; - // Single value header (internal) - ($(#[$a:meta])*($id:ident, $n:expr) => danger [$value:ty]) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub $value); - __hyper__deref!($id => $value); - impl $crate::header::Header for $id { - #[inline] - fn header_name() -> &'static str { - static NAME: &'static str = $n; - NAME - } - #[inline] - fn parse_header(raw: &$crate::header::Raw) -> $crate::Result { - $crate::header::parsing::from_one_raw_str(raw).map($id) - } - #[inline] - fn fmt_header(&self, f: &mut $crate::header::Formatter) -> ::std::fmt::Result { - f.danger_fmt_line_without_newline_replacer(self) - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - ::std::fmt::Display::fmt(&self.0, f) - } - } - }; - // Single value cow header - ($(#[$a:meta])*($id:ident, $n:expr) => Cow[$value:ty]) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(::std::borrow::Cow<'static,$value>); - impl $id { - /// Creates a new $id - pub fn new>>(value: I) -> Self { - $id(value.into()) - } - } - impl ::std::ops::Deref for $id { - type Target = $value; - #[inline] - fn deref(&self) -> &Self::Target { - &(self.0) - } - } - impl $crate::header::Header for $id { - #[inline] - fn header_name() -> &'static str { - static NAME: &'static str = $n; - NAME - } - #[inline] - fn parse_header(raw: &$crate::header::Raw) -> $crate::Result { - $crate::header::parsing::from_one_raw_str::<<$value as ::std::borrow::ToOwned>::Owned>(raw).map($id::new) - } - #[inline] - fn fmt_header(&self, f: &mut $crate::header::Formatter) -> ::std::fmt::Result { - f.fmt_line(self) - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - ::std::fmt::Display::fmt(&self.0, f) - } - } - }; - // List header, one or more items with "*" option - ($(#[$a:meta])*($id:ident, $n:expr) => {Any / ($item:ty)+}) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub enum $id { - /// Any value is a match - Any, - /// Only the listed items are a match - Items(Vec<$item>), - } - impl $crate::header::Header for $id { - #[inline] - fn header_name() -> &'static str { - static NAME: &'static str = $n; - NAME - } - #[inline] - fn parse_header(raw: &$crate::header::Raw) -> $crate::Result { - // FIXME: Return None if no item is in $id::Only - if raw.len() == 1 { - if &raw[0] == b"*" { - return Ok($id::Any) - } - } - $crate::header::parsing::from_comma_delimited(raw).map($id::Items) - } - #[inline] - fn fmt_header(&self, f: &mut $crate::header::Formatter) -> ::std::fmt::Result { - f.fmt_line(self) - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - match *self { - $id::Any => f.write_str("*"), - $id::Items(ref fields) => $crate::header::parsing::fmt_comma_delimited( - f, &fields[..]) - } - } - } - }; - - // optional test module - ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)* $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $n) => ($item)* - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+ $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $n) => ($item)+ - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $n:expr) => [$item:ty] $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $n) => [$item] - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $n:expr) => danger [$item:ty] $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $n) => danger [$item] - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $n:expr) => Cow[$item:ty] $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $n) => Cow[$item] - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $n:expr) => {Any / ($item:ty)+} $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $n) => {Any / ($item)+} - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; -} - - -mod accept_charset; -mod accept_encoding; -mod accept_language; -mod accept_ranges; -mod accept; -mod access_control_allow_credentials; -mod access_control_allow_headers; -mod access_control_allow_methods; -mod access_control_allow_origin; -mod access_control_expose_headers; -mod access_control_max_age; -mod access_control_request_headers; -mod access_control_request_method; -mod allow; -mod authorization; -mod cache_control; -mod connection; -mod content_disposition; -mod content_encoding; -mod content_language; -mod content_length; -mod content_location; -mod content_range; -mod content_type; -mod cookie; -mod date; -mod etag; -mod expect; -mod expires; -mod from; -mod host; -mod if_match; -mod if_modified_since; -mod if_none_match; -mod if_range; -mod if_unmodified_since; -mod last_event_id; -mod last_modified; -mod link; -mod location; -mod origin; -mod pragma; -mod prefer; -mod preference_applied; -mod proxy_authorization; -mod range; -mod referer; -mod referrer_policy; -mod retry_after; -mod server; -mod set_cookie; -mod strict_transport_security; -mod te; -mod transfer_encoding; -mod upgrade; -mod user_agent; -mod vary; -mod warning; diff --git a/src/header/common/origin.rs b/src/header/common/origin.rs deleted file mode 100644 index 4320d38a91..0000000000 --- a/src/header/common/origin.rs +++ /dev/null @@ -1,181 +0,0 @@ -use header::{Header, Raw, Host}; -use std::borrow::Cow; -use std::fmt; -use std::str::FromStr; -use header::parsing::from_one_raw_str; - -/// The `Origin` header. -/// -/// The `Origin` header is a version of the `Referer` header that is used for all HTTP fetches and `POST`s whose CORS flag is set. -/// This header is often used to inform recipients of the security context of where the request was initiated. -/// -/// Following the spec, [https://fetch.spec.whatwg.org/#origin-header][url], the value of this header is composed of -/// a String (scheme), header::Host (host/port) -/// -/// [url]: https://fetch.spec.whatwg.org/#origin-header -/// -/// # Examples -/// -/// ``` -/// use hyper::header::{Headers, Origin}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// Origin::new("http", "hyper.rs", None) -/// ); -/// ``` -/// -/// ``` -/// use hyper::header::{Headers, Origin}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// Origin::new("https", "wikipedia.org", Some(443)) -/// ); -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub struct Origin(OriginOrNull); - -#[derive(PartialEq, Clone, Debug)] -enum OriginOrNull { - Origin { - /// The scheme, such as http or https - scheme: Cow<'static,str>, - /// The host, such as Host{hostname: "hyper.rs".to_owned(), port: None} - host: Host, - }, - Null, -} - -impl Origin { - /// Creates a new `Origin` header. - pub fn new>, H: Into>>(scheme: S, hostname: H, port: Option) -> Origin{ - Origin(OriginOrNull::Origin { - scheme: scheme.into(), - host: Host::new(hostname, port), - }) - } - - /// Creates a `Null` `Origin` header. - pub fn null() -> Origin { - Origin(OriginOrNull::Null) - } - - /// Checks if `Origin` is `Null`. - pub fn is_null(&self) -> bool { - match self { - &Origin(OriginOrNull::Null) => true, - _ => false, - } - } - - /// The scheme, such as http or https. - /// - /// ``` - /// use hyper::header::Origin; - /// let origin = Origin::new("https", "foo.com", Some(443)); - /// assert_eq!(origin.scheme(), Some("https")); - /// ``` - pub fn scheme(&self) -> Option<&str> { - match self { - &Origin(OriginOrNull::Origin { ref scheme, .. }) => Some(&scheme), - _ => None, - } - } - - /// The host, such as `Host { hostname: "hyper.rs".to_owned(), port: None}`. - /// - /// ``` - /// use hyper::header::{Origin,Host}; - /// let origin = Origin::new("https", "foo.com", Some(443)); - /// assert_eq!(origin.host(), Some(&Host::new("foo.com", Some(443)))); - /// ``` - pub fn host(&self) -> Option<&Host> { - match self { - &Origin(OriginOrNull::Origin { ref host, .. }) => Some(&host), - _ => None, - } - } -} - -impl Header for Origin { - fn header_name() -> &'static str { - static NAME: &'static str = "Origin"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - from_one_raw_str(raw) - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -static HTTP : &'static str = "http"; -static HTTPS : &'static str = "https"; - -impl FromStr for Origin { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - let idx = match s.find("://") { - Some(idx) => idx, - None => return Err(::Error::Header) - }; - // idx + 3 because that's how long "://" is - let (scheme, etc) = (&s[..idx], &s[idx + 3..]); - let host = try!(Host::from_str(etc)); - let scheme = match scheme { - "http" => Cow::Borrowed(HTTP), - "https" => Cow::Borrowed(HTTPS), - s => Cow::Owned(s.to_owned()) - }; - - Ok(Origin(OriginOrNull::Origin { - scheme: scheme, - host: host - })) - } -} - -impl fmt::Display for Origin { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.0 { - OriginOrNull::Origin { ref scheme, ref host } => write!(f, "{}://{}", scheme, host), - // Serialized as "null" per ASCII serialization of an origin - // https://html.spec.whatwg.org/multipage/browsers.html#ascii-serialisation-of-an-origin - OriginOrNull::Null => f.write_str("null") - } - } -} - -#[cfg(test)] -mod tests { - use super::Origin; - use header::Header; - use std::borrow::Cow; - - macro_rules! assert_borrowed{ - ($expr : expr) => { - match $expr { - Cow::Owned(ref v) => panic!("assertion failed: `{}` owns {:?}", stringify!($expr), v), - _ => {} - } - } - } - - #[test] - fn test_origin() { - let origin : Origin = Header::parse_header(&vec![b"http://foo.com".to_vec()].into()).unwrap(); - assert_eq!(&origin, &Origin::new("http", "foo.com", None)); - assert_borrowed!(origin.scheme().unwrap().into()); - - let origin : Origin = Header::parse_header(&vec![b"https://foo.com:443".to_vec()].into()).unwrap(); - assert_eq!(&origin, &Origin::new("https", "foo.com", Some(443))); - assert_borrowed!(origin.scheme().unwrap().into()); - } -} - -bench_header!(bench, Origin, { vec![b"https://foo.com".to_vec()] }); diff --git a/src/header/common/pragma.rs b/src/header/common/pragma.rs deleted file mode 100644 index a6dcd63d01..0000000000 --- a/src/header/common/pragma.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::fmt; -#[allow(unused)] -use std::ascii::AsciiExt; - -use header::{Header, Raw, parsing}; - -/// The `Pragma` header defined by HTTP/1.0. -/// -/// > The "Pragma" header field allows backwards compatibility with -/// > HTTP/1.0 caches, so that clients can specify a "no-cache" request -/// > that they will understand (as Cache-Control was not defined until -/// > HTTP/1.1). When the Cache-Control header field is also present and -/// > understood in a request, Pragma is ignored. -/// > In HTTP/1.0, Pragma was defined as an extensible field for -/// > implementation-specified directives for recipients. This -/// > specification deprecates such extensions to improve interoperability. -/// -/// Spec: [https://tools.ietf.org/html/rfc7234#section-5.4][url] -/// -/// [url]: https://tools.ietf.org/html/rfc7234#section-5.4 -/// -/// # Examples -/// -/// ``` -/// use hyper::header::{Headers, Pragma}; -/// -/// let mut headers = Headers::new(); -/// headers.set(Pragma::NoCache); -/// ``` -/// -/// ``` -/// use hyper::header::{Headers, Pragma}; -/// -/// let mut headers = Headers::new(); -/// headers.set(Pragma::Ext("foobar".to_owned())); -/// ``` -#[derive(Clone, PartialEq, Debug)] -pub enum Pragma { - /// Corresponds to the `no-cache` value. - NoCache, - /// Every value other than `no-cache`. - Ext(String), -} - -impl Header for Pragma { - fn header_name() -> &'static str { - static NAME: &'static str = "Pragma"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - parsing::from_one_raw_str(raw).and_then(|s: String| { - let slice = &s.to_ascii_lowercase()[..]; - match slice { - "no-cache" => Ok(Pragma::NoCache), - _ => Ok(Pragma::Ext(s)), - } - }) - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -impl fmt::Display for Pragma { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match *self { - Pragma::NoCache => "no-cache", - Pragma::Ext(ref string) => &string[..], - }) - } -} - -#[test] -fn test_parse_header() { - let a: Pragma = Header::parse_header(&"no-cache".into()).unwrap(); - let b = Pragma::NoCache; - assert_eq!(a, b); - let c: Pragma = Header::parse_header(&"FoObar".into()).unwrap(); - let d = Pragma::Ext("FoObar".to_owned()); - assert_eq!(c, d); - let e: ::Result = Header::parse_header(&"".into()); - assert_eq!(e.ok(), None); -} diff --git a/src/header/common/prefer.rs b/src/header/common/prefer.rs deleted file mode 100644 index 0f0675a471..0000000000 --- a/src/header/common/prefer.rs +++ /dev/null @@ -1,210 +0,0 @@ -use std::fmt; -use std::str::FromStr; -use header::{Header, Raw}; -use header::parsing::{from_comma_delimited, fmt_comma_delimited}; - -/// `Prefer` header, defined in [RFC7240](http://tools.ietf.org/html/rfc7240) -/// -/// The `Prefer` header field can be used by a client to request that certain -/// behaviors be employed by a server while processing a request. -/// -/// # ABNF -/// -/// ```text -/// Prefer = "Prefer" ":" 1#preference -/// preference = token [ BWS "=" BWS word ] -/// *( OWS ";" [ OWS parameter ] ) -/// parameter = token [ BWS "=" BWS word ] -/// ``` -/// -/// # Example values -/// * `respond-async` -/// * `return=minimal` -/// * `wait=30` -/// -/// # Examples -/// -/// ``` -/// use hyper::header::{Headers, Prefer, Preference}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// Prefer(vec![Preference::RespondAsync]) -/// ); -/// ``` -/// -/// ``` -/// use hyper::header::{Headers, Prefer, Preference}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// Prefer(vec![ -/// Preference::RespondAsync, -/// Preference::ReturnRepresentation, -/// Preference::Wait(10u32), -/// Preference::Extension("foo".to_owned(), -/// "bar".to_owned(), -/// vec![]), -/// ]) -/// ); -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub struct Prefer(pub Vec); - -__hyper__deref!(Prefer => Vec); - -impl Header for Prefer { - fn header_name() -> &'static str { - static NAME: &'static str = "Prefer"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - let preferences = try!(from_comma_delimited(raw)); - if !preferences.is_empty() { - Ok(Prefer(preferences)) - } else { - Err(::Error::Header) - } - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -impl fmt::Display for Prefer { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt_comma_delimited(f, &self[..]) - } -} - -/// Prefer contains a list of these preferences. -#[derive(PartialEq, Clone, Debug)] -pub enum Preference { - /// "respond-async" - RespondAsync, - /// "return=representation" - ReturnRepresentation, - /// "return=minimal" - ReturnMinimal, - /// "handling=strict" - HandlingStrict, - /// "handling=lenient" - HandlingLenient, - /// "wait=delta" - Wait(u32), - - /// Extension preferences. Always has a value, if none is specified it is - /// just "". A preference can also have a list of parameters. - Extension(String, String, Vec<(String, String)>) -} - -impl fmt::Display for Preference { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use self::Preference::*; - fmt::Display::fmt(match *self { - RespondAsync => "respond-async", - ReturnRepresentation => "return=representation", - ReturnMinimal => "return=minimal", - HandlingStrict => "handling=strict", - HandlingLenient => "handling=lenient", - - Wait(secs) => return write!(f, "wait={}", secs), - - Extension(ref name, ref value, ref params) => { - try!(write!(f, "{}", name)); - if value != "" { try!(write!(f, "={}", value)); } - if !params.is_empty() { - for &(ref name, ref value) in params { - try!(write!(f, "; {}", name)); - if value != "" { try!(write!(f, "={}", value)); } - } - } - return Ok(()); - } - }, f) - } -} - -impl FromStr for Preference { - type Err = Option<::Err>; - fn from_str(s: &str) -> Result::Err>> { - use self::Preference::*; - let mut params = s.split(';').map(|p| { - let mut param = p.splitn(2, '='); - match (param.next(), param.next()) { - (Some(name), Some(value)) => (name.trim(), value.trim().trim_matches('"')), - (Some(name), None) => (name.trim(), ""), - // This can safely be unreachable because the [`splitn`][1] - // function (used above) will always have at least one value. - // - // [1]: http://doc.rust-lang.org/std/primitive.str.html#method.splitn - _ => { unreachable!(); } - } - }); - match params.nth(0) { - Some(param) => { - let rest: Vec<(String, String)> = params.map(|(l, r)| (l.to_owned(), r.to_owned())).collect(); - match param { - ("respond-async", "") => if rest.is_empty() { Ok(RespondAsync) } else { Err(None) }, - ("return", "representation") => if rest.is_empty() { Ok(ReturnRepresentation) } else { Err(None) }, - ("return", "minimal") => if rest.is_empty() { Ok(ReturnMinimal) } else { Err(None) }, - ("handling", "strict") => if rest.is_empty() { Ok(HandlingStrict) } else { Err(None) }, - ("handling", "lenient") => if rest.is_empty() { Ok(HandlingLenient) } else { Err(None) }, - ("wait", secs) => if rest.is_empty() { secs.parse().map(Wait).map_err(Some) } else { Err(None) }, - (left, right) => Ok(Extension(left.to_owned(), right.to_owned(), rest)) - } - }, - None => Err(None) - } - } -} - -#[cfg(test)] -mod tests { - use header::Header; - use super::*; - - #[test] - fn test_parse_multiple_headers() { - let prefer = Header::parse_header(&"respond-async, return=representation".into()); - assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::RespondAsync, - Preference::ReturnRepresentation]))) - } - - #[test] - fn test_parse_argument() { - let prefer = Header::parse_header(&"wait=100, handling=lenient, respond-async".into()); - assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::Wait(100), - Preference::HandlingLenient, - Preference::RespondAsync]))) - } - - #[test] - fn test_parse_quote_form() { - let prefer = Header::parse_header(&"wait=\"200\", handling=\"strict\"".into()); - assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::Wait(200), - Preference::HandlingStrict]))) - } - - #[test] - fn test_parse_extension() { - let prefer = Header::parse_header(&"foo, bar=baz, baz; foo; bar=baz, bux=\"\"; foo=\"\", buz=\"some parameter\"".into()); - assert_eq!(prefer.ok(), Some(Prefer(vec![ - Preference::Extension("foo".to_owned(), "".to_owned(), vec![]), - Preference::Extension("bar".to_owned(), "baz".to_owned(), vec![]), - Preference::Extension("baz".to_owned(), "".to_owned(), vec![("foo".to_owned(), "".to_owned()), ("bar".to_owned(), "baz".to_owned())]), - Preference::Extension("bux".to_owned(), "".to_owned(), vec![("foo".to_owned(), "".to_owned())]), - Preference::Extension("buz".to_owned(), "some parameter".to_owned(), vec![])]))) - } - - #[test] - fn test_fail_with_args() { - let prefer: ::Result = Header::parse_header(&"respond-async; foo=bar".into()); - assert_eq!(prefer.ok(), None); - } -} - -bench_header!(normal, - Prefer, { vec![b"respond-async, return=representation".to_vec(), b"wait=100".to_vec()] }); diff --git a/src/header/common/preference_applied.rs b/src/header/common/preference_applied.rs deleted file mode 100644 index 409d027799..0000000000 --- a/src/header/common/preference_applied.rs +++ /dev/null @@ -1,110 +0,0 @@ -use std::fmt; -use header::{Header, Raw, Preference}; -use header::parsing::{from_comma_delimited, fmt_comma_delimited}; - -/// `Preference-Applied` header, defined in [RFC7240](http://tools.ietf.org/html/rfc7240) -/// -/// The `Preference-Applied` response header may be included within a -/// response message as an indication as to which `Prefer` header tokens were -/// honored by the server and applied to the processing of a request. -/// -/// # ABNF -/// -/// ```text -/// Preference-Applied = "Preference-Applied" ":" 1#applied-pref -/// applied-pref = token [ BWS "=" BWS word ] -/// ``` -/// -/// # Example values -/// -/// * `respond-async` -/// * `return=minimal` -/// * `wait=30` -/// -/// # Examples -/// -/// ``` -/// use hyper::header::{Headers, PreferenceApplied, Preference}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// PreferenceApplied(vec![Preference::RespondAsync]) -/// ); -/// ``` -/// -/// ``` -/// use hyper::header::{Headers, PreferenceApplied, Preference}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// PreferenceApplied(vec![ -/// Preference::RespondAsync, -/// Preference::ReturnRepresentation, -/// Preference::Wait(10u32), -/// Preference::Extension("foo".to_owned(), -/// "bar".to_owned(), -/// vec![]), -/// ]) -/// ); -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub struct PreferenceApplied(pub Vec); - -__hyper__deref!(PreferenceApplied => Vec); - -impl Header for PreferenceApplied { - fn header_name() -> &'static str { - static NAME: &'static str = "Preference-Applied"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - let preferences = try!(from_comma_delimited(raw)); - if !preferences.is_empty() { - Ok(PreferenceApplied(preferences)) - } else { - Err(::Error::Header) - } - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -impl fmt::Display for PreferenceApplied { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - //TODO: format this without allocating a Vec and cloning contents - let preferences: Vec<_> = self.0.iter().map(|pref| match pref { - // The spec ignores parameters in `Preferences-Applied` - &Preference::Extension(ref name, ref value, _) => Preference::Extension( - name.to_owned(), - value.to_owned(), - vec![] - ), - preference => preference.clone() - }).collect(); - fmt_comma_delimited(f, &preferences) - } -} - -#[cfg(test)] -mod tests { - use header::Preference; - use super::*; - - #[test] - fn test_format_ignore_parameters() { - assert_eq!( - format!("{}", PreferenceApplied(vec![Preference::Extension( - "foo".to_owned(), - "bar".to_owned(), - vec![("bar".to_owned(), "foo".to_owned()), ("buz".to_owned(), "".to_owned())] - )])), - "foo=bar".to_owned() - ); - } -} - -bench_header!(normal, - PreferenceApplied, { vec![b"respond-async, return=representation".to_vec(), b"wait=100".to_vec()] }); diff --git a/src/header/common/proxy_authorization.rs b/src/header/common/proxy_authorization.rs deleted file mode 100644 index fe64b30ec9..0000000000 --- a/src/header/common/proxy_authorization.rs +++ /dev/null @@ -1,198 +0,0 @@ -use std::any::Any; -use std::fmt; -use std::str::{FromStr, from_utf8}; -use std::ops::{Deref, DerefMut}; -use header::{Header, Raw, Scheme}; - -/// `Proxy-Authorization` header, defined in [RFC7235](https://tools.ietf.org/html/rfc7235#section-4.4) -/// -/// The `Proxy-Authorization` header field allows a user agent to authenticate -/// itself with an HTTP proxy -- usually, but not necessarily, after -/// receiving a 407 (Proxy Authentication Required) response and the -/// `Proxy-Authenticate` header. Its value consists of credentials containing -/// the authentication information of the user agent for the realm of the -/// resource being requested. -/// -/// # ABNF -/// -/// ```text -/// Authorization = credentials -/// ``` -/// -/// # Example values -/// * `Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==` -/// * `Bearer fpKL54jvWmEGVoRdCNjG` -/// -/// # Examples -/// -/// ``` -/// use hyper::header::{Headers, ProxyAuthorization}; -/// -/// let mut headers = Headers::new(); -/// headers.set(ProxyAuthorization("let me in".to_owned())); -/// ``` -/// ``` -/// use hyper::header::{Headers, ProxyAuthorization, Basic}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// ProxyAuthorization( -/// Basic { -/// username: "Aladdin".to_owned(), -/// password: Some("open sesame".to_owned()) -/// } -/// ) -/// ); -/// ``` -/// -/// ``` -/// use hyper::header::{Headers, ProxyAuthorization, Bearer}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// ProxyAuthorization( -/// Bearer { -/// token: "QWxhZGRpbjpvcGVuIHNlc2FtZQ".to_owned() -/// } -/// ) -/// ); -/// ``` -#[derive(Clone, PartialEq, Debug)] -pub struct ProxyAuthorization(pub S); - -impl Deref for ProxyAuthorization { - type Target = S; - - fn deref(&self) -> &S { - &self.0 - } -} - -impl DerefMut for ProxyAuthorization { - fn deref_mut(&mut self) -> &mut S { - &mut self.0 - } -} - -impl Header for ProxyAuthorization where ::Err: 'static { - fn header_name() -> &'static str { - static NAME: &'static str = "Proxy-Authorization"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result> { - if let Some(line) = raw.one() { - let header = try!(from_utf8(line)); - if let Some(scheme) = ::scheme() { - if header.starts_with(scheme) && header.len() > scheme.len() + 1 { - match header[scheme.len() + 1..].parse::().map(ProxyAuthorization) { - Ok(h) => Ok(h), - Err(_) => Err(::Error::Header) - } - } else { - Err(::Error::Header) - } - } else { - match header.parse::().map(ProxyAuthorization) { - Ok(h) => Ok(h), - Err(_) => Err(::Error::Header) - } - } - } else { - Err(::Error::Header) - } - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -impl fmt::Display for ProxyAuthorization { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(scheme) = ::scheme() { - try!(write!(f, "{} ", scheme)) - }; - self.0.fmt_scheme(f) - } -} - -#[cfg(test)] -mod tests { - use super::ProxyAuthorization; - use super::super::super::{Headers, Header, Basic, Bearer}; - - #[test] - fn test_raw_auth() { - let mut headers = Headers::new(); - headers.set(ProxyAuthorization("foo bar baz".to_owned())); - assert_eq!(headers.to_string(), "Proxy-Authorization: foo bar baz\r\n".to_owned()); - } - - #[test] - fn test_raw_auth_parse() { - let header: ProxyAuthorization = Header::parse_header(&b"foo bar baz".as_ref().into()).unwrap(); - assert_eq!(header.0, "foo bar baz"); - } - - #[test] - fn test_basic_auth() { - let mut headers = Headers::new(); - headers.set(ProxyAuthorization( - Basic { username: "Aladdin".to_owned(), password: Some("open sesame".to_owned()) })); - assert_eq!( - headers.to_string(), - "Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n".to_owned()); - } - - #[test] - fn test_basic_auth_no_password() { - let mut headers = Headers::new(); - headers.set(ProxyAuthorization(Basic { username: "Aladdin".to_owned(), password: None })); - assert_eq!(headers.to_string(), "Proxy-Authorization: Basic QWxhZGRpbjo=\r\n".to_owned()); - } - - #[test] - fn test_basic_auth_parse() { - let auth: ProxyAuthorization = Header::parse_header( - &b"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==".as_ref().into()).unwrap(); - assert_eq!(auth.0.username, "Aladdin"); - assert_eq!(auth.0.password, Some("open sesame".to_owned())); - } - - #[test] - fn test_basic_auth_parse_no_password() { - let auth: ProxyAuthorization = Header::parse_header( - &b"Basic QWxhZGRpbjo=".as_ref().into()).unwrap(); - assert_eq!(auth.0.username, "Aladdin"); - assert_eq!(auth.0.password, Some("".to_owned())); - } - - #[test] - fn test_bearer_auth() { - let mut headers = Headers::new(); - headers.set(ProxyAuthorization( - Bearer { token: "fpKL54jvWmEGVoRdCNjG".to_owned() })); - assert_eq!( - headers.to_string(), - "Proxy-Authorization: Bearer fpKL54jvWmEGVoRdCNjG\r\n".to_owned()); - } - - #[test] - fn test_bearer_auth_parse() { - let auth: ProxyAuthorization = Header::parse_header( - &b"Bearer fpKL54jvWmEGVoRdCNjG".as_ref().into()).unwrap(); - assert_eq!(auth.0.token, "fpKL54jvWmEGVoRdCNjG"); - } -} - -#[cfg(test)] -#[cfg(feature = "nightly")] -mod benches { - use super::ProxyAuthorization; - use ::header::{Basic, Bearer}; - - bench_header!(raw, ProxyAuthorization, { vec![b"foo bar baz".to_vec()] }); - bench_header!(basic, ProxyAuthorization, { vec![b"Basic QWxhZGRpbjpuIHNlc2FtZQ==".to_vec()] }); - bench_header!(bearer, ProxyAuthorization, { vec![b"Bearer fpKL54jvWmEGVoRdCNjG".to_vec()] }); -} diff --git a/src/header/common/range.rs b/src/header/common/range.rs deleted file mode 100644 index d90f755a28..0000000000 --- a/src/header/common/range.rs +++ /dev/null @@ -1,389 +0,0 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; - -use header::{Header, Raw}; -use header::parsing::{from_one_raw_str}; - -/// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1) -/// -/// The "Range" header field on a GET request modifies the method -/// semantics to request transfer of only one or more subranges of the -/// selected representation data, rather than the entire selected -/// representation data. -/// -/// # ABNF -/// -/// ```text -/// Range = byte-ranges-specifier / other-ranges-specifier -/// other-ranges-specifier = other-range-unit "=" other-range-set -/// other-range-set = 1*VCHAR -/// -/// bytes-unit = "bytes" -/// -/// byte-ranges-specifier = bytes-unit "=" byte-range-set -/// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec) -/// byte-range-spec = first-byte-pos "-" [last-byte-pos] -/// first-byte-pos = 1*DIGIT -/// last-byte-pos = 1*DIGIT -/// ``` -/// -/// # Example values -/// -/// * `bytes=1000-` -/// * `bytes=-2000` -/// * `bytes=0-1,30-40` -/// * `bytes=0-10,20-90,-100` -/// * `custom_unit=0-123` -/// * `custom_unit=xxx-yyy` -/// -/// # Examples -/// -/// ``` -/// use hyper::header::{Headers, Range, ByteRangeSpec}; -/// -/// let mut headers = Headers::new(); -/// headers.set(Range::Bytes( -/// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)] -/// )); -/// -/// headers.clear(); -/// headers.set(Range::Unregistered("letters".to_owned(), "a-f".to_owned())); -/// ``` -/// -/// ``` -/// use hyper::header::{Headers, Range}; -/// -/// let mut headers = Headers::new(); -/// headers.set(Range::bytes(1, 100)); -/// -/// headers.clear(); -/// headers.set(Range::bytes_multi(vec![(1, 100), (200, 300)])); -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub enum Range { - /// Byte range - Bytes(Vec), - /// Custom range, with unit not registered at IANA - /// (`other-range-unit`: String , `other-range-set`: String) - Unregistered(String, String) -} - -/// Each `Range::Bytes` header can contain one or more `ByteRangeSpecs`. -/// Each `ByteRangeSpec` defines a range of bytes to fetch -#[derive(PartialEq, Clone, Debug)] -pub enum ByteRangeSpec { - /// Get all bytes between x and y ("x-y") - FromTo(u64, u64), - /// Get all bytes starting from x ("x-") - AllFrom(u64), - /// Get last x bytes ("-x") - Last(u64) -} - -impl ByteRangeSpec { - /// Given the full length of the entity, attempt to normalize the byte range - /// into an satisfiable end-inclusive (from, to) range. - /// - /// The resulting range is guaranteed to be a satisfiable range within the bounds - /// of `0 <= from <= to < full_length`. - /// - /// If the byte range is deemed unsatisfiable, `None` is returned. - /// An unsatisfiable range is generally cause for a server to either reject - /// the client request with a `416 Range Not Satisfiable` status code, or to - /// simply ignore the range header and serve the full entity using a `200 OK` - /// status code. - /// - /// This function closely follows [RFC 7233][1] section 2.1. - /// As such, it considers ranges to be satisfiable if they meet the following - /// conditions: - /// - /// > If a valid byte-range-set includes at least one byte-range-spec with - /// a first-byte-pos that is less than the current length of the - /// representation, or at least one suffix-byte-range-spec with a - /// non-zero suffix-length, then the byte-range-set is satisfiable. - /// Otherwise, the byte-range-set is unsatisfiable. - /// - /// The function also computes remainder ranges based on the RFC: - /// - /// > If the last-byte-pos value is - /// absent, or if the value is greater than or equal to the current - /// length of the representation data, the byte range is interpreted as - /// the remainder of the representation (i.e., the server replaces the - /// value of last-byte-pos with a value that is one less than the current - /// length of the selected representation). - /// - /// [1]: https://tools.ietf.org/html/rfc7233 - pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> { - // If the full length is zero, there is no satisfiable end-inclusive range. - if full_length == 0 { - return None; - } - match self { - &ByteRangeSpec::FromTo(from, to) => { - if from < full_length && from <= to { - Some((from, ::std::cmp::min(to, full_length - 1))) - } else { - None - } - }, - &ByteRangeSpec::AllFrom(from) => { - if from < full_length { - Some((from, full_length - 1)) - } else { - None - } - }, - &ByteRangeSpec::Last(last) => { - if last > 0 { - // From the RFC: If the selected representation is shorter - // than the specified suffix-length, - // the entire representation is used. - if last > full_length { - Some((0, full_length - 1)) - } else { - Some((full_length - last, full_length - 1)) - } - } else { - None - } - } - } - } -} - -impl Range { - /// Get the most common byte range header ("bytes=from-to") - pub fn bytes(from: u64, to: u64) -> Range { - Range::Bytes(vec![ByteRangeSpec::FromTo(from, to)]) - } - - /// Get byte range header with multiple subranges - /// ("bytes=from1-to1,from2-to2,fromX-toX") - pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range { - Range::Bytes(ranges.iter().map(|r| ByteRangeSpec::FromTo(r.0, r.1)).collect()) - } -} - - -impl fmt::Display for ByteRangeSpec { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ByteRangeSpec::FromTo(from, to) => write!(f, "{}-{}", from, to), - ByteRangeSpec::Last(pos) => write!(f, "-{}", pos), - ByteRangeSpec::AllFrom(pos) => write!(f, "{}-", pos), - } - } -} - - -impl fmt::Display for Range { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Range::Bytes(ref ranges) => { - try!(write!(f, "bytes=")); - - for (i, range) in ranges.iter().enumerate() { - if i != 0 { - try!(f.write_str(",")); - } - try!(Display::fmt(range, f)); - } - Ok(()) - }, - Range::Unregistered(ref unit, ref range_str) => { - write!(f, "{}={}", unit, range_str) - }, - } - } -} - -impl FromStr for Range { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - let mut iter = s.splitn(2, '='); - - match (iter.next(), iter.next()) { - (Some("bytes"), Some(ranges)) => { - let ranges = from_comma_delimited(ranges); - if ranges.is_empty() { - return Err(::Error::Header); - } - Ok(Range::Bytes(ranges)) - } - (Some(unit), Some(range_str)) if unit != "" && range_str != "" => { - Ok(Range::Unregistered(unit.to_owned(), range_str.to_owned())) - - }, - _ => Err(::Error::Header) - } - } -} - -impl FromStr for ByteRangeSpec { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - let mut parts = s.splitn(2, '-'); - - match (parts.next(), parts.next()) { - (Some(""), Some(end)) => { - end.parse().or(Err(::Error::Header)).map(ByteRangeSpec::Last) - }, - (Some(start), Some("")) => { - start.parse().or(Err(::Error::Header)).map(ByteRangeSpec::AllFrom) - }, - (Some(start), Some(end)) => { - match (start.parse(), end.parse()) { - (Ok(start), Ok(end)) if start <= end => Ok(ByteRangeSpec::FromTo(start, end)), - _ => Err(::Error::Header) - } - }, - _ => Err(::Error::Header) - } - } -} - -fn from_comma_delimited(s: &str) -> Vec { - s.split(',') - .filter_map(|x| match x.trim() { - "" => None, - y => Some(y) - }) - .filter_map(|x| x.parse().ok()) - .collect() -} - -impl Header for Range { - - fn header_name() -> &'static str { - static NAME: &'static str = "Range"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - from_one_raw_str(raw) - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } - -} - -#[test] -fn test_parse_bytes_range_valid() { - let r: Range = Header::parse_header(&"bytes=1-100".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes=1-100,-".into()).unwrap(); - let r3 = Range::bytes(1, 100); - assert_eq!(r, r2); - assert_eq!(r2, r3); - - let r: Range = Header::parse_header(&"bytes=1-100,200-".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap(); - let r3 = Range::Bytes( - vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)] - ); - assert_eq!(r, r2); - assert_eq!(r2, r3); - - let r: Range = Header::parse_header(&"bytes=1-100,-100".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes=1-100, ,,-100".into()).unwrap(); - let r3 = Range::Bytes( - vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::Last(100)] - ); - assert_eq!(r, r2); - assert_eq!(r2, r3); - - let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); - assert_eq!(r, r2); - -} - -#[test] -fn test_parse_unregistered_range_valid() { - let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); - assert_eq!(r, r2); - - let r: Range = Header::parse_header(&"custom=abcd".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned()); - assert_eq!(r, r2); - - let r: Range = Header::parse_header(&"custom=xxx-yyy".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned()); - assert_eq!(r, r2); -} - -#[test] -fn test_parse_invalid() { - let r: ::Result = Header::parse_header(&"bytes=1-a,-".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"bytes=1-2-3".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"abc".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"bytes=1-100=".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"bytes=".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"custom=".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"=1-100".into()); - assert_eq!(r.ok(), None); -} - -#[test] -fn test_fmt() { - use header::Headers; - - let mut headers = Headers::new(); - - headers.set( - Range::Bytes( - vec![ByteRangeSpec::FromTo(0, 1000), ByteRangeSpec::AllFrom(2000)] - )); - assert_eq!(&headers.to_string(), "Range: bytes=0-1000,2000-\r\n"); - - headers.clear(); - headers.set(Range::Bytes(vec![])); - - assert_eq!(&headers.to_string(), "Range: bytes=\r\n"); - - headers.clear(); - headers.set(Range::Unregistered("custom".to_owned(), "1-xxx".to_owned())); - - assert_eq!(&headers.to_string(), "Range: custom=1-xxx\r\n"); -} - -#[test] -fn test_byte_range_spec_to_satisfiable_range() { - assert_eq!(Some((0, 0)), ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(3)); - assert_eq!(Some((1, 2)), ByteRangeSpec::FromTo(1, 2).to_satisfiable_range(3)); - assert_eq!(Some((1, 2)), ByteRangeSpec::FromTo(1, 5).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::FromTo(3, 3).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0)); - - assert_eq!(Some((0, 2)), ByteRangeSpec::AllFrom(0).to_satisfiable_range(3)); - assert_eq!(Some((2, 2)), ByteRangeSpec::AllFrom(2).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::AllFrom(3).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::AllFrom(5).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::AllFrom(0).to_satisfiable_range(0)); - - assert_eq!(Some((1, 2)), ByteRangeSpec::Last(2).to_satisfiable_range(3)); - assert_eq!(Some((2, 2)), ByteRangeSpec::Last(1).to_satisfiable_range(3)); - assert_eq!(Some((0, 2)), ByteRangeSpec::Last(5).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::Last(0).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::Last(2).to_satisfiable_range(0)); -} - -bench_header!(bytes_multi, Range, { vec![b"bytes=1-1001,2001-3001,10001-".to_vec()]}); -bench_header!(custom_unit, Range, { vec![b"other=0-100000".to_vec()]}); diff --git a/src/header/common/referer.rs b/src/header/common/referer.rs deleted file mode 100644 index 56e568bb97..0000000000 --- a/src/header/common/referer.rs +++ /dev/null @@ -1,45 +0,0 @@ -header! { - /// `Referer` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.5.2) - /// - /// The `Referer` [sic] header field allows the user agent to specify a - /// URI reference for the resource from which the target URI was obtained - /// (i.e., the "referrer", though the field name is misspelled). A user - /// agent MUST NOT include the fragment and userinfo components of the - /// URI reference, if any, when generating the Referer field value. - /// - /// # ABNF - /// - /// ```text - /// Referer = absolute-URI / partial-URI - /// ``` - /// - /// # Example values - /// - /// * `http://www.example.org/hypertext/Overview.html` - /// - /// # Examples - /// - /// ``` - /// use hyper::header::{Headers, Referer}; - /// - /// let mut headers = Headers::new(); - /// headers.set(Referer::new("/People.html#tim")); - /// ``` - /// - /// ``` - /// use hyper::header::{Headers, Referer}; - /// - /// let mut headers = Headers::new(); - /// headers.set(Referer::new("http://www.example.com/index.html")); - /// ``` - // TODO Use URL - (Referer, "Referer") => Cow[str] - - test_referer { - // Testcase from the RFC - test_header!(test1, vec![b"http://www.example.org/hypertext/Overview.html"]); - } -} - -bench_header!(bench, Referer, { vec![b"http://foo.com/hello:3000".to_vec()] }); diff --git a/src/header/common/referrer_policy.rs b/src/header/common/referrer_policy.rs deleted file mode 100644 index 520ec4c554..0000000000 --- a/src/header/common/referrer_policy.rs +++ /dev/null @@ -1,121 +0,0 @@ -use std::fmt; -#[allow(unused)] -use std::ascii::AsciiExt; - -use header::{Header, Raw, parsing}; - -/// `Referrer-Policy` header, part of -/// [Referrer Policy](https://www.w3.org/TR/referrer-policy/#referrer-policy-header) -/// -/// The `Referrer-Policy` HTTP header specifies the referrer -/// policy that the user agent applies when determining what -/// referrer information should be included with requests made, -/// and with browsing contexts created from the context of the -/// protected resource. -/// -/// # ABNF -/// -/// ```text -/// Referrer-Policy: 1#policy-token -/// policy-token = "no-referrer" / "no-referrer-when-downgrade" -/// / "same-origin" / "origin" -/// / "origin-when-cross-origin" / "unsafe-url" -/// ``` -/// -/// # Example values -/// -/// * `no-referrer` -/// -/// # Example -/// -/// ``` -/// use hyper::header::{Headers, ReferrerPolicy}; -/// -/// let mut headers = Headers::new(); -/// headers.set(ReferrerPolicy::NoReferrer); -/// ``` -#[derive(Clone, PartialEq, Eq, Debug)] -pub enum ReferrerPolicy { - /// `no-referrer` - NoReferrer, - /// `no-referrer-when-downgrade` - NoReferrerWhenDowngrade, - /// `same-origin` - SameOrigin, - /// `origin` - Origin, - /// `origin-when-cross-origin` - OriginWhenCrossOrigin, - /// `unsafe-url` - UnsafeUrl, - /// `strict-origin` - StrictOrigin, - ///`strict-origin-when-cross-origin` - StrictOriginWhenCrossOrigin, -} - -impl Header for ReferrerPolicy { - fn header_name() -> &'static str { - static NAME: &'static str = "Referrer-Policy"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - use self::ReferrerPolicy::*; - // See https://www.w3.org/TR/referrer-policy/#determine-policy-for-token - let headers: Vec = try!(parsing::from_comma_delimited(raw)); - - for h in headers.iter().rev() { - let slice = &h.to_ascii_lowercase()[..]; - match slice { - "no-referrer" | "never" => return Ok(NoReferrer), - "no-referrer-when-downgrade" | "default" => return Ok(NoReferrerWhenDowngrade), - "same-origin" => return Ok(SameOrigin), - "origin" => return Ok(Origin), - "origin-when-cross-origin" => return Ok(OriginWhenCrossOrigin), - "strict-origin" => return Ok(StrictOrigin), - "strict-origin-when-cross-origin" => return Ok(StrictOriginWhenCrossOrigin), - "unsafe-url" | "always" => return Ok(UnsafeUrl), - _ => continue, - } - } - - Err(::Error::Header) - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -impl fmt::Display for ReferrerPolicy { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use self::ReferrerPolicy::*; - f.write_str(match *self { - NoReferrer => "no-referrer", - NoReferrerWhenDowngrade => "no-referrer-when-downgrade", - SameOrigin => "same-origin", - Origin => "origin", - OriginWhenCrossOrigin => "origin-when-cross-origin", - StrictOrigin => "strict-origin", - StrictOriginWhenCrossOrigin => "strict-origin-when-cross-origin", - UnsafeUrl => "unsafe-url", - }) - } -} - -#[test] -fn test_parse_header() { - let a: ReferrerPolicy = Header::parse_header(&"origin".into()).unwrap(); - let b = ReferrerPolicy::Origin; - assert_eq!(a, b); - let e: ::Result = Header::parse_header(&"foobar".into()); - assert!(e.is_err()); -} - -#[test] -fn test_rightmost_header() { - let a: ReferrerPolicy = Header::parse_header(&"same-origin, origin, foobar".into()).unwrap(); - let b = ReferrerPolicy::Origin; - assert_eq!(a, b); -} diff --git a/src/header/common/retry_after.rs b/src/header/common/retry_after.rs deleted file mode 100644 index ddc2454904..0000000000 --- a/src/header/common/retry_after.rs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) 2016 retry-after Developers -// -// This file is dual licensed under MIT and Apache 2.0 -// -// ******************************************************* -// -// Permission is hereby granted, free of charge, to any -// person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the -// Software without restriction, including without -// limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software -// is furnished to do so, subject to the following -// -// conditions: -// -// The above copyright notice and this permission notice -// shall be included in all copies or substantial portions -// of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -// -// ******************************************************* -// -// Apache License -// Version 2.0, January 2004 -// http://www.apache.org/licenses/ - -use std::fmt; -use std::time::Duration; - -use header::{Header, Raw}; -use header::shared::HttpDate; - -/// The `Retry-After` header. -/// -/// The `Retry-After` response-header field can be used with a 503 (Service -/// Unavailable) response to indicate how long the service is expected to be -/// unavailable to the requesting client. This field MAY also be used with any -/// 3xx (Redirection) response to indicate the minimum time the user-agent is -/// asked wait before issuing the redirected request. The value of this field -/// can be either an HTTP-date or an integer number of seconds (in decimal) -/// after the time of the response. -/// -/// # Examples -/// ``` -/// use std::time::Duration; -/// use hyper::header::{Headers, RetryAfter}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// RetryAfter::Delay(Duration::from_secs(300)) -/// ); -/// ``` -/// ``` -/// use std::time::{SystemTime, Duration}; -/// use hyper::header::{Headers, RetryAfter}; -/// -/// let mut headers = Headers::new(); -/// let date = SystemTime::now() + Duration::from_secs(300); -/// headers.set( -/// RetryAfter::DateTime(date.into()) -/// ); -/// ``` - -/// Retry-After header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.3) -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum RetryAfter { - /// Retry after this duration has elapsed - /// - /// This can be coupled with a response time header to produce a DateTime. - Delay(Duration), - - /// Retry after the given DateTime - DateTime(HttpDate), -} - -impl Header for RetryAfter { - fn header_name() -> &'static str { - static NAME: &'static str = "Retry-After"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - if let Some(ref line) = raw.one() { - let utf8_str = match ::std::str::from_utf8(line) { - Ok(utf8_str) => utf8_str, - Err(_) => return Err(::Error::Header), - }; - - if let Ok(datetime) = utf8_str.parse::() { - return Ok(RetryAfter::DateTime(datetime)) - } - - if let Ok(seconds) = utf8_str.parse::() { - return Ok(RetryAfter::Delay(Duration::from_secs(seconds))); - } - - Err(::Error::Header) - } else { - Err(::Error::Header) - } - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> ::std::fmt::Result { - f.fmt_line(self) - } -} - -impl fmt::Display for RetryAfter { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - RetryAfter::Delay(ref duration) => { - write!(f, "{}", duration.as_secs()) - }, - RetryAfter::DateTime(ref datetime) => { - fmt::Display::fmt(datetime, f) - } - } - } -} - -#[cfg(test)] -mod tests { - use std::time::Duration; - use header::Header; - use header::shared::HttpDate; - - use super::RetryAfter; - - #[test] - fn header_name_regression() { - assert_eq!(RetryAfter::header_name(), "Retry-After"); - } - - #[test] - fn parse_delay() { - let retry_after = RetryAfter::parse_header(&vec![b"1234".to_vec()].into()).unwrap(); - - assert_eq!(RetryAfter::Delay(Duration::from_secs(1234)), retry_after); - } - - macro_rules! test_retry_after_datetime { - ($name:ident, $bytes:expr) => { - #[test] - fn $name() { - let dt = "Sun, 06 Nov 1994 08:49:37 GMT".parse::().unwrap(); - let retry_after = RetryAfter::parse_header(&vec![$bytes.to_vec()].into()).expect("parse_header ok"); - - assert_eq!(RetryAfter::DateTime(dt), retry_after); - } - } - } - - test_retry_after_datetime!(header_parse_rfc1123, b"Sun, 06 Nov 1994 08:49:37 GMT"); - test_retry_after_datetime!(header_parse_rfc850, b"Sunday, 06-Nov-94 08:49:37 GMT"); - test_retry_after_datetime!(header_parse_asctime, b"Sun Nov 6 08:49:37 1994"); - - #[test] - fn hyper_headers_from_raw_delay() { - let retry_after = RetryAfter::parse_header(&b"300".to_vec().into()).unwrap(); - assert_eq!(retry_after, RetryAfter::Delay(Duration::from_secs(300))); - } - - #[test] - fn hyper_headers_from_raw_datetime() { - let retry_after = RetryAfter::parse_header(&b"Sun, 06 Nov 1994 08:49:37 GMT".to_vec().into()).unwrap(); - let expected = "Sun, 06 Nov 1994 08:49:37 GMT".parse::().unwrap(); - - assert_eq!(retry_after, RetryAfter::DateTime(expected)); - } -} diff --git a/src/header/common/server.rs b/src/header/common/server.rs deleted file mode 100644 index 70e2a19305..0000000000 --- a/src/header/common/server.rs +++ /dev/null @@ -1,38 +0,0 @@ -header! { - /// `Server` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.2) - /// - /// The `Server` header field contains information about the software - /// used by the origin server to handle the request, which is often used - /// by clients to help identify the scope of reported interoperability - /// problems, to work around or tailor requests to avoid particular - /// server limitations, and for analytics regarding server or operating - /// system use. An origin server MAY generate a Server field in its - /// responses. - /// - /// # ABNF - /// - /// ```text - /// Server = product *( RWS ( product / comment ) ) - /// ``` - /// - /// # Example values - /// * `CERN/3.0 libwww/2.17` - /// - /// # Example - /// - /// ``` - /// use hyper::header::{Headers, Server}; - /// - /// let mut headers = Headers::new(); - /// headers.set(Server::new("hyper/0.5.2")); - /// ``` - // TODO: Maybe parse as defined in the spec? - (Server, "Server") => Cow[str] - - test_server { - // Testcase from RFC - test_header!(test1, vec![b"CERN/3.0 libwww/2.17"]); - } -} - -bench_header!(bench, Server, { vec![b"Some String".to_vec()] }); diff --git a/src/header/common/set_cookie.rs b/src/header/common/set_cookie.rs deleted file mode 100644 index 826b139376..0000000000 --- a/src/header/common/set_cookie.rs +++ /dev/null @@ -1,114 +0,0 @@ -use header::{Header, Raw}; -use std::fmt; -use std::str::from_utf8; - - -/// `Set-Cookie` header, defined [RFC6265](http://tools.ietf.org/html/rfc6265#section-4.1) -/// -/// The Set-Cookie HTTP response header is used to send cookies from the -/// server to the user agent. -/// -/// Informally, the Set-Cookie response header contains the header name -/// "Set-Cookie" followed by a ":" and a cookie. Each cookie begins with -/// a name-value-pair, followed by zero or more attribute-value pairs. -/// -/// # ABNF -/// -/// ```text -/// set-cookie-header = "Set-Cookie:" SP set-cookie-string -/// set-cookie-string = cookie-pair *( ";" SP cookie-av ) -/// cookie-pair = cookie-name "=" cookie-value -/// cookie-name = token -/// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) -/// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E -/// ; US-ASCII characters excluding CTLs, -/// ; whitespace DQUOTE, comma, semicolon, -/// ; and backslash -/// token = -/// -/// cookie-av = expires-av / max-age-av / domain-av / -/// path-av / secure-av / httponly-av / -/// extension-av -/// expires-av = "Expires=" sane-cookie-date -/// sane-cookie-date = -/// max-age-av = "Max-Age=" non-zero-digit *DIGIT -/// ; In practice, both expires-av and max-age-av -/// ; are limited to dates representable by the -/// ; user agent. -/// non-zero-digit = %x31-39 -/// ; digits 1 through 9 -/// domain-av = "Domain=" domain-value -/// domain-value = -/// ; defined in [RFC1034], Section 3.5, as -/// ; enhanced by [RFC1123], Section 2.1 -/// path-av = "Path=" path-value -/// path-value = -/// secure-av = "Secure" -/// httponly-av = "HttpOnly" -/// extension-av = -/// ``` -/// -/// # Example values -/// -/// * `SID=31d4d96e407aad42` -/// * `lang=en-US; Expires=Wed, 09 Jun 2021 10:18:14 GMT` -/// * `lang=; Expires=Sun, 06 Nov 1994 08:49:37 GMT` -/// * `lang=en-US; Path=/; Domain=example.com` -/// -/// # Example -/// -/// ``` -/// use hyper::header::{Headers, SetCookie}; -/// -/// let mut headers = Headers::new(); -/// -/// headers.set( -/// SetCookie(vec![ -/// String::from("foo=bar; Path=/path; Domain=example.com") -/// ]) -/// ); -/// ``` -#[derive(Clone, PartialEq, Debug)] -pub struct SetCookie(pub Vec); - -__hyper__deref!(SetCookie => Vec); - -impl Header for SetCookie { - fn header_name() -> &'static str { - static NAME: &'static str = "Set-Cookie"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - let mut set_cookies = Vec::with_capacity(raw.len()); - for set_cookies_raw in raw { - if let Ok(s) = from_utf8(&set_cookies_raw[..]) { - set_cookies.push(s.trim().to_owned()); - } - } - - if !set_cookies.is_empty() { - Ok(SetCookie(set_cookies)) - } else { - Err(::Error::Header) - } - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - for cookie in &self.0 { - try!(f.fmt_line(cookie)); - } - Ok(()) - } -} - -#[test] -fn test_set_cookie_fmt() { - use ::header::Headers; - let mut headers = Headers::new(); - headers.set(SetCookie(vec![ - "foo=bar".into(), - "baz=quux".into(), - ])); - assert_eq!(headers.to_string(), "Set-Cookie: foo=bar\r\nSet-Cookie: baz=quux\r\n"); -} diff --git a/src/header/common/strict_transport_security.rs b/src/header/common/strict_transport_security.rs deleted file mode 100644 index ff28ddac99..0000000000 --- a/src/header/common/strict_transport_security.rs +++ /dev/null @@ -1,202 +0,0 @@ -use std::fmt; -use std::str::{self, FromStr}; - -use unicase; - -use header::{Header, Raw, parsing}; - -/// `StrictTransportSecurity` header, defined in [RFC6797](https://tools.ietf.org/html/rfc6797) -/// -/// This specification defines a mechanism enabling web sites to declare -/// themselves accessible only via secure connections and/or for users to be -/// able to direct their user agent(s) to interact with given sites only over -/// secure connections. This overall policy is referred to as HTTP Strict -/// Transport Security (HSTS). The policy is declared by web sites via the -/// Strict-Transport-Security HTTP response header field and/or by other means, -/// such as user agent configuration, for example. -/// -/// # ABNF -/// -/// ```text -/// [ directive ] *( ";" [ directive ] ) -/// -/// directive = directive-name [ "=" directive-value ] -/// directive-name = token -/// directive-value = token | quoted-string -/// -/// ``` -/// -/// # Example values -/// -/// * `max-age=31536000` -/// * `max-age=15768000 ; includeSubDomains` -/// -/// # Example -/// -/// ``` -/// # extern crate hyper; -/// # fn main() { -/// use hyper::header::{Headers, StrictTransportSecurity}; -/// -/// let mut headers = Headers::new(); -/// -/// headers.set( -/// StrictTransportSecurity::including_subdomains(31536000u64) -/// ); -/// # } -/// ``` -#[derive(Clone, PartialEq, Debug)] -pub struct StrictTransportSecurity { - /// Signals the UA that the HSTS Policy applies to this HSTS Host as well as - /// any subdomains of the host's domain name. - pub include_subdomains: bool, - - /// Specifies the number of seconds, after the reception of the STS header - /// field, during which the UA regards the host (from whom the message was - /// received) as a Known HSTS Host. - pub max_age: u64 -} - -impl StrictTransportSecurity { - /// Create an STS header that includes subdomains - pub fn including_subdomains(max_age: u64) -> StrictTransportSecurity { - StrictTransportSecurity { - max_age: max_age, - include_subdomains: true - } - } - - /// Create an STS header that excludes subdomains - pub fn excluding_subdomains(max_age: u64) -> StrictTransportSecurity { - StrictTransportSecurity { - max_age: max_age, - include_subdomains: false - } - } -} - -enum Directive { - MaxAge(u64), - IncludeSubdomains, - Unknown -} - -impl FromStr for StrictTransportSecurity { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - s.split(';') - .map(str::trim) - .map(|sub| if unicase::eq_ascii(sub, "includeSubdomains") { - Ok(Directive::IncludeSubdomains) - } else { - let mut sub = sub.splitn(2, '='); - match (sub.next(), sub.next()) { - (Some(left), Some(right)) - if unicase::eq_ascii(left.trim(), "max-age") => { - right - .trim() - .trim_matches('"') - .parse() - .map(Directive::MaxAge) - }, - _ => Ok(Directive::Unknown) - } - }) - .fold(Ok((None, None)), |res, dir| match (res, dir) { - (Ok((None, sub)), Ok(Directive::MaxAge(age))) => Ok((Some(age), sub)), - (Ok((age, None)), Ok(Directive::IncludeSubdomains)) => Ok((age, Some(()))), - (Ok((Some(_), _)), Ok(Directive::MaxAge(_))) | - (Ok((_, Some(_))), Ok(Directive::IncludeSubdomains)) | - (_, Err(_)) => Err(::Error::Header), - (res, _) => res - }) - .and_then(|res| match res { - (Some(age), sub) => Ok(StrictTransportSecurity { - max_age: age, - include_subdomains: sub.is_some() - }), - _ => Err(::Error::Header) - }) - } -} - -impl Header for StrictTransportSecurity { - fn header_name() -> &'static str { - static NAME: &'static str = "Strict-Transport-Security"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - parsing::from_one_raw_str(raw) - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -impl fmt::Display for StrictTransportSecurity { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.include_subdomains { - write!(f, "max-age={}; includeSubdomains", self.max_age) - } else { - write!(f, "max-age={}", self.max_age) - } - } -} - -#[cfg(test)] -mod tests { - use super::StrictTransportSecurity; - use header::Header; - - #[test] - fn test_parse_max_age() { - let h = Header::parse_header(&"max-age=31536000".into()); - assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 })); - } - - #[test] - fn test_parse_max_age_no_value() { - let h: ::Result = Header::parse_header(&"max-age".into()); - assert!(h.is_err()); - } - - #[test] - fn test_parse_quoted_max_age() { - let h = Header::parse_header(&"max-age=\"31536000\"".into()); - assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 })); - } - - #[test] - fn test_parse_spaces_max_age() { - let h = Header::parse_header(&"max-age = 31536000".into()); - assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 })); - } - - #[test] - fn test_parse_include_subdomains() { - let h = Header::parse_header(&"max-age=15768000 ; includeSubDomains".into()); - assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: true, max_age: 15768000u64 })); - } - - #[test] - fn test_parse_no_max_age() { - let h: ::Result = Header::parse_header(&"includeSubDomains".into()); - assert!(h.is_err()); - } - - #[test] - fn test_parse_max_age_nan() { - let h: ::Result = Header::parse_header(&"max-age = derp".into()); - assert!(h.is_err()); - } - - #[test] - fn test_parse_duplicate_directives() { - assert!(StrictTransportSecurity::parse_header(&"max-age=100; max-age=5; max-age=0".into()).is_err()); - } -} - -bench_header!(bench, StrictTransportSecurity, { vec![b"max-age=15768000 ; includeSubDomains".to_vec()] }); diff --git a/src/header/common/te.rs b/src/header/common/te.rs deleted file mode 100644 index 49f2117ea2..0000000000 --- a/src/header/common/te.rs +++ /dev/null @@ -1,71 +0,0 @@ -use header::{Encoding, QualityItem}; - -header! { - /// `TE` header, defined in - /// [RFC7230](http://tools.ietf.org/html/rfc7230#section-4.3) - /// - /// As RFC7230 states, "The "TE" header field in a request indicates what transfer codings, - /// besides chunked, the client is willing to accept in response, and - /// whether or not the client is willing to accept trailer fields in a - /// chunked transfer coding." - /// - /// For HTTP/1.1 compliant clients `chunked` transfer codings are assumed to be acceptable and - /// so should never appear in this header. - /// - /// # ABNF - /// - /// ```text - /// TE = "TE" ":" #( t-codings ) - /// t-codings = "trailers" | ( transfer-extension [ accept-params ] ) - /// ``` - /// - /// # Example values - /// * `trailers` - /// * `trailers, deflate;q=0.5` - /// * `` - /// - /// # Examples - /// - /// ``` - /// use hyper::header::{Headers, Te, Encoding, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// Te(vec![qitem(Encoding::Trailers)]) - /// ); - /// ``` - /// - /// ``` - /// use hyper::header::{Headers, Te, Encoding, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// Te(vec![ - /// qitem(Encoding::Trailers), - /// qitem(Encoding::Gzip), - /// qitem(Encoding::Deflate), - /// ]) - /// ); - /// ``` - /// - /// ``` - /// use hyper::header::{Headers, Te, Encoding, QualityItem, q, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// Te(vec![ - /// qitem(Encoding::Trailers), - /// QualityItem::new(Encoding::Gzip, q(600)), - /// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)), - /// ]) - /// ); - /// ``` - (Te, "TE") => (QualityItem)* - - test_te { - // From the RFC - test_header!(test1, vec![b"trailers"]); - test_header!(test2, vec![b"trailers, deflate;q=0.5"]); - test_header!(test3, vec![b""]); - } -} diff --git a/src/header/common/transfer_encoding.rs b/src/header/common/transfer_encoding.rs deleted file mode 100644 index 75788f078d..0000000000 --- a/src/header/common/transfer_encoding.rs +++ /dev/null @@ -1,70 +0,0 @@ -use header::Encoding; - -header! { - /// `Transfer-Encoding` header, defined in - /// [RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.1) - /// - /// The `Transfer-Encoding` header field lists the transfer coding names - /// corresponding to the sequence of transfer codings that have been (or - /// will be) applied to the payload body in order to form the message - /// body. - /// - /// Note that setting this header will *remove* any previously set - /// `Content-Length` header, in accordance with - /// [RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.2): - /// - /// > A sender MUST NOT send a Content-Length header field in any message - /// > that contains a Transfer-Encoding header field. - /// - /// # ABNF - /// - /// ```text - /// Transfer-Encoding = 1#transfer-coding - /// ``` - /// - /// # Example values - /// - /// * `gzip, chunked` - /// - /// # Example - /// - /// ``` - /// use hyper::header::{Headers, TransferEncoding, Encoding}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// TransferEncoding(vec![ - /// Encoding::Gzip, - /// Encoding::Chunked, - /// ]) - /// ); - /// ``` - (TransferEncoding, "Transfer-Encoding") => (Encoding)+ - - transfer_encoding { - test_header!( - test1, - vec![b"gzip, chunked"], - Some(HeaderField( - vec![Encoding::Gzip, Encoding::Chunked] - ))); - // Issue: #683 - test_header!( - test2, - vec![b"chunked", b"chunked"], - Some(HeaderField( - vec![Encoding::Chunked, Encoding::Chunked] - ))); - - } -} - -impl TransferEncoding { - /// Constructor for the most common Transfer-Encoding, `chunked`. - pub fn chunked() -> TransferEncoding { - TransferEncoding(vec![Encoding::Chunked]) - } -} - -bench_header!(normal, TransferEncoding, { vec![b"chunked, gzip".to_vec()] }); -bench_header!(ext, TransferEncoding, { vec![b"ext".to_vec()] }); diff --git a/src/header/common/upgrade.rs b/src/header/common/upgrade.rs deleted file mode 100644 index 85b60b1679..0000000000 --- a/src/header/common/upgrade.rs +++ /dev/null @@ -1,162 +0,0 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; -use unicase; - -header! { - /// `Upgrade` header, defined in [RFC7230](http://tools.ietf.org/html/rfc7230#section-6.7) - /// - /// The `Upgrade` header field is intended to provide a simple mechanism - /// for transitioning from HTTP/1.1 to some other protocol on the same - /// connection. A client MAY send a list of protocols in the Upgrade - /// header field of a request to invite the server to switch to one or - /// more of those protocols, in order of descending preference, before - /// sending the final response. A server MAY ignore a received Upgrade - /// header field if it wishes to continue using the current protocol on - /// that connection. Upgrade cannot be used to insist on a protocol - /// change. - /// - /// # ABNF - /// - /// ```text - /// Upgrade = 1#protocol - /// - /// protocol = protocol-name ["/" protocol-version] - /// protocol-name = token - /// protocol-version = token - /// ``` - /// - /// # Example values - /// - /// * `HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11` - /// - /// # Examples - /// - /// ``` - /// use hyper::header::{Headers, Upgrade, Protocol, ProtocolName}; - /// - /// let mut headers = Headers::new(); - /// headers.set(Upgrade(vec![Protocol::new(ProtocolName::WebSocket, None)])); - /// ``` - /// - /// ``` - /// use hyper::header::{Headers, Upgrade, Protocol, ProtocolName}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// Upgrade(vec![ - /// Protocol::new(ProtocolName::Http, Some("2.0".to_owned())), - /// Protocol::new(ProtocolName::Unregistered("SHTTP".to_owned()), - /// Some("1.3".to_owned())), - /// Protocol::new(ProtocolName::Unregistered("IRC".to_owned()), - /// Some("6.9".to_owned())), - /// ]) - /// ); - /// ``` - (Upgrade, "Upgrade") => (Protocol)+ - - test_upgrade { - // Testcase from the RFC - test_header!( - test1, - vec![b"HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11"], - Some(Upgrade(vec![ - Protocol::new(ProtocolName::Http, Some("2.0".to_owned())), - Protocol::new(ProtocolName::Unregistered("SHTTP".to_owned()), - Some("1.3".to_owned())), - Protocol::new(ProtocolName::Unregistered("IRC".to_owned()), Some("6.9".to_owned())), - Protocol::new(ProtocolName::Unregistered("RTA".to_owned()), Some("x11".to_owned())), - ]))); - // Own tests - test_header!( - test2, vec![b"websocket"], - Some(Upgrade(vec![Protocol::new(ProtocolName::WebSocket, None)]))); - #[test] - fn test3() { - let x: ::Result = Header::parse_header(&"WEbSOCKet".into()); - assert_eq!(x.ok(), Some(Upgrade(vec![Protocol::new(ProtocolName::WebSocket, None)]))); - } - } -} - -/// A protocol name used to identify a specific protocol. Names are case-sensitive -/// except for the `WebSocket` value. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum ProtocolName { - /// `HTTP` value, Hypertext Transfer Protocol - Http, - /// `TLS` value, Transport Layer Security [RFC2817](http://tools.ietf.org/html/rfc2817) - Tls, - /// `WebSocket` value, matched case insensitively,Web Socket Protocol - /// [RFC6455](http://tools.ietf.org/html/rfc6455) - WebSocket, - /// `h2c` value, HTTP/2 over cleartext TCP - H2c, - /// Any other protocol name not known to hyper - Unregistered(String), -} - -impl FromStr for ProtocolName { - type Err = (); - fn from_str(s: &str) -> Result { - Ok(match s { - "HTTP" => ProtocolName::Http, - "TLS" => ProtocolName::Tls, - "h2c" => ProtocolName::H2c, - _ => { - if unicase::eq_ascii(s, "websocket") { - ProtocolName::WebSocket - } else { - ProtocolName::Unregistered(s.to_owned()) - } - } - }) - } -} - -impl Display for ProtocolName { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match *self { - ProtocolName::Http => "HTTP", - ProtocolName::Tls => "TLS", - ProtocolName::WebSocket => "websocket", - ProtocolName::H2c => "h2c", - ProtocolName::Unregistered(ref s) => s, - }) - } -} - -/// Protocols that appear in the `Upgrade` header field -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Protocol { - /// The protocol identifier - pub name: ProtocolName, - /// The optional version of the protocol, often in the format "DIGIT.DIGIT" (e.g.. "1.2") - pub version: Option, -} - -impl Protocol { - /// Creates a new Protocol with the given name and version - pub fn new(name: ProtocolName, version: Option) -> Protocol { - Protocol { name: name, version: version } - } -} - -impl FromStr for Protocol { - type Err =(); - fn from_str(s: &str) -> Result { - let mut parts = s.splitn(2, '/'); - Ok(Protocol::new(try!(parts.next().unwrap().parse()), parts.next().map(|x| x.to_owned()))) - } -} - -impl Display for Protocol { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - try!(fmt::Display::fmt(&self.name, f)); - if let Some(ref version) = self.version { - try!(write!(f, "/{}", version)); - } - Ok(()) - } -} - -bench_header!(bench, Upgrade, { vec![b"HTTP/2.0, RTA/x11, websocket".to_vec()] }); diff --git a/src/header/common/user_agent.rs b/src/header/common/user_agent.rs deleted file mode 100644 index d51a7b55cd..0000000000 --- a/src/header/common/user_agent.rs +++ /dev/null @@ -1,46 +0,0 @@ -header! { - /// `User-Agent` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.5.3) - /// - /// The `User-Agent` header field contains information about the user - /// agent originating the request, which is often used by servers to help - /// identify the scope of reported interoperability problems, to work - /// around or tailor responses to avoid particular user agent - /// limitations, and for analytics regarding browser or operating system - /// use. A user agent SHOULD send a User-Agent field in each request - /// unless specifically configured not to do so. - /// - /// # ABNF - /// - /// ```text - /// User-Agent = product *( RWS ( product / comment ) ) - /// product = token ["/" product-version] - /// product-version = token - /// ``` - /// - /// # Example values - /// - /// * `CERN-LineMode/2.15 libwww/2.17b3` - /// * `Bunnies` - /// - /// # Notes - /// - /// * The parser does not split the value - /// - /// # Example - /// - /// ``` - /// use hyper::header::{Headers, UserAgent}; - /// - /// let mut headers = Headers::new(); - /// headers.set(UserAgent::new("hyper/0.5.2")); - /// ``` - (UserAgent, "User-Agent") => Cow[str] - - test_user_agent { - // Testcase from RFC - test_header!(test1, vec![b"CERN-LineMode/2.15 libwww/2.17b3"]); - // Own testcase - test_header!(test2, vec![b"Bunnies"], Some(UserAgent::new("Bunnies"))); - } -} diff --git a/src/header/common/vary.rs b/src/header/common/vary.rs deleted file mode 100644 index 236b9b8c5b..0000000000 --- a/src/header/common/vary.rs +++ /dev/null @@ -1,70 +0,0 @@ -use unicase::Ascii; - -header! { - /// `Vary` header, defined in [RFC7231](https://tools.ietf.org/html/rfc7231#section-7.1.4) - /// - /// The "Vary" header field in a response describes what parts of a - /// request message, aside from the method, Host header field, and - /// request target, might influence the origin server's process for - /// selecting and representing this response. The value consists of - /// either a single asterisk ("*") or a list of header field names - /// (case-insensitive). - /// - /// # ABNF - /// - /// ```text - /// Vary = "*" / 1#field-name - /// ``` - /// - /// # Example values - /// - /// * `accept-encoding, accept-language` - /// - /// # Example - /// - /// ``` - /// use hyper::header::{Headers, Vary}; - /// - /// let mut headers = Headers::new(); - /// headers.set(Vary::Any); - /// ``` - /// - /// # Example - /// - /// ``` - /// # extern crate hyper; - /// # extern crate unicase; - /// # fn main() { - /// // extern crate unicase; - /// - /// use hyper::header::{Headers, Vary}; - /// use unicase::Ascii; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// Vary::Items(vec![ - /// Ascii::new("accept-encoding".to_owned()), - /// Ascii::new("accept-language".to_owned()), - /// ]) - /// ); - /// # } - /// ``` - (Vary, "Vary") => {Any / (Ascii)+} - - test_vary { - test_header!(test1, vec![b"accept-encoding, accept-language"]); - - #[test] - fn test2() { - let mut vary: ::Result; - - vary = Header::parse_header(&"*".into()); - assert_eq!(vary.ok(), Some(Vary::Any)); - - vary = Header::parse_header(&"etag,cookie,allow".into()); - assert_eq!(vary.ok(), Some(Vary::Items(vec!["eTag".parse().unwrap(), - "cookIE".parse().unwrap(), - "AlLOw".parse().unwrap(),]))); - } - } -} diff --git a/src/header/common/warning.rs b/src/header/common/warning.rs deleted file mode 100644 index 4ec93784e0..0000000000 --- a/src/header/common/warning.rs +++ /dev/null @@ -1,182 +0,0 @@ -use std::fmt; -use std::str::{FromStr}; -use header::{Header, HttpDate, Raw}; -use header::parsing::from_one_raw_str; - -/// `Warning` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.5) -/// -/// The `Warning` header field can be be used to carry additional information -/// about the status or transformation of a message that might not be reflected -/// in the status code. This header is sometimes used as backwards -/// compatible way to notify of a deprecated API. -/// -/// # ABNF -/// -/// ```text -/// Warning = 1#warning-value -/// warning-value = warn-code SP warn-agent SP warn-text -/// [ SP warn-date ] -/// warn-code = 3DIGIT -/// warn-agent = ( uri-host [ ":" port ] ) / pseudonym -/// ; the name or pseudonym of the server adding -/// ; the Warning header field, for use in debugging -/// ; a single "-" is recommended when agent unknown -/// warn-text = quoted-string -/// warn-date = DQUOTE HTTP-date DQUOTE -/// ``` -/// -/// # Example values -/// -/// * `Warning: 112 - "network down" "Sat, 25 Aug 2012 23:34:45 GMT"` -/// * `Warning: 299 - "Deprecated API " "Tue, 15 Nov 1994 08:12:31 GMT"` -/// * `Warning: 299 api.hyper.rs:8080 "Deprecated API : use newapi.hyper.rs instead."` -/// * `Warning: 299 api.hyper.rs:8080 "Deprecated API : use newapi.hyper.rs instead." "Tue, 15 Nov 1994 08:12:31 GMT"` -/// -/// # Examples -/// -/// ``` -/// use hyper::header::{Headers, Warning}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// Warning{ -/// code: 299, -/// agent: "api.hyper.rs".to_owned(), -/// text: "Deprecated".to_owned(), -/// date: None -/// } -/// ); -/// ``` -/// -/// ``` -/// use hyper::header::{Headers, HttpDate, Warning}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// Warning{ -/// code: 299, -/// agent: "api.hyper.rs".to_owned(), -/// text: "Deprecated".to_owned(), -/// date: "Tue, 15 Nov 1994 08:12:31 GMT".parse::().ok() -/// } -/// ); -/// ``` -/// -/// ``` -/// use std::time::SystemTime; -/// use hyper::header::{Headers, Warning}; -/// -/// let mut headers = Headers::new(); -/// headers.set( -/// Warning{ -/// code: 199, -/// agent: "api.hyper.rs".to_owned(), -/// text: "Deprecated".to_owned(), -/// date: Some(SystemTime::now().into()) -/// } -/// ); -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub struct Warning { - /// The 3 digit warn code. - pub code: u16, - /// The name or pseudonym of the server adding this header. - pub agent: String, - /// The warning message describing the error. - pub text: String, - /// An optional warning date. - pub date: Option -} - -impl Header for Warning { - fn header_name() -> &'static str { - static NAME: &'static str = "Warning"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - from_one_raw_str(raw) - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -impl fmt::Display for Warning { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.date { - Some(date) => write!(f, "{:03} {} \"{}\" \"{}\"", self.code, self.agent, self.text, date), - None => write!(f, "{:03} {} \"{}\"", self.code, self.agent, self.text) - } - } -} - -impl FromStr for Warning { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - let mut warning_split = s.split_whitespace(); - let code = match warning_split.next() { - Some(c) => match c.parse::() { - Ok(c) => c, - Err(..) => return Err(::Error::Header) - }, - None => return Err(::Error::Header) - }; - let agent = match warning_split.next() { - Some(a) => a.to_string(), - None => return Err(::Error::Header) - }; - - let mut warning_split = s.split('"').skip(1); - let text = match warning_split.next() { - Some(t) => t.to_string(), - None => return Err(::Error::Header) - }; - let date = match warning_split.skip(1).next() { - Some(d) => d.parse::().ok(), - None => None // Optional - }; - - Ok(Warning { - code: code, - agent: agent, - text: text, - date: date - }) - } -} - -#[cfg(test)] -mod tests { - use super::Warning; - use header::{Header, HttpDate}; - - #[test] - fn test_parsing() { - let warning = Header::parse_header(&vec![b"112 - \"network down\" \"Sat, 25 Aug 2012 23:34:45 GMT\"".to_vec()].into()); - assert_eq!(warning.ok(), Some(Warning { - code: 112, - agent: "-".to_owned(), - text: "network down".to_owned(), - date: "Sat, 25 Aug 2012 23:34:45 GMT".parse::().ok() - })); - - let warning = Header::parse_header(&vec![b"299 api.hyper.rs:8080 \"Deprecated API : use newapi.hyper.rs instead.\"".to_vec()].into()); - assert_eq!(warning.ok(), Some(Warning { - code: 299, - agent: "api.hyper.rs:8080".to_owned(), - text: "Deprecated API : use newapi.hyper.rs instead.".to_owned(), - date: None - })); - - let warning = Header::parse_header(&vec![b"299 api.hyper.rs:8080 \"Deprecated API : use newapi.hyper.rs instead.\" \"Tue, 15 Nov 1994 08:12:31 GMT\"".to_vec()].into()); - assert_eq!(warning.ok(), Some(Warning { - code: 299, - agent: "api.hyper.rs:8080".to_owned(), - text: "Deprecated API : use newapi.hyper.rs instead.".to_owned(), - date: "Tue, 15 Nov 1994 08:12:31 GMT".parse::().ok() - })); - } -} diff --git a/src/header/internals/cell.rs b/src/header/internals/cell.rs deleted file mode 100644 index c2774d3199..0000000000 --- a/src/header/internals/cell.rs +++ /dev/null @@ -1,226 +0,0 @@ -use std::any::{Any, TypeId}; -use std::cell::UnsafeCell; -use std::collections::HashMap; -use std::mem; -use std::ops::Deref; - -pub struct OptCell(UnsafeCell>); - -impl OptCell { - #[inline] - pub fn new(val: Option) -> OptCell { - OptCell(UnsafeCell::new(val)) - } - - #[inline] - pub fn set(&self, val: T) { - unsafe { - let opt = self.0.get(); - debug_assert!((*opt).is_none()); - *opt = Some(val) - } - } - - #[inline] - pub unsafe fn get_mut(&mut self) -> &mut T { - let opt = &mut *self.0.get(); - opt.as_mut().unwrap() - } -} - -impl Deref for OptCell { - type Target = Option; - #[inline] - fn deref(&self) -> &Option { - unsafe { &*self.0.get() } - } -} - -impl Clone for OptCell { - #[inline] - fn clone(&self) -> OptCell { - OptCell::new((**self).clone()) - } -} - -pub struct PtrMapCell(UnsafeCell>>); - -#[derive(Clone, Debug)] -enum PtrMap { - Empty, - One(TypeId, T), - Many(HashMap) -} - -impl PtrMapCell { - #[inline] - pub fn new() -> PtrMapCell { - PtrMapCell(UnsafeCell::new(PtrMap::Empty)) - } - - #[inline] - pub fn with_one(key: TypeId, val: Box) -> PtrMapCell { - PtrMapCell(UnsafeCell::new(PtrMap::One(key, val))) - } - - #[inline] - pub fn get(&self, key: TypeId) -> Option<&V> { - let map = unsafe { &*self.0.get() }; - match *map { - PtrMap::Empty => None, - PtrMap::One(id, ref v) => if id == key { - Some(v) - } else { - None - }, - PtrMap::Many(ref hm) => hm.get(&key) - }.map(|val| &**val) - } - - #[inline] - pub fn get_mut(&mut self, key: TypeId) -> Option<&mut V> { - let map = unsafe { &mut *self.0.get() }; - match *map { - PtrMap::Empty => None, - PtrMap::One(id, ref mut v) => if id == key { - Some(v) - } else { - None - }, - PtrMap::Many(ref mut hm) => hm.get_mut(&key) - }.map(|val| &mut **val) - } - - #[inline] - pub fn into_value(self, key: TypeId) -> Option> { - // UnsafeCell::into_inner was unsafe forever, and 1.25 has removed - // the unsafe modifier, resulting in a warning. This allow can be - // removed when the minimum supported rust version is at least 1.25. - #[allow(unused_unsafe)] - let map = unsafe { self.0.into_inner() }; - match map { - PtrMap::Empty => None, - PtrMap::One(id, v) => if id == key { - Some(v) - } else { - None - }, - PtrMap::Many(mut hm) => hm.remove(&key) - } - } - - #[inline] - pub unsafe fn insert(&self, key: TypeId, val: Box) { - let map = &mut *self.0.get(); - match *map { - PtrMap::Empty => *map = PtrMap::One(key, val), - PtrMap::One(..) => { - let one = mem::replace(map, PtrMap::Empty); - match one { - PtrMap::One(id, one) => { - debug_assert_ne!(id, key); - let mut hm = HashMap::with_capacity(2); - hm.insert(id, one); - hm.insert(key, val); - mem::replace(map, PtrMap::Many(hm)); - }, - _ => unreachable!() - } - }, - PtrMap::Many(ref mut hm) => { hm.insert(key, val); } - } - } - - #[inline] - pub unsafe fn one(&self) -> &V { - let map = &*self.0.get(); - match *map { - PtrMap::One(_, ref one) => one, - _ => panic!("not PtrMap::One value") - } - } -} - -impl Clone for PtrMapCell where Box: Clone { - #[inline] - fn clone(&self) -> PtrMapCell { - let cell = PtrMapCell::new(); - unsafe { - *cell.0.get() = (&*self.0.get()).clone() - } - cell - } -} - -#[cfg(test)] -mod test { - use std::any::TypeId; - use super::*; - - #[test] - fn test_opt_cell_set() { - let one:OptCell = OptCell::new(None); - one.set(1); - assert_eq!(*one,Some(1)); - } - - #[test] - fn test_opt_cell_clone() { - let one:OptCell = OptCell::new(Some(3)); - let stored = *one.clone(); - assert_eq!(stored,Some(3)); - } - - - #[test] - fn test_ptr_map_cell_none() { - let type_id = TypeId::of::(); - let pm:PtrMapCell = PtrMapCell::new(); - assert_eq!(pm.get(type_id),None); - } - - #[test] - fn test_ptr_map_cell_one() { - let type_id = TypeId::of::(); - let pm:PtrMapCell = PtrMapCell::new(); - unsafe { pm.insert(type_id, Box::new("a".to_string())); } - assert_eq!(pm.get(type_id), Some(&"a".to_string())); - assert_eq!(unsafe {pm.one()}, "a"); - } - - #[test] - fn test_ptr_map_cell_two() { - let type_id = TypeId::of::(); - let type_id2 = TypeId::of::>(); - let pm:PtrMapCell = PtrMapCell::new(); - unsafe { pm.insert(type_id, Box::new("a".to_string())); } - unsafe { pm.insert(type_id2, Box::new("b".to_string())); } - assert_eq!(pm.get(type_id), Some(&"a".to_string())); - assert_eq!(pm.get(type_id2), Some(&"b".to_string())); - } - - #[test] - fn test_ptr_map_cell_many() { - let id1 = TypeId::of::(); - let id2 = TypeId::of::>(); - let id3 = TypeId::of::>(); - let pm:PtrMapCell = PtrMapCell::new(); - unsafe { pm.insert(id1, Box::new("a".to_string())); } - unsafe { pm.insert(id2, Box::new("b".to_string())); } - unsafe { pm.insert(id3, Box::new("c".to_string())); } - assert_eq!(pm.get(id1), Some(&"a".to_string())); - assert_eq!(pm.get(id2), Some(&"b".to_string())); - assert_eq!(pm.get(id3), Some(&"c".to_string())); - } - - - #[test] - fn test_ptr_map_cell_clone() { - let type_id = TypeId::of::(); - let pm:PtrMapCell = PtrMapCell::new(); - unsafe { pm.insert(type_id, Box::new("a".to_string())); } - let cloned = pm.clone(); - assert_eq!(cloned.get(type_id), Some(&"a".to_string())); - } - -} diff --git a/src/header/internals/item.rs b/src/header/internals/item.rs deleted file mode 100644 index 2e19beb943..0000000000 --- a/src/header/internals/item.rs +++ /dev/null @@ -1,119 +0,0 @@ -use std::any::Any; -use std::any::TypeId; -use std::fmt; -use std::str::from_utf8; - -use super::cell::{OptCell, PtrMapCell}; -use header::{Header, Formatter, Multi, raw, Raw}; - - -#[derive(Clone)] -pub struct Item { - raw: OptCell, - typed: PtrMapCell
-} - -impl Item { - #[inline] - pub fn new_raw(data: Raw) -> Item { - Item { - raw: OptCell::new(Some(data)), - typed: PtrMapCell::new(), - } - } - - #[inline] - - pub fn new_typed(val: H) -> Item { - Item { - raw: OptCell::new(None), - typed: PtrMapCell::with_one(TypeId::of::(), Box::new(val)), - } - } - - #[inline] - pub fn raw_mut(&mut self) -> &mut Raw { - self.raw(); - self.typed = PtrMapCell::new(); - unsafe { - self.raw.get_mut() - } - } - - pub fn raw(&self) -> &Raw { - if let Some(ref raw) = *self.raw { - return raw; - } - - let mut raw = raw::new(); - self.write_h1(&mut Formatter(Multi::Raw(&mut raw))).expect("fmt failed"); - self.raw.set(raw); - - self.raw.as_ref().unwrap() - } - - pub fn typed(&self) -> Option<&H> { - let tid = TypeId::of::(); - match self.typed.get(tid) { - Some(val) => Some(val), - None => { - parse::(self.raw.as_ref().expect("item.raw must exist")).and_then(|typed| { - unsafe { self.typed.insert(tid, typed); } - self.typed.get(tid) - }) - } - }.map(|typed| unsafe { typed.downcast_ref_unchecked() }) - } - - pub fn typed_mut(&mut self) -> Option<&mut H> { - let tid = TypeId::of::(); - if self.typed.get_mut(tid).is_none() { - parse::(self.raw.as_ref().expect("item.raw must exist")).map(|typed| { - unsafe { self.typed.insert(tid, typed); } - }); - } - if self.raw.is_some() && self.typed.get_mut(tid).is_some() { - self.raw = OptCell::new(None); - } - self.typed.get_mut(tid).map(|typed| unsafe { typed.downcast_mut_unchecked() }) - } - - pub fn into_typed(self) -> Option { - let tid = TypeId::of::(); - let Item { typed, raw } = self; - typed.into_value(tid) - .or_else(|| raw.as_ref().and_then(parse::)) - .map(|typed| unsafe { typed.downcast_unchecked() }) - } - - pub fn write_h1(&self, f: &mut Formatter) -> fmt::Result { - match *self.raw { - Some(ref raw) => { - for part in raw.iter() { - match from_utf8(&part[..]) { - Ok(s) => { - try!(f.fmt_line(&s)); - }, - Err(_) => { - error!("raw header value is not utf8, value={:?}", part); - return Err(fmt::Error); - } - } - } - Ok(()) - }, - None => { - let typed = unsafe { self.typed.one() }; - typed.fmt_header(f) - } - } - } -} - -#[inline] -fn parse(raw: &Raw) -> Option> { - H::parse_header(raw).map(|h| { - let h: Box
= Box::new(h); - h - }).ok() -} diff --git a/src/header/internals/mod.rs b/src/header/internals/mod.rs deleted file mode 100644 index 89a655d203..0000000000 --- a/src/header/internals/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub use self::item::Item; -pub use self::vec_map::{VecMap, Entry}; - -mod cell; -mod item; -mod vec_map; diff --git a/src/header/internals/vec_map.rs b/src/header/internals/vec_map.rs deleted file mode 100644 index 8ddc6a5a5c..0000000000 --- a/src/header/internals/vec_map.rs +++ /dev/null @@ -1,146 +0,0 @@ -#[derive(Clone)] -pub struct VecMap { - vec: Vec<(K, V)>, -} - -impl VecMap { - #[inline] - pub fn with_capacity(cap: usize) -> VecMap { - VecMap { - vec: Vec::with_capacity(cap) - } - } - - #[inline] - pub fn insert(&mut self, key: K, value: V) { - // not using entry or find_mut because of borrowck - for entry in &mut self.vec { - if key == entry.0 { - *entry = (key, value); - return; - } - } - self.vec.push((key, value)); - } - - pub fn insert_pos(&mut self, key: K, value: V, pos: usize) { - debug_assert!(!self.contains_key(&key)); - self.vec.insert(pos, (key, value)) - } - - #[inline] - pub fn append(&mut self, key: K, value: V) { - self.vec.push((key, value)); - } - - #[inline] - pub fn entry(&mut self, key: K) -> Entry { - match self.pos(&key) { - Some(pos) => Entry::Occupied(OccupiedEntry { - vec: &mut self.vec, - pos: pos, - }), - None => Entry::Vacant(VacantEntry { - vec: &mut self.vec, - key: key, - }) - } - } - - #[inline] - pub fn get + ?Sized>(&self, key: &K2) -> Option<&V> { - self.find(key).map(|entry| &entry.1) - } - - #[inline] - pub fn get_mut + ?Sized>(&mut self, key: &K2) -> Option<&mut V> { - self.find_mut(key).map(|entry| &mut entry.1) - } - - #[inline] - pub fn contains_key + ?Sized>(&self, key: &K2) -> bool { - self.find(key).is_some() - } - - #[inline] - pub fn len(&self) -> usize { self.vec.len() } - - #[inline] - pub fn iter(&self) -> ::std::slice::Iter<(K, V)> { - self.vec.iter() - } - - #[inline] - pub fn remove + ?Sized>(&mut self, key: &K2) -> Option { - self.pos(key).map(|pos| self.vec.remove(pos)).map(|(_, v)| v) - } - - #[inline] - pub fn remove_all + ?Sized>(&mut self, key: &K2) { - let len = self.vec.len(); - for i in (0..len).rev() { - if key == &self.vec[i].0 { - self.vec.remove(i); - } - } - } - - #[inline] - pub fn clear(&mut self) { - self.vec.clear(); - } - - #[inline] - fn find + ?Sized>(&self, key: &K2) -> Option<&(K, V)> { - for entry in &self.vec { - if key == &entry.0 { - return Some(entry); - } - } - None - } - - #[inline] - fn find_mut + ?Sized>(&mut self, key: &K2) -> Option<&mut (K, V)> { - for entry in &mut self.vec { - if key == &entry.0 { - return Some(entry); - } - } - None - } - - #[inline] - fn pos + ?Sized>(&self, key: &K2) -> Option { - self.vec.iter().position(|entry| key == &entry.0) - } -} - -pub enum Entry<'a, K: 'a, V: 'a> { - Vacant(VacantEntry<'a, K, V>), - Occupied(OccupiedEntry<'a, K, V>) -} - -pub struct VacantEntry<'a, K: 'a, V: 'a> { - vec: &'a mut Vec<(K, V)>, - key: K, -} - -impl<'a, K, V> VacantEntry<'a, K, V> { - pub fn insert(self, val: V) -> &'a mut V { - self.vec.push((self.key, val)); - let pos = self.vec.len() - 1; - &mut self.vec[pos].1 - } -} - -pub struct OccupiedEntry<'a, K: 'a, V: 'a> { - vec: &'a mut Vec<(K, V)>, - pos: usize, -} - -impl<'a, K, V> OccupiedEntry<'a, K, V> { - pub fn into_mut(self) -> &'a mut V { - &mut self.vec[self.pos].1 - } -} diff --git a/src/header/mod.rs b/src/header/mod.rs deleted file mode 100644 index 866d2521e0..0000000000 --- a/src/header/mod.rs +++ /dev/null @@ -1,1129 +0,0 @@ -//! Headers container, and common header fields. -//! -//! hyper has the opinion that Headers should be strongly-typed, because that's -//! why we're using Rust in the first place. To set or get any header, an object -//! must implement the `Header` trait from this module. Several common headers -//! are already provided, such as `Host`, `ContentType`, `UserAgent`, and others. -//! -//! # Why Typed? -//! -//! Or, why not stringly-typed? Types give the following advantages: -//! -//! - More difficult to typo, since typos in types should be caught by the compiler -//! - Parsing to a proper type by default -//! -//! # Defining Custom Headers -//! -//! Hyper provides many of the most commonly used headers in HTTP. If -//! you need to define a custom header, it's easy to do while still taking -//! advantage of the type system. Hyper includes a `header!` macro for defining -//! many wrapper-style headers. -//! -//! ``` -//! #[macro_use] extern crate hyper; -//! use hyper::header::Headers; -//! header! { (XRequestGuid, "X-Request-Guid") => [String] } -//! -//! fn main () { -//! let mut headers = Headers::new(); -//! -//! headers.set(XRequestGuid("a proper guid".to_owned())) -//! } -//! ``` -//! -//! This works well for simple "string" headers. If you need more control, -//! you can implement the trait directly. -//! -//! ## Implementing the `Header` trait -//! -//! Consider a Do Not Track header. It can be true or false, but it represents -//! that via the numerals `1` and `0`. -//! -//! ``` -//! use std::fmt; -//! use hyper::header::{self, Header, Raw}; -//! -//! #[derive(Debug, Clone, Copy)] -//! struct Dnt(bool); -//! -//! impl Header for Dnt { -//! fn header_name() -> &'static str { -//! "DNT" -//! } -//! -//! fn parse_header(raw: &Raw) -> hyper::Result { -//! if raw.len() == 1 { -//! let line = &raw[0]; -//! if line.len() == 1 { -//! let byte = line[0]; -//! match byte { -//! b'0' => return Ok(Dnt(true)), -//! b'1' => return Ok(Dnt(false)), -//! _ => () -//! } -//! } -//! } -//! Err(hyper::Error::Header) -//! } -//! -//! fn fmt_header(&self, f: &mut header::Formatter) -> fmt::Result { -//! let value = if self.0 { -//! "1" -//! } else { -//! "0" -//! }; -//! f.fmt_line(&value) -//! } -//! } -//! ``` -use std::borrow::{Cow, ToOwned}; -#[cfg(feature = "compat")] -use std::convert::From; -use std::iter::{FromIterator, IntoIterator}; -use std::{mem, fmt}; - -#[cfg(feature = "compat")] -use http; - -use unicase::Ascii; - -use self::internals::{Item, VecMap, Entry}; -use self::sealed::HeaderClone; - -pub use self::shared::*; -pub use self::common::*; -pub use self::raw::Raw; -use bytes::Bytes; - -mod common; -mod internals; -mod raw; -mod shared; -pub mod parsing; - - -/// A trait for any object that will represent a header field and value. -/// -/// This trait represents the construction and identification of headers, -/// and contains trait-object unsafe methods. -pub trait Header: 'static + HeaderClone + Send + Sync { - /// Returns the name of the header field this belongs to. - /// - /// This will become an associated constant once available. - fn header_name() -> &'static str where Self: Sized; - /// Parse a header from a raw stream of bytes. - /// - /// It's possible that a request can include a header field more than once, - /// and in that case, the slice will have a length greater than 1. However, - /// it's not necessarily the case that a Header is *allowed* to have more - /// than one field value. If that's the case, you **should** return `None` - /// if `raw.len() > 1`. - fn parse_header(raw: &Raw) -> ::Result where Self: Sized; - /// Format a header to outgoing stream. - /// - /// Most headers should be formatted on one line, and so a common pattern - /// would be to implement `std::fmt::Display` for this type as well, and - /// then just call `f.fmt_line(self)`. - /// - /// ## Note - /// - /// This has the ability to format a header over multiple lines. - /// - /// The main example here is `Set-Cookie`, which requires that every - /// cookie being set be specified in a separate line. Almost every other - /// case should only format as 1 single line. - #[inline] - fn fmt_header(&self, f: &mut Formatter) -> fmt::Result; -} - -mod sealed { - use super::Header; - - #[doc(hidden)] - pub trait HeaderClone { - fn clone_box(&self) -> Box
; - } - - impl HeaderClone for T { - #[inline] - fn clone_box(&self) -> Box
{ - Box::new(self.clone()) - } - } -} - - -/// A formatter used to serialize headers to an output stream. -#[allow(missing_debug_implementations)] -pub struct Formatter<'a, 'b: 'a>(Multi<'a, 'b>); - -enum Multi<'a, 'b: 'a> { - Line(&'a str, &'a mut fmt::Formatter<'b>), - Join(bool, &'a mut fmt::Formatter<'b>), - Raw(&'a mut Raw), -} - -impl<'a, 'b> Formatter<'a, 'b> { - - /// Format one 'line' of a header. - /// - /// This writes the header name plus the `Display` value as a single line. - /// - /// ## Note - /// - /// This has the ability to format a header over multiple lines. - /// - /// The main example here is `Set-Cookie`, which requires that every - /// cookie being set be specified in a separate line. Almost every other - /// case should only format as 1 single line. - pub fn fmt_line(&mut self, line: &fmt::Display) -> fmt::Result { - use std::fmt::Write; - match self.0 { - Multi::Line(name, ref mut f) => { - try!(f.write_str(name)); - try!(f.write_str(": ")); - try!(write!(NewlineReplacer(*f), "{}", line)); - f.write_str("\r\n") - }, - Multi::Join(ref mut first, ref mut f) => { - if !*first { - try!(f.write_str(", ")); - } else { - *first = false; - } - write!(NewlineReplacer(*f), "{}", line) - } - Multi::Raw(ref mut raw) => { - let mut s = String::new(); - try!(write!(NewlineReplacer(&mut s), "{}", line)); - raw.push(s); - Ok(()) - } - } - } - - fn danger_fmt_line_without_newline_replacer(&mut self, line: &T) -> fmt::Result { - use std::fmt::Write; - match self.0 { - Multi::Line(name, ref mut f) => { - try!(f.write_str(name)); - try!(f.write_str(": ")); - try!(fmt::Display::fmt(line, f)); - f.write_str("\r\n") - }, - Multi::Join(ref mut first, ref mut f) => { - if !*first { - try!(f.write_str(", ")); - } else { - *first = false; - } - fmt::Display::fmt(line, f) - } - Multi::Raw(ref mut raw) => { - let mut s = String::new(); - try!(write!(s, "{}", line)); - raw.push(s); - Ok(()) - } - } - } -} - -struct ValueString<'a>(&'a Item); - -impl<'a> fmt::Debug for ValueString<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - try!(f.write_str("\"")); - try!(self.0.write_h1(&mut Formatter(Multi::Join(true, f)))); - f.write_str("\"") - } -} - -impl<'a> fmt::Display for ValueString<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.write_h1(&mut Formatter(Multi::Join(true, f))) - } -} - -struct NewlineReplacer<'a, F: fmt::Write + 'a>(&'a mut F); - -impl<'a, F: fmt::Write + 'a> fmt::Write for NewlineReplacer<'a, F> { - #[inline] - fn write_str(&mut self, s: &str) -> fmt::Result { - let mut since = 0; - for (i, &byte) in s.as_bytes().iter().enumerate() { - if byte == b'\r' || byte == b'\n' { - try!(self.0.write_str(&s[since..i])); - try!(self.0.write_str(" ")); - since = i + 1; - } - } - if since < s.len() { - self.0.write_str(&s[since..]) - } else { - Ok(()) - } - } - - #[inline] - fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { - fmt::write(self, args) - } -} - - -impl Header + Send + Sync { - // A trait object looks like this: - // - // TraitObject { data: *mut (), vtable: *mut () } - // - // So, we transmute &Trait into a (*mut (), *mut ()). This depends on the - // order the compiler has chosen to represent a TraitObject. - // - // It has been assured that this order will be stable. - #[inline] - unsafe fn downcast_ref_unchecked(&self) -> &T { - &*(mem::transmute::<*const _, (*const (), *const ())>(self).0 as *const T) - } - - #[inline] - unsafe fn downcast_mut_unchecked(&mut self) -> &mut T { - &mut *(mem::transmute::<*mut _, (*mut (), *mut ())>(self).0 as *mut T) - } - - #[inline] - unsafe fn downcast_unchecked(self: Box) -> T { - *Box::from_raw(mem::transmute::<*mut _, (*mut (), *mut ())>(Box::into_raw(self)).0 as *mut T) - } -} - -impl Clone for Box
{ - #[inline] - fn clone(&self) -> Box
{ - self.clone_box() - } -} - -#[inline] -fn header_name() -> &'static str { - ::header_name() -} - -/// A map of header fields on requests and responses. -#[derive(Clone)] -pub struct Headers { - data: VecMap, -} - -impl Default for Headers { - fn default() -> Headers { - Headers::new() - } -} - -macro_rules! literals { - ($($len:expr => $($header:path),+;)+) => ( - fn maybe_literal(s: &str) -> Cow<'static, str> { - match s.len() { - $($len => { - $( - if Ascii::new(<$header>::header_name()) == Ascii::new(s) { - return Cow::Borrowed(<$header>::header_name()); - } - )+ - })+ - - _ => () - } - - trace!("maybe_literal not found, copying {:?}", s); - Cow::Owned(s.to_owned()) - } - - #[test] - fn test_literal_lens() { - $( - $({ - let s = <$header>::header_name(); - assert!(s.len() == $len, "{:?} has len of {}, listed as {}", s, s.len(), $len); - })+ - )+ - } - ); -} - -literals! { - 4 => Host, Date, ETag; - 5 => Allow, Range; - 6 => Accept, Cookie, Server, Expect; - 7 => Upgrade, Referer, Expires; - 8 => Location, IfMatch, IfRange; - 10 => UserAgent, Connection, SetCookie; - 12 => ContentType; - 13 => Authorization, CacheControl, LastModified, IfNoneMatch, AcceptRanges, ContentRange; - 14 => ContentLength, AcceptCharset; - 15 => AcceptEncoding, AcceptLanguage; - 17 => TransferEncoding; - 25 => StrictTransportSecurity; - 27 => AccessControlAllowOrigin; -} - -impl Headers { - - /// Creates a new, empty headers map. - #[inline] - pub fn new() -> Headers { - Headers::with_capacity(0) - } - - /// Creates a new `Headers` struct with space reserved for `len` headers. - #[inline] - pub fn with_capacity(len: usize) -> Headers { - Headers { - data: VecMap::with_capacity(len) - } - } - - /// Set a header field to the corresponding value. - /// - /// The field is determined by the type of the value being set. - pub fn set(&mut self, value: H) { - self.data.insert(HeaderName(Ascii::new(Cow::Borrowed(header_name::()))), - Item::new_typed(value)); - } - - pub(crate) fn set_pos(&mut self, pos: usize, value: H) { - self.data.insert_pos( - HeaderName(Ascii::new(Cow::Borrowed(header_name::()))), - Item::new_typed(value), - pos, - ); - } - - /// Get a reference to the header field's value, if it exists. - pub fn get(&self) -> Option<&H> { - self.data.get(&HeaderName(Ascii::new(Cow::Borrowed(header_name::())))) - .and_then(Item::typed::) - } - - /// Get a mutable reference to the header field's value, if it exists. - pub fn get_mut(&mut self) -> Option<&mut H> { - self.data.get_mut(&HeaderName(Ascii::new(Cow::Borrowed(header_name::())))) - .and_then(Item::typed_mut::) - } - - /// Returns a boolean of whether a certain header is in the map. - /// - /// Example: - /// - /// ``` - /// # use hyper::header::Headers; - /// # use hyper::header::ContentType; - /// # let mut headers = Headers::new(); - /// headers.set(ContentType::json()); - /// assert!(headers.has::()); - /// ``` - pub fn has(&self) -> bool { - self.data.contains_key(&HeaderName(Ascii::new(Cow::Borrowed(header_name::())))) - } - - /// Removes a header from the map, if one existed. - /// Returns the header, if one has been removed and could be parsed. - /// - /// Note that this function may return `None` even though a header was removed. If you want to - /// know whether a header exists, rather rely on `has`. - pub fn remove(&mut self) -> Option { - self.data.remove(&HeaderName(Ascii::new(Cow::Borrowed(header_name::())))) - .and_then(Item::into_typed::) - } - - /// Returns an iterator over the header fields. - pub fn iter(&self) -> HeadersItems { - HeadersItems { - inner: self.data.iter() - } - } - - /// Returns the number of headers in the map. - pub fn len(&self) -> usize { - self.data.len() - } - - /// Remove all headers from the map. - pub fn clear(&mut self) { - self.data.clear() - } - - /// Access the raw value of a header. - /// - /// Prefer to use the typed getters instead. - /// - /// Example: - /// - /// ``` - /// # use hyper::header::Headers; - /// # let mut headers = Headers::new(); - /// # headers.set_raw("content-type", "text/plain"); - /// let raw = headers.get_raw("content-type").unwrap(); - /// assert_eq!(raw, "text/plain"); - /// ``` - pub fn get_raw(&self, name: &str) -> Option<&Raw> { - self.data - .get(name) - .map(Item::raw) - } - - /// Set the raw value of a header, bypassing any typed headers. - /// - /// Example: - /// - /// ``` - /// # use hyper::header::Headers; - /// # let mut headers = Headers::new(); - /// headers.set_raw("content-length", b"1".as_ref()); - /// headers.set_raw("content-length", "2"); - /// headers.set_raw("content-length", "3".to_string()); - /// headers.set_raw("content-length", vec![vec![b'4']]); - /// ``` - pub fn set_raw>, V: Into>(&mut self, name: K, value: V) { - let name = name.into(); - let value = value.into(); - self.data.insert(HeaderName(Ascii::new(name)), Item::new_raw(value)); - } - - /// Append a value to raw value of this header. - /// - /// If a header already contains a value, this will add another line to it. - /// - /// If a header does not exist for this name, a new one will be created with - /// the value. - /// - /// Example: - /// - /// ``` - /// # use hyper::header::Headers; - /// # let mut headers = Headers::new(); - /// headers.append_raw("x-foo", b"bar".to_vec()); - /// headers.append_raw("x-foo", b"quux".to_vec()); - /// ``` - pub fn append_raw>, V: Into>(&mut self, name: K, value: V) { - let name = name.into(); - let value = value.into(); - let name = HeaderName(Ascii::new(name)); - if let Some(item) = self.data.get_mut(&name) { - item.raw_mut().push(value); - return; - } - self.data.insert(name, Item::new_raw(value)); - } - - /// Remove a header by name. - pub fn remove_raw(&mut self, name: &str) { - self.data.remove(name); - } - -} - -impl PartialEq for Headers { - fn eq(&self, other: &Headers) -> bool { - if self.len() != other.len() { - return false; - } - - for header in self.iter() { - match other.get_raw(header.name()) { - Some(val) if val == self.get_raw(header.name()).unwrap() => {}, - _ => { return false; } - } - } - true - } -} - -impl fmt::Display for Headers { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - for header in self.iter() { - try!(fmt::Display::fmt(&header, f)); - } - Ok(()) - } -} - -impl fmt::Debug for Headers { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_map() - .entries(self.iter().map(|view| (view.0.as_ref(), ValueString(view.1)))) - .finish() - } -} - -#[cfg(feature = "compat")] -impl From for Headers { - fn from(mut header_map: http::HeaderMap) -> Headers { - let mut headers = Headers::new(); - for (name, mut value_drain) in header_map.drain() { - if let Some(first_value) = value_drain.next() { - let mut raw: Raw = first_value.as_bytes().into(); - for value in value_drain { - raw.push(value.as_bytes()); - } - headers.append_raw(name.as_str().to_string(), raw); - } - } - headers - } -} - -#[cfg(feature = "compat")] -impl From for http::HeaderMap { - fn from(headers: Headers) -> http::HeaderMap { - let mut header_map = http::HeaderMap::new(); - for header in headers.iter() { - let entry = header_map.entry(header.name()) - .expect("attempted to convert invalid header name"); - let mut value_iter = header.raw().iter().map(|line| { - http::header::HeaderValue::from_bytes(line) - .expect("attempted to convert invalid header value") - }); - match entry { - http::header::Entry::Occupied(mut occupied) => { - for value in value_iter { - occupied.append(value); - } - }, - http::header::Entry::Vacant(vacant) => { - if let Some(first_value) = value_iter.next() { - let mut occupied = vacant.insert_entry(first_value); - for value in value_iter { - occupied.append(value); - } - } - } - } - } - header_map - } -} - -/// An `Iterator` over the fields in a `Headers` map. -#[allow(missing_debug_implementations)] -pub struct HeadersItems<'a> { - inner: ::std::slice::Iter<'a, (HeaderName, Item)> -} - -impl<'a> Iterator for HeadersItems<'a> { - type Item = HeaderView<'a>; - - fn next(&mut self) -> Option> { - self.inner.next().map(|&(ref k, ref v)| HeaderView(k, v)) - } -} - -/// Returned with the `HeadersItems` iterator. -pub struct HeaderView<'a>(&'a HeaderName, &'a Item); - -impl<'a> HeaderView<'a> { - /// Check if a HeaderView is a certain Header. - #[inline] - pub fn is(&self) -> bool { - HeaderName(Ascii::new(Cow::Borrowed(header_name::()))) == *self.0 - } - - /// Get the Header name as a slice. - #[inline] - pub fn name(&self) -> &'a str { - self.0.as_ref() - } - - /// Cast the value to a certain Header type. - #[inline] - pub fn value(&self) -> Option<&'a H> { - self.1.typed::() - } - - /// Get just the header value as a String. - /// - /// This will join multiple values of this header with a `, `. - /// - /// **Warning:** This may not be the format that should be used to send - /// a Request or Response. - #[inline] - pub fn value_string(&self) -> String { - ValueString(self.1).to_string() - } - - /// Access the raw value of the header. - #[inline] - pub fn raw(&self) -> &Raw { - self.1.raw() - } -} - -impl<'a> fmt::Display for HeaderView<'a> { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.1.write_h1(&mut Formatter(Multi::Line(self.0.as_ref(), f))) - } -} - -impl<'a> fmt::Debug for HeaderView<'a> { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} - -impl<'a> Extend> for Headers { - fn extend>>(&mut self, iter: I) { - for header in iter { - self.data.insert((*header.0).clone(), (*header.1).clone()); - } - } -} - -impl<'a> Extend<(&'a str, Bytes)> for Headers { - fn extend>(&mut self, iter: I) { - for (name, value) in iter { - let name = HeaderName(Ascii::new(maybe_literal(name))); - //let trim = header.value.iter().rev().take_while(|&&x| x == b' ').count(); - - match self.data.entry(name) { - Entry::Vacant(entry) => { - entry.insert(Item::new_raw(self::raw::parsed(value))); - } - Entry::Occupied(entry) => { - self::raw::push(entry.into_mut().raw_mut(), value); - } - }; - } - } -} - -impl<'a> FromIterator> for Headers { - fn from_iter>>(iter: I) -> Headers { - let mut headers = Headers::new(); - headers.extend(iter); - headers - } -} - -#[derive(Clone, Debug)] -struct HeaderName(Ascii>); - -impl fmt::Display for HeaderName { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(self.0.as_ref(), f) - } -} - -impl AsRef for HeaderName { - fn as_ref(&self) -> &str { - self.0.as_ref() - } -} - -impl PartialEq for HeaderName { - #[inline] - fn eq(&self, other: &HeaderName) -> bool { - let s = self.as_ref(); - let k = other.as_ref(); - if s.as_ptr() == k.as_ptr() && s.len() == k.len() { - true - } else { - self.0 == other.0 - } - } -} - -impl PartialEq for str { - fn eq(&self, other: &HeaderName) -> bool { - let k = other.as_ref(); - if self.as_ptr() == k.as_ptr() && self.len() == k.len() { - true - } else { - other.0 == self - } - } -} - -#[cfg(test)] -mod tests { - use std::fmt; - use super::{Headers, Header, Raw, ContentLength, ContentType, Host, SetCookie}; - - #[cfg(feature = "nightly")] - use test::Bencher; - - macro_rules! make_header { - ($name:expr, $value:expr) => ({ - let mut headers = Headers::new(); - headers.set_raw(String::from_utf8($name.to_vec()).unwrap(), $value.to_vec()); - headers - }); - ($text:expr) => ({ - let bytes = $text; - let colon = bytes.iter().position(|&x| x == b':').unwrap(); - make_header!(&bytes[..colon], &bytes[colon + 2..]) - }) - } - #[test] - fn test_from_raw() { - let headers = make_header!(b"Content-Length", b"10"); - assert_eq!(headers.get(), Some(&ContentLength(10))); - } - - #[derive(Clone, PartialEq, Debug)] - struct CrazyLength(Option, usize); - - impl Header for CrazyLength { - fn header_name() -> &'static str { - "content-length" - } - fn parse_header(raw: &Raw) -> ::Result { - use std::str::from_utf8; - use std::str::FromStr; - - if let Some(line) = raw.one() { - let s = try!(from_utf8(line).map(|s| FromStr::from_str(s).map_err(|_| ::Error::Header))); - s.map(|u| CrazyLength(Some(false), u)) - } else { - Err(::Error::Header) - } - } - - fn fmt_header(&self, f: &mut super::Formatter) -> fmt::Result { - f.fmt_line(self) - } - } - - impl fmt::Display for CrazyLength { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let CrazyLength(ref opt, ref value) = *self; - write!(f, "{:?}, {:?}", opt, value) - } - } - - #[test] - fn test_different_structs_for_same_header() { - let headers = make_header!(b"Content-Length: 10"); - assert_eq!(headers.get::(), Some(&ContentLength(10))); - assert_eq!(headers.get::(), Some(&CrazyLength(Some(false), 10))); - } - - #[test] - fn test_trailing_whitespace() { - let headers = make_header!(b"Content-Length: 10 "); - assert_eq!(headers.get::(), Some(&ContentLength(10))); - } - - #[test] - fn test_multiple_reads() { - let headers = make_header!(b"Content-Length: 10"); - let ContentLength(one) = *headers.get::().unwrap(); - let ContentLength(two) = *headers.get::().unwrap(); - assert_eq!(one, two); - } - - #[test] - fn test_different_reads() { - let mut headers = Headers::new(); - headers.set_raw("Content-Length", "10"); - headers.set_raw("Content-Type", "text/plain"); - let ContentLength(_) = *headers.get::().unwrap(); - let ContentType(_) = *headers.get::().unwrap(); - } - - #[test] - fn test_typed_get_raw() { - let mut headers = Headers::new(); - headers.set(ContentLength(15)); - assert_eq!(headers.get_raw("content-length").unwrap(), "15"); - - headers.set(SetCookie(vec![ - "foo=bar".to_string(), - "baz=quux; Path=/path".to_string() - ])); - assert_eq!(headers.get_raw("set-cookie").unwrap(), &["foo=bar", "baz=quux; Path=/path"][..]); - } - - #[test] - fn test_get_mutable() { - let mut headers = make_header!(b"Content-Length: 10"); - *headers.get_mut::().unwrap() = ContentLength(20); - assert_eq!(headers.get_raw("content-length").unwrap(), &[b"20".to_vec()][..]); - assert_eq!(*headers.get::().unwrap(), ContentLength(20)); - } - - #[test] - fn test_headers_to_string() { - let mut headers = Headers::new(); - headers.set(ContentLength(15)); - headers.set(Host::new("foo.bar", None)); - - let s = headers.to_string(); - assert!(s.contains("Host: foo.bar\r\n")); - assert!(s.contains("Content-Length: 15\r\n")); - } - - #[test] - fn test_headers_to_string_raw() { - let mut headers = make_header!(b"Content-Length: 10"); - headers.set_raw("x-foo", vec![b"foo".to_vec(), b"bar".to_vec()]); - let s = headers.to_string(); - assert_eq!(s, "Content-Length: 10\r\nx-foo: foo\r\nx-foo: bar\r\n"); - } - - #[test] - fn test_set_raw() { - let mut headers = Headers::new(); - headers.set(ContentLength(10)); - headers.set_raw("content-LENGTH", vec![b"20".to_vec()]); - assert_eq!(headers.get_raw("Content-length").unwrap(), &[b"20".to_vec()][..]); - assert_eq!(headers.get(), Some(&ContentLength(20))); - } - - #[test] - fn test_append_raw() { - let mut headers = Headers::new(); - headers.set(ContentLength(10)); - headers.append_raw("content-LENGTH", b"20".to_vec()); - assert_eq!(headers.get_raw("Content-length").unwrap(), &[b"10".to_vec(), b"20".to_vec()][..]); - headers.append_raw("x-foo", "bar"); - assert_eq!(headers.get_raw("x-foo").unwrap(), &[b"bar".to_vec()][..]); - } - - #[test] - fn test_remove_raw() { - let mut headers = Headers::new(); - headers.set_raw("content-LENGTH", vec![b"20".to_vec()]); - headers.remove_raw("content-LENGTH"); - assert_eq!(headers.get_raw("Content-length"), None); - } - - #[test] - fn test_remove() { - let mut headers = Headers::new(); - headers.set(ContentLength(10)); - assert_eq!(headers.remove(), Some(ContentLength(10))); - assert_eq!(headers.len(), 0); - - headers.set(ContentLength(9)); - assert_eq!(headers.len(), 1); - assert!(headers.remove::().is_none()); - assert_eq!(headers.len(), 0); - } - - #[test] - fn test_len() { - let mut headers = Headers::new(); - headers.set(ContentLength(10)); - assert_eq!(headers.len(), 1); - headers.set(ContentType::json()); - assert_eq!(headers.len(), 2); - // Redundant, should not increase count. - headers.set(ContentLength(20)); - assert_eq!(headers.len(), 2); - } - - #[test] - fn test_clear() { - let mut headers = Headers::new(); - headers.set(ContentLength(10)); - headers.set(ContentType::json()); - assert_eq!(headers.len(), 2); - headers.clear(); - assert_eq!(headers.len(), 0); - } - - #[test] - fn test_iter() { - let mut headers = Headers::new(); - headers.set(ContentLength(11)); - for header in headers.iter() { - assert!(header.is::()); - assert_eq!(header.name(), ::header_name()); - assert_eq!(header.value(), Some(&ContentLength(11))); - assert_eq!(header.value_string(), "11".to_owned()); - } - } - - #[test] - fn test_header_view_value_string() { - let mut headers = Headers::new(); - headers.set_raw("foo", vec![b"one".to_vec(), b"two".to_vec()]); - for header in headers.iter() { - assert_eq!(header.name(), "foo"); - assert_eq!(header.value_string(), "one, two"); - } - } - - #[test] - fn test_header_view_raw() { - let mut headers = Headers::new(); - headers.set_raw("foo", vec![b"one".to_vec(), b"two".to_vec()]); - for header in headers.iter() { - assert_eq!(header.name(), "foo"); - let values: Vec<&[u8]> = header.raw().iter().collect(); - assert_eq!(values, vec![b"one", b"two"]); - } - } - - #[test] - fn test_eq() { - let mut headers1 = Headers::new(); - let mut headers2 = Headers::new(); - - assert_eq!(headers1, headers2); - - headers1.set(ContentLength(11)); - headers2.set(Host::new("foo.bar", None)); - assert_ne!(headers1, headers2); - - headers1 = Headers::new(); - headers2 = Headers::new(); - - headers1.set(ContentLength(11)); - headers2.set(ContentLength(11)); - assert_eq!(headers1, headers2); - - headers1.set(ContentLength(10)); - assert_ne!(headers1, headers2); - - headers1 = Headers::new(); - headers2 = Headers::new(); - - headers1.set(Host::new("foo.bar", None)); - headers1.set(ContentLength(11)); - headers2.set(ContentLength(11)); - assert_ne!(headers1, headers2); - } - - #[test] - #[cfg(feature = "compat")] - fn test_compat() { - use http; - - let mut orig_hyper_headers = Headers::new(); - orig_hyper_headers.set(ContentLength(11)); - orig_hyper_headers.set(Host::new("foo.bar", None)); - orig_hyper_headers.append_raw("x-foo", b"bar".to_vec()); - orig_hyper_headers.append_raw("x-foo", b"quux".to_vec()); - - let mut orig_http_headers = http::HeaderMap::new(); - orig_http_headers.insert(http::header::CONTENT_LENGTH, "11".parse().unwrap()); - orig_http_headers.insert(http::header::HOST, "foo.bar".parse().unwrap()); - orig_http_headers.append("x-foo", "bar".parse().unwrap()); - orig_http_headers.append("x-foo", "quux".parse().unwrap()); - - let conv_hyper_headers: Headers = orig_http_headers.clone().into(); - let conv_http_headers: http::HeaderMap = orig_hyper_headers.clone().into(); - assert_eq!(orig_hyper_headers, conv_hyper_headers); - assert_eq!(orig_http_headers, conv_http_headers); - } - - #[cfg(feature = "nightly")] - #[bench] - fn bench_headers_new(b: &mut Bencher) { - b.iter(|| { - let mut h = Headers::new(); - h.set(ContentLength(11)); - h - }) - } - - #[cfg(feature = "nightly")] - #[bench] - fn bench_headers_get(b: &mut Bencher) { - let mut headers = Headers::new(); - headers.set(ContentLength(11)); - b.iter(|| assert_eq!(headers.get::(), Some(&ContentLength(11)))) - } - - #[cfg(feature = "nightly")] - #[bench] - fn bench_headers_get_miss(b: &mut Bencher) { - let headers = Headers::new(); - b.iter(|| assert!(headers.get::().is_none())) - } - - #[cfg(feature = "nightly")] - #[bench] - fn bench_headers_get_miss_previous_10(b: &mut Bencher) { - let mut headers = Headers::new(); - for i in 0..10 { - headers.set_raw(format!("non-standard-{}", i), "hi"); - } - b.iter(|| assert!(headers.get::().is_none())) - } - - #[cfg(feature = "nightly")] - #[bench] - fn bench_headers_set(b: &mut Bencher) { - let mut headers = Headers::new(); - b.iter(|| headers.set(ContentLength(12))) - } - - #[cfg(feature = "nightly")] - #[bench] - fn bench_headers_set_previous_10(b: &mut Bencher) { - let mut headers = Headers::new(); - for i in 0..10 { - headers.set_raw(format!("non-standard-{}", i), "hi"); - } - b.iter(|| headers.set(ContentLength(12))) - } - - #[cfg(feature = "nightly")] - #[bench] - fn bench_headers_set_raw(b: &mut Bencher) { - let mut headers = Headers::new(); - b.iter(|| headers.set_raw("non-standard", "hello")) - } - - #[cfg(feature = "nightly")] - #[bench] - fn bench_headers_set_raw_previous_10(b: &mut Bencher) { - let mut headers = Headers::new(); - for i in 0..10 { - headers.set_raw(format!("non-standard-{}", i), "hi"); - } - b.iter(|| headers.set_raw("non-standard", "hello")) - } - - #[cfg(feature = "nightly")] - #[bench] - fn bench_headers_has(b: &mut Bencher) { - let mut headers = Headers::new(); - headers.set(ContentLength(11)); - b.iter(|| assert!(headers.has::())) - } - - #[cfg(feature = "nightly")] - #[bench] - fn bench_headers_view_is(b: &mut Bencher) { - let mut headers = Headers::new(); - headers.set(ContentLength(11)); - let mut iter = headers.iter(); - let view = iter.next().unwrap(); - b.iter(|| assert!(view.is::())) - } - - #[cfg(feature = "nightly")] - #[bench] - fn bench_headers_fmt(b: &mut Bencher) { - use std::fmt::Write; - let mut buf = String::with_capacity(64); - let mut headers = Headers::new(); - headers.set(ContentLength(11)); - headers.set(ContentType::json()); - b.bytes = headers.to_string().len() as u64; - b.iter(|| { - let _ = write!(buf, "{}", headers); - ::test::black_box(&buf); - unsafe { buf.as_mut_vec().set_len(0); } - }) - } -} diff --git a/src/header/parsing.rs b/src/header/parsing.rs deleted file mode 100644 index 609ce68951..0000000000 --- a/src/header/parsing.rs +++ /dev/null @@ -1,249 +0,0 @@ -//! Utility functions for Header implementations. - -use language_tags::LanguageTag; -use std::str; -use std::str::FromStr; -use std::fmt::{self, Display}; -use percent_encoding; - -use header::Raw; -use header::shared::Charset; - - -/// Reads a single raw string when parsing a header. -pub fn from_one_raw_str(raw: &Raw) -> ::Result { - if let Some(line) = raw.one() { - if !line.is_empty() { - return from_raw_str(line) - } - } - Err(::Error::Header) -} - -/// Reads a raw string into a value. -pub fn from_raw_str(raw: &[u8]) -> ::Result { - let s = try!(str::from_utf8(raw)).trim(); - T::from_str(s).or(Err(::Error::Header)) -} - -/// Reads a comma-delimited raw header into a Vec. -#[inline] -pub fn from_comma_delimited(raw: &Raw) -> ::Result> { - let mut result = Vec::new(); - for s in raw { - let s = try!(str::from_utf8(s.as_ref())); - result.extend(s.split(',') - .filter_map(|x| match x.trim() { - "" => None, - y => Some(y) - }) - .filter_map(|x| x.trim().parse().ok())) - } - Ok(result) -} - -/// Format an array into a comma-delimited string. -pub fn fmt_comma_delimited(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result { - let mut iter = parts.iter(); - if let Some(part) = iter.next() { - try!(Display::fmt(part, f)); - } - for part in iter { - try!(f.write_str(", ")); - try!(Display::fmt(part, f)); - } - Ok(()) -} - -/// An extended header parameter value (i.e., tagged with a character set and optionally, -/// a language), as defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). -#[derive(Clone, Debug, PartialEq)] -pub struct ExtendedValue { - /// The character set that is used to encode the `value` to a string. - pub charset: Charset, - /// The human language details of the `value`, if available. - pub language_tag: Option, - /// The parameter value, as expressed in octets. - pub value: Vec, -} - -/// Parses extended header parameter values (`ext-value`), as defined in -/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). -/// -/// Extended values are denoted by parameter names that end with `*`. -/// -/// ## ABNF -/// -/// ```text -/// ext-value = charset "'" [ language ] "'" value-chars -/// ; like RFC 2231's -/// ; (see [RFC2231], Section 7) -/// -/// charset = "UTF-8" / "ISO-8859-1" / mime-charset -/// -/// mime-charset = 1*mime-charsetc -/// mime-charsetc = ALPHA / DIGIT -/// / "!" / "#" / "$" / "%" / "&" -/// / "+" / "-" / "^" / "_" / "`" -/// / "{" / "}" / "~" -/// ; as in Section 2.3 of [RFC2978] -/// ; except that the single quote is not included -/// ; SHOULD be registered in the IANA charset registry -/// -/// language = -/// -/// value-chars = *( pct-encoded / attr-char ) -/// -/// pct-encoded = "%" HEXDIG HEXDIG -/// ; see [RFC3986], Section 2.1 -/// -/// attr-char = ALPHA / DIGIT -/// / "!" / "#" / "$" / "&" / "+" / "-" / "." -/// / "^" / "_" / "`" / "|" / "~" -/// ; token except ( "*" / "'" / "%" ) -/// ``` -pub fn parse_extended_value(val: &str) -> ::Result { - - // Break into three pieces separated by the single-quote character - let mut parts = val.splitn(3,'\''); - - // Interpret the first piece as a Charset - let charset: Charset = match parts.next() { - None => return Err(::Error::Header), - Some(n) => try!(FromStr::from_str(n)), - }; - - // Interpret the second piece as a language tag - let lang: Option = match parts.next() { - None => return Err(::Error::Header), - Some("") => None, - Some(s) => match s.parse() { - Ok(lt) => Some(lt), - Err(_) => return Err(::Error::Header), - } - }; - - // Interpret the third piece as a sequence of value characters - let value: Vec = match parts.next() { - None => return Err(::Error::Header), - Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(), - }; - - Ok(ExtendedValue { - charset: charset, - language_tag: lang, - value: value, - }) -} - - -impl Display for ExtendedValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let encoded_value = - percent_encoding::percent_encode(&self.value[..], self::percent_encoding_http::HTTP_VALUE); - if let Some(ref lang) = self.language_tag { - write!(f, "{}'{}'{}", self.charset, lang, encoded_value) - } else { - write!(f, "{}''{}", self.charset, encoded_value) - } - } -} - -/// Percent encode a sequence of bytes with a character set defined in -/// [https://tools.ietf.org/html/rfc5987#section-3.2][url] -/// -/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 -pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { - let encoded = percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); - fmt::Display::fmt(&encoded, f) -} - -mod percent_encoding_http { - use percent_encoding; - - // internal module because macro is hard-coded to make a public item - // but we don't want to public export this item - define_encode_set! { - // This encode set is used for HTTP header values and is defined at - // https://tools.ietf.org/html/rfc5987#section-3.2 - pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { - ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', - '[', '\\', ']', '{', '}' - } - } -} - -#[cfg(test)] -mod tests { - use header::shared::Charset; - use super::{ExtendedValue, parse_extended_value}; - use language_tags::LanguageTag; - - #[test] - fn test_parse_extended_value_with_encoding_and_language_tag() { - let expected_language_tag = "en".parse::().unwrap(); - // RFC 5987, Section 3.2.2 - // Extended notation, using the Unicode character U+00A3 (POUND SIGN) - let result = parse_extended_value("iso-8859-1'en'%A3%20rates"); - assert!(result.is_ok()); - let extended_value = result.unwrap(); - assert_eq!(Charset::Iso_8859_1, extended_value.charset); - assert!(extended_value.language_tag.is_some()); - assert_eq!(expected_language_tag, extended_value.language_tag.unwrap()); - assert_eq!(vec![163, b' ', b'r', b'a', b't', b'e', b's'], extended_value.value); - } - - #[test] - fn test_parse_extended_value_with_encoding() { - // RFC 5987, Section 3.2.2 - // Extended notation, using the Unicode characters U+00A3 (POUND SIGN) - // and U+20AC (EURO SIGN) - let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates"); - assert!(result.is_ok()); - let extended_value = result.unwrap(); - assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset); - assert!(extended_value.language_tag.is_none()); - assert_eq!(vec![194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', b't', b'e', b's'], extended_value.value); - } - - #[test] - fn test_parse_extended_value_missing_language_tag_and_encoding() { - // From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2 - let result = parse_extended_value("foo%20bar.html"); - assert!(result.is_err()); - } - - #[test] - fn test_parse_extended_value_partially_formatted() { - let result = parse_extended_value("UTF-8'missing third part"); - assert!(result.is_err()); - } - - #[test] - fn test_parse_extended_value_partially_formatted_blank() { - let result = parse_extended_value("blank second part'"); - assert!(result.is_err()); - } - - #[test] - fn test_fmt_extended_value_with_encoding_and_language_tag() { - let extended_value = ExtendedValue { - charset: Charset::Iso_8859_1, - language_tag: Some("en".parse().expect("Could not parse language tag")), - value: vec![163, b' ', b'r', b'a', b't', b'e', b's'], - }; - assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value)); - } - - #[test] - fn test_fmt_extended_value_with_encoding() { - let extended_value = ExtendedValue { - charset: Charset::Ext("UTF-8".to_string()), - language_tag: None, - value: vec![194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', - b't', b'e', b's'], - }; - assert_eq!("UTF-8''%C2%A3%20and%20%E2%82%AC%20rates", - format!("{}", extended_value)); - } -} diff --git a/src/header/raw.rs b/src/header/raw.rs deleted file mode 100644 index f0f72f5707..0000000000 --- a/src/header/raw.rs +++ /dev/null @@ -1,312 +0,0 @@ -use std::borrow::Cow; -use std::fmt; -use bytes::Bytes; - -/// A raw header value. -#[derive(Clone, Debug)] -pub struct Raw(Lines); - -impl Raw { - /// Returns the amount of lines. - #[inline] - pub fn len(&self) -> usize { - match self.0 { - Lines::Empty => 0, - Lines::One(..) => 1, - Lines::Many(ref lines) => lines.len() - } - } - - /// Returns the line if there is only 1. - #[inline] - pub fn one(&self) -> Option<&[u8]> { - match self.0 { - Lines::One(ref line) => Some(line.as_ref()), - Lines::Many(ref lines) if lines.len() == 1 => Some(lines[0].as_ref()), - _ => None - } - } - - /// Iterate the lines of raw bytes. - #[inline] - pub fn iter(&self) -> RawLines { - RawLines { - inner: &self.0, - pos: 0, - } - } - - /// Append a line to this `Raw` header value. - pub fn push>(&mut self, val: V) { - let raw = val.into(); - match raw.0 { - Lines::Empty => (), - Lines::One(one) => self.push_line(one), - Lines::Many(lines) => { - for line in lines { - self.push_line(line); - } - } - } - } - - fn push_line(&mut self, line: Bytes) { - let lines = ::std::mem::replace(&mut self.0, Lines::Empty); - match lines { - Lines::Empty => { - self.0 = Lines::One(line); - } - Lines::One(one) => { - self.0 = Lines::Many(vec![one, line]); - } - Lines::Many(mut lines) => { - lines.push(line); - self.0 = Lines::Many(lines); - } - } - } -} - -#[derive(Clone)] -enum Lines { - Empty, - One(Bytes), - Many(Vec), -} - -fn eq_many, B: AsRef<[u8]>>(a: &[A], b: &[B]) -> bool { - if a.len() != b.len() { - false - } else { - for (a, b) in a.iter().zip(b.iter()) { - if a.as_ref() != b.as_ref() { - return false - } - } - true - } -} - -fn eq>(raw: &Raw, b: &[B]) -> bool { - match raw.0 { - Lines::Empty => b.is_empty(), - Lines::One(ref line) => eq_many(&[line], b), - Lines::Many(ref lines) => eq_many(lines, b) - } -} - -impl PartialEq for Raw { - fn eq(&self, other: &Raw) -> bool { - match other.0 { - Lines::Empty => eq(self, &[] as &[Bytes]), - Lines::One(ref line) => eq(self, &[line]), - Lines::Many(ref lines) => eq(self, lines), - } - } -} - -impl Eq for Raw {} - -impl PartialEq<[Vec]> for Raw { - fn eq(&self, bytes: &[Vec]) -> bool { - eq(self, bytes) - } -} - -impl<'a> PartialEq<[&'a [u8]]> for Raw { - fn eq(&self, bytes: &[&[u8]]) -> bool { - eq(self, bytes) - } -} - -impl PartialEq<[String]> for Raw { - fn eq(&self, bytes: &[String]) -> bool { - eq(self, bytes) - } -} - -impl<'a> PartialEq<[&'a str]> for Raw { - fn eq(&self, bytes: &[&'a str]) -> bool { - eq(self, bytes) - } -} - -impl PartialEq<[u8]> for Raw { - fn eq(&self, bytes: &[u8]) -> bool { - match self.0 { - Lines::Empty => bytes.is_empty(), - Lines::One(ref line) => line.as_ref() == bytes, - Lines::Many(..) => false - } - } -} - -impl PartialEq for Raw { - fn eq(&self, s: &str) -> bool { - self == s.as_bytes() - } -} - -impl From>> for Raw { - #[inline] - fn from(val: Vec>) -> Raw { - Raw(Lines::Many( - val.into_iter() - .map(|vec| maybe_literal(vec.into())) - .collect() - )) - } -} - -impl From for Raw { - #[inline] - fn from(val: String) -> Raw { - Raw::from(val.into_bytes()) - } -} - -impl From> for Raw { - #[inline] - fn from(val: Vec) -> Raw { - Raw(Lines::One(maybe_literal(val.into()))) - } -} - -impl<'a> From<&'a str> for Raw { - fn from(val: &'a str) -> Raw { - Raw::from(val.as_bytes()) - } -} - -impl<'a> From<&'a [u8]> for Raw { - fn from(val: &'a [u8]) -> Raw { - Raw(Lines::One(maybe_literal(val.into()))) - } -} - -impl From for Raw { - #[inline] - fn from(val: Bytes) -> Raw { - Raw(Lines::One(val)) - } -} - -pub fn parsed(val: Bytes) -> Raw { - Raw(Lines::One(From::from(val))) -} - -pub fn push(raw: &mut Raw, val: Bytes) { - raw.push_line(val); -} - -pub fn new() -> Raw { - Raw(Lines::Empty) -} - -impl fmt::Debug for Lines { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Lines::Empty => f.pad("[]"), - Lines::One(ref line) => fmt::Debug::fmt(&[line], f), - Lines::Many(ref lines) => fmt::Debug::fmt(lines, f) - } - } -} - -impl ::std::ops::Index for Raw { - type Output = [u8]; - - fn index(&self, idx: usize) -> &[u8] { - match self.0 { - Lines::Empty => panic!("index of out of bounds: {}", idx), - Lines::One(ref line) => if idx == 0 { - line.as_ref() - } else { - panic!("index out of bounds: {}", idx) - }, - Lines::Many(ref lines) => lines[idx].as_ref() - } - } -} - -macro_rules! literals { - ($($len:expr => $($value:expr),+;)+) => ( - fn maybe_literal(s: Cow<[u8]>) -> Bytes { - match s.len() { - $($len => { - $( - if s.as_ref() == $value { - return Bytes::from_static($value); - } - )+ - })+ - - _ => () - } - - Bytes::from(s.into_owned()) - } - - #[test] - fn test_literal_lens() { - $( - $({ - let s = $value; - assert!(s.len() == $len, "{:?} has len of {}, listed as {}", s, s.len(), $len); - })+ - )+ - } - ); -} - -literals! { - 1 => b"*", b"0"; - 3 => b"*/*"; - 4 => b"gzip"; - 5 => b"close"; - 7 => b"chunked"; - 10 => b"keep-alive"; -} - -impl<'a> IntoIterator for &'a Raw { - type IntoIter = RawLines<'a>; - type Item = &'a [u8]; - - fn into_iter(self) -> RawLines<'a> { - self.iter() - } -} - -pub struct RawLines<'a> { - inner: &'a Lines, - pos: usize, -} - -impl<'a> fmt::Debug for RawLines<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_tuple("RawLines") - .field(&self.inner) - .finish() - } -} - -impl<'a> Iterator for RawLines<'a> { - type Item = &'a [u8]; - - #[inline] - fn next(&mut self) -> Option<&'a [u8]> { - let current_pos = self.pos; - self.pos += 1; - match *self.inner { - Lines::Empty => None, - Lines::One(ref line) => { - if current_pos == 0 { - Some(line.as_ref()) - } else { - None - } - } - Lines::Many(ref lines) => lines.get(current_pos).map(|l| l.as_ref()), - } - } -} diff --git a/src/header/shared/charset.rs b/src/header/shared/charset.rs deleted file mode 100644 index 758a75c255..0000000000 --- a/src/header/shared/charset.rs +++ /dev/null @@ -1,154 +0,0 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; -#[allow(unused)] -use std::ascii::AsciiExt; - -use self::Charset::*; - -/// A Mime charset. -/// -/// The string representation is normalised to upper case. -/// -/// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url]. -/// -/// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml -#[derive(Clone,Debug,PartialEq)] -#[allow(non_camel_case_types)] -pub enum Charset{ - /// US ASCII - Us_Ascii, - /// ISO-8859-1 - Iso_8859_1, - /// ISO-8859-2 - Iso_8859_2, - /// ISO-8859-3 - Iso_8859_3, - /// ISO-8859-4 - Iso_8859_4, - /// ISO-8859-5 - Iso_8859_5, - /// ISO-8859-6 - Iso_8859_6, - /// ISO-8859-7 - Iso_8859_7, - /// ISO-8859-8 - Iso_8859_8, - /// ISO-8859-9 - Iso_8859_9, - /// ISO-8859-10 - Iso_8859_10, - /// Shift_JIS - Shift_Jis, - /// EUC-JP - Euc_Jp, - /// ISO-2022-KR - Iso_2022_Kr, - /// EUC-KR - Euc_Kr, - /// ISO-2022-JP - Iso_2022_Jp, - /// ISO-2022-JP-2 - Iso_2022_Jp_2, - /// ISO-8859-6-E - Iso_8859_6_E, - /// ISO-8859-6-I - Iso_8859_6_I, - /// ISO-8859-8-E - Iso_8859_8_E, - /// ISO-8859-8-I - Iso_8859_8_I, - /// GB2312 - Gb2312, - /// Big5 - Big5, - /// KOI8-R - Koi8_R, - /// An arbitrary charset specified as a string - Ext(String) -} - -impl Charset { - fn name(&self) -> &str { - match *self { - Us_Ascii => "US-ASCII", - Iso_8859_1 => "ISO-8859-1", - Iso_8859_2 => "ISO-8859-2", - Iso_8859_3 => "ISO-8859-3", - Iso_8859_4 => "ISO-8859-4", - Iso_8859_5 => "ISO-8859-5", - Iso_8859_6 => "ISO-8859-6", - Iso_8859_7 => "ISO-8859-7", - Iso_8859_8 => "ISO-8859-8", - Iso_8859_9 => "ISO-8859-9", - Iso_8859_10 => "ISO-8859-10", - Shift_Jis => "Shift-JIS", - Euc_Jp => "EUC-JP", - Iso_2022_Kr => "ISO-2022-KR", - Euc_Kr => "EUC-KR", - Iso_2022_Jp => "ISO-2022-JP", - Iso_2022_Jp_2 => "ISO-2022-JP-2", - Iso_8859_6_E => "ISO-8859-6-E", - Iso_8859_6_I => "ISO-8859-6-I", - Iso_8859_8_E => "ISO-8859-8-E", - Iso_8859_8_I => "ISO-8859-8-I", - Gb2312 => "GB2312", - Big5 => "5", - Koi8_R => "KOI8-R", - Ext(ref s) => s - } - } -} - -impl Display for Charset { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(self.name()) - } -} - -impl FromStr for Charset { - type Err = ::Error; - fn from_str(s: &str) -> ::Result { - Ok(match s.to_ascii_uppercase().as_ref() { - "US-ASCII" => Us_Ascii, - "ISO-8859-1" => Iso_8859_1, - "ISO-8859-2" => Iso_8859_2, - "ISO-8859-3" => Iso_8859_3, - "ISO-8859-4" => Iso_8859_4, - "ISO-8859-5" => Iso_8859_5, - "ISO-8859-6" => Iso_8859_6, - "ISO-8859-7" => Iso_8859_7, - "ISO-8859-8" => Iso_8859_8, - "ISO-8859-9" => Iso_8859_9, - "ISO-8859-10" => Iso_8859_10, - "SHIFT-JIS" => Shift_Jis, - "EUC-JP" => Euc_Jp, - "ISO-2022-KR" => Iso_2022_Kr, - "EUC-KR" => Euc_Kr, - "ISO-2022-JP" => Iso_2022_Jp, - "ISO-2022-JP-2" => Iso_2022_Jp_2, - "ISO-8859-6-E" => Iso_8859_6_E, - "ISO-8859-6-I" => Iso_8859_6_I, - "ISO-8859-8-E" => Iso_8859_8_E, - "ISO-8859-8-I" => Iso_8859_8_I, - "GB2312" => Gb2312, - "5" => Big5, - "KOI8-R" => Koi8_R, - s => Ext(s.to_owned()) - }) - } -} - -#[test] -fn test_parse() { - assert_eq!(Us_Ascii,"us-ascii".parse().unwrap()); - assert_eq!(Us_Ascii,"US-Ascii".parse().unwrap()); - assert_eq!(Us_Ascii,"US-ASCII".parse().unwrap()); - assert_eq!(Shift_Jis,"Shift-JIS".parse().unwrap()); - assert_eq!(Ext("ABCD".to_owned()),"abcd".parse().unwrap()); -} - -#[test] -fn test_display() { - assert_eq!("US-ASCII", format!("{}", Us_Ascii)); - assert_eq!("ABCD", format!("{}", Ext("ABCD".to_owned()))); -} diff --git a/src/header/shared/encoding.rs b/src/header/shared/encoding.rs deleted file mode 100644 index fc972dd3c7..0000000000 --- a/src/header/shared/encoding.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::fmt; -use std::str; - -pub use self::Encoding::{Chunked, Brotli, Gzip, Deflate, Compress, Identity, EncodingExt, Trailers}; - -/// A value to represent an encoding used in `Transfer-Encoding` -/// or `Accept-Encoding` header. -#[derive(Clone, PartialEq, Debug)] -pub enum Encoding { - /// The `chunked` encoding. - Chunked, - /// The `br` encoding. - Brotli, - /// The `gzip` encoding. - Gzip, - /// The `deflate` encoding. - Deflate, - /// The `compress` encoding. - Compress, - /// The `identity` encoding. - Identity, - /// The `trailers` encoding. - Trailers, - /// Some other encoding that is less common, can be any String. - EncodingExt(String) -} - -impl fmt::Display for Encoding { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match *self { - Chunked => "chunked", - Brotli => "br", - Gzip => "gzip", - Deflate => "deflate", - Compress => "compress", - Identity => "identity", - Trailers => "trailers", - EncodingExt(ref s) => s.as_ref() - }) - } -} - -impl str::FromStr for Encoding { - type Err = ::Error; - fn from_str(s: &str) -> ::Result { - match s { - "chunked" => Ok(Chunked), - "br" => Ok(Brotli), - "deflate" => Ok(Deflate), - "gzip" => Ok(Gzip), - "compress" => Ok(Compress), - "identity" => Ok(Identity), - "trailers" => Ok(Trailers), - _ => Ok(EncodingExt(s.to_owned())) - } - } -} diff --git a/src/header/shared/entity.rs b/src/header/shared/entity.rs deleted file mode 100644 index 0ffef9e79d..0000000000 --- a/src/header/shared/entity.rs +++ /dev/null @@ -1,218 +0,0 @@ -use std::str::FromStr; -use std::fmt::{self, Display}; - -/// check that each char in the slice is either: -/// 1. `%x21`, or -/// 2. in the range `%x23` to `%x7E`, or -/// 3. above `%x80` -fn check_slice_validity(slice: &str) -> bool { - slice.bytes().all(|c| - c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80')) -} - -/// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3) -/// -/// An entity tag consists of a string enclosed by two literal double quotes. -/// Preceding the first double quote is an optional weakness indicator, -/// which always looks like `W/`. Examples for valid tags are `"xyzzy"` and `W/"xyzzy"`. -/// -/// # ABNF -/// -/// ```text -/// entity-tag = [ weak ] opaque-tag -/// weak = %x57.2F ; "W/", case-sensitive -/// opaque-tag = DQUOTE *etagc DQUOTE -/// etagc = %x21 / %x23-7E / obs-text -/// ; VCHAR except double quotes, plus obs-text -/// ``` -/// -/// # Comparison -/// To check if two entity tags are equivalent in an application always use the `strong_eq` or -/// `weak_eq` methods based on the context of the Tag. Only use `==` to check if two tags are -/// identical. -/// -/// The example below shows the results for a set of entity-tag pairs and -/// both the weak and strong comparison function results: -/// -/// | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison | -/// |---------|---------|-------------------|-----------------| -/// | `W/"1"` | `W/"1"` | no match | match | -/// | `W/"1"` | `W/"2"` | no match | no match | -/// | `W/"1"` | `"1"` | no match | match | -/// | `"1"` | `"1"` | match | match | -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct EntityTag { - /// Weakness indicator for the tag - pub weak: bool, - /// The opaque string in between the DQUOTEs - tag: String -} - -impl EntityTag { - /// Constructs a new EntityTag. - /// # Panics - /// If the tag contains invalid characters. - pub fn new(weak: bool, tag: String) -> EntityTag { - assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); - EntityTag { weak: weak, tag: tag } - } - - /// Constructs a new weak EntityTag. - /// # Panics - /// If the tag contains invalid characters. - pub fn weak(tag: String) -> EntityTag { - EntityTag::new(true, tag) - } - - /// Constructs a new strong EntityTag. - /// # Panics - /// If the tag contains invalid characters. - pub fn strong(tag: String) -> EntityTag { - EntityTag::new(false, tag) - } - - /// Get the tag. - pub fn tag(&self) -> &str { - self.tag.as_ref() - } - - /// Set the tag. - /// # Panics - /// If the tag contains invalid characters. - pub fn set_tag(&mut self, tag: String) { - assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); - self.tag = tag - } - - /// For strong comparison two entity-tags are equivalent if both are not weak and their - /// opaque-tags match character-by-character. - pub fn strong_eq(&self, other: &EntityTag) -> bool { - !self.weak && !other.weak && self.tag == other.tag - } - - /// For weak comparison two entity-tags are equivalent if their - /// opaque-tags match character-by-character, regardless of either or - /// both being tagged as "weak". - pub fn weak_eq(&self, other: &EntityTag) -> bool { - self.tag == other.tag - } - - /// The inverse of `EntityTag.strong_eq()`. - pub fn strong_ne(&self, other: &EntityTag) -> bool { - !self.strong_eq(other) - } - - /// The inverse of `EntityTag.weak_eq()`. - pub fn weak_ne(&self, other: &EntityTag) -> bool { - !self.weak_eq(other) - } -} - -impl Display for EntityTag { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.weak { - write!(f, "W/\"{}\"", self.tag) - } else { - write!(f, "\"{}\"", self.tag) - } - } -} - -impl FromStr for EntityTag { - type Err = ::Error; - fn from_str(s: &str) -> ::Result { - let length: usize = s.len(); - let slice = &s[..]; - // Early exits if it doesn't terminate in a DQUOTE. - if !slice.ends_with('"') || slice.len() < 2 { - return Err(::Error::Header); - } - // The etag is weak if its first char is not a DQUOTE. - if slice.len() >= 2 && slice.starts_with('"') - && check_slice_validity(&slice[1..length-1]) { - // No need to check if the last char is a DQUOTE, - // we already did that above. - return Ok(EntityTag { weak: false, tag: slice[1..length-1].to_owned() }); - } else if slice.len() >= 4 && slice.starts_with("W/\"") - && check_slice_validity(&slice[3..length-1]) { - return Ok(EntityTag { weak: true, tag: slice[3..length-1].to_owned() }); - } - Err(::Error::Header) - } -} - -#[cfg(test)] -mod tests { - use super::EntityTag; - - #[test] - fn test_etag_parse_success() { - // Expected success - assert_eq!("\"foobar\"".parse::().unwrap(), - EntityTag::strong("foobar".to_owned())); - assert_eq!("\"\"".parse::().unwrap(), - EntityTag::strong("".to_owned())); - assert_eq!("W/\"weaktag\"".parse::().unwrap(), - EntityTag::weak("weaktag".to_owned())); - assert_eq!("W/\"\x65\x62\"".parse::().unwrap(), - EntityTag::weak("\x65\x62".to_owned())); - assert_eq!("W/\"\"".parse::().unwrap(), EntityTag::weak("".to_owned())); - } - - #[test] - fn test_etag_parse_failures() { - // Expected failures - assert!("no-dquotes".parse::().is_err()); - assert!("w/\"the-first-w-is-case-sensitive\"".parse::().is_err()); - assert!("".parse::().is_err()); - assert!("\"unmatched-dquotes1".parse::().is_err()); - assert!("unmatched-dquotes2\"".parse::().is_err()); - assert!("matched-\"dquotes\"".parse::().is_err()); - } - - #[test] - fn test_etag_fmt() { - assert_eq!(format!("{}", EntityTag::strong("foobar".to_owned())), "\"foobar\""); - assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\""); - assert_eq!(format!("{}", EntityTag::weak("weak-etag".to_owned())), "W/\"weak-etag\""); - assert_eq!(format!("{}", EntityTag::weak("\u{0065}".to_owned())), "W/\"\x65\""); - assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\""); - } - - #[test] - fn test_cmp() { - // | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison | - // |---------|---------|-------------------|-----------------| - // | `W/"1"` | `W/"1"` | no match | match | - // | `W/"1"` | `W/"2"` | no match | no match | - // | `W/"1"` | `"1"` | no match | match | - // | `"1"` | `"1"` | match | match | - let mut etag1 = EntityTag::weak("1".to_owned()); - let mut etag2 = EntityTag::weak("1".to_owned()); - assert!(!etag1.strong_eq(&etag2)); - assert!(etag1.weak_eq(&etag2)); - assert!(etag1.strong_ne(&etag2)); - assert!(!etag1.weak_ne(&etag2)); - - etag1 = EntityTag::weak("1".to_owned()); - etag2 = EntityTag::weak("2".to_owned()); - assert!(!etag1.strong_eq(&etag2)); - assert!(!etag1.weak_eq(&etag2)); - assert!(etag1.strong_ne(&etag2)); - assert!(etag1.weak_ne(&etag2)); - - etag1 = EntityTag::weak("1".to_owned()); - etag2 = EntityTag::strong("1".to_owned()); - assert!(!etag1.strong_eq(&etag2)); - assert!(etag1.weak_eq(&etag2)); - assert!(etag1.strong_ne(&etag2)); - assert!(!etag1.weak_ne(&etag2)); - - etag1 = EntityTag::strong("1".to_owned()); - etag2 = EntityTag::strong("1".to_owned()); - assert!(etag1.strong_eq(&etag2)); - assert!(etag1.weak_eq(&etag2)); - assert!(!etag1.strong_ne(&etag2)); - assert!(!etag1.weak_ne(&etag2)); - } -} diff --git a/src/header/shared/httpdate.rs b/src/header/shared/httpdate.rs deleted file mode 100644 index a88cd21f03..0000000000 --- a/src/header/shared/httpdate.rs +++ /dev/null @@ -1,117 +0,0 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -use time; - -/// A timestamp with HTTP formatting and parsing -// Prior to 1995, there were three different formats commonly used by -// servers to communicate timestamps. For compatibility with old -// implementations, all three are defined here. The preferred format is -// a fixed-length and single-zone subset of the date and time -// specification used by the Internet Message Format [RFC5322]. -// -// HTTP-date = IMF-fixdate / obs-date -// -// An example of the preferred format is -// -// Sun, 06 Nov 1994 08:49:37 GMT ; IMF-fixdate -// -// Examples of the two obsolete formats are -// -// Sunday, 06-Nov-94 08:49:37 GMT ; obsolete RFC 850 format -// Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format -// -// A recipient that parses a timestamp value in an HTTP header field -// MUST accept all three HTTP-date formats. When a sender generates a -// header field that contains one or more timestamps defined as -// HTTP-date, the sender MUST generate those timestamps in the -// IMF-fixdate format. -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct HttpDate(time::Tm); - -impl FromStr for HttpDate { - type Err = ::Error; - fn from_str(s: &str) -> ::Result { - match time::strptime(s, "%a, %d %b %Y %T %Z").or_else(|_| { - time::strptime(s, "%A, %d-%b-%y %T %Z") - }).or_else(|_| { - time::strptime(s, "%c") - }) { - Ok(t) => Ok(HttpDate(t)), - Err(_) => Err(::Error::Header), - } - } -} - -impl Display for HttpDate { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0.to_utc().rfc822(), f) - } -} - -impl From for HttpDate { - fn from(sys: SystemTime) -> HttpDate { - let tmspec = match sys.duration_since(UNIX_EPOCH) { - Ok(dur) => { - time::Timespec::new(dur.as_secs() as i64, dur.subsec_nanos() as i32) - }, - Err(err) => { - let neg = err.duration(); - time::Timespec::new(-(neg.as_secs() as i64), -(neg.subsec_nanos() as i32)) - }, - }; - HttpDate(time::at_utc(tmspec)) - } -} - -impl From for SystemTime { - fn from(date: HttpDate) -> SystemTime { - let spec = date.0.to_timespec(); - if spec.sec >= 0 { - UNIX_EPOCH + Duration::new(spec.sec as u64, spec.nsec as u32) - } else { - UNIX_EPOCH - Duration::new(spec.sec as u64, spec.nsec as u32) - } - } -} - -#[cfg(test)] -mod tests { - use time::Tm; - use super::HttpDate; - - const NOV_07: HttpDate = HttpDate(Tm { - tm_nsec: 0, - tm_sec: 37, - tm_min: 48, - tm_hour: 8, - tm_mday: 7, - tm_mon: 10, - tm_year: 94, - tm_wday: 0, - tm_isdst: 0, - tm_yday: 0, - tm_utcoff: 0, - }); - - #[test] - fn test_imf_fixdate() { - assert_eq!("Sun, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), NOV_07); - } - - #[test] - fn test_rfc_850() { - assert_eq!("Sunday, 07-Nov-94 08:48:37 GMT".parse::().unwrap(), NOV_07); - } - - #[test] - fn test_asctime() { - assert_eq!("Sun Nov 7 08:48:37 1994".parse::().unwrap(), NOV_07); - } - - #[test] - fn test_no_date() { - assert!("this-is-no-date".parse::().is_err()); - } -} diff --git a/src/header/shared/mod.rs b/src/header/shared/mod.rs deleted file mode 100644 index 58040e9a8a..0000000000 --- a/src/header/shared/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub use self::charset::Charset; -pub use self::encoding::Encoding; -pub use self::entity::EntityTag; -pub use self::httpdate::HttpDate; -pub use language_tags::LanguageTag; -pub use self::quality_item::{Quality, QualityItem, qitem, q}; - -mod charset; -mod encoding; -mod entity; -mod httpdate; -mod quality_item; diff --git a/src/header/shared/quality_item.rs b/src/header/shared/quality_item.rs deleted file mode 100644 index 2b3dc578ce..0000000000 --- a/src/header/shared/quality_item.rs +++ /dev/null @@ -1,267 +0,0 @@ -#[allow(unused)] -use std::ascii::AsciiExt; -use std::cmp; -use std::default::Default; -use std::fmt; -use std::str; - -use self::internal::IntoQuality; - -/// Represents a quality used in quality values. -/// -/// Can be created with the `q` function. -/// -/// # Implementation notes -/// -/// The quality value is defined as a number between 0 and 1 with three decimal places. This means -/// there are 1001 possible values. Since floating point numbers are not exact and the smallest -/// floating point data type (`f32`) consumes four bytes, hyper uses an `u16` value to store the -/// quality internally. For performance reasons you may set quality directly to a value between -/// 0 and 1000 e.g. `Quality(532)` matches the quality `q=0.532`. -/// -/// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1) -/// gives more information on quality values in HTTP header fields. -#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct Quality(u16); - -impl Default for Quality { - fn default() -> Quality { - Quality(1000) - } -} - -/// Represents an item with a quality value as defined in -/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.1). -#[derive(Clone, PartialEq, Debug)] -pub struct QualityItem { - /// The actual contents of the field. - pub item: T, - /// The quality (client or server preference) for the value. - pub quality: Quality, -} - -impl QualityItem { - /// Creates a new `QualityItem` from an item and a quality. - /// The item can be of any type. - /// The quality should be a value in the range [0, 1]. - pub fn new(item: T, quality: Quality) -> QualityItem { - QualityItem { - item: item, - quality: quality - } - } -} - -impl cmp::PartialOrd for QualityItem { - fn partial_cmp(&self, other: &QualityItem) -> Option { - self.quality.partial_cmp(&other.quality) - } -} - -impl fmt::Display for QualityItem { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - try!(fmt::Display::fmt(&self.item, f)); - match self.quality.0 { - 1000 => Ok(()), - 0 => f.write_str("; q=0"), - x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')) - } - } -} - -impl str::FromStr for QualityItem { - type Err = ::Error; - fn from_str(s: &str) -> ::Result> { - if !s.is_ascii() { - return Err(::Error::Header); - } - // Set defaults used if parsing fails. - let mut raw_item = s; - let mut quality = 1f32; - - let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect(); - if parts.len() == 2 { - if parts[0].len() < 2 { - return Err(::Error::Header); - } - let start = &parts[0][0..2]; - if start == "q=" || start == "Q=" { - let q_part = &parts[0][2..parts[0].len()]; - if q_part.len() > 5 { - return Err(::Error::Header); - } - match q_part.parse::() { - Ok(q_value) => { - if 0f32 <= q_value && q_value <= 1f32 { - quality = q_value; - raw_item = parts[1]; - } else { - return Err(::Error::Header); - } - }, - Err(_) => return Err(::Error::Header), - } - } - } - match raw_item.parse::() { - // we already checked above that the quality is within range - Ok(item) => Ok(QualityItem::new(item, from_f32(quality))), - Err(_) => Err(::Error::Header), - } - } -} - -#[inline] -fn from_f32(f: f32) -> Quality { - // this function is only used internally. A check that `f` is within range - // should be done before calling this method. Just in case, this - // debug_assert should catch if we were forgetful - debug_assert!(f >= 0f32 && f <= 1f32, "q value must be between 0.0 and 1.0"); - Quality((f * 1000f32) as u16) -} - -/// Convenience function to wrap a value in a `QualityItem` -/// Sets `q` to the default 1.0 -pub fn qitem(item: T) -> QualityItem { - QualityItem::new(item, Default::default()) -} - -/// Convenience function to create a `Quality` from a float or integer. -/// -/// Implemented for `u16` and `f32`. Panics if value is out of range. -pub fn q(val: T) -> Quality { - val.into_quality() -} - -mod internal { - use super::Quality; - - // TryFrom is probably better, but it's not stable. For now, we want to - // keep the functionality of the `q` function, while allowing it to be - // generic over `f32` and `u16`. - // - // `q` would panic before, so keep that behavior. `TryFrom` can be - // introduced later for a non-panicking conversion. - - pub trait IntoQuality: Sealed + Sized { - fn into_quality(self) -> Quality; - } - - impl IntoQuality for f32 { - fn into_quality(self) -> Quality { - assert!(self >= 0f32 && self <= 1f32, "float must be between 0.0 and 1.0"); - super::from_f32(self) - } - } - - impl IntoQuality for u16 { - fn into_quality(self) -> Quality { - assert!(self <= 1000, "u16 must be between 0 and 1000"); - Quality(self) - } - } - - - pub trait Sealed {} - impl Sealed for u16 {} - impl Sealed for f32 {} -} - -#[cfg(test)] -mod tests { - use super::*; - use super::super::encoding::*; - - #[test] - fn test_quality_item_fmt_q_1() { - let x = qitem(Chunked); - assert_eq!(format!("{}", x), "chunked"); - } - #[test] - fn test_quality_item_fmt_q_0001() { - let x = QualityItem::new(Chunked, Quality(1)); - assert_eq!(format!("{}", x), "chunked; q=0.001"); - } - #[test] - fn test_quality_item_fmt_q_05() { - // Custom value - let x = QualityItem{ - item: EncodingExt("identity".to_owned()), - quality: Quality(500), - }; - assert_eq!(format!("{}", x), "identity; q=0.5"); - } - - #[test] - fn test_quality_item_fmt_q_0() { - // Custom value - let x = QualityItem{ - item: EncodingExt("identity".to_owned()), - quality: Quality(0), - }; - assert_eq!(x.to_string(), "identity; q=0"); - } - - #[test] - fn test_quality_item_from_str1() { - let x: ::Result> = "chunked".parse(); - assert_eq!(x.unwrap(), QualityItem{ item: Chunked, quality: Quality(1000), }); - } - #[test] - fn test_quality_item_from_str2() { - let x: ::Result> = "chunked; q=1".parse(); - assert_eq!(x.unwrap(), QualityItem{ item: Chunked, quality: Quality(1000), }); - } - #[test] - fn test_quality_item_from_str3() { - let x: ::Result> = "gzip; q=0.5".parse(); - assert_eq!(x.unwrap(), QualityItem{ item: Gzip, quality: Quality(500), }); - } - #[test] - fn test_quality_item_from_str4() { - let x: ::Result> = "gzip; q=0.273".parse(); - assert_eq!(x.unwrap(), QualityItem{ item: Gzip, quality: Quality(273), }); - } - #[test] - fn test_quality_item_from_str5() { - let x: ::Result> = "gzip; q=0.2739999".parse(); - assert!(x.is_err()); - } - #[test] - fn test_quality_item_from_str6() { - let x: ::Result> = "gzip; q=2".parse(); - assert!(x.is_err()); - } - #[test] - fn test_quality_item_ordering() { - let x: QualityItem = "gzip; q=0.5".parse().ok().unwrap(); - let y: QualityItem = "gzip; q=0.273".parse().ok().unwrap(); - let comparision_result: bool = x.gt(&y); - assert!(comparision_result) - } - - #[test] - fn test_quality() { - assert_eq!(q(0.5), Quality(500)); - } - - #[test] - #[should_panic] // FIXME - 32-bit msvc unwinding broken - #[cfg_attr(all(target_arch="x86", target_env="msvc"), ignore)] - fn test_quality_invalid() { - q(-1.0); - } - - #[test] - #[should_panic] // FIXME - 32-bit msvc unwinding broken - #[cfg_attr(all(target_arch="x86", target_env="msvc"), ignore)] - fn test_quality_invalid2() { - q(2.0); - } - - #[test] - fn test_fuzzing_bugs() { - assert!("99999;".parse::>().is_err()); - assert!("\x0d;;;=\u{d6aa}==".parse::>().is_err()) - } -} diff --git a/src/headers.rs b/src/headers.rs new file mode 100644 index 0000000000..1e98ee4ce2 --- /dev/null +++ b/src/headers.rs @@ -0,0 +1,85 @@ +use http::HeaderMap; +use http::header::{CONNECTION, CONTENT_LENGTH, EXPECT, HeaderValue, TRANSFER_ENCODING}; +use unicase; + +pub fn connection_keep_alive(headers: &HeaderMap) -> bool { + for line in headers.get_all(CONNECTION) { + if let Ok(s) = line.to_str() { + for val in s.split(',') { + if unicase::eq_ascii(val.trim(), "keep-alive") { + return true; + } + } + } + } + + false +} + +pub fn connection_close(headers: &HeaderMap) -> bool { + for line in headers.get_all(CONNECTION) { + if let Ok(s) = line.to_str() { + for val in s.split(',') { + if unicase::eq_ascii(val.trim(), "close") { + return true; + } + } + } + } + + false +} + +pub fn content_length_parse(headers: &HeaderMap) -> Option { + // If multiple Content-Length headers were sent, everything can still + // be alright if they all contain the same value, and all parse + // correctly. If not, then it's an error. + + let values = headers.get_all(CONTENT_LENGTH); + let folded = values + .into_iter() + .fold(None, |prev, line| match prev { + Some(Ok(prev)) => { + Some(line + .to_str() + .map_err(|_| ()) + .and_then(|s| s.parse().map_err(|_| ())) + .and_then(|n| if prev == n { Ok(n) } else { Err(()) })) + }, + None => { + Some(line + .to_str() + .map_err(|_| ()) + .and_then(|s| s.parse().map_err(|_| ()))) + }, + Some(Err(())) => Some(Err(())), + }); + + if let Some(Ok(n)) = folded { + Some(n) + } else { + None + } +} + +pub fn content_length_zero(headers: &mut HeaderMap) { + headers.insert(CONTENT_LENGTH, HeaderValue::from_static("0")); +} + +pub fn expect_continue(headers: &HeaderMap) -> bool { + Some(&b"100-continue"[..]) == headers.get(EXPECT).map(|v| v.as_bytes()) +} + +pub fn transfer_encoding_is_chunked(headers: &HeaderMap) -> bool { + let mut encodings = headers.get_all(TRANSFER_ENCODING).into_iter(); + // chunked must always be the last encoding, according to spec + if let Some(line) = encodings.next_back() { + if let Ok(s) = line.to_str() { + if let Some(encoding) = s.rsplit(',').next() { + return unicase::eq_ascii(encoding.trim(), "chunked"); + } + } + } + + false +} diff --git a/src/lib.rs b/src/lib.rs index f39bc83ece..18d13c2bfc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,24 +11,18 @@ //! layer over "stringly-typed" HTTP. //! //! Hyper provides both a [Client](client/index.html) and a -//! [Server](server/index.html), along with a -//! [typed Headers system](header/index.html). +//! [Server](server/index.html). //! //! If just starting out, **check out the [Guides](https://hyper.rs/guides) //! first.** -extern crate base64; extern crate bytes; #[macro_use] extern crate futures; extern crate futures_cpupool; -#[cfg(feature = "compat")] extern crate http; extern crate httparse; extern crate iovec; -extern crate language_tags; #[macro_use] extern crate log; -pub extern crate mime; -#[macro_use] extern crate percent_encoding; extern crate relay; extern crate time; extern crate tokio_core as tokio; @@ -39,29 +33,25 @@ extern crate unicase; #[cfg(all(test, feature = "nightly"))] extern crate test; -pub use uri::Uri; +pub use http::{ + Method, + Request, + Response, + StatusCode, + Uri, + Version, +}; + pub use client::Client; pub use error::{Result, Error}; -pub use header::Headers; pub use proto::{Body, Chunk}; -pub use proto::request::Request; -pub use proto::response::Response; -pub use method::Method::{self, Get, Head, Post, Put, Delete}; -pub use status::StatusCode::{self, Ok, BadRequest, NotFound}; pub use server::Server; -pub use version::HttpVersion; -#[cfg(feature = "raw_status")] -pub use proto::RawStatus; mod common; #[cfg(test)] mod mock; pub mod client; pub mod error; -mod method; -pub mod header; +mod headers; mod proto; pub mod server; -mod status; -mod uri; -mod version; diff --git a/src/method.rs b/src/method.rs deleted file mode 100644 index c91199c0a8..0000000000 --- a/src/method.rs +++ /dev/null @@ -1,299 +0,0 @@ -//! The HTTP request method -use std::fmt; -use std::str::FromStr; -use std::convert::AsRef; - -#[cfg(feature = "compat")] -use http; - -use error::Error; -use self::Method::{Options, Get, Post, Put, Delete, Head, Trace, Connect, Patch, - Extension}; - - -/// The Request Method (VERB) -/// -/// Currently includes 8 variants representing the 8 methods defined in -/// [RFC 7230](https://tools.ietf.org/html/rfc7231#section-4.1), plus PATCH, -/// and an Extension variant for all extensions. -/// -/// It may make sense to grow this to include all variants currently -/// registered with IANA, if they are at all common to use. -#[derive(Clone, PartialEq, Eq, Hash, Debug)] -pub enum Method { - /// OPTIONS - Options, - /// GET - Get, - /// POST - Post, - /// PUT - Put, - /// DELETE - Delete, - /// HEAD - Head, - /// TRACE - Trace, - /// CONNECT - Connect, - /// PATCH - Patch, - /// Method extensions. An example would be `let m = Extension("FOO".to_string())`. - Extension(String) -} - -impl AsRef for Method { - fn as_ref(&self) -> &str { - match *self { - Options => "OPTIONS", - Get => "GET", - Post => "POST", - Put => "PUT", - Delete => "DELETE", - Head => "HEAD", - Trace => "TRACE", - Connect => "CONNECT", - Patch => "PATCH", - Extension(ref s) => s.as_ref() - } - } -} - -impl Method { - /// Whether a method is considered "safe", meaning the request is - /// essentially read-only. - /// - /// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.1) - /// for more words. - pub fn safe(&self) -> bool { - match *self { - Get | Head | Options | Trace => true, - _ => false - } - } - - /// Whether a method is considered "idempotent", meaning the request has - /// the same result if executed multiple times. - /// - /// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.2) for - /// more words. - pub fn idempotent(&self) -> bool { - if self.safe() { - true - } else { - match *self { - Put | Delete => true, - _ => false - } - } - } -} - -macro_rules! from_str { - ($s:ident, { $($n:pat => { $($text:pat => $var:ident,)* },)* }) => ({ - let s = $s; - match s.len() { - $( - $n => match s { - $( - $text => return Ok($var), - )* - _ => {}, - }, - )* - 0 => return Err(::Error::Method), - _ => {}, - } - Ok(Extension(s.to_owned())) - }) -} - -impl FromStr for Method { - type Err = Error; - fn from_str(s: &str) -> Result { - from_str!(s, { - 3 => { - "GET" => Get, - "PUT" => Put, - }, - 4 => { - "HEAD" => Head, - "POST" => Post, - }, - 5 => { - "PATCH" => Patch, - "TRACE" => Trace, - }, - 6 => { - "DELETE" => Delete, - }, - 7 => { - "OPTIONS" => Options, - "CONNECT" => Connect, - }, - }) - } -} - -impl fmt::Display for Method { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.write_str(match *self { - Options => "OPTIONS", - Get => "GET", - Post => "POST", - Put => "PUT", - Delete => "DELETE", - Head => "HEAD", - Trace => "TRACE", - Connect => "CONNECT", - Patch => "PATCH", - Extension(ref s) => s.as_ref() - }) - } -} - -impl Default for Method { - fn default() -> Method { - Method::Get - } -} - -#[cfg(feature = "compat")] -impl From for Method { - fn from(method: http::Method) -> Method { - match method { - http::Method::GET => - Method::Get, - http::Method::POST => - Method::Post, - http::Method::PUT => - Method::Put, - http::Method::DELETE => - Method::Delete, - http::Method::HEAD => - Method::Head, - http::Method::OPTIONS => - Method::Options, - http::Method::CONNECT => - Method::Connect, - http::Method::PATCH => - Method::Patch, - http::Method::TRACE => - Method::Trace, - _ => { - method.as_ref().parse() - .expect("attempted to convert invalid method") - } - } - } -} - -#[cfg(feature = "compat")] -impl From for http::Method { - fn from(method: Method) -> http::Method { - use http::HttpTryFrom; - - match method { - Method::Get => - http::Method::GET, - Method::Post => - http::Method::POST, - Method::Put => - http::Method::PUT, - Method::Delete => - http::Method::DELETE, - Method::Head => - http::Method::HEAD, - Method::Options => - http::Method::OPTIONS, - Method::Connect => - http::Method::CONNECT, - Method::Patch => - http::Method::PATCH, - Method::Trace => - http::Method::TRACE, - Method::Extension(s) => { - HttpTryFrom::try_from(s.as_str()) - .expect("attempted to convert invalid method") - } - } - } -} - -#[cfg(test)] -mod tests { - use std::collections::HashMap; - use std::str::FromStr; - use error::Error; - use super::Method; - use super::Method::{Get, Post, Put, Extension}; - - #[test] - fn test_safe() { - assert_eq!(true, Get.safe()); - assert_eq!(false, Post.safe()); - } - - #[test] - fn test_idempotent() { - assert_eq!(true, Get.idempotent()); - assert_eq!(true, Put.idempotent()); - assert_eq!(false, Post.idempotent()); - } - - #[test] - fn test_from_str() { - assert_eq!(Get, FromStr::from_str("GET").unwrap()); - assert_eq!(Extension("MOVE".to_owned()), - FromStr::from_str("MOVE").unwrap()); - let x: Result = FromStr::from_str(""); - if let Err(Error::Method) = x { - } else { - panic!("An empty method is invalid!") - } - } - - #[test] - fn test_fmt() { - assert_eq!("GET".to_owned(), format!("{}", Get)); - assert_eq!("MOVE".to_owned(), - format!("{}", Extension("MOVE".to_owned()))); - } - - #[test] - fn test_hashable() { - let mut counter: HashMap = HashMap::new(); - counter.insert(Get, 1); - assert_eq!(Some(&1), counter.get(&Get)); - } - - #[test] - fn test_as_str() { - assert_eq!(Get.as_ref(), "GET"); - assert_eq!(Post.as_ref(), "POST"); - assert_eq!(Put.as_ref(), "PUT"); - assert_eq!(Extension("MOVE".to_owned()).as_ref(), "MOVE"); - } - - #[test] - #[cfg(feature = "compat")] - fn test_compat() { - use http::{self, HttpTryFrom}; - - let methods = vec![ - "GET", - "POST", - "PUT", - "MOVE" - ]; - for method in methods { - let orig_hyper_method = Method::from_str(method).unwrap(); - let orig_http_method = http::Method::try_from(method).unwrap(); - let conv_hyper_method: Method = orig_http_method.clone().into(); - let conv_http_method: http::Method = orig_hyper_method.clone().into(); - assert_eq!(orig_hyper_method, conv_hyper_method); - assert_eq!(orig_http_method, conv_http_method); - } - } -} diff --git a/src/mock.rs b/src/mock.rs index d871e73d23..b8b8a55ea9 100644 --- a/src/mock.rs +++ b/src/mock.rs @@ -418,11 +418,11 @@ impl Service for MockConnector { fn call(&self, uri: Uri) -> Self::Future { use futures::future; - trace!("mock connect: {:?}", uri.as_ref()); + trace!("mock connect: {}", uri); let mut mocks = self.mocks.borrow_mut(); - let mocks = mocks.get_mut(uri.as_ref()) - .expect(&format!("unknown mocks uri: {:?}", uri.as_ref())); - assert!(!mocks.is_empty(), "no additional mocks for {:?}", uri.as_ref()); + let mocks = mocks.get_mut(&uri.to_string()) + .expect(&format!("unknown mocks uri: {}", uri)); + assert!(!mocks.is_empty(), "no additional mocks for {}", uri); future::ok(mocks.remove(0)) } } diff --git a/src/proto/h1/conn.rs b/src/proto/h1/conn.rs index 02559cf263..30f9df87f3 100644 --- a/src/proto/h1/conn.rs +++ b/src/proto/h1/conn.rs @@ -5,13 +5,12 @@ use std::marker::PhantomData; use bytes::Bytes; use futures::{Async, AsyncSink, Poll, StartSend}; use futures::task::Task; +use http::{Method, Version}; use tokio_io::{AsyncRead, AsyncWrite}; use proto::{Chunk, Decode, Http1Transaction, MessageHead}; use super::io::{Cursor, Buffered}; use super::{EncodedBuf, Encoder, Decoder}; -use method::Method; -use version::HttpVersion; /// This handles a connection, which will have been established over an @@ -55,7 +54,7 @@ where I: AsyncRead + AsyncWrite, writing: Writing::Init, // We assume a modern world where the remote speaks HTTP/1.1. // If they tell us otherwise, we'll downgrade in `read_head`. - version: Version::Http11, + version: Version::HTTP_11, }, _marker: PhantomData, } @@ -141,15 +140,16 @@ where I: AsyncRead + AsyncWrite, } }; - self.state.version = match version { - HttpVersion::Http10 => Version::Http10, - HttpVersion::Http11 => Version::Http11, + match version { + Version::HTTP_10 | + Version::HTTP_11 => {}, _ => { error!("unimplemented HTTP Version = {:?}", version); self.state.close_read(); return Err(::Error::Version); } }; + self.state.version = version; let decoder = match T::decoder(&head, &mut self.state.method) { Ok(Decode::Normal(d)) => { @@ -448,7 +448,7 @@ where I: AsyncRead + AsyncWrite, // If we know the remote speaks an older version, we try to fix up any messages // to work with our older peer. fn enforce_version(&mut self, head: &mut MessageHead) { - use header::Connection; + //use header::Connection; let wants_keep_alive = if self.state.wants_keep_alive() { let ka = head.should_keep_alive(); @@ -459,15 +459,15 @@ where I: AsyncRead + AsyncWrite, }; match self.state.version { - Version::Http10 => { + Version::HTTP_10 => { // If the remote only knows HTTP/1.0, we should force ourselves // to do only speak HTTP/1.0 as well. - head.version = HttpVersion::Http10; + head.version = Version::HTTP_10; if wants_keep_alive { - head.headers.set(Connection::keep_alive()); + //TODO: head.headers.set(Connection::keep_alive()); } }, - Version::Http11 => { + _ => { // If the remote speaks HTTP/1.1, then it *should* be fine with // both HTTP/1.0 and HTTP/1.1 from us. So again, we just let // the user's headers be. @@ -789,12 +789,6 @@ impl State { } } -#[derive(Debug, Clone, Copy)] -enum Version { - Http10, - Http11, -} - #[cfg(test)] //TODO: rewrite these using dispatch mod tests { diff --git a/src/proto/h1/dispatch.rs b/src/proto/h1/dispatch.rs index 9b10d55912..31539e3887 100644 --- a/src/proto/h1/dispatch.rs +++ b/src/proto/h1/dispatch.rs @@ -2,11 +2,11 @@ use std::io; use bytes::Bytes; use futures::{Async, AsyncSink, Future, Poll, Stream}; +use http::{Request, Response, StatusCode}; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_service::Service; -use proto::{Body, Conn, Http1Transaction, MessageHead, RequestHead, ResponseHead}; -use ::StatusCode; +use proto::{Body, Conn, Http1Transaction, MessageHead, RequestHead, RequestLine, ResponseHead}; pub struct Dispatcher { conn: Conn, @@ -21,7 +21,7 @@ pub trait Dispatch { type PollBody; type RecvItem; fn poll_msg(&mut self) -> Poll)>, ::Error>; - fn recv_msg(&mut self, msg: ::Result<(Self::RecvItem, Option)>) -> ::Result<()>; + fn recv_msg(&mut self, msg: ::Result<(Self::RecvItem, Body)>) -> ::Result<()>; fn poll_ready(&mut self) -> Poll<(), ()>; fn should_poll(&self) -> bool; } @@ -32,13 +32,13 @@ pub struct Server { } pub struct Client { - callback: Option<::client::dispatch::Callback, ::Response>>, + callback: Option<::client::dispatch::Callback, Response>>, rx: ClientRx, } -pub type ClientMsg = (RequestHead, Option); +pub type ClientMsg = Request; -type ClientRx = ::client::dispatch::Receiver, ::Response>; +type ClientRx = ::client::dispatch::Receiver, Response>; impl Dispatcher where @@ -184,9 +184,9 @@ where let (mut tx, rx) = ::proto::body::channel(); let _ = tx.poll_ready(); // register this task if rx is dropped self.body_tx = Some(tx); - Some(rx) + rx } else { - None + Body::empty() }; self.dispatch.recv_msg(Ok((head, body)))?; Ok(Async::Ready(())) @@ -315,7 +315,7 @@ impl Server where S: Service { impl Dispatch for Server where - S: Service, Error=::Error>, + S: Service, Response=Response, Error=::Error>, Bs: Stream, Bs::Item: AsRef<[u8]>, { @@ -332,16 +332,25 @@ where return Ok(Async::NotReady); } }; - let (head, body) = ::proto::response::split(resp); - Ok(Async::Ready(Some((head.into(), body)))) + let (parts, body) = resp.into_parts(); + let head = MessageHead { + version: parts.version, + subject: parts.status, + headers: parts.headers, + }; + Ok(Async::Ready(Some((head, Some(body))))) } else { unreachable!("poll_msg shouldn't be called if no inflight"); } } - fn recv_msg(&mut self, msg: ::Result<(Self::RecvItem, Option)>) -> ::Result<()> { + fn recv_msg(&mut self, msg: ::Result<(Self::RecvItem, Body)>) -> ::Result<()> { let (msg, body) = msg?; - let req = ::proto::request::from_wire(None, msg, body); + let mut req = Request::new(body); + *req.method_mut() = msg.subject.0; + *req.uri_mut() = msg.subject.1; + *req.headers_mut() = msg.headers; + *req.version_mut() = msg.version; self.in_flight = Some(self.service.call(req)); Ok(()) } @@ -382,7 +391,7 @@ where fn poll_msg(&mut self) -> Poll)>, ::Error> { match self.rx.poll() { - Ok(Async::Ready(Some(((head, body), mut cb)))) => { + Ok(Async::Ready(Some((req, mut cb)))) => { // check that future hasn't been canceled already match cb.poll_cancel().expect("poll_cancel cannot error") { Async::Ready(()) => { @@ -390,8 +399,14 @@ where Ok(Async::Ready(None)) }, Async::NotReady => { + let (parts, body) = req.into_parts(); + let head = RequestHead { + version: parts.version, + subject: RequestLine(parts.method, parts.uri), + headers: parts.headers, + }; self.callback = Some(cb); - Ok(Async::Ready(Some((head, body)))) + Ok(Async::Ready(Some((head, Some(body))))) } } }, @@ -405,11 +420,14 @@ where } } - fn recv_msg(&mut self, msg: ::Result<(Self::RecvItem, Option)>) -> ::Result<()> { + fn recv_msg(&mut self, msg: ::Result<(Self::RecvItem, Body)>) -> ::Result<()> { match msg { Ok((msg, body)) => { if let Some(cb) = self.callback.take() { - let res = ::proto::response::from_wire(msg, body); + let mut res = Response::new(body); + *res.status_mut() = msg.subject; + *res.headers_mut() = msg.headers; + *res.version_mut() = msg.version; let _ = cb.send(Ok(res)); Ok(()) } else { @@ -469,12 +487,7 @@ mod tests { let conn = Conn::<_, ::Chunk, ClientTransaction>::new(io); let mut dispatcher = Dispatcher::new(Client::new(rx), conn); - let req = RequestHead { - version: ::HttpVersion::Http11, - subject: ::proto::RequestLine::default(), - headers: Default::default(), - }; - let res_rx = tx.try_send((req, None::<::Body>)).unwrap(); + let res_rx = tx.try_send(::Request::new(::Body::empty())).unwrap(); let a1 = dispatcher.poll().expect("error should be sent on channel"); assert!(a1.is_ready(), "dispatcher should be closed"); diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index 56826c8fea..10bfbdcafa 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -1,16 +1,13 @@ -use std::borrow::Cow; use std::fmt::{self, Write}; -use httparse; use bytes::{BytesMut, Bytes}; +use http::header::{CONTENT_LENGTH, DATE, HeaderName, HeaderValue, TRANSFER_ENCODING}; +use http::{HeaderMap, Method, StatusCode, Uri, Version}; +use httparse; -use header::{self, Headers, ContentLength, TransferEncoding}; -use proto::{Decode, MessageHead, RawStatus, Http1Transaction, ParseResult, - RequestLine, RequestHead}; +use headers; +use proto::{Decode, MessageHead, Http1Transaction, ParseResult, RequestLine, RequestHead}; use proto::h1::{Encoder, Decoder, date}; -use method::Method; -use status::StatusCode; -use version::HttpVersion::{Http10, Http11}; const MAX_HEADERS: usize = 100; const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific @@ -46,13 +43,17 @@ where match try!(req.parse(&buf)) { httparse::Status::Complete(len) => { trace!("Request.parse Complete({})", len); - let method = try!(req.method.unwrap().parse()); + let method = Method::from_bytes(req.method.unwrap().as_bytes())?; let path = req.path.unwrap(); let bytes_ptr = buf.as_ref().as_ptr() as usize; let path_start = path.as_ptr() as usize - bytes_ptr; let path_end = path_start + path.len(); let path = (path_start, path_end); - let version = if req.version.unwrap() == 1 { Http11 } else { Http10 }; + let version = if req.version.unwrap() == 1 { + Version::HTTP_11 + } else { + Version::HTTP_10 + }; record_header_indices(buf.as_ref(), &req.headers, &mut headers_indices); let headers_len = req.headers.len(); @@ -62,11 +63,11 @@ where } }; - let mut headers = Headers::with_capacity(headers_len); + let mut headers = HeaderMap::with_capacity(headers_len); let slice = buf.split_to(len).freeze(); let path = slice.slice(path.0, path.1); // path was found to be utf8 by httparse - let path = try!(unsafe { ::uri::from_utf8_unchecked(path) }); + let path = Uri::from_shared(path)?; let subject = RequestLine( method, path, @@ -85,8 +86,6 @@ where } fn decoder(head: &MessageHead, method: &mut Option) -> ::Result { - use ::header; - *method = Some(head.subject.0.clone()); // According to https://tools.ietf.org/html/rfc7230#section-3.3.3 @@ -98,24 +97,24 @@ where // 6. Length 0. // 7. (irrelevant to Request) - if let Some(&header::TransferEncoding(ref encodings)) = head.headers.get() { + if head.headers.contains_key(TRANSFER_ENCODING) { // https://tools.ietf.org/html/rfc7230#section-3.3.3 // If Transfer-Encoding header is present, and 'chunked' is // not the final encoding, and this is a Request, then it is // mal-formed. A server should respond with 400 Bad Request. - if head.version == Http10 { - debug!("HTTP/1.0 has Transfer-Encoding header"); + if head.version == Version::HTTP_10 { + debug!("HTTP/1.0 cannot have Transfer-Encoding header"); Err(::Error::Header) - } else if encodings.last() == Some(&header::Encoding::Chunked) { + } else if headers::transfer_encoding_is_chunked(&head.headers) { Ok(Decode::Normal(Decoder::chunked())) } else { debug!("request with transfer-encoding header, but not chunked, bad request"); Err(::Error::Header) } - } else if let Some(&header::ContentLength(len)) = head.headers.get() { + } else if let Some(len) = headers::content_length_parse(&head.headers) { Ok(Decode::Normal(Decoder::length(len))) - } else if head.headers.has::() { - debug!("illegal Content-Length: {:?}", head.headers.get_raw("Content-Length")); + } else if head.headers.contains_key(CONTENT_LENGTH) { + debug!("illegal Content-Length header"); Err(::Error::Header) } else { Ok(Decode::Normal(Decoder::length(0))) @@ -130,7 +129,7 @@ where // This is because Service only allows returning a single Response, and // so if you try to reply with a e.g. 100 Continue, you have no way of // replying with the latter status code response. - let ret = if ::StatusCode::SwitchingProtocols == head.subject { + let ret = if StatusCode::SWITCHING_PROTOCOLS == head.subject { T::on_encode_upgrade(&mut head) .map(|_| { let mut enc = Server::set_length(&mut head, has_body, method.as_ref()); @@ -140,8 +139,8 @@ where } else if head.subject.is_informational() { error!("response with 1xx status code not supported"); head = MessageHead::default(); - head.subject = ::StatusCode::InternalServerError; - head.headers.set(ContentLength(0)); + head.subject = StatusCode::INTERNAL_SERVER_ERROR; + headers::content_length_zero(&mut head.headers); Err(::Error::Status) } else { Ok(Server::set_length(&mut head, has_body, method.as_ref())) @@ -150,15 +149,24 @@ where let init_cap = 30 + head.headers.len() * AVERAGE_HEADER_SIZE; dst.reserve(init_cap); - if head.version == ::HttpVersion::Http11 && head.subject == ::StatusCode::Ok { + if head.version == Version::HTTP_11 && head.subject == StatusCode::OK { extend(dst, b"HTTP/1.1 200 OK\r\n"); - let _ = write!(FastWrite(dst), "{}", head.headers); } else { - let _ = write!(FastWrite(dst), "{} {}\r\n{}", head.version, head.subject, head.headers); + match head.version { + Version::HTTP_10 => extend(dst, b"HTTP/1.0 "), + Version::HTTP_11 => extend(dst, b"HTTP/1.1 "), + _ => unreachable!(), + } + + extend(dst, head.subject.as_str().as_bytes()); + extend(dst, b" "); + extend(dst, head.subject.canonical_reason().unwrap_or("").as_bytes()); + extend(dst, b"\r\n"); } + write_headers(&head.headers, dst); // using http::h1::date is quite a lot faster than generating a unique Date header each time // like req/s goes up about 10% - if !head.headers.has::() { + if !head.headers.contains_key(DATE) { dst.reserve(date::DATE_VALUE_LENGTH + 8); extend(dst, b"Date: "); date::extend(dst); @@ -173,12 +181,12 @@ where let status = match err { &::Error::Method | &::Error::Version | - &::Error::Header | - &::Error::Uri(_) => { - StatusCode::BadRequest + &::Error::Header /*| + &::Error::Uri(_)*/ => { + StatusCode::BAD_REQUEST }, &::Error::TooLarge => { - StatusCode::RequestHeaderFieldsTooLarge + StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE } _ => return None, }; @@ -202,8 +210,8 @@ impl Server<()> { fn set_length(head: &mut MessageHead, has_body: bool, method: Option<&Method>) -> Encoder { // these are here thanks to borrowck // `if method == Some(&Method::Get)` says the RHS doesn't live long enough - const HEAD: Option<&'static Method> = Some(&Method::Head); - const CONNECT: Option<&'static Method> = Some(&Method::Connect); + const HEAD: Option<&'static Method> = Some(&Method::HEAD); + const CONNECT: Option<&'static Method> = Some(&Method::CONNECT); let can_have_body = { if method == HEAD { @@ -214,20 +222,20 @@ impl Server<()> { match head.subject { // TODO: support for 1xx codes needs improvement everywhere // would be 100...199 => false - StatusCode::SwitchingProtocols | - StatusCode::NoContent | - StatusCode::NotModified => false, + StatusCode::SWITCHING_PROTOCOLS | + StatusCode::NO_CONTENT | + StatusCode::NOT_MODIFIED => false, _ => true, } } }; if has_body && can_have_body { - set_length(&mut head.headers, head.version == Http11) + set_length(&mut head.headers, head.version == Version::HTTP_11) } else { - head.headers.remove::(); + head.headers.remove(TRANSFER_ENCODING); if can_have_body { - head.headers.set(ContentLength(0)); + headers::content_length_zero(&mut head.headers); } Encoder::length(0) } @@ -238,10 +246,10 @@ impl Http1Transaction for Client where T: OnUpgrade, { - type Incoming = RawStatus; + type Incoming = StatusCode; type Outgoing = RequestLine; - fn parse(buf: &mut BytesMut) -> ParseResult { + fn parse(buf: &mut BytesMut) -> ParseResult { if buf.len() == 0 { return Ok(None); } @@ -249,7 +257,7 @@ where name: (0, 0), value: (0, 0) }; MAX_HEADERS]; - let (len, code, reason, version, headers_len) = { + let (len, status, version, headers_len) = { let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS]; trace!("Response.parse([Header; {}], [u8; {}])", headers.len(), buf.len()); let mut res = httparse::Response::new(&mut headers); @@ -257,22 +265,21 @@ where match try!(res.parse(bytes)) { httparse::Status::Complete(len) => { trace!("Response.parse Complete({})", len); - let code = res.code.unwrap(); - let status = try!(StatusCode::try_from(code).map_err(|_| ::Error::Status)); - let reason = match status.canonical_reason() { - Some(reason) if reason == res.reason.unwrap() => Cow::Borrowed(reason), - _ => Cow::Owned(res.reason.unwrap().to_owned()) + let status = try!(StatusCode::from_u16(res.code.unwrap()).map_err(|_| ::Error::Status)); + let version = if res.version.unwrap() == 1 { + Version::HTTP_11 + } else { + Version::HTTP_10 }; - let version = if res.version.unwrap() == 1 { Http11 } else { Http10 }; record_header_indices(bytes, &res.headers, &mut headers_indices); let headers_len = res.headers.len(); - (len, code, reason, version, headers_len) + (len, status, version, headers_len) }, httparse::Status::Partial => return Ok(None), } }; - let mut headers = Headers::with_capacity(headers_len); + let mut headers = HeaderMap::with_capacity(headers_len); let slice = buf.split_to(len).freeze(); headers.extend(HeadersAsBytesIter { headers: headers_indices[..headers_len].iter(), @@ -280,7 +287,7 @@ where }); Ok(Some((MessageHead { version: version, - subject: RawStatus(code, reason), + subject: status, headers: headers, }, len))) } @@ -295,12 +302,12 @@ where // 6. (irrelevant to Response) // 7. Read till EOF. - match inc.subject.0 { + match inc.subject.as_u16() { 101 => { return T::on_decode_upgrade().map(Decode::Final); }, 100...199 => { - trace!("ignoring informational response: {}", inc.subject.0); + trace!("ignoring informational response: {}", inc.subject.as_u16()); return Ok(Decode::Ignore); }, 204 | @@ -308,10 +315,10 @@ where _ => (), } match *method { - Some(Method::Head) => { + Some(Method::HEAD) => { return Ok(Decode::Normal(Decoder::length(0))); } - Some(Method::Connect) => match inc.subject.0 { + Some(Method::CONNECT) => match inc.subject.as_u16() { 200...299 => { return Ok(Decode::Final(Decoder::length(0))); }, @@ -323,21 +330,24 @@ where } } - - if let Some(&header::TransferEncoding(ref codings)) = inc.headers.get() { - if inc.version == Http10 { - debug!("HTTP/1.0 has Transfer-Encoding header"); + if inc.headers.contains_key(TRANSFER_ENCODING) { + // https://tools.ietf.org/html/rfc7230#section-3.3.3 + // If Transfer-Encoding header is present, and 'chunked' is + // not the final encoding, and this is a Request, then it is + // mal-formed. A server should respond with 400 Bad Request. + if inc.version == Version::HTTP_10 { + debug!("HTTP/1.0 cannot have Transfer-Encoding header"); Err(::Error::Header) - } else if codings.last() == Some(&header::Encoding::Chunked) { + } else if headers::transfer_encoding_is_chunked(&inc.headers) { Ok(Decode::Normal(Decoder::chunked())) } else { - trace!("not chunked. read till eof"); + trace!("not chunked, read till eof"); Ok(Decode::Normal(Decoder::eof())) } - } else if let Some(&header::ContentLength(len)) = inc.headers.get() { + } else if let Some(len) = headers::content_length_parse(&inc.headers) { Ok(Decode::Normal(Decoder::length(len))) - } else if inc.headers.has::() { - debug!("illegal Content-Length: {:?}", inc.headers.get_raw("Content-Length")); + } else if inc.headers.contains_key(CONTENT_LENGTH) { + debug!("illegal Content-Length header"); Err(::Error::Header) } else { trace!("neither Transfer-Encoding nor Content-Length"); @@ -354,7 +364,22 @@ where let init_cap = 30 + head.headers.len() * AVERAGE_HEADER_SIZE; dst.reserve(init_cap); - let _ = write!(FastWrite(dst), "{} {}\r\n{}\r\n", head.subject, head.version, head.headers); + + + extend(dst, head.subject.0.as_str().as_bytes()); + extend(dst, b" "); + //TODO: add API to http::Uri to encode without std::fmt + let _ = write!(FastWrite(dst), "{} ", head.subject.1); + + match head.version { + Version::HTTP_10 => extend(dst, b"HTTP/1.0"), + Version::HTTP_11 => extend(dst, b"HTTP/1.1"), + _ => unreachable!(), + } + extend(dst, b"\r\n"); + + write_headers(&head.headers, dst); + extend(dst, b"\r\n"); Ok(body) } @@ -376,41 +401,30 @@ where impl Client<()> { fn set_length(head: &mut RequestHead, has_body: bool) -> Encoder { if has_body { - let can_chunked = head.version == Http11 - && (head.subject.0 != Method::Head) - && (head.subject.0 != Method::Get) - && (head.subject.0 != Method::Connect); + let can_chunked = head.version == Version::HTTP_11 + && (head.subject.0 != Method::HEAD) + && (head.subject.0 != Method::GET) + && (head.subject.0 != Method::CONNECT); set_length(&mut head.headers, can_chunked) } else { - head.headers.remove::(); - head.headers.remove::(); + head.headers.remove(CONTENT_LENGTH); + head.headers.remove(TRANSFER_ENCODING); Encoder::length(0) } } } -fn set_length(headers: &mut Headers, can_chunked: bool) -> Encoder { - let len = headers.get::().map(|n| **n); +fn set_length(headers: &mut HeaderMap, can_chunked: bool) -> Encoder { + let len = headers::content_length_parse(&headers); if let Some(len) = len { Encoder::length(len) } else if can_chunked { - let encodings = match headers.get_mut::() { - Some(&mut header::TransferEncoding(ref mut encodings)) => { - if encodings.last() != Some(&header::Encoding::Chunked) { - encodings.push(header::Encoding::Chunked); - } - false - }, - None => true - }; - - if encodings { - headers.set(header::TransferEncoding(vec![header::Encoding::Chunked])); - } + //TODO: maybe not overwrite existing transfer-encoding + headers.insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked")); Encoder::chunked() } else { - headers.remove::(); + headers.remove(TRANSFER_ENCODING); Encoder::eof() } } @@ -440,8 +454,8 @@ impl OnUpgrade for NoUpgrades { fn on_encode_upgrade(head: &mut MessageHead) -> ::Result<()> { error!("response with 101 status code not supported"); *head = MessageHead::default(); - head.subject = ::StatusCode::InternalServerError; - head.headers.set(ContentLength(0)); + head.subject = ::StatusCode::INTERNAL_SERVER_ERROR; + headers::content_length_zero(&mut head.headers); Err(::Error::Status) } @@ -475,7 +489,7 @@ struct HeadersAsBytesIter<'a> { } impl<'a> Iterator for HeadersAsBytesIter<'a> { - type Item = (&'a str, Bytes); + type Item = (HeaderName, HeaderValue); fn next(&mut self) -> Option { self.headers.next().map(|header| { let name = unsafe { @@ -485,11 +499,27 @@ impl<'a> Iterator for HeadersAsBytesIter<'a> { ); ::std::str::from_utf8_unchecked(bytes) }; - (name, self.slice.slice(header.value.0, header.value.1)) + let name = HeaderName::from_bytes(name.as_bytes()) + .expect("header name already validated"); + let value = unsafe { + HeaderValue::from_shared_unchecked( + self.slice.slice(header.value.0, header.value.1) + ) + }; + (name, value) }) } } +fn write_headers(headers: &HeaderMap, dst: &mut Vec) { + for (name, value) in headers { + extend(dst, name.as_str().as_bytes()); + extend(dst, b": "); + extend(dst, value.as_bytes()); + extend(dst, b"\r\n"); + } +} + struct FastWrite<'a>(&'a mut Vec); impl<'a> fmt::Write for FastWrite<'a> { @@ -516,7 +546,6 @@ mod tests { use proto::{Decode, MessageHead}; use super::{Decoder, Server as S, Client as C, NoUpgrades, Http1Transaction}; - use header::{ContentLength, TransferEncoding}; type Server = S; type Client = C; @@ -552,11 +581,11 @@ mod tests { let expected_len = raw.len(); let (req, len) = Server::parse(&mut raw).unwrap().unwrap(); assert_eq!(len, expected_len); - assert_eq!(req.subject.0, ::Method::Get); + assert_eq!(req.subject.0, ::Method::GET); assert_eq!(req.subject.1, "/echo"); - assert_eq!(req.version, ::HttpVersion::Http11); + assert_eq!(req.version, ::Version::HTTP_11); assert_eq!(req.headers.len(), 1); - assert_eq!(req.headers.get_raw("Host").map(|raw| &raw[0]), Some(b"hyper.rs".as_ref())); + assert_eq!(req.headers["Host"], "hyper.rs"); } @@ -568,11 +597,10 @@ mod tests { let expected_len = raw.len(); let (req, len) = Client::parse(&mut raw).unwrap().unwrap(); assert_eq!(len, expected_len); - assert_eq!(req.subject.0, 200); - assert_eq!(req.subject.1, "OK"); - assert_eq!(req.version, ::HttpVersion::Http11); + assert_eq!(req.subject, ::StatusCode::OK); + assert_eq!(req.version, ::Version::HTTP_11); assert_eq!(req.headers.len(), 1); - assert_eq!(req.headers.get_raw("Content-Length").map(|raw| &raw[0]), Some(b"0".as_ref())); + assert_eq!(req.headers["Content-Length"], "0"); } #[test] @@ -581,18 +609,6 @@ mod tests { Server::parse(&mut raw).unwrap_err(); } - #[test] - fn test_parse_raw_status() { - let mut raw = BytesMut::from(b"HTTP/1.1 200 OK\r\n\r\n".to_vec()); - let (res, _) = Client::parse(&mut raw).unwrap().unwrap(); - assert_eq!(res.subject.1, "OK"); - - let mut raw = BytesMut::from(b"HTTP/1.1 200 Howdy\r\n\r\n".to_vec()); - let (res, _) = Client::parse(&mut raw).unwrap().unwrap(); - assert_eq!(res.subject.1, "Howdy"); - } - - #[test] fn test_decoder_request() { use super::Decoder; @@ -600,47 +616,49 @@ mod tests { let method = &mut None; let mut head = MessageHead::<::proto::RequestLine>::default(); - head.subject.0 = ::Method::Get; + head.subject.0 = ::Method::GET; assert_eq!(Decoder::length(0), Server::decoder(&head, method).unwrap().normal()); - assert_eq!(*method, Some(::Method::Get)); + assert_eq!(*method, Some(::Method::GET)); - head.subject.0 = ::Method::Post; + head.subject.0 = ::Method::POST; assert_eq!(Decoder::length(0), Server::decoder(&head, method).unwrap().normal()); - assert_eq!(*method, Some(::Method::Post)); + assert_eq!(*method, Some(::Method::POST)); - head.headers.set(TransferEncoding::chunked()); + head.headers.insert("transfer-encoding", ::http::header::HeaderValue::from_static("chunked")); assert_eq!(Decoder::chunked(), Server::decoder(&head, method).unwrap().normal()); // transfer-encoding and content-length = chunked - head.headers.set(ContentLength(10)); + head.headers.insert("content-length", ::http::header::HeaderValue::from_static("10")); assert_eq!(Decoder::chunked(), Server::decoder(&head, method).unwrap().normal()); - head.headers.remove::(); + head.headers.remove("transfer-encoding"); assert_eq!(Decoder::length(10), Server::decoder(&head, method).unwrap().normal()); - head.headers.set_raw("Content-Length", vec![b"5".to_vec(), b"5".to_vec()]); + head.headers.insert("content-length", ::http::header::HeaderValue::from_static("5")); + head.headers.append("content-length", ::http::header::HeaderValue::from_static("5")); assert_eq!(Decoder::length(5), Server::decoder(&head, method).unwrap().normal()); - head.headers.set_raw("Content-Length", vec![b"10".to_vec(), b"11".to_vec()]); + head.headers.insert("content-length", ::http::header::HeaderValue::from_static("5")); + head.headers.append("content-length", ::http::header::HeaderValue::from_static("6")); Server::decoder(&head, method).unwrap_err(); - head.headers.remove::(); + head.headers.remove("content-length"); - head.headers.set_raw("Transfer-Encoding", "gzip"); + head.headers.insert("transfer-encoding", ::http::header::HeaderValue::from_static("gzip")); Server::decoder(&head, method).unwrap_err(); // http/1.0 - head.version = ::HttpVersion::Http10; + head.version = ::Version::HTTP_10; head.headers.clear(); // 1.0 requests can only have bodies if content-length is set assert_eq!(Decoder::length(0), Server::decoder(&head, method).unwrap().normal()); - head.headers.set(TransferEncoding::chunked()); + head.headers.insert("transfer-encoding", ::http::header::HeaderValue::from_static("chunked")); Server::decoder(&head, method).unwrap_err(); - head.headers.remove::(); + head.headers.remove("transfer-encoding"); - head.headers.set(ContentLength(15)); + head.headers.insert("content-length", ::http::header::HeaderValue::from_static("15")); assert_eq!(Decoder::length(15), Server::decoder(&head, method).unwrap().normal()); } @@ -648,67 +666,69 @@ mod tests { fn test_decoder_response() { use super::Decoder; - let method = &mut Some(::Method::Get); - let mut head = MessageHead::<::proto::RawStatus>::default(); + let method = &mut Some(::Method::GET); + let mut head = MessageHead::<::StatusCode>::default(); - head.subject.0 = 204; + head.subject = ::StatusCode::from_u16(204).unwrap(); assert_eq!(Decoder::length(0), Client::decoder(&head, method).unwrap().normal()); - head.subject.0 = 304; + head.subject = ::StatusCode::from_u16(304).unwrap(); assert_eq!(Decoder::length(0), Client::decoder(&head, method).unwrap().normal()); - head.subject.0 = 200; + head.subject = ::StatusCode::OK; assert_eq!(Decoder::eof(), Client::decoder(&head, method).unwrap().normal()); - *method = Some(::Method::Head); + *method = Some(::Method::HEAD); assert_eq!(Decoder::length(0), Client::decoder(&head, method).unwrap().normal()); - *method = Some(::Method::Connect); + *method = Some(::Method::CONNECT); assert_eq!(Decoder::length(0), Client::decoder(&head, method).unwrap().final_()); // CONNECT receiving non 200 can have a body - head.subject.0 = 404; - head.headers.set(ContentLength(10)); + head.subject = ::StatusCode::NOT_FOUND; + head.headers.insert("content-length", ::http::header::HeaderValue::from_static("10")); assert_eq!(Decoder::length(10), Client::decoder(&head, method).unwrap().normal()); - head.headers.remove::(); + head.headers.remove("content-length"); - *method = Some(::Method::Get); - head.headers.set(TransferEncoding::chunked()); + *method = Some(::Method::GET); + head.headers.insert("transfer-encoding", ::http::header::HeaderValue::from_static("chunked")); assert_eq!(Decoder::chunked(), Client::decoder(&head, method).unwrap().normal()); // transfer-encoding and content-length = chunked - head.headers.set(ContentLength(10)); + head.headers.insert("content-length", ::http::header::HeaderValue::from_static("10")); assert_eq!(Decoder::chunked(), Client::decoder(&head, method).unwrap().normal()); - head.headers.remove::(); + head.headers.remove("transfer-encoding"); assert_eq!(Decoder::length(10), Client::decoder(&head, method).unwrap().normal()); - head.headers.set_raw("Content-Length", vec![b"5".to_vec(), b"5".to_vec()]); + head.headers.insert("content-length", ::http::header::HeaderValue::from_static("5")); + head.headers.append("content-length", ::http::header::HeaderValue::from_static("5")); assert_eq!(Decoder::length(5), Client::decoder(&head, method).unwrap().normal()); - head.headers.set_raw("Content-Length", vec![b"10".to_vec(), b"11".to_vec()]); + head.headers.insert("content-length", ::http::header::HeaderValue::from_static("5")); + head.headers.append("content-length", ::http::header::HeaderValue::from_static("6")); Client::decoder(&head, method).unwrap_err(); head.headers.clear(); // 1xx status codes - head.subject.0 = 100; + head.subject = ::StatusCode::CONTINUE; Client::decoder(&head, method).unwrap().ignore(); - head.subject.0 = 103; + head.subject = ::StatusCode::from_u16(103).unwrap(); Client::decoder(&head, method).unwrap().ignore(); // 101 upgrade not supported yet - head.subject.0 = 101; + head.subject = ::StatusCode::SWITCHING_PROTOCOLS; Client::decoder(&head, method).unwrap_err(); - head.subject.0 = 200; + head.subject = ::StatusCode::OK; // http/1.0 - head.version = ::HttpVersion::Http10; + head.version = ::Version::HTTP_10; assert_eq!(Decoder::eof(), Client::decoder(&head, method).unwrap().normal()); - head.headers.set(TransferEncoding::chunked()); + head.headers.insert("transfer-encoding", ::http::header::HeaderValue::from_static("chunked")); Client::decoder(&head, method).unwrap_err(); } @@ -757,23 +777,19 @@ mod tests { #[cfg(feature = "nightly")] #[bench] fn bench_server_transaction_encode(b: &mut Bencher) { - use header::{Headers, ContentLength, ContentType}; - use ::{StatusCode, HttpVersion}; + use http::header::HeaderValue; + use proto::BodyLength; let len = 108; b.bytes = len as u64; - let mut head = MessageHead { - subject: StatusCode::Ok, - headers: Headers::new(), - version: HttpVersion::Http11, - }; - head.headers.set(ContentLength(10)); - head.headers.set(ContentType::json()); + let mut head = MessageHead::default(); + head.headers.insert("content-length", HeaderValue::from_static("10")); + head.headers.insert("content-type", HeaderValue::from_static("application/json")); b.iter(|| { let mut vec = Vec::new(); - Server::encode(head.clone(), true, &mut None, &mut vec).unwrap(); + Server::encode(head.clone(), Some(BodyLength::Known(10)), &mut None, &mut vec).unwrap(); assert_eq!(vec.len(), len); ::test::black_box(vec); }) diff --git a/src/proto/mod.rs b/src/proto/mod.rs index 7c91bd047c..68b7be0cf3 100644 --- a/src/proto/mod.rs +++ b/src/proto/mod.rs @@ -1,16 +1,8 @@ //! Pieces pertaining to the HTTP message protocol. -use std::borrow::Cow; -use std::fmt; - use bytes::BytesMut; +use http::{HeaderMap, Method, StatusCode, Uri, Version}; -use header::{Connection, ConnectionOption, Expect}; -use header::Headers; -use method::Method; -use status::StatusCode; -use uri::Uri; -use version::HttpVersion; -use version::HttpVersion::{Http10, Http11}; +use headers; pub use self::body::Body; pub use self::chunk::Chunk; @@ -20,19 +12,17 @@ mod body; mod chunk; mod h1; //mod h2; -pub mod request; -pub mod response; /// An Incoming Message head. Includes request/status line, and headers. #[derive(Clone, Debug, Default, PartialEq)] pub struct MessageHead { /// HTTP version of the message. - pub version: HttpVersion, + pub version: Version, /// Subject (request line or status line) of Incoming message. pub subject: S, /// Headers of the Incoming message. - pub headers: Headers + pub headers: HeaderMap, } /// An incoming request message. @@ -41,14 +31,8 @@ pub type RequestHead = MessageHead; #[derive(Debug, Default, PartialEq)] pub struct RequestLine(pub Method, pub Uri); -impl fmt::Display for RequestLine { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {}", self.0, self.1) - } -} - /// An incoming response message. -pub type ResponseHead = MessageHead; +pub type ResponseHead = MessageHead; impl MessageHead { pub fn should_keep_alive(&self) -> bool { @@ -60,76 +44,20 @@ impl MessageHead { } } -impl ResponseHead { - /// Converts this head's RawStatus into a StatusCode. - #[inline] - pub fn status(&self) -> StatusCode { - self.subject.status() - } -} - -/// The raw status code and reason-phrase. -#[derive(Clone, PartialEq, Debug)] -pub struct RawStatus(pub u16, pub Cow<'static, str>); - -impl RawStatus { - /// Converts this into a StatusCode. - #[inline] - pub fn status(&self) -> StatusCode { - StatusCode::try_from(self.0).unwrap() - } -} - -impl fmt::Display for RawStatus { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {}", self.0, self.1) - } -} - -impl From for RawStatus { - fn from(status: StatusCode) -> RawStatus { - RawStatus(status.into(), Cow::Borrowed(status.canonical_reason().unwrap_or(""))) - } -} - -impl Default for RawStatus { - fn default() -> RawStatus { - RawStatus(200, Cow::Borrowed("OK")) - } -} - -impl From> for MessageHead { - fn from(head: MessageHead<::StatusCode>) -> MessageHead { - MessageHead { - subject: head.subject.into(), - version: head.version, - headers: head.headers, - } - } -} - /// Checks if a connection should be kept alive. #[inline] -pub fn should_keep_alive(version: HttpVersion, headers: &Headers) -> bool { - let ret = match (version, headers.get::()) { - (Http10, None) => false, - (Http10, Some(conn)) if !conn.contains(&ConnectionOption::KeepAlive) => false, - (Http11, Some(conn)) if conn.contains(&ConnectionOption::Close) => false, - _ => true - }; - trace!("should_keep_alive(version={:?}, header={:?}) = {:?}", version, headers.get::(), ret); - ret +pub fn should_keep_alive(version: Version, headers: &HeaderMap) -> bool { + if version == Version::HTTP_10 { + headers::connection_keep_alive(headers) + } else { + !headers::connection_close(headers) + } } /// Checks if a connection is expecting a `100 Continue` before sending its body. #[inline] -pub fn expecting_continue(version: HttpVersion, headers: &Headers) -> bool { - let ret = match (version, headers.get::()) { - (Http11, Some(&Expect::Continue)) => true, - _ => false - }; - trace!("expecting_continue(version={:?}, header={:?}) = {:?}", version, headers.get::(), ret); - ret +pub fn expecting_continue(version: Version, headers: &HeaderMap) -> bool { + version == Version::HTTP_11 && headers::expect_continue(headers) } pub type ServerTransaction = h1::role::Server; @@ -143,7 +71,7 @@ pub trait Http1Transaction { type Incoming; type Outgoing: Default; fn parse(bytes: &mut BytesMut) -> ParseResult; - fn decoder(head: &MessageHead, method: &mut Option<::Method>) -> ::Result; + fn decoder(head: &MessageHead, method: &mut Option) -> ::Result; fn encode(head: MessageHead, has_body: bool, method: &mut Option, dst: &mut Vec) -> ::Result; fn on_error(err: &::Error) -> Option>; @@ -165,28 +93,28 @@ pub enum Decode { #[test] fn test_should_keep_alive() { - let mut headers = Headers::new(); + let mut headers = HeaderMap::new(); - assert!(!should_keep_alive(Http10, &headers)); - assert!(should_keep_alive(Http11, &headers)); + assert!(!should_keep_alive(Version::HTTP_10, &headers)); + assert!(should_keep_alive(Version::HTTP_11, &headers)); - headers.set(Connection::close()); - assert!(!should_keep_alive(Http10, &headers)); - assert!(!should_keep_alive(Http11, &headers)); + headers.insert("connection", ::http::header::HeaderValue::from_static("close")); + assert!(!should_keep_alive(Version::HTTP_10, &headers)); + assert!(!should_keep_alive(Version::HTTP_11, &headers)); - headers.set(Connection::keep_alive()); - assert!(should_keep_alive(Http10, &headers)); - assert!(should_keep_alive(Http11, &headers)); + headers.insert("connection", ::http::header::HeaderValue::from_static("keep-alive")); + assert!(should_keep_alive(Version::HTTP_10, &headers)); + assert!(should_keep_alive(Version::HTTP_11, &headers)); } #[test] fn test_expecting_continue() { - let mut headers = Headers::new(); + let mut headers = HeaderMap::new(); - assert!(!expecting_continue(Http10, &headers)); - assert!(!expecting_continue(Http11, &headers)); + assert!(!expecting_continue(Version::HTTP_10, &headers)); + assert!(!expecting_continue(Version::HTTP_11, &headers)); - headers.set(Expect::Continue); - assert!(!expecting_continue(Http10, &headers)); - assert!(expecting_continue(Http11, &headers)); + headers.insert("expect", ::http::header::HeaderValue::from_static("100-continue")); + assert!(!expecting_continue(Version::HTTP_10, &headers)); + assert!(expecting_continue(Version::HTTP_11, &headers)); } diff --git a/src/proto/request.rs b/src/proto/request.rs deleted file mode 100644 index c989b9c5bc..0000000000 --- a/src/proto/request.rs +++ /dev/null @@ -1,322 +0,0 @@ -use std::fmt; -#[cfg(feature = "compat")] -use std::mem::replace; -use std::net::SocketAddr; - -#[cfg(feature = "compat")] -use http; - -use header::Headers; -use proto::{Body, MessageHead, RequestHead, RequestLine}; -use method::Method; -use uri::{self, Uri}; -use version::HttpVersion; - -/// An HTTP Request -pub struct Request { - method: Method, - uri: Uri, - version: HttpVersion, - headers: Headers, - body: Option, - is_proxy: bool, - remote_addr: Option, -} - -impl Request { - /// Construct a new Request. - #[inline] - pub fn new(method: Method, uri: Uri) -> Request { - Request { - method: method, - uri: uri, - version: HttpVersion::default(), - headers: Headers::new(), - body: None, - is_proxy: false, - remote_addr: None, - } - } - - /// Read the Request Uri. - #[inline] - pub fn uri(&self) -> &Uri { &self.uri } - - /// Read the Request Version. - #[inline] - pub fn version(&self) -> HttpVersion { self.version } - - /// Read the Request headers. - #[inline] - pub fn headers(&self) -> &Headers { &self.headers } - - /// Read the Request method. - #[inline] - pub fn method(&self) -> &Method { &self.method } - - /// Read the Request body. - #[inline] - pub fn body_ref(&self) -> Option<&B> { self.body.as_ref() } - - /// Get a mutable reference to the Request body. - #[inline] - pub fn body_mut(&mut self) -> &mut Option { &mut self.body } - - #[doc(hidden)] - #[inline] - #[deprecated(since="0.11.12", note="This method will be gone in future versions.")] - pub fn remote_addr(&self) -> Option { self.remote_addr } - - /// The target path of this Request. - #[inline] - pub fn path(&self) -> &str { - self.uri.path() - } - - /// The query string of this Request. - #[inline] - pub fn query(&self) -> Option<&str> { - self.uri.query() - } - - /// Set the Method of this request. - #[inline] - pub fn set_method(&mut self, method: Method) { self.method = method; } - - /// Get a mutable reference to the Request headers. - #[inline] - pub fn headers_mut(&mut self) -> &mut Headers { &mut self.headers } - - /// Set the `Uri` of this request. - #[inline] - pub fn set_uri(&mut self, uri: Uri) { self.uri = uri; } - - /// Set the `HttpVersion` of this request. - #[inline] - pub fn set_version(&mut self, version: HttpVersion) { self.version = version; } - - /// Set the body of the request. - /// - /// By default, the body will be sent using `Transfer-Encoding: chunked`. To - /// override this behavior, manually set a [`ContentLength`] header with the - /// length of `body`. - #[inline] - pub fn set_body>(&mut self, body: T) { self.body = Some(body.into()); } - - /// Set that the URI should use the absolute form. - /// - /// This is only needed when talking to HTTP/1 proxies to URLs not - /// protected by TLS. - #[inline] - pub fn set_proxy(&mut self, is_proxy: bool) { self.is_proxy = is_proxy; } - - pub(crate) fn is_proxy(&self) -> bool { self.is_proxy } -} - -impl Request { - /// Deconstruct this Request into its pieces. - /// - /// Modifying these pieces will have no effect on how hyper behaves. - #[inline] - pub fn deconstruct(self) -> (Method, Uri, HttpVersion, Headers, Body) { - (self.method, self.uri, self.version, self.headers, self.body.unwrap_or_default()) - } - - /// Take the Request body. - #[inline] - pub fn body(self) -> Body { self.body.unwrap_or_default() } -} - -impl fmt::Debug for Request { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Request") - .field("method", &self.method) - .field("uri", &self.uri) - .field("version", &self.version) - .field("remote_addr", &self.remote_addr) - .field("headers", &self.headers) - .finish() - } -} - -#[cfg(feature = "compat")] -impl From for http::Request { - fn from(from_req: Request) -> http::Request { - let (m, u, v, h, b) = from_req.deconstruct(); - - let to_req = http::Request::new(()); - let (mut to_parts, _) = to_req.into_parts(); - - to_parts.method = m.into(); - to_parts.uri = u.into(); - to_parts.version = v.into(); - to_parts.headers = h.into(); - - http::Request::from_parts(to_parts, b) - } -} - -#[cfg(feature = "compat")] -impl From> for Request { - fn from(from_req: http::Request) -> Request { - let (from_parts, body) = from_req.into_parts(); - - let mut to_req = Request::new(from_parts.method.into(), from_parts.uri.into()); - to_req.set_version(from_parts.version.into()); - replace(to_req.headers_mut(), from_parts.headers.into()); - to_req.set_body(body); - to_req - } -} - -/// Constructs a request using a received ResponseHead and optional body -pub fn from_wire(addr: Option, incoming: RequestHead, body: Option) -> Request { - Request { - remote_addr: addr, - ..join((incoming, body)) - } -} - -pub fn split(req: Request) -> (RequestHead, Option) { - let uri = if req.is_proxy { - req.uri - } else { - uri::origin_form(&req.uri) - }; - let head = RequestHead { - subject: ::proto::RequestLine(req.method, uri), - headers: req.headers, - version: req.version, - }; - (head, req.body) -} - -pub fn join((head, body): (RequestHead, Option)) -> Request { - let MessageHead { version, subject: RequestLine(method, uri), headers } = head; - - Request { - method: method, - uri: uri, - headers: headers, - version: version, - remote_addr: None, - body: body, - is_proxy: false, - } -} - -pub fn addr(req: &mut Request, addr: SocketAddr) { - req.remote_addr = Some(addr); -} - -#[cfg(test)] -mod tests { - /* - use std::io::Write; - use std::str::from_utf8; - use Url; - use method::Method::{Get, Head, Post}; - use mock::{MockStream, MockConnector}; - use net::Fresh; - use header::{ContentLength,TransferEncoding,Encoding}; - use url::form_urlencoded; - use super::Request; - use http::h1::Http11Message; - - fn run_request(req: Request) -> Vec { - let req = req.start().unwrap(); - let message = req.message; - let mut message = message.downcast::().ok().unwrap(); - message.flush_outgoing().unwrap(); - let stream = *message - .into_inner().downcast::().ok().unwrap(); - stream.write - } - - fn assert_no_body(s: &str) { - assert!(!s.contains("Content-Length:")); - assert!(!s.contains("Transfer-Encoding:")); - } - - #[test] - fn test_get_empty_body() { - let req = Request::with_connector( - Get, Url::parse("http://example.dom").unwrap(), &mut MockConnector - ).unwrap(); - let bytes = run_request(req); - let s = from_utf8(&bytes[..]).unwrap(); - assert_no_body(s); - } - - #[test] - fn test_head_empty_body() { - let req = Request::with_connector( - Head, Url::parse("http://example.dom").unwrap(), &mut MockConnector - ).unwrap(); - let bytes = run_request(req); - let s = from_utf8(&bytes[..]).unwrap(); - assert_no_body(s); - } - - #[test] - fn test_url_query() { - let url = Url::parse("http://example.dom?q=value").unwrap(); - let req = Request::with_connector( - Get, url, &mut MockConnector - ).unwrap(); - let bytes = run_request(req); - let s = from_utf8(&bytes[..]).unwrap(); - assert!(s.contains("?q=value")); - } - - #[test] - fn test_post_content_length() { - let url = Url::parse("http://example.dom").unwrap(); - let mut req = Request::with_connector( - Post, url, &mut MockConnector - ).unwrap(); - let mut body = String::new(); - form_urlencoded::Serializer::new(&mut body).append_pair("q", "value"); - req.headers_mut().set(ContentLength(body.len() as u64)); - let bytes = run_request(req); - let s = from_utf8(&bytes[..]).unwrap(); - assert!(s.contains("Content-Length:")); - } - - #[test] - fn test_post_chunked() { - let url = Url::parse("http://example.dom").unwrap(); - let req = Request::with_connector( - Post, url, &mut MockConnector - ).unwrap(); - let bytes = run_request(req); - let s = from_utf8(&bytes[..]).unwrap(); - assert!(!s.contains("Content-Length:")); - } - - #[test] - fn test_host_header() { - let url = Url::parse("http://example.dom").unwrap(); - let req = Request::with_connector( - Get, url, &mut MockConnector - ).unwrap(); - let bytes = run_request(req); - let s = from_utf8(&bytes[..]).unwrap(); - assert!(s.contains("Host: example.dom")); - } - - #[test] - fn test_proxy() { - let url = Url::parse("http://example.dom").unwrap(); - let mut req = Request::with_connector( - Get, url, &mut MockConnector - ).unwrap(); - req.message.set_proxied(true); - let bytes = run_request(req); - let s = from_utf8(&bytes[..]).unwrap(); - let request_line = "GET http://example.dom/ HTTP/1.1"; - assert_eq!(&s[..request_line.len()], request_line); - assert!(s.contains("Host: example.dom")); - } - */ -} diff --git a/src/proto/response.rs b/src/proto/response.rs deleted file mode 100644 index 0154cb15f1..0000000000 --- a/src/proto/response.rs +++ /dev/null @@ -1,212 +0,0 @@ -use std::fmt; -#[cfg(feature = "compat")] -use std::mem::replace; - -#[cfg(feature = "compat")] -use http; - -use header::{Header, Headers}; -use proto::{MessageHead, ResponseHead, Body}; -use status::StatusCode; -use version::HttpVersion; - -/// An HTTP Response -pub struct Response { - version: HttpVersion, - headers: Headers, - status: StatusCode, - #[cfg(feature = "raw_status")] - raw_status: ::proto::RawStatus, - body: Option, -} - -impl Response { - /// Constructs a default response - #[inline] - pub fn new() -> Response { - Response::default() - } - - /// Get the HTTP version of this response. - #[inline] - pub fn version(&self) -> HttpVersion { self.version } - - /// Get the headers from the response. - #[inline] - pub fn headers(&self) -> &Headers { &self.headers } - - /// Get a mutable reference to the headers. - #[inline] - pub fn headers_mut(&mut self) -> &mut Headers { &mut self.headers } - - /// Get the status from the server. - #[inline] - pub fn status(&self) -> StatusCode { self.status } - - /// Get the raw status code and reason. - /// - /// This method is only useful when inspecting the raw subject line from - /// a received response. - #[inline] - #[cfg(feature = "raw_status")] - pub fn status_raw(&self) -> &::proto::RawStatus { &self.raw_status } - - /// Set the `StatusCode` for this response. - #[inline] - pub fn set_status(&mut self, status: StatusCode) { - self.status = status; - } - - /// Set the status and move the Response. - /// - /// Useful for the "builder-style" pattern. - #[inline] - pub fn with_status(mut self, status: StatusCode) -> Self { - self.set_status(status); - self - } - - /// Set a header and move the Response. - /// - /// Useful for the "builder-style" pattern. - #[inline] - pub fn with_header(mut self, header: H) -> Self { - self.headers.set(header); - self - } - - /// Set the headers and move the Response. - /// - /// Useful for the "builder-style" pattern. - #[inline] - pub fn with_headers(mut self, headers: Headers) -> Self { - self.headers = headers; - self - } - - /// Set the body. - #[inline] - pub fn set_body>(&mut self, body: T) { - self.body = Some(body.into()); - } - - /// Set the body and move the Response. - /// - /// Useful for the "builder-style" pattern. - #[inline] - pub fn with_body>(mut self, body: T) -> Self { - self.set_body(body); - self - } - - /// Read the body. - #[inline] - pub fn body_ref(&self) -> Option<&B> { self.body.as_ref() } -} - -impl Response { - /// Take the `Body` of this response. - #[inline] - pub fn body(self) -> Body { - self.body.unwrap_or_default() - } -} - -#[cfg(not(feature = "raw_status"))] -impl Default for Response { - fn default() -> Response { - Response:: { - version: Default::default(), - headers: Default::default(), - status: Default::default(), - body: None, - } - } -} - -#[cfg(feature = "raw_status")] -impl Default for Response { - fn default() -> Response { - Response:: { - version: Default::default(), - headers: Default::default(), - status: Default::default(), - raw_status: Default::default(), - body: None, - } - } -} - -impl fmt::Debug for Response { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Response") - .field("status", &self.status) - .field("version", &self.version) - .field("headers", &self.headers) - .finish() - } -} - -#[cfg(feature = "compat")] -impl From> for Response { - fn from(from_res: http::Response) -> Response { - let (from_parts, body) = from_res.into_parts(); - let mut to_res = Response::new(); - to_res.version = from_parts.version.into(); - to_res.set_status(from_parts.status.into()); - replace(to_res.headers_mut(), from_parts.headers.into()); - to_res.with_body(body) - } -} - -#[cfg(feature = "compat")] -impl From for http::Response { - fn from(mut from_res: Response) -> http::Response { - let (mut to_parts, ()) = http::Response::new(()).into_parts(); - to_parts.version = from_res.version().into(); - to_parts.status = from_res.status().into(); - let from_headers = replace(from_res.headers_mut(), Headers::new()); - to_parts.headers = from_headers.into(); - http::Response::from_parts(to_parts, from_res.body()) - } -} - -/// Constructs a response using a received ResponseHead and optional body -#[inline] -#[cfg(not(feature = "raw_status"))] -pub fn from_wire(incoming: ResponseHead, body: Option) -> Response { - let status = incoming.status(); - - Response:: { - status: status, - version: incoming.version, - headers: incoming.headers, - body: body, - } -} - -/// Constructs a response using a received ResponseHead and optional body -#[inline] -#[cfg(feature = "raw_status")] -pub fn from_wire(incoming: ResponseHead, body: Option) -> Response { - let status = incoming.status(); - - Response:: { - status: status, - version: incoming.version, - headers: incoming.headers, - raw_status: incoming.subject, - body: body, - } -} - -/// Splits this response into a `MessageHead` and its body -#[inline] -pub fn split(res: Response) -> (MessageHead, Option) { - let head = MessageHead:: { - version: res.version, - headers: res.headers, - subject: res.status - }; - (head, res.body) -} diff --git a/src/server/compat.rs b/src/server/compat.rs deleted file mode 100644 index 3a44bd2552..0000000000 --- a/src/server/compat.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Wrappers to build compatibility with the `http` crate. - -use std::io::{Error as IoError}; - -use futures::{Future, Poll}; -use http; -use tokio_service::{NewService, Service}; - -use error::Error; -use proto::Body; -use proto::request::Request; -use proto::response::Response; - -/// Wraps a `Future` returning an `http::Response` into -/// a `Future` returning a `hyper::server::Response`. -#[derive(Debug)] -pub struct CompatFuture { - future: F -} - -impl Future for CompatFuture - where F: Future, Error=Error> -{ - type Item = Response; - type Error = Error; - - fn poll(&mut self) -> Poll { - self.future.poll() - .map(|a| a.map(|res| res.into())) - } -} - -/// Wraps a `Service` taking an `http::Request` and returning -/// an `http::Response` into a `Service` taking a `hyper::server::Request`, -/// and returning a `hyper::server::Response`. -#[derive(Debug)] -pub struct CompatService { - service: S -} - -pub(super) fn service(service: S) -> CompatService { - CompatService { service: service } -} - -impl Service for CompatService - where S: Service, Response=http::Response, Error=Error> -{ - type Request = Request; - type Response = Response; - type Error = Error; - type Future = CompatFuture; - - fn call(&self, req: Self::Request) -> Self::Future { - CompatFuture { - future: self.service.call(req.into()) - } - } -} - -/// Wraps a `NewService` taking an `http::Request` and returning -/// an `http::Response` into a `NewService` taking a `hyper::server::Request`, -/// and returning a `hyper::server::Response`. -#[derive(Debug)] -pub struct NewCompatService { - new_service: S -} - -pub(super) fn new_service(new_service: S) -> NewCompatService { - NewCompatService { new_service: new_service } -} - -impl NewService for NewCompatService - where S: NewService, Response=http::Response, Error=Error> -{ - type Request = Request; - type Response = Response; - type Error = Error; - type Instance = CompatService; - - fn new_service(&self) -> Result { - self.new_service.new_service() - .map(service) - } -} diff --git a/src/server/conn.rs b/src/server/conn.rs index 040037b788..389e28c708 100644 --- a/src/server/conn.rs +++ b/src/server/conn.rs @@ -14,7 +14,7 @@ use bytes::Bytes; use futures::{Future, Poll, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; -use proto; +use proto::{self, Body}; use super::{HyperService, Request, Response, Service}; /// A future binding a connection with a Service. @@ -59,7 +59,7 @@ pub struct Parts { // ===== impl Connection ===== impl Connection -where S: Service, Error = ::Error> + 'static, +where S: Service, Response = Response, Error = ::Error> + 'static, I: AsyncRead + AsyncWrite + 'static, B: Stream + 'static, B::Item: AsRef<[u8]>, @@ -97,7 +97,7 @@ where S: Service, Error = ::Error> + ' } impl Future for Connection -where S: Service, Error = ::Error> + 'static, +where S: Service, Response = Response, Error = ::Error> + 'static, I: AsyncRead + AsyncWrite + 'static, B: Stream + 'static, B::Item: AsRef<[u8]>, diff --git a/src/server/mod.rs b/src/server/mod.rs index 5e27c97a15..0c2c93100d 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -3,8 +3,6 @@ //! A `Server` is created to listen on a port, parse HTTP requests, and hand //! them off to a `Service`. -#[cfg(feature = "compat")] -pub mod compat; pub mod conn; mod service; @@ -19,24 +17,16 @@ use std::time::Duration; use futures::task::{self, Task}; use futures::future::{self}; use futures::{Future, Stream, Poll, Async}; - -#[cfg(feature = "compat")] -use http; - +use http::{Request, Response}; use tokio_io::{AsyncRead, AsyncWrite}; use tokio::reactor::{Core, Handle, Timeout}; use tokio::net::TcpListener; pub use tokio_service::{NewService, Service}; -use proto; -#[cfg(feature = "compat")] -use proto::Body; +use proto::{self, Body}; use self::addr_stream::AddrStream; use self::hyper_service::HyperService; -pub use proto::response::Response; -pub use proto::request::Request; - pub use self::conn::Connection; pub use self::service::{const_service, service_fn}; @@ -163,7 +153,7 @@ impl + 'static> Http { /// The returned `Server` contains one method, `run`, which is used to /// actually run the server. pub fn bind(&self, addr: &SocketAddr, new_service: S) -> ::Result> - where S: NewService, Error = ::Error> + 'static, + where S: NewService, Response = Response, Error = ::Error> + 'static, Bd: Stream, { let core = try!(Core::new()); @@ -179,19 +169,6 @@ impl + 'static> Http { }) } - - /// Bind a `NewService` using types from the `http` crate. - /// - /// See `Http::bind`. - #[cfg(feature = "compat")] - pub fn bind_compat(&self, addr: &SocketAddr, new_service: S) -> ::Result, Bd>> - where S: NewService, Response = http::Response, Error = ::Error> + - Send + Sync + 'static, - Bd: Stream, - { - self.bind(addr, self::compat::new_service(new_service)) - } - /// Bind the provided `addr` and return a server with a shared `Core`. /// /// This method allows the ability to share a `Core` with multiple servers. @@ -201,7 +178,7 @@ impl + 'static> Http { /// `new_service` object provided as well, creating a new service per /// connection. pub fn serve_addr_handle(&self, addr: &SocketAddr, handle: &Handle, new_service: S) -> ::Result> - where S: NewService, Error = ::Error>, + where S: NewService, Response = Response, Error = ::Error>, Bd: Stream, { let listener = TcpListener::bind(addr, &handle)?; @@ -218,7 +195,7 @@ impl + 'static> Http { pub fn serve_incoming(&self, incoming: I, new_service: S) -> Serve where I: Stream, I::Item: AsyncRead + AsyncWrite, - S: NewService, Error = ::Error>, + S: NewService, Response = Response, Error = ::Error>, Bd: Stream, { Serve { @@ -247,13 +224,14 @@ impl + 'static> Http { /// # extern crate tokio_core; /// # extern crate tokio_io; /// # use futures::Future; - /// # use hyper::server::{Http, Request, Response, Service}; + /// # use hyper::{Body, Request, Response}; + /// # use hyper::server::{Http, Service}; /// # use tokio_io::{AsyncRead, AsyncWrite}; /// # use tokio_core::reactor::Handle; /// # fn run(some_io: I, some_service: S, some_handle: &Handle) /// # where /// # I: AsyncRead + AsyncWrite + 'static, - /// # S: Service + 'static, + /// # S: Service, Response=Response, Error=hyper::Error> + 'static, /// # { /// let http = Http::::new(); /// let conn = http.serve_connection(some_io, some_service); @@ -267,7 +245,7 @@ impl + 'static> Http { /// # fn main() {} /// ``` pub fn serve_connection(&self, io: I, service: S) -> Connection - where S: Service, Error = ::Error>, + where S: Service, Response = Response, Error = ::Error>, Bd: Stream, Bd::Item: AsRef<[u8]>, I: AsyncRead + AsyncWrite, @@ -311,7 +289,7 @@ impl fmt::Debug for Http { // ===== impl Server ===== impl Server - where S: NewService, Error = ::Error> + 'static, + where S: NewService, Response = Response, Error = ::Error> + 'static, B: Stream + 'static, B::Item: AsRef<[u8]>, { @@ -384,9 +362,9 @@ impl Server let addr = socket.remote_addr; debug!("accepted new connection ({})", addr); - let addr_service = SocketAddrService::new(addr, new_service.new_service()?); + let service = new_service.new_service()?; let s = NotifyService { - inner: addr_service, + inner: service, info: Rc::downgrade(&info), }; info.borrow_mut().active += 1; @@ -466,7 +444,7 @@ impl Stream for Serve where I: Stream, I::Item: AsyncRead + AsyncWrite, - S: NewService, Error=::Error>, + S: NewService, Response=Response, Error=::Error>, B: Stream, B::Item: AsRef<[u8]>, { @@ -490,7 +468,7 @@ impl Future for SpawnAll where I: Stream, I::Item: AsyncRead + AsyncWrite, - S: NewService, Error=::Error>, + S: NewService, Response=Response, Error=::Error>, B: Stream, B::Item: AsRef<[u8]>, //E: Executor>, @@ -683,39 +661,6 @@ mod addr_stream { } } -// ===== SocketAddrService - -// This is used from `Server::run`, which captures the remote address -// in this service, and then injects it into each `Request`. -struct SocketAddrService { - addr: SocketAddr, - inner: S, -} - -impl SocketAddrService { - fn new(addr: SocketAddr, service: S) -> SocketAddrService { - SocketAddrService { - addr: addr, - inner: service, - } - } -} - -impl Service for SocketAddrService -where - S: Service, -{ - type Request = S::Request; - type Response = S::Response; - type Error = S::Error; - type Future = S::Future; - - fn call(&self, mut req: Self::Request) -> Self::Future { - proto::request::addr(&mut req, self.addr); - self.inner.call(req) - } -} - // ===== NotifyService ===== struct NotifyService { @@ -775,7 +720,7 @@ impl Future for WaitUntilZero { } mod hyper_service { - use super::{Request, Response, Service, Stream}; + use super::{Body, Request, Response, Service, Stream}; /// A "trait alias" for any type that implements `Service` with hyper's /// Request, Response, and Error types, and a streaming body. /// @@ -802,7 +747,7 @@ mod hyper_service { impl Sealed for S where S: Service< - Request=Request, + Request=Request, Response=Response, Error=::Error, >, @@ -813,7 +758,7 @@ mod hyper_service { impl HyperService for S where S: Service< - Request=Request, + Request=Request, Response=Response, Error=::Error, >, diff --git a/src/status.rs b/src/status.rs deleted file mode 100644 index bc09328363..0000000000 --- a/src/status.rs +++ /dev/null @@ -1,782 +0,0 @@ -//! HTTP status codes -use std::fmt; -use std::cmp::Ordering; - -#[cfg(feature = "compat")] -use http; - -/// An HTTP status code (`status-code` in RFC 7230 et al.). -/// -/// This enum contains all common status codes and an Unregistered -/// extension variant. It allows status codes in the range [0, 65535], as any -/// `u16` integer may be used as a status code for XHR requests. It is -/// recommended to only use values between [100, 599], since only these are -/// defined as valid status codes with a status class by HTTP. -/// -/// IANA maintain the [Hypertext Transfer Protocol (HTTP) Status Code -/// Registry](http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml) which is -/// the source for this enum (with one exception, 418 I'm a teapot, which is -/// inexplicably not in the register). -#[derive(Debug, Hash)] -pub enum StatusCode { - /// 100 Continue - /// [[RFC7231, Section 6.2.1](https://tools.ietf.org/html/rfc7231#section-6.2.1)] - Continue, - /// 101 Switching Protocols - /// [[RFC7231, Section 6.2.2](https://tools.ietf.org/html/rfc7231#section-6.2.2)] - SwitchingProtocols, - /// 102 Processing - /// [[RFC2518](https://tools.ietf.org/html/rfc2518)] - Processing, - - /// 200 OK - /// [[RFC7231, Section 6.3.1](https://tools.ietf.org/html/rfc7231#section-6.3.1)] - Ok, - /// 201 Created - /// [[RFC7231, Section 6.3.2](https://tools.ietf.org/html/rfc7231#section-6.3.2)] - Created, - /// 202 Accepted - /// [[RFC7231, Section 6.3.3](https://tools.ietf.org/html/rfc7231#section-6.3.3)] - Accepted, - /// 203 Non-Authoritative Information - /// [[RFC7231, Section 6.3.4](https://tools.ietf.org/html/rfc7231#section-6.3.4)] - NonAuthoritativeInformation, - /// 204 No Content - /// [[RFC7231, Section 6.3.5](https://tools.ietf.org/html/rfc7231#section-6.3.5)] - NoContent, - /// 205 Reset Content - /// [[RFC7231, Section 6.3.6](https://tools.ietf.org/html/rfc7231#section-6.3.6)] - ResetContent, - /// 206 Partial Content - /// [[RFC7233, Section 4.1](https://tools.ietf.org/html/rfc7233#section-4.1)] - PartialContent, - /// 207 Multi-Status - /// [[RFC4918](https://tools.ietf.org/html/rfc4918)] - MultiStatus, - /// 208 Already Reported - /// [[RFC5842](https://tools.ietf.org/html/rfc5842)] - AlreadyReported, - - /// 226 IM Used - /// [[RFC3229](https://tools.ietf.org/html/rfc3229)] - ImUsed, - - /// 300 Multiple Choices - /// [[RFC7231, Section 6.4.1](https://tools.ietf.org/html/rfc7231#section-6.4.1)] - MultipleChoices, - /// 301 Moved Permanently - /// [[RFC7231, Section 6.4.2](https://tools.ietf.org/html/rfc7231#section-6.4.2)] - MovedPermanently, - /// 302 Found - /// [[RFC7231, Section 6.4.3](https://tools.ietf.org/html/rfc7231#section-6.4.3)] - Found, - /// 303 See Other - /// [[RFC7231, Section 6.4.4](https://tools.ietf.org/html/rfc7231#section-6.4.4)] - SeeOther, - /// 304 Not Modified - /// [[RFC7232, Section 4.1](https://tools.ietf.org/html/rfc7232#section-4.1)] - NotModified, - /// 305 Use Proxy - /// [[RFC7231, Section 6.4.5](https://tools.ietf.org/html/rfc7231#section-6.4.5)] - UseProxy, - /// 307 Temporary Redirect - /// [[RFC7231, Section 6.4.7](https://tools.ietf.org/html/rfc7231#section-6.4.7)] - TemporaryRedirect, - /// 308 Permanent Redirect - /// [[RFC7238](https://tools.ietf.org/html/rfc7238)] - PermanentRedirect, - - /// 400 Bad Request - /// [[RFC7231, Section 6.5.1](https://tools.ietf.org/html/rfc7231#section-6.5.1)] - BadRequest, - /// 401 Unauthorized - /// [[RFC7235, Section 3.1](https://tools.ietf.org/html/rfc7235#section-3.1)] - Unauthorized, - /// 402 Payment Required - /// [[RFC7231, Section 6.5.2](https://tools.ietf.org/html/rfc7231#section-6.5.2)] - PaymentRequired, - /// 403 Forbidden - /// [[RFC7231, Section 6.5.3](https://tools.ietf.org/html/rfc7231#section-6.5.3)] - Forbidden, - /// 404 Not Found - /// [[RFC7231, Section 6.5.4](https://tools.ietf.org/html/rfc7231#section-6.5.4)] - NotFound, - /// 405 Method Not Allowed - /// [[RFC7231, Section 6.5.5](https://tools.ietf.org/html/rfc7231#section-6.5.5)] - MethodNotAllowed, - /// 406 Not Acceptable - /// [[RFC7231, Section 6.5.6](https://tools.ietf.org/html/rfc7231#section-6.5.6)] - NotAcceptable, - /// 407 Proxy Authentication Required - /// [[RFC7235, Section 3.2](https://tools.ietf.org/html/rfc7235#section-3.2)] - ProxyAuthenticationRequired, - /// 408 Request Timeout - /// [[RFC7231, Section 6.5.7](https://tools.ietf.org/html/rfc7231#section-6.5.7)] - RequestTimeout, - /// 409 Conflict - /// [[RFC7231, Section 6.5.8](https://tools.ietf.org/html/rfc7231#section-6.5.8)] - Conflict, - /// 410 Gone - /// [[RFC7231, Section 6.5.9](https://tools.ietf.org/html/rfc7231#section-6.5.9)] - Gone, - /// 411 Length Required - /// [[RFC7231, Section 6.5.10](https://tools.ietf.org/html/rfc7231#section-6.5.10)] - LengthRequired, - /// 412 Precondition Failed - /// [[RFC7232, Section 4.2](https://tools.ietf.org/html/rfc7232#section-4.2)] - PreconditionFailed, - /// 413 Payload Too Large - /// [[RFC7231, Section 6.5.11](https://tools.ietf.org/html/rfc7231#section-6.5.11)] - PayloadTooLarge, - /// 414 URI Too Long - /// [[RFC7231, Section 6.5.12](https://tools.ietf.org/html/rfc7231#section-6.5.12)] - UriTooLong, - /// 415 Unsupported Media Type - /// [[RFC7231, Section 6.5.13](https://tools.ietf.org/html/rfc7231#section-6.5.13)] - UnsupportedMediaType, - /// 416 Range Not Satisfiable - /// [[RFC7233, Section 4.4](https://tools.ietf.org/html/rfc7233#section-4.4)] - RangeNotSatisfiable, - /// 417 Expectation Failed - /// [[RFC7231, Section 6.5.14](https://tools.ietf.org/html/rfc7231#section-6.5.14)] - ExpectationFailed, - /// 418 I'm a teapot - /// [curiously, not registered by IANA, but [RFC2324](https://tools.ietf.org/html/rfc2324)] - ImATeapot, - - /// 421 Misdirected Request - /// [RFC7540, Section 9.1.2](http://tools.ietf.org/html/rfc7540#section-9.1.2) - MisdirectedRequest, - /// 422 Unprocessable Entity - /// [[RFC4918](https://tools.ietf.org/html/rfc4918)] - UnprocessableEntity, - /// 423 Locked - /// [[RFC4918](https://tools.ietf.org/html/rfc4918)] - Locked, - /// 424 Failed Dependency - /// [[RFC4918](https://tools.ietf.org/html/rfc4918)] - FailedDependency, - - /// 426 Upgrade Required - /// [[RFC7231, Section 6.5.15](https://tools.ietf.org/html/rfc7231#section-6.5.15)] - UpgradeRequired, - - /// 428 Precondition Required - /// [[RFC6585](https://tools.ietf.org/html/rfc6585)] - PreconditionRequired, - /// 429 Too Many Requests - /// [[RFC6585](https://tools.ietf.org/html/rfc6585)] - TooManyRequests, - - /// 431 Request Header Fields Too Large - /// [[RFC6585](https://tools.ietf.org/html/rfc6585)] - RequestHeaderFieldsTooLarge, - - /// 451 Unavailable For Legal Reasons - /// [[RFC7725](http://tools.ietf.org/html/rfc7725)] - UnavailableForLegalReasons, - - /// 500 Internal Server Error - /// [[RFC7231, Section 6.6.1](https://tools.ietf.org/html/rfc7231#section-6.6.1)] - InternalServerError, - /// 501 Not Implemented - /// [[RFC7231, Section 6.6.2](https://tools.ietf.org/html/rfc7231#section-6.6.2)] - NotImplemented, - /// 502 Bad Gateway - /// [[RFC7231, Section 6.6.3](https://tools.ietf.org/html/rfc7231#section-6.6.3)] - BadGateway, - /// 503 Service Unavailable - /// [[RFC7231, Section 6.6.4](https://tools.ietf.org/html/rfc7231#section-6.6.4)] - ServiceUnavailable, - /// 504 Gateway Timeout - /// [[RFC7231, Section 6.6.5](https://tools.ietf.org/html/rfc7231#section-6.6.5)] - GatewayTimeout, - /// 505 HTTP Version Not Supported - /// [[RFC7231, Section 6.6.6](https://tools.ietf.org/html/rfc7231#section-6.6.6)] - HttpVersionNotSupported, - /// 506 Variant Also Negotiates - /// [[RFC2295](https://tools.ietf.org/html/rfc2295)] - VariantAlsoNegotiates, - /// 507 Insufficient Storage - /// [[RFC4918](https://tools.ietf.org/html/rfc4918)] - InsufficientStorage, - /// 508 Loop Detected - /// [[RFC5842](https://tools.ietf.org/html/rfc5842)] - LoopDetected, - - /// 510 Not Extended - /// [[RFC2774](https://tools.ietf.org/html/rfc2774)] - NotExtended, - /// 511 Network Authentication Required - /// [[RFC6585](https://tools.ietf.org/html/rfc6585)] - NetworkAuthenticationRequired, - - /// A status code not in the IANA HTTP status code registry or very well known - // `ImATeapot` is not registered. - Unregistered(u16), -} - -#[derive(Debug)] -pub struct InvalidStatusCode { - _inner: (), -} - -impl StatusCode { - - /// Try to convert a `u16` into a `StatusCode`. - /// - /// # Errors - /// - /// This will return an error if the provided argument is not within the - /// range `100...599`. - /// - /// # Note - /// - /// This function is temporary. When the `TryFrom` trait becomes stable, - /// this will be deprecated and replaced by `TryFrom`. - pub fn try_from(n: u16) -> Result { - if n < 100 || n > 599 { - Err(InvalidStatusCode { - _inner: (), - }) - } else { - Ok(StatusCode::from_u16(n)) - } - } - - fn from_u16(n: u16) -> StatusCode { - match n { - 100 => StatusCode::Continue, - 101 => StatusCode::SwitchingProtocols, - 102 => StatusCode::Processing, - 200 => StatusCode::Ok, - 201 => StatusCode::Created, - 202 => StatusCode::Accepted, - 203 => StatusCode::NonAuthoritativeInformation, - 204 => StatusCode::NoContent, - 205 => StatusCode::ResetContent, - 206 => StatusCode::PartialContent, - 207 => StatusCode::MultiStatus, - 208 => StatusCode::AlreadyReported, - 226 => StatusCode::ImUsed, - 300 => StatusCode::MultipleChoices, - 301 => StatusCode::MovedPermanently, - 302 => StatusCode::Found, - 303 => StatusCode::SeeOther, - 304 => StatusCode::NotModified, - 305 => StatusCode::UseProxy, - 307 => StatusCode::TemporaryRedirect, - 308 => StatusCode::PermanentRedirect, - 400 => StatusCode::BadRequest, - 401 => StatusCode::Unauthorized, - 402 => StatusCode::PaymentRequired, - 403 => StatusCode::Forbidden, - 404 => StatusCode::NotFound, - 405 => StatusCode::MethodNotAllowed, - 406 => StatusCode::NotAcceptable, - 407 => StatusCode::ProxyAuthenticationRequired, - 408 => StatusCode::RequestTimeout, - 409 => StatusCode::Conflict, - 410 => StatusCode::Gone, - 411 => StatusCode::LengthRequired, - 412 => StatusCode::PreconditionFailed, - 413 => StatusCode::PayloadTooLarge, - 414 => StatusCode::UriTooLong, - 415 => StatusCode::UnsupportedMediaType, - 416 => StatusCode::RangeNotSatisfiable, - 417 => StatusCode::ExpectationFailed, - 418 => StatusCode::ImATeapot, - 421 => StatusCode::MisdirectedRequest, - 422 => StatusCode::UnprocessableEntity, - 423 => StatusCode::Locked, - 424 => StatusCode::FailedDependency, - 426 => StatusCode::UpgradeRequired, - 428 => StatusCode::PreconditionRequired, - 429 => StatusCode::TooManyRequests, - 431 => StatusCode::RequestHeaderFieldsTooLarge, - 451 => StatusCode::UnavailableForLegalReasons, - 500 => StatusCode::InternalServerError, - 501 => StatusCode::NotImplemented, - 502 => StatusCode::BadGateway, - 503 => StatusCode::ServiceUnavailable, - 504 => StatusCode::GatewayTimeout, - 505 => StatusCode::HttpVersionNotSupported, - 506 => StatusCode::VariantAlsoNegotiates, - 507 => StatusCode::InsufficientStorage, - 508 => StatusCode::LoopDetected, - 510 => StatusCode::NotExtended, - 511 => StatusCode::NetworkAuthenticationRequired, - _ => StatusCode::Unregistered(n), - } - } - - /// Get the `u16` code from this `StatusCode`. - /// - /// Also available as `From`/`Into`. - /// - /// # Example - /// - /// ``` - /// use hyper::StatusCode; - /// - /// let status = StatusCode::Ok; - /// assert_eq!(status.as_u16(), 200); - /// - /// // Into - /// let num: u16 = status.into(); - /// assert_eq!(num, 200); - /// - /// // From - /// let other = u16::from(status); - /// assert_eq!(num, other); - /// ``` - #[inline] - pub fn as_u16(&self) -> u16 { - match *self { - StatusCode::Continue => 100, - StatusCode::SwitchingProtocols => 101, - StatusCode::Processing => 102, - StatusCode::Ok => 200, - StatusCode::Created => 201, - StatusCode::Accepted => 202, - StatusCode::NonAuthoritativeInformation => 203, - StatusCode::NoContent => 204, - StatusCode::ResetContent => 205, - StatusCode::PartialContent => 206, - StatusCode::MultiStatus => 207, - StatusCode::AlreadyReported => 208, - StatusCode::ImUsed => 226, - StatusCode::MultipleChoices => 300, - StatusCode::MovedPermanently => 301, - StatusCode::Found => 302, - StatusCode::SeeOther => 303, - StatusCode::NotModified => 304, - StatusCode::UseProxy => 305, - StatusCode::TemporaryRedirect => 307, - StatusCode::PermanentRedirect => 308, - StatusCode::BadRequest => 400, - StatusCode::Unauthorized => 401, - StatusCode::PaymentRequired => 402, - StatusCode::Forbidden => 403, - StatusCode::NotFound => 404, - StatusCode::MethodNotAllowed => 405, - StatusCode::NotAcceptable => 406, - StatusCode::ProxyAuthenticationRequired => 407, - StatusCode::RequestTimeout => 408, - StatusCode::Conflict => 409, - StatusCode::Gone => 410, - StatusCode::LengthRequired => 411, - StatusCode::PreconditionFailed => 412, - StatusCode::PayloadTooLarge => 413, - StatusCode::UriTooLong => 414, - StatusCode::UnsupportedMediaType => 415, - StatusCode::RangeNotSatisfiable => 416, - StatusCode::ExpectationFailed => 417, - StatusCode::ImATeapot => 418, - StatusCode::MisdirectedRequest => 421, - StatusCode::UnprocessableEntity => 422, - StatusCode::Locked => 423, - StatusCode::FailedDependency => 424, - StatusCode::UpgradeRequired => 426, - StatusCode::PreconditionRequired => 428, - StatusCode::TooManyRequests => 429, - StatusCode::RequestHeaderFieldsTooLarge => 431, - StatusCode::UnavailableForLegalReasons => 451, - StatusCode::InternalServerError => 500, - StatusCode::NotImplemented => 501, - StatusCode::BadGateway => 502, - StatusCode::ServiceUnavailable => 503, - StatusCode::GatewayTimeout => 504, - StatusCode::HttpVersionNotSupported => 505, - StatusCode::VariantAlsoNegotiates => 506, - StatusCode::InsufficientStorage => 507, - StatusCode::LoopDetected => 508, - StatusCode::NotExtended => 510, - StatusCode::NetworkAuthenticationRequired => 511, - StatusCode::Unregistered(n) => n, - } - } - - /// Get the standardised `reason-phrase` for this status code. - /// - /// This is mostly here for servers writing responses, but could potentially have application - /// at other times. - /// - /// The reason phrase is defined as being exclusively for human readers. You should avoid - /// deriving any meaning from it at all costs. - /// - /// Bear in mind also that in HTTP/2.0 the reason phrase is abolished from transmission, and so - /// this canonical reason phrase really is the only reason phrase you’ll find. - pub fn canonical_reason(&self) -> Option<&'static str> { - match *self { - StatusCode::Continue => Some("Continue"), - StatusCode::SwitchingProtocols => Some("Switching Protocols"), - StatusCode::Processing => Some("Processing"), - - StatusCode::Ok => Some("OK"), - StatusCode::Created => Some("Created"), - StatusCode::Accepted => Some("Accepted"), - StatusCode::NonAuthoritativeInformation => Some("Non-Authoritative Information"), - StatusCode::NoContent => Some("No Content"), - StatusCode::ResetContent => Some("Reset Content"), - StatusCode::PartialContent => Some("Partial Content"), - StatusCode::MultiStatus => Some("Multi-Status"), - StatusCode::AlreadyReported => Some("Already Reported"), - - StatusCode::ImUsed => Some("IM Used"), - - StatusCode::MultipleChoices => Some("Multiple Choices"), - StatusCode::MovedPermanently => Some("Moved Permanently"), - StatusCode::Found => Some("Found"), - StatusCode::SeeOther => Some("See Other"), - StatusCode::NotModified => Some("Not Modified"), - StatusCode::UseProxy => Some("Use Proxy"), - - StatusCode::TemporaryRedirect => Some("Temporary Redirect"), - StatusCode::PermanentRedirect => Some("Permanent Redirect"), - - StatusCode::BadRequest => Some("Bad Request"), - StatusCode::Unauthorized => Some("Unauthorized"), - StatusCode::PaymentRequired => Some("Payment Required"), - StatusCode::Forbidden => Some("Forbidden"), - StatusCode::NotFound => Some("Not Found"), - StatusCode::MethodNotAllowed => Some("Method Not Allowed"), - StatusCode::NotAcceptable => Some("Not Acceptable"), - StatusCode::ProxyAuthenticationRequired => Some("Proxy Authentication Required"), - StatusCode::RequestTimeout => Some("Request Timeout"), - StatusCode::Conflict => Some("Conflict"), - StatusCode::Gone => Some("Gone"), - StatusCode::LengthRequired => Some("Length Required"), - StatusCode::PreconditionFailed => Some("Precondition Failed"), - StatusCode::PayloadTooLarge => Some("Payload Too Large"), - StatusCode::UriTooLong => Some("URI Too Long"), - StatusCode::UnsupportedMediaType => Some("Unsupported Media Type"), - StatusCode::RangeNotSatisfiable => Some("Range Not Satisfiable"), - StatusCode::ExpectationFailed => Some("Expectation Failed"), - StatusCode::ImATeapot => Some("I'm a teapot"), - - StatusCode::MisdirectedRequest => Some("Misdirected Request"), - StatusCode::UnprocessableEntity => Some("Unprocessable Entity"), - StatusCode::Locked => Some("Locked"), - StatusCode::FailedDependency => Some("Failed Dependency"), - - StatusCode::UpgradeRequired => Some("Upgrade Required"), - - StatusCode::PreconditionRequired => Some("Precondition Required"), - StatusCode::TooManyRequests => Some("Too Many Requests"), - - StatusCode::RequestHeaderFieldsTooLarge => Some("Request Header Fields Too Large"), - - StatusCode::UnavailableForLegalReasons => Some("Unavailable For Legal Reasons"), - - StatusCode::InternalServerError => Some("Internal Server Error"), - StatusCode::NotImplemented => Some("Not Implemented"), - StatusCode::BadGateway => Some("Bad Gateway"), - StatusCode::ServiceUnavailable => Some("Service Unavailable"), - StatusCode::GatewayTimeout => Some("Gateway Timeout"), - StatusCode::HttpVersionNotSupported => Some("HTTP Version Not Supported"), - StatusCode::VariantAlsoNegotiates => Some("Variant Also Negotiates"), - StatusCode::InsufficientStorage => Some("Insufficient Storage"), - StatusCode::LoopDetected => Some("Loop Detected"), - - StatusCode::NotExtended => Some("Not Extended"), - StatusCode::NetworkAuthenticationRequired => Some("Network Authentication Required"), - StatusCode::Unregistered(..) => None - } - } - - /// Check if this `StatusCode` is within 100-199. - #[inline] - pub fn is_informational(&self) -> bool { - self.class() == StatusClass::Informational - } - - /// Check if this `StatusCode` is within 200-299. - #[inline] - pub fn is_success(&self) -> bool { - self.class() == StatusClass::Success - } - - /// Check if this `StatusCode` is within 300-399. - #[inline] - pub fn is_redirection(&self) -> bool { - self.class() == StatusClass::Redirection - } - - /// Check if this `StatusCode` is within 400-499. - #[inline] - pub fn is_client_error(&self) -> bool { - self.class() == StatusClass::ClientError - } - - /// Check if this `StatusCode` is within 500-599. - #[inline] - pub fn is_server_error(&self) -> bool { - self.class() == StatusClass::ServerError - } - - /// Check if this `StatusCode` is not within 100-599. - #[inline] - pub fn is_strange_status(&self) -> bool { - self.class() == StatusClass::NoClass - } - - fn class(&self) -> StatusClass { - match self.as_u16() { - 100...199 => StatusClass::Informational, - 200...299 => StatusClass::Success, - 300...399 => StatusClass::Redirection, - 400...499 => StatusClass::ClientError, - 500...599 => StatusClass::ServerError, - _ => StatusClass::NoClass, - } - } -} - -impl Copy for StatusCode {} - -/// Formats the status code, *including* the canonical reason. -/// -/// ```rust -/// # use hyper::StatusCode::{ImATeapot, Unregistered}; -/// assert_eq!(format!("{}", ImATeapot), "418 I'm a teapot"); -/// assert_eq!(format!("{}", Unregistered(123)), -/// "123 "); -/// ``` -impl fmt::Display for StatusCode { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {}", self.as_u16(), - self.canonical_reason().unwrap_or("")) - } -} - -impl PartialEq for StatusCode { - #[inline] - fn eq(&self, other: &StatusCode) -> bool { - self.as_u16() == other.as_u16() - } -} - -impl Eq for StatusCode {} - -impl Clone for StatusCode { - #[inline] - fn clone(&self) -> StatusCode { - *self - } -} - -impl PartialOrd for StatusCode { - #[inline] - fn partial_cmp(&self, other: &StatusCode) -> Option { - self.as_u16().partial_cmp(&(other.as_u16())) - } -} - -impl Ord for StatusCode { - #[inline] - fn cmp(&self, other: &StatusCode) -> Ordering { - if *self < *other { - Ordering::Less - } else if *self > *other { - Ordering::Greater - } else { - Ordering::Equal - } - } -} - -impl Default for StatusCode { - fn default() -> StatusCode { - StatusCode::Ok - } -} - -impl From for u16 { - fn from(code: StatusCode) -> u16 { - code.as_u16() - } -} - -#[cfg(feature = "compat")] -impl From for StatusCode { - fn from(status: http::StatusCode) -> StatusCode { - StatusCode::try_from(status.as_u16()) - .expect("attempted to convert invalid status code") - } -} - -#[cfg(feature = "compat")] -impl From for http::StatusCode { - fn from(status: StatusCode) -> http::StatusCode { - http::StatusCode::from_u16(status.as_u16()) - .expect("attempted to convert invalid status code") - } -} - -/// The class of an HTTP `status-code`. -/// -/// [RFC 7231, section 6 (Response Status Codes)](https://tools.ietf.org/html/rfc7231#section-6): -/// -/// > The first digit of the status-code defines the class of response. -/// > The last two digits do not have any categorization role. -/// -/// And: -/// -/// > HTTP status codes are extensible. HTTP clients are not required to -/// > understand the meaning of all registered status codes, though such -/// > understanding is obviously desirable. However, a client MUST -/// > understand the class of any status code, as indicated by the first -/// > digit, and treat an unrecognized status code as being equivalent to -/// > the x00 status code of that class, with the exception that a -/// > recipient MUST NOT cache a response with an unrecognized status code. -/// > -/// > For example, if an unrecognized status code of 471 is received by a -/// > client, the client can assume that there was something wrong with its -/// > request and treat the response as if it had received a 400 (Bad -/// > Request) status code. The response message will usually contain a -/// > representation that explains the status. -/// -/// This can be used in cases where a status code’s meaning is unknown, also, -/// to get the appropriate *category* of status. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy)] -enum StatusClass { - /// 1xx (Informational): The request was received, continuing process - Informational, - - /// 2xx (Success): The request was successfully received, understood, and accepted - Success, - - /// 3xx (Redirection): Further action needs to be taken in order to complete the request - Redirection, - - /// 4xx (Client Error): The request contains bad syntax or cannot be fulfilled - ClientError, - - /// 5xx (Server Error): The server failed to fulfill an apparently valid request - ServerError, - - /// A status code lower than 100 or higher than 599. These codes do no belong to any class. - NoClass, -} - -#[cfg(test)] -mod tests { - use super::*; - use super::StatusCode::*; - - // Check that the following entities are properly inter-connected: - // - numerical code - // - status code - // - default code (for the given status code) - // - canonical reason - fn validate(num: u16, status_code: StatusCode, _default_code: StatusCode, reason: Option<&str>) { - assert_eq!(StatusCode::from_u16(num), status_code); - assert_eq!(status_code.as_u16(), num); - //assert_eq!(status_code.class().default_code(), default_code); - assert_eq!(status_code.canonical_reason(), reason); - } - - #[test] - fn test_status_code() { - validate(99, Unregistered(99), Ok, None); - - validate(100, Continue, Continue, Some("Continue")); - validate(101, SwitchingProtocols, Continue, Some("Switching Protocols")); - validate(102, Processing, Continue, Some("Processing")); - - validate(200, Ok, Ok, Some("OK")); - validate(201, Created, Ok, Some("Created")); - validate(202, Accepted, Ok, Some("Accepted")); - validate(203, NonAuthoritativeInformation, Ok, Some("Non-Authoritative Information")); - validate(204, NoContent, Ok, Some("No Content")); - validate(205, ResetContent, Ok, Some("Reset Content")); - validate(206, PartialContent, Ok, Some("Partial Content")); - validate(207, MultiStatus, Ok, Some("Multi-Status")); - validate(208, AlreadyReported, Ok, Some("Already Reported")); - validate(226, ImUsed, Ok, Some("IM Used")); - - validate(300, MultipleChoices, MultipleChoices, Some("Multiple Choices")); - validate(301, MovedPermanently, MultipleChoices, Some("Moved Permanently")); - validate(302, Found, MultipleChoices, Some("Found")); - validate(303, SeeOther, MultipleChoices, Some("See Other")); - validate(304, NotModified, MultipleChoices, Some("Not Modified")); - validate(305, UseProxy, MultipleChoices, Some("Use Proxy")); - validate(307, TemporaryRedirect, MultipleChoices, Some("Temporary Redirect")); - validate(308, PermanentRedirect, MultipleChoices, Some("Permanent Redirect")); - - validate(400, BadRequest, BadRequest, Some("Bad Request")); - validate(401, Unauthorized, BadRequest, Some("Unauthorized")); - validate(402, PaymentRequired, BadRequest, Some("Payment Required")); - validate(403, Forbidden, BadRequest, Some("Forbidden")); - validate(404, NotFound, BadRequest, Some("Not Found")); - validate(405, MethodNotAllowed, BadRequest, Some("Method Not Allowed")); - validate(406, NotAcceptable, BadRequest, Some("Not Acceptable")); - validate(407, ProxyAuthenticationRequired, BadRequest, - Some("Proxy Authentication Required")); - validate(408, RequestTimeout, BadRequest, Some("Request Timeout")); - validate(409, Conflict, BadRequest, Some("Conflict")); - validate(410, Gone, BadRequest, Some("Gone")); - validate(411, LengthRequired, BadRequest, Some("Length Required")); - validate(412, PreconditionFailed, BadRequest, Some("Precondition Failed")); - validate(413, PayloadTooLarge, BadRequest, Some("Payload Too Large")); - validate(414, UriTooLong, BadRequest, Some("URI Too Long")); - validate(415, UnsupportedMediaType, BadRequest, Some("Unsupported Media Type")); - validate(416, RangeNotSatisfiable, BadRequest, Some("Range Not Satisfiable")); - validate(417, ExpectationFailed, BadRequest, Some("Expectation Failed")); - validate(418, ImATeapot, BadRequest, Some("I'm a teapot")); - validate(421, MisdirectedRequest, BadRequest, Some("Misdirected Request")); - validate(422, UnprocessableEntity, BadRequest, Some("Unprocessable Entity")); - validate(423, Locked, BadRequest, Some("Locked")); - validate(424, FailedDependency, BadRequest, Some("Failed Dependency")); - validate(426, UpgradeRequired, BadRequest, Some("Upgrade Required")); - validate(428, PreconditionRequired, BadRequest, Some("Precondition Required")); - validate(429, TooManyRequests, BadRequest, Some("Too Many Requests")); - validate(431, RequestHeaderFieldsTooLarge, BadRequest, - Some("Request Header Fields Too Large")); - validate(451, UnavailableForLegalReasons, BadRequest, - Some("Unavailable For Legal Reasons")); - - validate(500, InternalServerError, InternalServerError, Some("Internal Server Error")); - validate(501, NotImplemented, InternalServerError, Some("Not Implemented")); - validate(502, BadGateway, InternalServerError, Some("Bad Gateway")); - validate(503, ServiceUnavailable, InternalServerError, Some("Service Unavailable")); - validate(504, GatewayTimeout, InternalServerError, Some("Gateway Timeout")); - validate(505, HttpVersionNotSupported, InternalServerError, - Some("HTTP Version Not Supported")); - validate(506, VariantAlsoNegotiates, InternalServerError, Some("Variant Also Negotiates")); - validate(507, InsufficientStorage, InternalServerError, Some("Insufficient Storage")); - validate(508, LoopDetected, InternalServerError, Some("Loop Detected")); - validate(510, NotExtended, InternalServerError, Some("Not Extended")); - validate(511, NetworkAuthenticationRequired, InternalServerError, - Some("Network Authentication Required")); - - } - - #[test] - fn try_from() { - StatusCode::try_from(100).unwrap(); - StatusCode::try_from(599).unwrap(); - StatusCode::try_from(200).unwrap(); - - StatusCode::try_from(99).unwrap_err(); - StatusCode::try_from(600).unwrap_err(); - StatusCode::try_from(0).unwrap_err(); - StatusCode::try_from(1000).unwrap_err(); - } - - #[test] - #[cfg(feature = "compat")] - fn test_compat() { - use http::{self, HttpTryFrom}; - for i in 100..600 { - let orig_hyper_status = StatusCode::try_from(i).unwrap(); - let orig_http_status = http::StatusCode::try_from(i).unwrap(); - let conv_hyper_status: StatusCode = orig_http_status.into(); - let conv_http_status: http::StatusCode = orig_hyper_status.into(); - assert_eq!(orig_hyper_status, conv_hyper_status); - assert_eq!(orig_http_status, conv_http_status); - } - } -} diff --git a/src/uri.rs b/src/uri.rs deleted file mode 100644 index 9e1382a50a..0000000000 --- a/src/uri.rs +++ /dev/null @@ -1,710 +0,0 @@ -use std::error::Error as StdError; -use std::fmt::{Display, self}; -use std::str::{self, FromStr}; - -#[cfg(feature = "compat")] -use http; - -use ::common::ByteStr; -use bytes::{BufMut, Bytes, BytesMut}; - -/// The Request-URI of a Request's StartLine. -/// -/// From Section 5.3, Request Target: -/// > Once an inbound connection is obtained, the client sends an HTTP -/// > request message (Section 3) with a request-target derived from the -/// > target URI. There are four distinct formats for the request-target, -/// > depending on both the method being requested and whether the request -/// > is to a proxy. -/// > -/// > ```notrust -/// > request-target = origin-form -/// > / absolute-form -/// > / authority-form -/// > / asterisk-form -/// > ``` -/// -/// # Uri explanations -/// ```notrust -/// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1 -/// |-| |-------------------------------||--------| |-------------------| |-----| -/// | | | | | -/// scheme authority path query fragment -/// ``` -#[derive(Clone, Hash)] -pub struct Uri { - source: ByteStr, - scheme_end: Option, - authority_end: Option, - query_start: Option, - fragment_start: Option, -} - -impl Uri { - /// Parse a string into a `Uri`. - fn new(mut s: ByteStr) -> Result { - if s.len() == 0 { - Err(UriError(ErrorKind::Empty)) - } else if s.as_bytes() == b"*" { - // asterisk-form - Ok(asterisk_form()) - } else if s.as_bytes() == b"/" { - // shortcut for '/' - Ok(Uri::default()) - } else if s.as_bytes()[0] == b'/' { - // origin-form - let query = parse_query(&s); - let fragment = parse_fragment(&s); - Ok(Uri { - source: s, - scheme_end: None, - authority_end: None, - query_start: query, - fragment_start: fragment, - }) - } else if s.contains("://") { - // absolute-form - let scheme = parse_scheme(&s); - let auth = Some(parse_authority(&s)); - let scheme_end = scheme.expect("just checked for ':' above"); - let auth_end = auth.expect("just checked for ://"); - if scheme_end + 3 == auth_end { - // authority was empty - return Err(UriError(ErrorKind::MissingAuthority)); - } - { - let authority = &s.as_bytes()[scheme_end + 3..auth_end]; - let has_start_bracket = authority.contains(&b'['); - let has_end_bracket = authority.contains(&b']'); - if has_start_bracket ^ has_end_bracket { - // has only 1 of [ and ] - return Err(UriError(ErrorKind::Malformed)); - } - } - - // absolute-form must always have a path - // if there isn't a '/', for consistency, add one. - let slash = auth_end; - if s.len() == slash { - s.insert(slash, '/'); - } else if s.as_bytes()[slash] != b'/' { - s.insert(slash, '/'); - } - - let query = parse_query(&s); - let fragment = parse_fragment(&s); - - Ok(Uri { - source: s, - scheme_end: scheme, - authority_end: auth, - query_start: query, - fragment_start: fragment, - }) - } else if s.contains("/") || s.contains("?") { - // last possibility is authority-form, above are illegal characters - Err(UriError(ErrorKind::Malformed)) - } else { - // authority-form - let len = s.len(); - Ok(Uri { - source: s, - scheme_end: None, - authority_end: Some(len), - query_start: None, - fragment_start: None, - }) - } - } - - /// Get the path of this `Uri`. - #[inline] - pub fn path(&self) -> &str { - let index = self.path_start(); - let end = self.path_end(); - if index >= end { - if self.scheme().is_some() { - "/" // absolute-form MUST have path - } else { - "" - } - } else { - &self.source[index..end] - } - } - - #[inline] - fn path_start(&self) -> usize { - self.authority_end.unwrap_or(self.scheme_end.unwrap_or(0)) - } - - #[inline] - fn path_end(&self) -> usize { - if let Some(query) = self.query_start { - query - } else if let Some(fragment) = self.fragment_start { - fragment - } else { - self.source.len() - } - } - - #[inline] - fn origin_form_end(&self) -> usize { - if let Some(fragment) = self.fragment_start { - fragment - } else { - self.source.len() - } - } - - /// Get the scheme of this `Uri`. - #[inline] - pub fn scheme(&self) -> Option<&str> { - if let Some(end) = self.scheme_end { - Some(&self.source[..end]) - } else { - None - } - } - - /// Get the authority of this `Uri`. - #[inline] - pub fn authority(&self) -> Option<&str> { - if let Some(end) = self.authority_end { - let index = self.scheme_end.map(|i| i + 3).unwrap_or(0); - - Some(&self.source[index..end]) - } else { - None - } - } - - /// Get the host of this `Uri`. - #[inline] - pub fn host(&self) -> Option<&str> { - self.authority().map(|auth| { - let host_port = auth.rsplit('@') - .next() - .expect("split always has at least 1 item"); - if host_port.as_bytes()[0] == b'[' { - let i = host_port.find(']') - .expect("parsing should validate matching brackets"); - &host_port[1..i] - } else { - host_port.split(':') - .next() - .expect("split always has at least 1 item") - } - }) - } - - /// Get the port of this `Uri`. - #[inline] - pub fn port(&self) -> Option { - match self.authority() { - Some(auth) => auth.rfind(':').and_then(|i| auth[i+1..].parse().ok()), - None => None, - } - } - - /// Get the query string of this `Uri`, starting after the `?`. - #[inline] - pub fn query(&self) -> Option<&str> { - self.query_start.map(|start| { - // +1 to remove '?' - let start = start + 1; - if let Some(end) = self.fragment_start { - &self.source[start..end] - } else { - &self.source[start..] - } - }) - } - - /// Returns whether this URI is in `absolute-form`. - /// - /// An example of absolute form is `https://hyper.rs`. - #[inline] - pub fn is_absolute(&self) -> bool { - self.scheme_end.is_some() - } - - #[cfg(test)] - fn fragment(&self) -> Option<&str> { - self.fragment_start.map(|start| { - // +1 to remove the '#' - &self.source[start + 1..] - }) - } -} - -fn parse_scheme(s: &str) -> Option { - s.find(':') -} - -fn parse_authority(s: &str) -> usize { - let i = s.find("://").map(|p| p + 3).unwrap_or(0); - s[i..] - .find(|ch| ch == '/' || ch == '?' || ch == '#') - .map(|end| end + i) - .unwrap_or(s.len()) -} - -fn parse_query(s: &str) -> Option { - s.find('?').and_then(|i| { - if let Some(frag) = s.find('#') { - if frag < i { - None - } else { - Some(i) - } - } else { - Some(i) - } - }) -} - -fn parse_fragment(s: &str) -> Option { - s.find('#') -} - -impl FromStr for Uri { - type Err = UriError; - - fn from_str(s: &str) -> Result { - //TODO: refactor such that the to_owned() is only required at the end - //of successful parsing, so an Err doesn't needlessly clone the string. - Uri::new(ByteStr::from(s)) - } -} - -impl PartialEq for Uri { - fn eq(&self, other: &Uri) -> bool { - self.source.as_str() == other.source.as_str() - } -} - -impl PartialEq for Uri { - fn eq(&self, other: &str) -> bool { - self.source.as_str() == other - } -} - -// FIXME delete for 0.12 -impl<'a> PartialEq<&'a str> for Uri { - fn eq(&self, other: & &'a str) -> bool { - self.source.as_str() == *other - } -} - -impl<'a> PartialEq for &'a str{ - fn eq(&self, other: &Uri) -> bool { - *self == other.source.as_str() - } -} - -impl Eq for Uri {} - -impl AsRef for Uri { - fn as_ref(&self) -> &str { - self.source.as_str() - } -} - -impl Default for Uri { - fn default() -> Uri { - Uri { - source: ByteStr::from_static("/"), - scheme_end: None, - authority_end: None, - query_start: None, - fragment_start: None, - } - } -} - -impl fmt::Debug for Uri { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(self.as_ref(), f) - } -} - -impl Display for Uri { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(self.as_ref()) - } -} - -#[cfg(feature = "compat")] -impl From for Uri { - fn from(uri: http::Uri) -> Uri { - uri.to_string().parse() - .expect("attempted to convert invalid uri") - } -} - -#[cfg(feature = "compat")] -impl From for http::Uri { - fn from(uri: Uri) -> http::Uri { - let bytes = uri.source.into_bytes(); - http::Uri::from_shared(bytes) - .expect("attempted to convert invalid uri") - } -} - -pub unsafe fn from_utf8_unchecked(slice: Bytes) -> Result { - Uri::new(ByteStr::from_utf8_unchecked(slice)) -} - -pub fn scheme_and_authority(uri: &Uri) -> Option { - if uri.scheme_end.is_some() { - Some(Uri { - source: uri.source.slice_to(uri.authority_end.expect("scheme without authority")), - scheme_end: uri.scheme_end, - authority_end: uri.authority_end, - query_start: None, - fragment_start: None, - }) - } else { - None - } -} - -#[inline] -fn asterisk_form() -> Uri { - Uri { - source: ByteStr::from_static("*"), - scheme_end: None, - authority_end: None, - query_start: None, - fragment_start: None, - } -} - -pub fn origin_form(uri: &Uri) -> Uri { - let range = Range(uri.path_start(), uri.origin_form_end()); - - let clone = if range.len() == 0 { - ByteStr::from_static("/") - } else if uri.source.as_bytes()[range.0] == b'*' { - return asterisk_form(); - } else if uri.source.as_bytes()[range.0] != b'/' { - let mut new = BytesMut::with_capacity(range.1 - range.0 + 1); - new.put_u8(b'/'); - new.put_slice(&uri.source.as_bytes()[range.0..range.1]); - // safety: the bytes are '/' + previous utf8 str - unsafe { ByteStr::from_utf8_unchecked(new.freeze()) } - } else if range.0 == 0 && range.1 == uri.source.len() { - uri.source.clone() - } else { - uri.source.slice(range.0, range.1) - }; - - Uri { - source: clone, - scheme_end: None, - authority_end: None, - query_start: uri.query_start, - fragment_start: None, - } -} - -struct Range(usize, usize); - -impl Range { - fn len(&self) -> usize { - self.1 - self.0 - } -} - -/// An error parsing a `Uri`. -#[derive(Clone, Debug)] -pub struct UriError(ErrorKind); - -#[derive(Clone, Debug)] -enum ErrorKind { - Empty, - Malformed, - MissingAuthority, -} - -impl fmt::Display for UriError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.pad(self.description()) - } -} - -impl StdError for UriError { - fn description(&self) -> &str { - match self.0 { - ErrorKind::Empty => "empty Uri string", - ErrorKind::Malformed => "invalid character in Uri authority", - ErrorKind::MissingAuthority => "absolute Uri missing authority segment", - } - } -} - -macro_rules! test_parse { - ( - $test_name:ident, - $str:expr, - $($method:ident = $value:expr,)* - ) => ( - #[test] - fn $test_name() { - let uri = Uri::from_str($str).unwrap(); - $( - assert_eq!(uri.$method(), $value, stringify!($method)); - )+ - } - ); -} - -test_parse! { - test_uri_parse_origin_form, - "/some/path/here?and=then&hello#and-bye", - - scheme = None, - authority = None, - path = "/some/path/here", - query = Some("and=then&hello"), - fragment = Some("and-bye"), -} - -test_parse! { - test_uri_parse_absolute_form, - "http://127.0.0.1:61761/chunks", - - scheme = Some("http"), - authority = Some("127.0.0.1:61761"), - host = Some("127.0.0.1"), - path = "/chunks", - query = None, - fragment = None, - port = Some(61761), -} - -test_parse! { - test_uri_parse_absolute_form_without_path, - "https://127.0.0.1:61761", - - scheme = Some("https"), - authority = Some("127.0.0.1:61761"), - host = Some("127.0.0.1"), - path = "/", - query = None, - fragment = None, - port = Some(61761), - - to_string = "https://127.0.0.1:61761/", -} - -test_parse! { - test_uri_parse_asterisk_form, - "*", - - scheme = None, - authority = None, - path = "*", - query = None, - fragment = None, - - to_string = "*", -} - -test_parse! { - test_uri_parse_authority_no_port, - "localhost", - - scheme = None, - authority = Some("localhost"), - host = Some("localhost"), - path = "", - query = None, - fragment = None, - port = None, - - to_string = "localhost", -} - -test_parse! { - test_uri_parse_authority_form, - "localhost:3000", - - scheme = None, - authority = Some("localhost:3000"), - host = Some("localhost"), - path = "", - query = None, - fragment = None, - port = Some(3000), -} - -test_parse! { - test_uri_parse_absolute_with_default_port_http, - "http://127.0.0.1:80/foo", - - scheme = Some("http"), - authority = Some("127.0.0.1:80"), - host = Some("127.0.0.1"), - path = "/foo", - query = None, - fragment = None, - port = Some(80), -} - -test_parse! { - test_uri_parse_absolute_with_default_port_https, - "https://127.0.0.1:443", - - scheme = Some("https"), - authority = Some("127.0.0.1:443"), - host = Some("127.0.0.1"), - path = "/", - query = None, - fragment = None, - port = Some(443), -} - -test_parse! { - test_uri_parse_absolute_with_ipv6, - "https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8008", - - scheme = Some("https"), - authority = Some("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8008"), - host = Some("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), - path = "/", - query = None, - fragment = None, - port = Some(8008), -} - -test_parse! { - test_uri_parse_absolute_with_ipv6_and_no_port, - "https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", - - scheme = Some("https"), - authority = Some("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"), - host = Some("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), - path = "/", - query = None, - fragment = None, - port = None, -} - -test_parse! { - test_uri_parse_absolute_with_userinfo, - "https://seanmonstar:password@hyper.rs", - - scheme = Some("https"), - authority = Some("seanmonstar:password@hyper.rs"), - host = Some("hyper.rs"), - path = "/", - query = None, - fragment = None, - port = None, -} - -test_parse! { - test_uri_parse_fragment_questionmark, - "http://127.0.0.1/#?", - - scheme = Some("http"), - authority = Some("127.0.0.1"), - host = Some("127.0.0.1"), - path = "/", - query = None, - fragment = Some("?"), - port = None, -} - -test_parse! { - test_uri_parse_path_with_terminating_questionmark, - "http://127.0.0.1/path?", - - scheme = Some("http"), - authority = Some("127.0.0.1"), - host = Some("127.0.0.1"), - path = "/path", - query = Some(""), - fragment = None, - port = None, -} - -test_parse! { - test_uri_parse_absolute_form_with_empty_path_and_nonempty_query, - "http://127.0.0.1?foo=bar", - - scheme = Some("http"), - authority = Some("127.0.0.1"), - host = Some("127.0.0.1"), - path = "/", - query = Some("foo=bar"), - fragment = None, - port = None, - - to_string = "http://127.0.0.1/?foo=bar", -} - -test_parse! { - test_uri_parse_absolute_form_with_empty_path_and_fragment_with_slash, - "http://127.0.0.1#foo/bar", - scheme = Some("http"), - authority = Some("127.0.0.1"), - host = Some("127.0.0.1"), - path = "/", - query = None, - fragment = Some("foo/bar"), - port = None, -} - -test_parse! { - test_uri_parse_absolute_form_with_empty_path_and_fragment_with_questionmark, - "http://127.0.0.1#foo?bar", - scheme = Some("http"), - authority = Some("127.0.0.1"), - host = Some("127.0.0.1"), - path = "/", - query = None, - fragment = Some("foo?bar"), - port = None, - - to_string = "http://127.0.0.1/#foo?bar", -} - -#[test] -fn test_uri_parse_error() { - fn err(s: &str) { - Uri::from_str(s).unwrap_err(); - } - - err("http://"); - err("htt:p//host"); - err("hyper.rs/"); - err("hyper.rs?key=val"); - err("?key=val"); - err("localhost/"); - err("localhost?key=val"); - err("http://::1]"); - err("http://[::1"); -} - -#[test] -fn test_uri_to_origin_form() { - let cases = vec![ - ("/", "/"), - ("/foo?bar", "/foo?bar"), - ("/foo?bar#nope", "/foo?bar"), - ("http://hyper.rs", "/"), - ("http://hyper.rs/", "/"), - ("http://hyper.rs/path", "/path"), - ("http://hyper.rs?query", "/?query"), - ("*", "*"), - ]; - - for case in cases { - let uri = Uri::from_str(case.0).unwrap(); - assert_eq!(origin_form(&uri), case.1); //, "{:?}", case); - } -} diff --git a/src/version.rs b/src/version.rs deleted file mode 100644 index ddb7d1a020..0000000000 --- a/src/version.rs +++ /dev/null @@ -1,127 +0,0 @@ -//! HTTP Versions enum -//! -//! Instead of relying on typo-prone Strings, use expected HTTP versions as -//! the `HttpVersion` enum. -use std::fmt; -use std::str::FromStr; - -#[cfg(feature = "compat")] -use http; - -use error::Error; -use self::HttpVersion::{Http09, Http10, Http11, H2, H2c}; - -/// Represents a version of the HTTP spec. -#[derive(PartialEq, PartialOrd, Copy, Clone, Eq, Ord, Hash, Debug)] -pub enum HttpVersion { - /// `HTTP/0.9` - Http09, - /// `HTTP/1.0` - Http10, - /// `HTTP/1.1` - Http11, - /// `HTTP/2.0` over TLS - H2, - /// `HTTP/2.0` over cleartext - H2c, - #[doc(hidden)] - __DontMatchMe, -} - -impl fmt::Display for HttpVersion { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.write_str(match *self { - Http09 => "HTTP/0.9", - Http10 => "HTTP/1.0", - Http11 => "HTTP/1.1", - H2 => "h2", - H2c => "h2c", - HttpVersion::__DontMatchMe => unreachable!(), - }) - } -} - -impl FromStr for HttpVersion { - type Err = Error; - fn from_str(s: &str) -> Result { - Ok(match s { - "HTTP/0.9" => Http09, - "HTTP/1.0" => Http10, - "HTTP/1.1" => Http11, - "h2" => H2, - "h2c" => H2c, - _ => return Err(Error::Version), - }) - } -} - -impl Default for HttpVersion { - fn default() -> HttpVersion { - Http11 - } -} - -#[cfg(feature = "compat")] -impl From for HttpVersion { - fn from(v: http::Version) -> HttpVersion { - match v { - http::Version::HTTP_09 => - HttpVersion::Http09, - http::Version::HTTP_10 => - HttpVersion::Http10, - http::Version::HTTP_11 => - HttpVersion::Http11, - http::Version::HTTP_2 => - HttpVersion::H2 - } - } -} - -#[cfg(feature = "compat")] -impl From for http::Version { - fn from(v: HttpVersion) -> http::Version { - match v { - HttpVersion::Http09 => - http::Version::HTTP_09, - HttpVersion::Http10 => - http::Version::HTTP_10, - HttpVersion::Http11 => - http::Version::HTTP_11, - HttpVersion::H2 => - http::Version::HTTP_2, - _ => panic!("attempted to convert unexpected http version") - } - } -} - -#[cfg(test)] -mod tests { - use std::str::FromStr; - use error::Error; - use super::HttpVersion; - use super::HttpVersion::{Http09,Http10,Http11,H2,H2c}; - - #[test] - fn test_default() { - assert_eq!(Http11, HttpVersion::default()); - } - - #[test] - fn test_from_str() { - assert_eq!(Http09, HttpVersion::from_str("HTTP/0.9").unwrap()); - assert_eq!(Http10, HttpVersion::from_str("HTTP/1.0").unwrap()); - assert_eq!(Http11, HttpVersion::from_str("HTTP/1.1").unwrap()); - assert_eq!(H2, HttpVersion::from_str("h2").unwrap()); - assert_eq!(H2c, HttpVersion::from_str("h2c").unwrap()); - } - - #[test] - fn test_from_str_panic() { - match HttpVersion::from_str("foo") { - Err(Error::Version) => assert!(true), - Err(_) => assert!(false), - Ok(_) => assert!(false), - } - } - -} diff --git a/tests/client.rs b/tests/client.rs index 72783b3dc0..51a29a6fd4 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -11,8 +11,7 @@ use std::net::TcpListener; use std::thread; use std::time::Duration; -use hyper::client::{Client, Request}; -use hyper::{Method, StatusCode}; +use hyper::{Body, Client, Method, Request, StatusCode}; use futures::{Future, Stream}; use futures::sync::oneshot; @@ -33,13 +32,13 @@ macro_rules! test { request: method: $client_method:ident, url: $client_url:expr, - headers: [ $($request_headers:expr,)* ], + headers: { $($request_header_name:expr => $request_header_val:expr,)* }, body: $request_body:expr, proxy: $request_proxy:expr, response: status: $client_status:ident, - headers: [ $($response_headers:expr,)* ], + headers: { $($response_header_name:expr => $response_header_val:expr,)* }, body: $response_body:expr, ) => ( test! { @@ -52,13 +51,13 @@ macro_rules! test { request: method: $client_method, url: $client_url, - headers: [ $($request_headers,)* ], + headers: { $($request_header_name => $request_header_val,)* }, body: $request_body, proxy: $request_proxy, response: status: $client_status, - headers: [ $($response_headers,)* ], + headers: { $($response_header_name => $response_header_val,)* }, body: $response_body, } ); @@ -72,19 +71,17 @@ macro_rules! test { request: method: $client_method:ident, url: $client_url:expr, - headers: [ $($request_headers:expr,)* ], + headers: { $($request_header_name:expr => $request_header_val:expr,)* }, body: $request_body:expr, proxy: $request_proxy:expr, response: status: $client_status:ident, - headers: [ $($response_headers:expr,)* ], + headers: { $($response_header_name:expr => $response_header_val:expr,)* }, body: $response_body:expr, ) => ( #[test] fn $name() { - #![allow(unused)] - use hyper::header::*; let _ = pretty_env_logger::try_init(); let mut core = Core::new().unwrap(); @@ -100,7 +97,7 @@ macro_rules! test { request: method: $client_method, url: $client_url, - headers: [ $($request_headers,)* ], + headers: { $($request_header_name => $request_header_val,)* }, body: $request_body, proxy: $request_proxy, }.unwrap(); @@ -108,10 +105,10 @@ macro_rules! test { assert_eq!(res.status(), StatusCode::$client_status); $( - assert_eq!(res.headers().get(), Some(&$response_headers)); + assert_eq!(res.headers()[$response_header_name], $response_header_val); )* - let body = core.run(res.body().concat2()).unwrap(); + let body = core.run(res.into_parts().1.concat2()).unwrap(); let expected_res_body = Option::<&[u8]>::from($response_body) .unwrap_or_default(); @@ -127,7 +124,7 @@ macro_rules! test { request: method: $client_method:ident, url: $client_url:expr, - headers: [ $($request_headers:expr,)* ], + headers: { $($request_header_name:expr => $request_header_val:expr,)* }, body: $request_body:expr, proxy: $request_proxy:expr, @@ -135,8 +132,6 @@ macro_rules! test { ) => ( #[test] fn $name() { - #![allow(unused)] - use hyper::header::*; let _ = pretty_env_logger::try_init(); let mut core = Core::new().unwrap(); @@ -152,7 +147,7 @@ macro_rules! test { request: method: $client_method, url: $client_url, - headers: [ $($request_headers,)* ], + headers: { $($request_header_name => $request_header_val,)* }, body: $request_body, proxy: $request_proxy, }.unwrap_err(); @@ -174,29 +169,45 @@ macro_rules! test { request: method: $client_method:ident, url: $client_url:expr, - headers: [ $($request_headers:expr,)* ], + headers: { $($request_header_name:expr => $request_header_val:expr,)* }, body: $request_body:expr, proxy: $request_proxy:expr, ) => ({ let server = TcpListener::bind("127.0.0.1:0").unwrap(); let addr = server.local_addr().unwrap(); - let mut core = $core; + let core = $core; let mut config = Client::configure(); if !$set_host { config = config.set_host(false); } let client = config.build(&core.handle()); - let mut req = Request::new(Method::$client_method, format!($client_url, addr=addr).parse().unwrap()); + + let mut is_empty = false; + let body = if let Some(body) = $request_body { + let body: &'static str = body; + body.into() + } else { + is_empty = true; + Body::empty() + }; + let mut req = Request::builder(); + req + .method(Method::$client_method) $( - req.headers_mut().set($request_headers); + .header($request_header_name, $request_header_val) )* + .uri(&*format!($client_url, addr=addr)); - if let Some(body) = $request_body { - let body: &'static str = body; - req.set_body(body); + //TODO: remove when client bodies are fixed + if is_empty { + req.header("content-length", "0"); } - req.set_proxy($request_proxy); + + let req = req.body(body) + .unwrap(); + + // req.set_proxy($request_proxy); let res = client.request(req); @@ -237,21 +248,21 @@ test! { name: client_get, server: - expected: "GET / HTTP/1.1\r\nHost: {addr}\r\n\r\n", + expected: "GET / HTTP/1.1\r\nhost: {addr}\r\n\r\n", reply: REPLY_OK, client: request: - method: Get, + method: GET, url: "http://{addr}/", - headers: [], + headers: {}, body: None, proxy: false, response: - status: Ok, - headers: [ - ContentLength(0), - ], + status: OK, + headers: { + "Content-Length" => "0", + }, body: None, } @@ -259,21 +270,21 @@ test! { name: client_get_query, server: - expected: "GET /foo?key=val HTTP/1.1\r\nHost: {addr}\r\n\r\n", + expected: "GET /foo?key=val HTTP/1.1\r\nhost: {addr}\r\n\r\n", reply: REPLY_OK, client: request: - method: Get, + method: GET, url: "http://{addr}/foo?key=val#dont_send_me", - headers: [], + headers: {}, body: None, proxy: false, response: - status: Ok, - headers: [ - ContentLength(0), - ], + status: OK, + headers: { + "Content-Length" => "0", + }, body: None, } @@ -281,21 +292,21 @@ test! { name: client_get_implicitly_empty, server: - expected: "GET / HTTP/1.1\r\nHost: {addr}\r\n\r\n", + expected: "GET / HTTP/1.1\r\nhost: {addr}\r\n\r\n", reply: REPLY_OK, client: request: - method: Get, + method: GET, url: "http://{addr}/", - headers: [], + headers: {}, body: Some(""), proxy: false, response: - status: Ok, - headers: [ - ContentLength(0), - ], + status: OK, + headers: { + "Content-Length" => "0", + }, body: None, } @@ -305,8 +316,8 @@ test! { server: expected: "\ POST /length HTTP/1.1\r\n\ - Host: {addr}\r\n\ - Content-Length: 7\r\n\ + content-length: 7\r\n\ + host: {addr}\r\n\ \r\n\ foo bar\ ", @@ -314,16 +325,16 @@ test! { client: request: - method: Post, + method: POST, url: "http://{addr}/length", - headers: [ - ContentLength(7), - ], + headers: { + "Content-Length" => "7", + }, body: Some("foo bar"), proxy: false, response: - status: Ok, - headers: [], + status: OK, + headers: {}, body: None, } @@ -333,8 +344,8 @@ test! { server: expected: "\ POST /chunks HTTP/1.1\r\n\ - Host: {addr}\r\n\ - Transfer-Encoding: chunked\r\n\ + transfer-encoding: chunked\r\n\ + host: {addr}\r\n\ \r\n\ B\r\n\ foo bar baz\r\n\ @@ -344,16 +355,16 @@ test! { client: request: - method: Post, + method: POST, url: "http://{addr}/chunks", - headers: [ - TransferEncoding::chunked(), - ], + headers: { + "Transfer-Encoding" => "chunked", + }, body: Some("foo bar baz"), proxy: false, response: - status: Ok, - headers: [], + status: OK, + headers: {}, body: None, } @@ -363,24 +374,24 @@ test! { server: expected: "\ POST /empty HTTP/1.1\r\n\ - Host: {addr}\r\n\ - Content-Length: 0\r\n\ + content-length: 0\r\n\ + host: {addr}\r\n\ \r\n\ ", reply: REPLY_OK, client: request: - method: Post, + method: POST, url: "http://{addr}/empty", - headers: [ - ContentLength(0), - ], + headers: { + "Content-Length" => "0", + }, body: Some(""), proxy: false, response: - status: Ok, - headers: [], + status: OK, + headers: {}, body: None, } @@ -390,21 +401,21 @@ test! { server: expected: "\ GET http://{addr}/proxy HTTP/1.1\r\n\ - Host: {addr}\r\n\ + host: {addr}\r\n\ \r\n\ ", reply: REPLY_OK, client: request: - method: Get, + method: GET, url: "http://{addr}/proxy", - headers: [], + headers: {}, body: None, proxy: true, response: - status: Ok, - headers: [], + status: OK, + headers: {}, body: None, } @@ -415,26 +426,26 @@ test! { server: expected: "\ HEAD /head HTTP/1.1\r\n\ - Host: {addr}\r\n\ + host: {addr}\r\n\ \r\n\ ", reply: "\ HTTP/1.1 200 OK\r\n\ - Content-Length: 11\r\n\ + content-Length: 11\r\n\ \r\n\ Hello World\ ", client: request: - method: Head, + method: HEAD, url: "http://{addr}/head", - headers: [], + headers: {}, body: None, proxy: false, response: - status: Ok, - headers: [], + status: OK, + headers: {}, body: None, } @@ -444,7 +455,7 @@ test! { server: expected: "\ GET /pipe HTTP/1.1\r\n\ - Host: {addr}\r\n\ + host: {addr}\r\n\ \r\n\ ", reply: "\ @@ -458,14 +469,14 @@ test! { client: request: - method: Get, + method: GET, url: "http://{addr}/pipe", - headers: [], + headers: {}, body: None, proxy: false, response: - status: Ok, - headers: [], + status: OK, + headers: {}, body: None, } @@ -476,7 +487,7 @@ test! { server: expected: "\ GET /err HTTP/1.1\r\n\ - Host: {addr}\r\n\ + host: {addr}\r\n\ \r\n\ ", reply: "\ @@ -485,9 +496,9 @@ test! { client: request: - method: Get, + method: GET, url: "http://{addr}/err", - headers: [], + headers: {}, body: None, proxy: false, error: |err| match err { @@ -502,7 +513,7 @@ test! { server: expected: "\ GET /err HTTP/1.1\r\n\ - Host: {addr}\r\n\ + host: {addr}\r\n\ \r\n\ ", reply: "\ @@ -512,9 +523,9 @@ test! { client: request: - method: Get, + method: GET, url: "http://{addr}/err", - headers: [], + headers: {}, body: None, proxy: false, error: |err| match err { @@ -530,8 +541,8 @@ test! { server: expected: "\ POST /continue HTTP/1.1\r\n\ - Host: {addr}\r\n\ - Content-Length: 7\r\n\ + content-length: 7\r\n\ + host: {addr}\r\n\ \r\n\ foo bar\ ", @@ -545,16 +556,16 @@ test! { client: request: - method: Post, + method: POST, url: "http://{addr}/continue", - headers: [ - ContentLength(7), - ], + headers: { + "Content-Length" => "7", + }, body: Some("foo bar"), proxy: false, response: - status: Ok, - headers: [], + status: OK, + headers: {}, body: None, } @@ -565,7 +576,7 @@ test! { server: expected: "\ GET /upgrade HTTP/1.1\r\n\ - Host: {addr}\r\n\ + host: {addr}\r\n\ \r\n\ ", reply: "\ @@ -577,9 +588,9 @@ test! { client: request: - method: Get, + method: GET, url: "http://{addr}/upgrade", - headers: [], + headers: {}, body: None, proxy: false, error: |err| match err { @@ -603,9 +614,9 @@ test! { client: request: - method: Connect, + method: CONNECT, url: "http://{addr}/", - headers: [], + headers: {}, body: None, proxy: false, error: |err| match err { @@ -633,18 +644,17 @@ test! { client: set_host: false, request: - method: Get, + method: GET, url: "http://{addr}/no-host/{addr}", - headers: [], + headers: {}, body: None, proxy: false, response: - status: Ok, - headers: [], + status: OK, + headers: {}, body: None, } - mod dispatch_impl { use super::*; use std::io::{self, Read, Write}; @@ -694,10 +704,12 @@ mod dispatch_impl { let _ = tx1.send(()); }); - let uri = format!("http://{}/a", addr).parse().unwrap(); - - let res = client.get(uri).and_then(move |res| { - assert_eq!(res.status(), hyper::StatusCode::Ok); + let req = Request::builder() + .uri(&*format!("http://{}/a", addr)) + .body(Body::empty()) + .unwrap(); + let res = client.request(req).and_then(move |res| { + assert_eq!(res.status(), hyper::StatusCode::OK); Timeout::new(Duration::from_secs(1), &handle).unwrap() .from_err() }); @@ -732,15 +744,18 @@ mod dispatch_impl { let _ = tx1.send(()); }); - let uri = format!("http://{}/a", addr).parse().unwrap(); - let res = { let client = Client::configure() .connector(DebugConnector::with_http_and_closes(HttpConnector::new(1, &handle), closes_tx)) .build(&handle); - client.get(uri).and_then(move |res| { - assert_eq!(res.status(), hyper::StatusCode::Ok); - res.body().concat2() + + let req = Request::builder() + .uri(&*format!("http://{}/a", addr)) + .body(Body::empty()) + .unwrap(); + client.request(req).and_then(move |res| { + assert_eq!(res.status(), hyper::StatusCode::OK); + res.into_parts().1.concat2() }).and_then(|_| { Timeout::new(Duration::from_secs(1), &handle).unwrap() .from_err() @@ -783,14 +798,17 @@ mod dispatch_impl { let _ = client_drop_rx.wait(); }); - let uri = format!("http://{}/a", addr).parse().unwrap(); - let client = Client::configure() .connector(DebugConnector::with_http_and_closes(HttpConnector::new(1, &handle), closes_tx)) .build(&handle); - let res = client.get(uri).and_then(move |res| { - assert_eq!(res.status(), hyper::StatusCode::Ok); - res.body().concat2() + + let req = Request::builder() + .uri(&*format!("http://{}/a", addr)) + .body(Body::empty()) + .unwrap(); + let res = client.request(req).and_then(move |res| { + assert_eq!(res.status(), hyper::StatusCode::OK); + res.into_parts().1.concat2() }); let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked"))); core.run(res.join(rx).map(|r| r.0)).unwrap(); @@ -843,13 +861,16 @@ mod dispatch_impl { let _ = client_drop_rx.wait(); }); - let uri = format!("http://{}/a", addr).parse().unwrap(); - let res = { let client = Client::configure() .connector(DebugConnector::with_http_and_closes(HttpConnector::new(1, &handle), closes_tx)) .build(&handle); - client.get(uri) + + let req = Request::builder() + .uri(&*format!("http://{}/a", addr)) + .body(Body::empty()) + .unwrap(); + client.request(req) }; //let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked"))); @@ -892,14 +913,17 @@ mod dispatch_impl { let _ = client_drop_rx.wait(); }); - let uri = format!("http://{}/a", addr).parse().unwrap(); - let res = { let client = Client::configure() .connector(DebugConnector::with_http_and_closes(HttpConnector::new(1, &handle), closes_tx)) .build(&handle); + + let req = Request::builder() + .uri(&*format!("http://{}/a", addr)) + .body(Body::empty()) + .unwrap(); // notably, havent read body yet - client.get(uri) + client.request(req) }; let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked"))); @@ -927,6 +951,7 @@ mod dispatch_impl { let (closes_tx, closes) = mpsc::channel(10); let (tx1, rx1) = oneshot::channel(); + let (_tx2, rx2) = oneshot::channel::<()>(); thread::spawn(move || { let mut sock = server.accept().unwrap().0; @@ -936,17 +961,21 @@ mod dispatch_impl { sock.read(&mut buf).expect("read 1"); sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").unwrap(); let _ = tx1.send(()); + let _ = rx2.wait(); }); - let uri = format!("http://{}/a", addr).parse().unwrap(); - let client = Client::configure() .connector(DebugConnector::with_http_and_closes(HttpConnector::new(1, &handle), closes_tx)) .keep_alive(false) .build(&handle); - let res = client.get(uri).and_then(move |res| { - assert_eq!(res.status(), hyper::StatusCode::Ok); - res.body().concat2() + + let req = Request::builder() + .uri(&*format!("http://{}/a", addr)) + .body(Body::empty()) + .unwrap(); + let res = client.request(req).and_then(move |res| { + assert_eq!(res.status(), hyper::StatusCode::OK); + res.into_parts().1.concat2() }); let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked"))); core.run(res.join(rx).map(|r| r.0)).unwrap(); @@ -984,14 +1013,17 @@ mod dispatch_impl { let _ = tx1.send(()); }); - let uri = format!("http://{}/a", addr).parse().unwrap(); - let client = Client::configure() .connector(DebugConnector::with_http_and_closes(HttpConnector::new(1, &handle), closes_tx)) .build(&handle); - let res = client.get(uri).and_then(move |res| { - assert_eq!(res.status(), hyper::StatusCode::Ok); - res.body().concat2() + + let req = Request::builder() + .uri(&*format!("http://{}/a", addr)) + .body(Body::empty()) + .unwrap(); + let res = client.request(req).and_then(move |res| { + assert_eq!(res.status(), hyper::StatusCode::OK); + res.into_parts().1.concat2() }); let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked"))); core.run(res.join(rx).map(|r| r.0)).unwrap(); @@ -1039,9 +1071,14 @@ mod dispatch_impl { let uri = format!("http://{}/a", addr).parse::().unwrap(); let client = Client::new(&handle); - let res = client.get(uri.clone()).and_then(move |res| { - assert_eq!(res.status(), hyper::StatusCode::Ok); - res.body().concat2() + + let req = Request::builder() + .uri(uri.clone()) + .body(Body::empty()) + .unwrap(); + let res = client.request(req).and_then(move |res| { + assert_eq!(res.status(), hyper::StatusCode::OK); + res.into_parts().1.concat2() }); core.run(res).unwrap(); @@ -1052,7 +1089,11 @@ mod dispatch_impl { let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked"))); let rx = rx.and_then(move |_| timeout.map_err(|e| e.into())); - let res = client.get(uri); + let req = Request::builder() + .uri(uri) + .body(Body::empty()) + .unwrap(); + let res = client.request(req); // this does trigger an 'event loop gone' error, but before, it would // panic internally on a `SendError`, which is what we're testing against. let err = core.run(res.join(rx).map(|r| r.0)).unwrap_err(); @@ -1079,14 +1120,17 @@ mod dispatch_impl { let _ = tx1.send(()); }); - let uri = format!("http://{}/a", addr).parse().unwrap(); - let client = Client::configure() .connector(DebugConnector::with_http_and_closes(HttpConnector::new(1, &handle), closes_tx)) .executor(handle.clone()); - let res = client.get(uri).and_then(move |res| { - assert_eq!(res.status(), hyper::StatusCode::Ok); - res.body().concat2() + + let req = Request::builder() + .uri(&*format!("http://{}/a", addr)) + .body(Body::empty()) + .unwrap(); + let res = client.request(req).and_then(move |res| { + assert_eq!(res.status(), hyper::StatusCode::OK); + res.into_parts().1.concat2() }); let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked"))); @@ -1111,21 +1155,21 @@ mod dispatch_impl { // idle connections that the Checkout would have found let _ = pretty_env_logger::try_init(); - let server = TcpListener::bind("127.0.0.1:0").unwrap(); - let addr = server.local_addr().unwrap(); let core = Core::new().unwrap(); let handle = core.handle(); let connector = DebugConnector::new(&handle); let connects = connector.connects.clone(); - let uri: hyper::Uri = format!("http://{}/a", addr).parse().unwrap(); - let client = Client::configure() .connector(connector) .build(&handle); assert_eq!(connects.load(Ordering::Relaxed), 0); - let _fut = client.get(uri); + let req = Request::builder() + .uri("http://hyper.local/a") + .body(Default::default()) + .unwrap(); + let _fut = client.request(req); // internal Connect::connect should have been lazy, and not // triggered an actual connect yet. assert_eq!(connects.load(Ordering::Relaxed), 0); @@ -1169,13 +1213,25 @@ mod dispatch_impl { assert_eq!(connects.load(Ordering::Relaxed), 0); let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked"))); - let res = client.get(format!("http://{}/a", addr).parse().unwrap()); + let req = Request::builder() + .uri(&*format!("http://{}/a", addr)) + //TODO: remove this header when auto lengths are fixed + .header("content-length", "0") + .body(Body::empty()) + .unwrap(); + let res = client.request(req); core.run(res.join(rx).map(|r| r.0)).unwrap(); assert_eq!(connects.load(Ordering::Relaxed), 1); let rx = rx2.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked"))); - let res = client.get(format!("http://{}/b", addr).parse().unwrap()); + let req = Request::builder() + .uri(&*format!("http://{}/b", addr)) + //TODO: remove this header when auto lengths are fixed + .header("content-length", "0") + .body(Body::empty()) + .unwrap(); + let res = client.request(req); core.run(res.join(rx).map(|r| r.0)).unwrap(); assert_eq!(connects.load(Ordering::Relaxed), 1, "second request should still only have 1 connect"); @@ -1222,14 +1278,26 @@ mod dispatch_impl { assert_eq!(connects.load(Ordering::Relaxed), 0); let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked"))); - let req = Request::new(Method::Head, format!("http://{}/a", addr).parse().unwrap()); + let req = Request::builder() + .method("HEAD") + .uri(&*format!("http://{}/a", addr)) + //TODO: remove this header when auto lengths are fixed + .header("content-length", "0") + .body(Body::empty()) + .unwrap(); let res = client.request(req); core.run(res.join(rx).map(|r| r.0)).unwrap(); assert_eq!(connects.load(Ordering::Relaxed), 1); let rx = rx2.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked"))); - let res = client.get(format!("http://{}/b", addr).parse().unwrap()); + let req = Request::builder() + .uri(&*format!("http://{}/b", addr)) + //TODO: remove this header when auto lengths are fixed + .header("content-length", "0") + .body(Body::empty()) + .unwrap(); + let res = client.request(req); core.run(res.join(rx).map(|r| r.0)).unwrap(); assert_eq!(connects.load(Ordering::Relaxed), 2); @@ -1323,7 +1391,7 @@ mod conn { use tokio_core::net::TcpStream; use tokio_io::{AsyncRead, AsyncWrite}; - use hyper::{self, Method, Request}; + use hyper::{self, Request}; use hyper::client::conn; use super::s; @@ -1360,12 +1428,13 @@ mod conn { handle.spawn(conn.map(|_| ()).map_err(|e| panic!("conn error: {}", e))); - let uri = "/a".parse().unwrap(); - let req = Request::new(Method::Get, uri); - + let req = Request::builder() + .uri("/a") + .body(Default::default()) + .unwrap(); let res = client.send_request(req).and_then(move |res| { - assert_eq!(res.status(), hyper::StatusCode::Ok); - res.body().concat2() + assert_eq!(res.status(), hyper::StatusCode::OK); + res.into_body().concat2() }); let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked"))); @@ -1405,12 +1474,14 @@ mod conn { handle.spawn(conn.map(|_| ()).map_err(|e| panic!("conn error: {}", e))); - let uri = "http://hyper.local/a".parse().unwrap(); - let req = Request::new(Method::Get, uri); + let req = Request::builder() + .uri("http://hyper.local/a") + .body(Default::default()) + .unwrap(); let res = client.send_request(req).and_then(move |res| { - assert_eq!(res.status(), hyper::StatusCode::Ok); - res.body().concat2() + assert_eq!(res.status(), hyper::StatusCode::OK); + res.into_body().concat2() }); let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked"))); @@ -1419,7 +1490,6 @@ mod conn { core.run(res.join(rx).map(|r| r.0)).unwrap(); } - #[test] fn pipeline() { let server = TcpListener::bind("127.0.0.1:0").unwrap(); @@ -1436,6 +1506,7 @@ mod conn { let mut buf = [0; 4096]; sock.read(&mut buf).expect("read 1"); sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n").unwrap(); + let _ = tx1.send(()); }); @@ -1445,16 +1516,20 @@ mod conn { handle.spawn(conn.map(|_| ()).map_err(|e| panic!("conn error: {}", e))); - let uri = "/a".parse().unwrap(); - let req = Request::new(Method::Get, uri); + let req = Request::builder() + .uri("/a") + .body(Default::default()) + .unwrap(); let res1 = client.send_request(req).and_then(move |res| { - assert_eq!(res.status(), hyper::StatusCode::Ok); - res.body().concat2() + assert_eq!(res.status(), hyper::StatusCode::OK); + res.into_body().concat2() }); // pipelined request will hit NotReady, and thus should return an Error::Cancel - let uri = "/b".parse().unwrap(); - let req = Request::new(Method::Get, uri); + let req = Request::builder() + .uri("/b") + .body(Default::default()) + .unwrap(); let res2 = client.send_request(req) .then(|result| { let err = result.expect_err("res2"); @@ -1517,12 +1592,14 @@ mod conn { conn.poll_without_shutdown() }); - let uri = "/a".parse().unwrap(); - let req = Request::new(Method::Get, uri); + let req = Request::builder() + .uri("/a") + .body(Default::default()) + .unwrap(); let res = client.send_request(req).and_then(move |res| { - assert_eq!(res.status(), hyper::StatusCode::SwitchingProtocols); - assert_eq!(res.headers().get_raw("Upgrade").unwrap(), "foobar"); - res.body().concat2() + assert_eq!(res.status(), hyper::StatusCode::SWITCHING_PROTOCOLS); + assert_eq!(res.headers()["Upgrade"], "foobar"); + res.into_body().concat2() }); let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked"))); @@ -1595,12 +1672,15 @@ mod conn { conn.poll_without_shutdown() }); - let uri = format!("http://{}", addr).parse().unwrap(); - let req = Request::new(Method::Connect, uri); + let req = Request::builder() + .method("CONNECT") + .uri(addr.to_string()) + .body(Default::default()) + .unwrap(); let res = client.send_request(req) .and_then(move |res| { - assert_eq!(res.status(), hyper::StatusCode::Ok); - res.body().concat2() + assert_eq!(res.status(), hyper::StatusCode::OK); + res.into_body().concat2() }) .map(|body| { assert_eq!(body.as_ref(), b""); diff --git a/tests/server.rs b/tests/server.rs index b3989db2bf..2bfc80b6e2 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -1,4 +1,5 @@ #![deny(warnings)] +extern crate http; extern crate hyper; #[macro_use] extern crate futures; @@ -7,14 +8,6 @@ extern crate pretty_env_logger; extern crate tokio_core; extern crate tokio_io; -use futures::{Future, Stream}; -use futures::future::{self, FutureResult, Either}; -use futures::sync::oneshot; - -use tokio_core::net::TcpListener; -use tokio_core::reactor::{Core, Timeout}; -use tokio_io::{AsyncRead, AsyncWrite}; - use std::net::{TcpStream, Shutdown, SocketAddr}; use std::io::{self, Read, Write}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -23,9 +16,17 @@ use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; -use hyper::StatusCode; -use hyper::header::ContentLength; -use hyper::server::{Http, Request, Response, Service, NewService, service_fn}; +use futures::{Future, Stream}; +use futures::future::{self, FutureResult, Either}; +use futures::sync::oneshot; +use http::header::{HeaderName, HeaderValue}; +use tokio_core::net::TcpListener; +use tokio_core::reactor::{Core, Timeout}; +use tokio_io::{AsyncRead, AsyncWrite}; + + +use hyper::{Body, Request, Response, StatusCode}; +use hyper::server::{Http, Service, NewService, service_fn}; #[test] @@ -92,14 +93,19 @@ fn get_implicitly_empty() { struct GetImplicitlyEmpty; impl Service for GetImplicitlyEmpty { - type Request = Request; - type Response = Response; + type Request = Request; + type Response = Response; type Error = hyper::Error; - type Future = FutureResult; - - fn call(&self, req: Request) -> Self::Future { - assert!(req.body_ref().is_none()); - future::ok(Response::new()) + type Future = Box>; + + fn call(&self, req: Request) -> Self::Future { + Box::new(req.into_parts() + .1 + .concat2() + .map(|buf| { + assert!(buf.is_empty()); + Response::new(Body::empty()) + })) } } } @@ -109,8 +115,7 @@ fn get_fixed_response() { let foo_bar = b"foo bar baz"; let server = serve(); server.reply() - .status(hyper::Ok) - .header(hyper::header::ContentLength(foo_bar.len() as u64)) + .header("content-length", foo_bar.len().to_string()) .body(foo_bar); let mut req = connect(server.addr()); req.write_all(b"\ @@ -131,8 +136,7 @@ fn get_chunked_response() { let foo_bar = b"foo bar baz"; let server = serve(); server.reply() - .status(hyper::Ok) - .header(hyper::header::TransferEncoding::chunked()) + .header("transfer-encoding", "chunked") .body(foo_bar); let mut req = connect(server.addr()); req.write_all(b"\ @@ -153,7 +157,6 @@ fn get_auto_response() { let foo_bar = b"foo bar baz"; let server = serve(); server.reply() - .status(hyper::Ok) .body(foo_bar); let mut req = connect(server.addr()); req.write_all(b"\ @@ -176,7 +179,6 @@ fn http_10_get_auto_response() { let foo_bar = b"foo bar baz"; let server = serve(); server.reply() - .status(hyper::Ok) .body(foo_bar); let mut req = connect(server.addr()); req.write_all(b"\ @@ -198,9 +200,8 @@ fn http_10_get_chunked_response() { let foo_bar = b"foo bar baz"; let server = serve(); server.reply() - .status(hyper::Ok) // this header should actually get removed - .header(hyper::header::TransferEncoding::chunked()) + .header("transfer-encoding", "chunked") .body(foo_bar); let mut req = connect(server.addr()); req.write_all(b"\ @@ -223,8 +224,7 @@ fn get_chunked_response_with_ka() { let foo_bar_chunk = b"\r\nfoo bar baz\r\n0\r\n\r\n"; let server = serve(); server.reply() - .status(hyper::Ok) - .header(hyper::header::TransferEncoding::chunked()) + .header("transfer-encoding", "chunked") .body(foo_bar); let mut req = connect(server.addr()); req.write_all(b"\ @@ -250,8 +250,7 @@ fn get_chunked_response_with_ka() { let quux = b"zar quux"; server.reply() - .status(hyper::Ok) - .header(hyper::header::ContentLength(quux.len() as u64)) + .header("content-length", quux.len().to_string()) .body(quux); req.write_all(b"\ GET /quux HTTP/1.1\r\n\ @@ -320,7 +319,6 @@ fn empty_response_chunked() { let server = serve(); server.reply() - .status(hyper::Ok) .body(""); let mut req = connect(server.addr()); @@ -355,8 +353,7 @@ fn empty_response_chunked_without_body_should_set_content_length() { let _ = pretty_env_logger::try_init(); let server = serve(); server.reply() - .status(hyper::Ok) - .header(hyper::header::TransferEncoding::chunked()); + .header("transfer-encoding", "chunked"); let mut req = connect(server.addr()); req.write_all(b"\ GET / HTTP/1.1\r\n\ @@ -385,8 +382,7 @@ fn head_response_can_send_content_length() { let _ = pretty_env_logger::try_init(); let server = serve(); server.reply() - .status(hyper::Ok) - .header(hyper::header::ContentLength(1024)); + .header("content-length", "1024"); let mut req = connect(server.addr()); req.write_all(b"\ HEAD / HTTP/1.1\r\n\ @@ -414,8 +410,8 @@ fn response_does_not_set_chunked_if_body_not_allowed() { let _ = pretty_env_logger::try_init(); let server = serve(); server.reply() - .status(hyper::StatusCode::NotModified) - .header(hyper::header::TransferEncoding::chunked()); + .status(hyper::StatusCode::NOT_MODIFIED) + .header("transfer-encoding", "chunked"); let mut req = connect(server.addr()); req.write_all(b"\ GET / HTTP/1.1\r\n\ @@ -443,8 +439,7 @@ fn keep_alive() { let foo_bar = b"foo bar baz"; let server = serve(); server.reply() - .status(hyper::Ok) - .header(hyper::header::ContentLength(foo_bar.len() as u64)) + .header("content-length", foo_bar.len().to_string()) .body(foo_bar); let mut req = connect(server.addr()); req.write_all(b"\ @@ -467,8 +462,7 @@ fn keep_alive() { let quux = b"zar quux"; server.reply() - .status(hyper::Ok) - .header(hyper::header::ContentLength(quux.len() as u64)) + .header("content-length", quux.len().to_string()) .body(quux); req.write_all(b"\ GET /quux HTTP/1.1\r\n\ @@ -494,8 +488,7 @@ fn http_10_keep_alive() { let foo_bar = b"foo bar baz"; let server = serve(); server.reply() - .status(hyper::Ok) - .header(hyper::header::ContentLength(foo_bar.len() as u64)) + .header("content-length", foo_bar.len().to_string()) .body(foo_bar); let mut req = connect(server.addr()); req.write_all(b"\ @@ -519,8 +512,7 @@ fn http_10_keep_alive() { let quux = b"zar quux"; server.reply() - .status(hyper::Ok) - .header(hyper::header::ContentLength(quux.len() as u64)) + .header("content-length", quux.len().to_string()) .body(quux); req.write_all(b"\ GET /quux HTTP/1.0\r\n\ @@ -548,8 +540,7 @@ fn disable_keep_alive() { .. Default::default() }); server.reply() - .status(hyper::Ok) - .header(hyper::header::ContentLength(foo_bar.len() as u64)) + .header("content-length", foo_bar.len().to_string()) .body(foo_bar); let mut req = connect(server.addr()); req.write_all(b"\ @@ -574,8 +565,7 @@ fn disable_keep_alive() { let quux = b"zar quux"; server.reply() - .status(hyper::Ok) - .header(hyper::header::ContentLength(quux.len() as u64)) + .header("content-length", quux.len().to_string()) .body(quux); // the write can possibly succeed, since it fills the kernel buffer on the first write @@ -602,7 +592,7 @@ fn disable_keep_alive() { fn expect_continue() { let server = serve(); let mut req = connect(server.addr()); - server.reply().status(hyper::Ok); + server.reply(); req.write_all(b"\ POST /foo HTTP/1.1\r\n\ @@ -633,12 +623,10 @@ fn pipeline_disabled() { let server = serve(); let mut req = connect(server.addr()); server.reply() - .status(hyper::Ok) - .header(ContentLength(12)) + .header("content-length", "12") .body("Hello World!"); server.reply() - .status(hyper::Ok) - .header(ContentLength(12)) + .header("content-length", "12") .body("Hello World!"); req.write_all(b"\ @@ -680,12 +668,10 @@ fn pipeline_enabled() { }); let mut req = connect(server.addr()); server.reply() - .status(hyper::Ok) - .header(ContentLength(12)) + .header("content-length", "12") .body("Hello World\n"); server.reply() - .status(hyper::Ok) - .header(ContentLength(12)) + .header("content-length", "12") .body("Hello World\n"); req.write_all(b"\ @@ -922,8 +908,10 @@ fn returning_1xx_response_is_error() { let (socket, _) = item.unwrap(); Http::::new() .serve_connection(socket, service_fn(|_| { - Ok(Response::::new() - .with_status(StatusCode::Continue)) + Ok(Response::builder() + .status(StatusCode::CONTINUE) + .body(Body::empty()) + .unwrap()) })) .map(|_| ()) }); @@ -968,9 +956,11 @@ fn upgrades() { let (socket, _) = item.unwrap(); let conn = Http::::new() .serve_connection(socket, service_fn(|_| { - let mut res = Response::::new() - .with_status(StatusCode::SwitchingProtocols); - res.headers_mut().set_raw("Upgrade", "foobar"); + let res = Response::builder() + .status(101) + .header("upgrade", "foobar") + .body(hyper::Body::empty()) + .unwrap(); Ok(res) })); @@ -1128,8 +1118,11 @@ fn streaming_body() { .serve_connection(socket, service_fn(|_| { static S: &'static [&'static [u8]] = &[&[b'x'; 1_000] as &[u8]; 1_00] as _; let b = ::futures::stream::iter_ok(S.iter()); + Ok(Response::new(b)) + /* Ok(Response::, ::hyper::Error>>::new() .with_body(b)) + */ })) .map(|_| ()) }); @@ -1137,22 +1130,6 @@ fn streaming_body() { core.run(fut.join(rx)).unwrap(); } -#[test] -fn remote_addr() { - let server = serve(); - - let mut req = connect(server.addr()); - req.write_all(b"\ - GET / HTTP/1.1\r\n\ - Host: example.domain\r\n\ - \r\n\ - ").unwrap(); - req.read(&mut [0; 256]).unwrap(); - - let client_addr = req.local_addr().unwrap(); - assert_eq!(server.remote_addr(), client_addr); -} - // ------------------------------------------------- // the Server that is used to run all the tests with // ------------------------------------------------- @@ -1170,13 +1147,6 @@ impl Serve { &self.addr } - pub fn remote_addr(&self) -> SocketAddr { - match self.msg_rx.recv() { - Ok(Msg::Addr(addr)) => addr, - other => panic!("expected remote addr, found: {:?}", other), - } - } - fn body(&self) -> Vec { self.try_body().expect("body") } @@ -1192,7 +1162,6 @@ impl Serve { Ok(Msg::Chunk(msg)) => { buf.extend(&msg); }, - Ok(Msg::Addr(_)) => {}, Ok(Msg::Error(e)) => return Err(e), Ok(Msg::End) => break, Err(e) => panic!("expected body, found: {:?}", e), @@ -1218,10 +1187,10 @@ impl<'a> ReplyBuilder<'a> { self } - fn header(self, header: H) -> Self { - let mut headers = hyper::Headers::new(); - headers.set(header); - self.tx.send(Reply::Headers(headers)).unwrap(); + fn header>(self, name: &str, value: V) -> Self { + let name = HeaderName::from_bytes(name.as_bytes()).expect("header name"); + let value = HeaderValue::from_str(value.as_ref()).expect("header value"); + self.tx.send(Reply::Header(name, value)).unwrap(); self } @@ -1253,7 +1222,7 @@ struct TestService { #[derive(Clone, Debug)] enum Reply { Status(hyper::StatusCode), - Headers(hyper::Headers), + Header(HeaderName, HeaderValue), Body(Vec), End, } @@ -1261,15 +1230,14 @@ enum Reply { #[derive(Debug)] enum Msg { //Head(Request), - Addr(SocketAddr), Chunk(Vec), Error(hyper::Error), End, } impl NewService for TestService { - type Request = Request; - type Response = Response; + type Request = Request; + type Response = Response; type Error = hyper::Error; type Instance = TestService; @@ -1280,20 +1248,16 @@ impl NewService for TestService { } impl Service for TestService { - type Request = Request; - type Response = Response; + type Request = Request; + type Response = Response; type Error = hyper::Error; - type Future = Box>; - fn call(&self, req: Request) -> Self::Future { + type Future = Box, Error=hyper::Error>>; + fn call(&self, req: Request) -> Self::Future { let tx1 = self.tx.clone(); let tx2 = self.tx.clone(); - #[allow(deprecated)] - let remote_addr = req.remote_addr().expect("remote_addr"); - tx1.lock().unwrap().send(Msg::Addr(remote_addr)).unwrap(); - let replies = self.reply.clone(); - Box::new(req.body().for_each(move |chunk| { + Box::new(req.into_parts().1.for_each(move |chunk| { tx1.lock().unwrap().send(Msg::Chunk(chunk.to_vec())).unwrap(); Ok(()) }).then(move |result| { @@ -1304,17 +1268,17 @@ impl Service for TestService { tx2.lock().unwrap().send(msg).unwrap(); Ok(()) }).map(move |_| { - let mut res = Response::new(); + let mut res = Response::new(Body::empty()); while let Ok(reply) = replies.try_recv() { match reply { Reply::Status(s) => { - res.set_status(s); + *res.status_mut() = s; }, - Reply::Headers(headers) => { - *res.headers_mut() = headers; + Reply::Header(name, value) => { + res.headers_mut().insert(name, value); }, Reply::Body(body) => { - res.set_body(body); + *res.body_mut() = body.into(); }, Reply::End => break, } @@ -1330,15 +1294,13 @@ const HELLO: &'static str = "hello"; struct HelloWorld; impl Service for HelloWorld { - type Request = Request; - type Response = Response; + type Request = Request; + type Response = Response; type Error = hyper::Error; type Future = FutureResult; - fn call(&self, _req: Request) -> Self::Future { - let mut response = Response::new(); - response.headers_mut().set(hyper::header::ContentLength(HELLO.len() as u64)); - response.set_body(HELLO); + fn call(&self, _req: Request) -> Self::Future { + let response = Response::new(HELLO.into()); future::ok(response) } }