feat(lib): replace types with those from http crate

BREAKING CHANGE: `Method`, `Request`, `Response`, `StatusCode`,
  `Version`, and `Uri` have been replaced with types from the `http`
  crate. The `hyper::header` module is gone for now.

  Removed `Client::get`, since it needed to construct a `Request<B>`
  with an empty body. Just use `Client::request` instead.

  Removed `compat` cargo feature, and `compat` related API.
This commit is contained in:
Sean McArthur
2018-02-28 16:37:17 -08:00
parent a37e6b59e6
commit 3cd48b45fb
109 changed files with 1004 additions and 14411 deletions

View File

@@ -9,10 +9,6 @@ matrix:
- rust: beta - rust: beta
- rust: stable - rust: stable
env: HYPER_DOCS=1 env: HYPER_DOCS=1
- rust: stable
env: FEATURES="--no-default-features"
- rust: stable
env: FEATURES="--features compat"
- rust: 1.21.0 - rust: 1.21.0
cache: cache:

View File

@@ -25,7 +25,7 @@ base64 = "0.9"
bytes = "0.4.4" bytes = "0.4.4"
futures = "0.1.17" futures = "0.1.17"
futures-cpupool = "0.1.6" futures-cpupool = "0.1.6"
http = { version = "0.1", optional = true } http = "0.1.5"
httparse = "1.0" httparse = "1.0"
iovec = "0.1" iovec = "0.1"
language-tags = "0.2" language-tags = "0.2"
@@ -46,7 +46,4 @@ spmc = "0.2"
url = "1.0" url = "1.0"
[features] [features]
default = []
nightly = [] nightly = []
raw_status = []
compat = [ "http" ]

View File

@@ -12,9 +12,7 @@ use futures::{Future, Stream};
use tokio_core::reactor::{Core, Handle}; use tokio_core::reactor::{Core, Handle};
use tokio_core::net::TcpListener; use tokio_core::net::TcpListener;
use hyper::client; use hyper::{Body, Method, Request, Response};
use hyper::header::{ContentLength, ContentType};
use hyper::Method;
#[bench] #[bench]
@@ -30,7 +28,7 @@ fn get_one_at_a_time(b: &mut test::Bencher) {
b.bytes = 160 * 2 + PHRASE.len() as u64; b.bytes = 160 * 2 + PHRASE.len() as u64;
b.iter(move || { b.iter(move || {
let work = client.get(url.clone()).and_then(|res| { let work = client.get(url.clone()).and_then(|res| {
res.body().for_each(|_chunk| { res.into_body().into_stream().for_each(|_chunk| {
Ok(()) Ok(())
}) })
}); });
@@ -54,12 +52,11 @@ fn post_one_at_a_time(b: &mut test::Bencher) {
let post = "foo bar baz quux"; let post = "foo bar baz quux";
b.bytes = 180 * 2 + post.len() as u64 + PHRASE.len() as u64; b.bytes = 180 * 2 + post.len() as u64 + PHRASE.len() as u64;
b.iter(move || { b.iter(move || {
let mut req = client::Request::new(Method::Post, url.clone()); let mut req = Request::new(post.into());
req.headers_mut().set(ContentLength(post.len() as u64)); *req.method_mut() = Method::POST;
req.set_body(post); *req.uri_mut() = url.clone();
let work = client.request(req).and_then(|res| { let work = client.request(req).and_then(|res| {
res.body().for_each(|_chunk| { res.into_body().into_stream().for_each(|_chunk| {
Ok(()) 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!"; static PHRASE: &'static [u8] = include_bytes!("../CHANGELOG.md"); //b"Hello, World!";
fn spawn_hello(handle: &Handle) -> SocketAddr { 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 addr = "127.0.0.1:0".parse().unwrap();
let listener = TcpListener::bind(&addr, handle).unwrap(); let listener = TcpListener::bind(&addr, handle).unwrap();
let addr = listener.local_addr().unwrap(); let addr = listener.local_addr().unwrap();
@@ -79,14 +76,12 @@ fn spawn_hello(handle: &Handle) -> SocketAddr {
let handle2 = handle.clone(); let handle2 = handle.clone();
let http = hyper::server::Http::<hyper::Chunk>::new(); let http = hyper::server::Http::<hyper::Chunk>::new();
let service = const_service(service_fn(|req: Request| { let service = const_service(service_fn(|req: Request<Body>| {
req.body() req.into_body()
.into_stream()
.concat2() .concat2()
.map(|_| { .map(|_| {
Response::<hyper::Body>::new() Response::new(Body::from(PHRASE))
.with_header(ContentLength(PHRASE.len() as u64))
.with_header(ContentType::plaintext())
.with_body(PHRASE)
}) })
})); }));

View File

@@ -10,11 +10,11 @@ use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream}; use std::net::{TcpListener, TcpStream};
use std::sync::mpsc; use std::sync::mpsc;
use futures::{future, stream, Future}; use futures::{future, stream, Future, Stream};
use futures::sync::oneshot; use futures::sync::oneshot;
use hyper::header::{ContentLength, ContentType, TransferEncoding}; use hyper::{Body, Request, Response};
use hyper::server::{self, Service}; use hyper::server::Service;
macro_rules! bench_server { macro_rules! bench_server {
($b:ident, $header:expr, $body:expr) => ({ ($b:ident, $header:expr, $body:expr) => ({
@@ -65,37 +65,37 @@ fn body(b: &'static [u8]) -> hyper::Body {
#[bench] #[bench]
fn throughput_fixedsize_small_payload(b: &mut test::Bencher) { 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] #[bench]
fn throughput_fixedsize_large_payload(b: &mut test::Bencher) { 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] #[bench]
fn throughput_fixedsize_many_chunks(b: &mut test::Bencher) { 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 _; 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] #[bench]
fn throughput_chunked_small_payload(b: &mut test::Bencher) { 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] #[bench]
fn throughput_chunked_large_payload(b: &mut test::Bencher) { 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] #[bench]
fn throughput_chunked_many_chunks(b: &mut test::Bencher) { 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 _; 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(); tx.send(()).unwrap();
} }
struct BenchPayload<H, F> { struct BenchPayload<F> {
header: H, header: (&'static str, &'static str),
body: F, body: F,
} }
impl<H, F, B> Service for BenchPayload<H, F> impl<F, B> Service for BenchPayload<F>
where where
H: hyper::header::Header + Clone,
F: Fn() -> B, F: Fn() -> B,
{ {
type Request = server::Request; type Request = Request<Body>;
type Response = server::Response<B>; type Response = Response<B>;
type Error = hyper::Error; type Error = hyper::Error;
type Future = future::FutureResult<Self::Response, hyper::Error>; type Future = future::FutureResult<Self::Response, hyper::Error>;
fn call(&self, _req: Self::Request) -> Self::Future { fn call(&self, _req: Self::Request) -> Self::Future {
future::ok( future::ok(
server::Response::new() Response::builder()
.with_header(self.header.clone()) .header(self.header.0, self.header.1)
.with_header(ContentType::plaintext()) .header("content-type", "text/plain")
.with_body((self.body)()) .body((self.body)())
.unwrap()
) )
} }
} }

View File

@@ -11,7 +11,7 @@ use std::io::{self, Write};
use futures::Future; use futures::Future;
use futures::stream::Stream; use futures::stream::Stream;
use hyper::Client; use hyper::{Body, Client, Request};
fn main() { fn main() {
pretty_env_logger::init(); pretty_env_logger::init();
@@ -25,7 +25,7 @@ fn main() {
}; };
let url = url.parse::<hyper::Uri>().unwrap(); let url = url.parse::<hyper::Uri>().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."); println!("This example only works with 'http' URLs.");
return; return;
} }
@@ -34,11 +34,13 @@ fn main() {
let handle = core.handle(); let handle = core.handle();
let client = Client::new(&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!("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) io::stdout().write_all(&chunk).map_err(From::from)
}) })
}).map(|_| { }).map(|_| {

View File

@@ -3,8 +3,8 @@ extern crate hyper;
extern crate futures; extern crate futures;
extern crate pretty_env_logger; extern crate pretty_env_logger;
use hyper::header::{ContentLength, ContentType}; use hyper::{Body, Response};
use hyper::server::{Http, Response, const_service, service_fn}; use hyper::server::{Http, const_service, service_fn};
static PHRASE: &'static [u8] = b"Hello World!"; static PHRASE: &'static [u8] = b"Hello World!";
@@ -13,10 +13,7 @@ fn main() {
let addr = ([127, 0, 0, 1], 3000).into(); let addr = ([127, 0, 0, 1], 3000).into();
let new_service = const_service(service_fn(|_| { let new_service = const_service(service_fn(|_| {
Ok(Response::<hyper::Body>::new() Ok(Response::new(Body::from(PHRASE)))
.with_header(ContentLength(PHRASE.len() as u64))
.with_header(ContentType::plaintext())
.with_body(PHRASE))
})); }));
let server = Http::new() let server = Http::new()

View File

@@ -7,10 +7,9 @@ extern crate pretty_env_logger;
use futures::{Future, Stream}; use futures::{Future, Stream};
use futures::future::FutureResult; use futures::future::FutureResult;
use hyper::{Get, StatusCode}; use hyper::{Body, Method, Request, Response, StatusCode};
use tokio_core::reactor::Core; use tokio_core::reactor::Core;
use hyper::header::ContentLength; use hyper::server::{Http, Service};
use hyper::server::{Http, Service, Request, Response};
static INDEX1: &'static [u8] = b"The 1st service!"; static INDEX1: &'static [u8] = b"The 1st service!";
static INDEX2: &'static [u8] = b"The 2nd 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]); struct Srv(&'static [u8]);
impl Service for Srv { impl Service for Srv {
type Request = Request; type Request = Request<Body>;
type Response = Response; type Response = Response<Body>;
type Error = hyper::Error; type Error = hyper::Error;
type Future = FutureResult<Response, hyper::Error>; type Future = FutureResult<Response<Body>, hyper::Error>;
fn call(&self, req: Request) -> Self::Future { fn call(&self, req: Request<Body>) -> Self::Future {
futures::future::ok(match (req.method(), req.path()) { futures::future::ok(match (req.method(), req.uri().path()) {
(&Get, "/") => { (&Method::GET, "/") => {
Response::new() Response::new(self.0.into())
.with_header(ContentLength(self.0.len() as u64))
.with_body(self.0)
}, },
_ => { _ => {
Response::new() Response::builder()
.with_status(StatusCode::NotFound) .status(StatusCode::NOT_FOUND)
.body(Body::empty())
.unwrap()
} }
}) })
} }

View File

@@ -6,9 +6,8 @@ extern crate url;
use futures::{Future, Stream}; use futures::{Future, Stream};
use hyper::{Get, Post, StatusCode}; use hyper::{Body, Method, Request, Response, StatusCode};
use hyper::header::ContentLength; use hyper::server::{Http, Service};
use hyper::server::{Http, Service, Request, Response};
use std::collections::HashMap; use std::collections::HashMap;
use url::form_urlencoded; use url::form_urlencoded;
@@ -20,20 +19,18 @@ static NOTNUMERIC: &[u8] = b"Number field is not numeric";
struct ParamExample; struct ParamExample;
impl Service for ParamExample { impl Service for ParamExample {
type Request = Request; type Request = Request<Body>;
type Response = Response; type Response = Response<Body>;
type Error = hyper::Error; type Error = hyper::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>; type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn call(&self, req: Request) -> Self::Future { fn call(&self, req: Request<Body>) -> Self::Future {
match (req.method(), req.path()) { match (req.method(), req.uri().path()) {
(&Get, "/") | (&Get, "/post") => { (&Method::GET, "/") | (&Method::GET, "/post") => {
Box::new(futures::future::ok(Response::new() Box::new(futures::future::ok(Response::new(INDEX.into())))
.with_header(ContentLength(INDEX.len() as u64))
.with_body(INDEX)))
}, },
(&Post, "/post") => { (&Method::POST, "/post") => {
Box::new(req.body().concat2().map(|b| { Box::new(req.into_parts().1.concat2().map(|b| {
// Parse the request body. form_urlencoded::parse // Parse the request body. form_urlencoded::parse
// always succeeds, but in general parsing may // always succeeds, but in general parsing may
// fail (for example, an invalid post of json), so // 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") { let name = if let Some(n) = params.get("name") {
n n
} else { } else {
return Response::new() return Response::builder()
.with_status(StatusCode::UnprocessableEntity) .status(StatusCode::UNPROCESSABLE_ENTITY)
.with_header(ContentLength(MISSING.len() as u64)) .body(MISSING.into())
.with_body(MISSING); .unwrap();
}; };
let number = if let Some(n) = params.get("number") { let number = if let Some(n) = params.get("number") {
if let Ok(v) = n.parse::<f64>() { if let Ok(v) = n.parse::<f64>() {
v v
} else { } else {
return Response::new() return Response::builder()
.with_status(StatusCode::UnprocessableEntity) .status(StatusCode::UNPROCESSABLE_ENTITY)
.with_header(ContentLength(NOTNUMERIC.len() as u64)) .body(NOTNUMERIC.into())
.with_body(NOTNUMERIC); .unwrap();
} }
} else { } else {
return Response::new() return Response::builder()
.with_status(StatusCode::UnprocessableEntity) .status(StatusCode::UNPROCESSABLE_ENTITY)
.with_header(ContentLength(MISSING.len() as u64)) .body(MISSING.into())
.with_body(MISSING); .unwrap();
}; };
// Render the response. This will often involve // Render the response. This will often involve
@@ -80,14 +77,14 @@ impl Service for ParamExample {
// responses such as InternalServiceError may be // responses such as InternalServiceError may be
// needed here, too. // needed here, too.
let body = format!("Hello {}, your number is {}", name, number); let body = format!("Hello {}, your number is {}", name, number);
Response::new() Response::new(body.into())
.with_header(ContentLength(body.len() as u64))
.with_body(body)
})) }))
}, },
_ => { _ => {
Box::new(futures::future::ok(Response::new() Box::new(futures::future::ok(Response::builder()
.with_status(StatusCode::NotFound))) .status(StatusCode::NOT_FOUND)
.body(Body::empty())
.unwrap()))
} }
} }
} }

View File

@@ -4,12 +4,11 @@ extern crate hyper;
extern crate pretty_env_logger; extern crate pretty_env_logger;
use futures::{Future, Sink}; 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::error::Error;
use hyper::header::ContentLength; use hyper::server::{Http, Service};
use hyper::server::{Http, Service, Request, Response};
use std::fs::File; use std::fs::File;
use std::io::{self, copy, Read}; use std::io::{self, copy, Read};
@@ -18,7 +17,7 @@ use std::thread;
static NOTFOUND: &[u8] = b"Not Found"; static NOTFOUND: &[u8] = b"Not Found";
static INDEX: &str = "examples/send_file_index.html"; static INDEX: &str = "examples/send_file_index.html";
fn simple_file_send(f: &str) -> Box<Future<Item = Response, Error = hyper::Error>> { fn simple_file_send(f: &str) -> Box<Future<Item = Response<Body>, Error = hyper::Error>> {
// Serve a file by reading it entirely into memory. As a result // Serve a file by reading it entirely into memory. As a result
// this is limited to serving small files, but it is somewhat // this is limited to serving small files, but it is somewhat
// simpler with a little less overhead. // simpler with a little less overhead.
@@ -31,10 +30,10 @@ fn simple_file_send(f: &str) -> Box<Future<Item = Response, Error = hyper::Error
let mut file = match File::open(filename) { let mut file = match File::open(filename) {
Ok(f) => f, Ok(f) => f,
Err(_) => { Err(_) => {
tx.send(Response::new() tx.send(Response::builder()
.with_status(StatusCode::NotFound) .status(StatusCode::NOT_FOUND)
.with_header(ContentLength(NOTFOUND.len() as u64)) .body(NOTFOUND.into())
.with_body(NOTFOUND)) .unwrap())
.expect("Send error on open"); .expect("Send error on open");
return; return;
}, },
@@ -42,14 +41,15 @@ fn simple_file_send(f: &str) -> Box<Future<Item = Response, Error = hyper::Error
let mut buf: Vec<u8> = Vec::new(); let mut buf: Vec<u8> = Vec::new();
match copy(&mut file, &mut buf) { match copy(&mut file, &mut buf) {
Ok(_) => { Ok(_) => {
let res = Response::new() let res = Response::new(buf.into());
.with_header(ContentLength(buf.len() as u64))
.with_body(buf);
tx.send(res).expect("Send error on successful file read"); tx.send(res).expect("Send error on successful file read");
}, },
Err(_) => { Err(_) => {
tx.send(Response::new().with_status(StatusCode::InternalServerError)). tx.send(Response::builder()
expect("Send error on error reading file"); .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<Future<Item = Response, Error = hyper::Error
struct ResponseExamples; struct ResponseExamples;
impl Service for ResponseExamples { impl Service for ResponseExamples {
type Request = Request; type Request = Request<Body>;
type Response = Response; type Response = Response<Body>;
type Error = hyper::Error; type Error = hyper::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>; type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn call(&self, req: Request) -> Self::Future { fn call(&self, req: Request<Body>) -> Self::Future {
match (req.method(), req.path()) { match (req.method(), req.uri().path()) {
(&Get, "/") | (&Get, "/index.html") => { (&Method::GET, "/") | (&Method::GET, "/index.html") => {
simple_file_send(INDEX) simple_file_send(INDEX)
}, },
(&Get, "/big_file.html") => { (&Method::GET, "/big_file.html") => {
// Stream a large file in chunks. This requires a // Stream a large file in chunks. This requires a
// little more overhead with two channels, (one for // little more overhead with two channels, (one for
// the response future, and a second for the response // the response future, and a second for the response
@@ -83,16 +83,16 @@ impl Service for ResponseExamples {
let mut file = match File::open(INDEX) { let mut file = match File::open(INDEX) {
Ok(f) => f, Ok(f) => f,
Err(_) => { Err(_) => {
tx.send(Response::new() tx.send(Response::builder()
.with_status(StatusCode::NotFound) .status(StatusCode::NOT_FOUND)
.with_header(ContentLength(NOTFOUND.len() as u64)) .body(NOTFOUND.into())
.with_body(NOTFOUND)) .unwrap())
.expect("Send error on open"); .expect("Send error on open");
return; return;
}, },
}; };
let (mut tx_body, rx_body) = mpsc::channel(1); let (mut tx_body, rx_body) = Body::pair();
let res = Response::new().with_body(rx_body); let res = Response::new(rx_body.into());
tx.send(res).expect("Send error on successful file read"); tx.send(res).expect("Send error on successful file read");
let mut buf = [0u8; 16]; let mut buf = [0u8; 16];
loop { loop {
@@ -114,16 +114,18 @@ impl Service for ResponseExamples {
} }
} }
}); });
Box::new(rx.map_err(|e| Error::from(io::Error::new(io::ErrorKind::Other, e)))) 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 // Test what happens when file cannot be be found
simple_file_send("this_file_should_not_exist.html") simple_file_send("this_file_should_not_exist.html")
}, },
_ => { _ => {
Box::new(futures::future::ok(Response::new() Box::new(futures::future::ok(Response::builder()
.with_status(StatusCode::NotFound))) .status(StatusCode::NOT_FOUND)
.body(Body::empty())
.unwrap()))
} }
} }
} }

View File

@@ -5,37 +5,31 @@ extern crate pretty_env_logger;
use futures::future::FutureResult; use futures::future::FutureResult;
use hyper::{Get, Post, StatusCode}; use hyper::{Body, Method, Request, Response, StatusCode};
use hyper::header::ContentLength; use hyper::server::{Http, Service};
use hyper::server::{Http, Service, Request, Response};
static INDEX: &'static [u8] = b"Try POST /echo"; static INDEX: &'static [u8] = b"Try POST /echo";
struct Echo; struct Echo;
impl Service for Echo { impl Service for Echo {
type Request = Request; type Request = Request<Body>;
type Response = Response; type Response = Response<Body>;
type Error = hyper::Error; type Error = hyper::Error;
type Future = FutureResult<Response, hyper::Error>; type Future = FutureResult<Self::Response, Self::Error>;
fn call(&self, req: Request) -> Self::Future { fn call(&self, req: Self::Request) -> Self::Future {
futures::future::ok(match (req.method(), req.path()) { futures::future::ok(match (req.method(), req.uri().path()) {
(&Get, "/") | (&Get, "/echo") => { (&Method::GET, "/") | (&Method::POST, "/") => {
Response::new() Response::new(INDEX.into())
.with_header(ContentLength(INDEX.len() as u64))
.with_body(INDEX)
}, },
(&Post, "/echo") => { (&Method::POST, "/echo") => {
let mut res = Response::new(); Response::new(req.into_parts().1)
if let Some(len) = req.headers().get::<ContentLength>() {
res.headers_mut().set(len.clone());
}
res.with_body(req.body())
}, },
_ => { _ => {
Response::new() let mut res = Response::new(Body::empty());
.with_status(StatusCode::NotFound) *res.status_mut() = StatusCode::NOT_FOUND;
res
} }
}) })
} }

View File

@@ -6,10 +6,9 @@ extern crate tokio_core;
use futures::{Future, Stream}; 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::error::Error;
use hyper::header::ContentLength; use hyper::server::{Http, Service};
use hyper::server::{Http, Service, Request, Response};
#[allow(unused)] #[allow(unused)]
use std::ascii::AsciiExt; use std::ascii::AsciiExt;
@@ -24,50 +23,51 @@ pub type ResponseStream = Box<Stream<Item=Chunk, Error=Error>>;
struct ResponseExamples(tokio_core::reactor::Handle); struct ResponseExamples(tokio_core::reactor::Handle);
impl Service for ResponseExamples { impl Service for ResponseExamples {
type Request = Request; type Request = Request<Body>;
type Response = Response<ResponseStream>; type Response = Response<ResponseStream>;
type Error = hyper::Error; type Error = hyper::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>; type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn call(&self, req: Request) -> Self::Future { fn call(&self, req: Self::Request) -> Self::Future {
match (req.method(), req.path()) { match (req.method(), req.uri().path()) {
(&Get, "/") | (&Get, "/index.html") => { (&Method::GET, "/") | (&Method::GET, "/index.html") => {
let body: ResponseStream = Box::new(Body::from(INDEX)); let body: ResponseStream = Box::new(Body::from(INDEX));
Box::new(futures::future::ok(Response::new() Box::new(futures::future::ok(Response::new(body)))
.with_header(ContentLength(INDEX.len() as u64))
.with_body(body)))
}, },
(&Get, "/test.html") => { (&Method::GET, "/test.html") => {
// Run a web query against the web api below // Run a web query against the web api below
let client = Client::configure().build(&self.0); let client = Client::configure().build(&self.0);
let mut req = Request::new(Post, URL.parse().unwrap()); let req = Request::builder()
req.set_body(LOWERCASE); .method(Method::POST)
.uri(URL)
.body(LOWERCASE.into())
.unwrap();
let web_res_future = client.request(req); let web_res_future = client.request(req);
Box::new(web_res_future.map(|web_res| { 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: '{:?}'<br>after: '{:?}'", Chunk::from(format!("before: '{:?}'<br>after: '{:?}'",
std::str::from_utf8(LOWERCASE).unwrap(), std::str::from_utf8(LOWERCASE).unwrap(),
std::str::from_utf8(&b).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. // 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()) let upper = chunk.iter().map(|byte| byte.to_ascii_uppercase())
.collect::<Vec<u8>>(); .collect::<Vec<u8>>();
Chunk::from(upper) 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)); let body: ResponseStream = Box::new(Body::from(NOTFOUND));
Box::new(futures::future::ok(Response::new() Box::new(futures::future::ok(Response::builder()
.with_status(StatusCode::NotFound) .status(StatusCode::NOT_FOUND)
.with_header(ContentLength(NOTFOUND.len() as u64)) .body(body)
.with_body(body))) .unwrap()))
} }
} }
} }

View File

@@ -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<C, B = Body> {
inner: Client<C, B>
}
pub(super) fn client<C, B>(client: Client<C, B>) -> CompatClient<C, B> {
CompatClient { inner: client }
}
impl<C, B> Service for CompatClient<C, B>
where C: Connect,
B: Stream<Error=Error> + 'static,
B::Item: AsRef<[u8]>,
{
type Request = http::Request<B>;
type Response = http::Response<Body>;
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<Body>;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Error> {
self.inner.poll()
.map(|a| a.map(|r| r.into()))
}
}

View File

@@ -16,7 +16,8 @@ use futures::future::{self, Either};
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use proto; use proto;
use super::{dispatch, Request, Response}; use super::dispatch;
use {Body, Request, Response, StatusCode};
/// Returns a `Handshake` future over some IO. /// Returns a `Handshake` future over some IO.
/// ///
@@ -31,7 +32,7 @@ where
/// The sender side of an established connection. /// The sender side of an established connection.
pub struct SendRequest<B> { pub struct SendRequest<B> {
dispatch: dispatch::Sender<proto::dispatch::ClientMsg<B>, ::Response>, dispatch: dispatch::Sender<proto::dispatch::ClientMsg<B>, Response<Body>>,
} }
@@ -79,7 +80,7 @@ pub struct Handshake<T, B> {
pub struct ResponseFuture { pub struct ResponseFuture {
// for now, a Box is used to hide away the internal `B` // for now, a Box is used to hide away the internal `B`
// that can be returned if canceled // that can be returned if canceled
inner: Box<Future<Item=Response, Error=::Error> + Send>, inner: Box<Future<Item=Response<Body>, Error=::Error> + Send>,
} }
/// Deconstructed parts of a `Connection`. /// Deconstructed parts of a `Connection`.
@@ -158,17 +159,20 @@ where
/// ``` /// ```
/// # extern crate futures; /// # extern crate futures;
/// # extern crate hyper; /// # extern crate hyper;
/// # extern crate http;
/// # use http::header::HOST;
/// # use hyper::client::conn::SendRequest; /// # use hyper::client::conn::SendRequest;
/// # use hyper::Body; /// # use hyper::Body;
/// use futures::Future; /// use futures::Future;
/// use hyper::{Method, Request}; /// use hyper::Request;
/// use hyper::header::Host;
/// ///
/// # fn doc(mut tx: SendRequest<Body>) { /// # fn doc(mut tx: SendRequest<Body>) {
/// // build a Request /// // build a Request
/// let path = "/foo/bar".parse().expect("valid path"); /// let req = Request::builder()
/// let mut req = Request::new(Method::Get, path); /// .uri("/foo/bar")
/// req.headers_mut().set(Host::new("hyper.rs", None)); /// .header(HOST, "hyper.rs")
/// .body(Body::empty())
/// .unwrap();
/// ///
/// // send it and get a future back /// // send it and get a future back
/// let fut = tx.send_request(req) /// let fut = tx.send_request(req)
@@ -180,7 +184,8 @@ where
/// # } /// # }
/// # fn main() {} /// # fn main() {}
/// ``` /// ```
pub fn send_request(&mut self, mut req: Request<B>) -> ResponseFuture { pub fn send_request(&mut self, req: Request<B>) -> ResponseFuture {
/* TODO?
// The Connection API does less things automatically than the Client // The Connection API does less things automatically than the Client
// API does. For instance, right here, we always assume set_proxy, so // 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. // 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 // It's important that this method isn't called directly from the
// `Client`, so that `set_proxy` there is still respected. // `Client`, so that `set_proxy` there is still respected.
req.set_proxy(true); req.set_proxy(true);
*/
let (head, body) = proto::request::split(req); let inner = match self.dispatch.send(req) {
let inner = match self.dispatch.send((head, body)) {
Ok(rx) => { Ok(rx) => {
Either::A(rx.then(move |res| { Either::A(rx.then(move |res| {
match res { match res {
@@ -210,15 +215,15 @@ where
Either::B(future::err(err)) Either::B(future::err(err))
} }
}; };
ResponseFuture { ResponseFuture {
inner: Box::new(inner), inner: Box::new(inner),
} }
} }
//TODO: replace with `impl Future` when stable //TODO: replace with `impl Future` when stable
pub(crate) fn send_request_retryable(&mut self, req: Request<B>) -> Box<Future<Item=Response, Error=(::Error, Option<(::proto::RequestHead, Option<B>)>)>> { pub(crate) fn send_request_retryable(&mut self, req: Request<B>) -> Box<Future<Item=Response<Body>, Error=(::Error, Option<Request<B>>)>> {
let (head, body) = proto::request::split(req); let inner = match self.dispatch.try_send(req) {
let inner = match self.dispatch.try_send((head, body)) {
Ok(rx) => { Ok(rx) => {
Either::A(rx.then(move |res| { Either::A(rx.then(move |res| {
match res { match res {
@@ -418,7 +423,7 @@ where
B: Stream<Error=::Error> + 'static, B: Stream<Error=::Error> + 'static,
B::Item: AsRef<[u8]>, B::Item: AsRef<[u8]>,
R: proto::Http1Transaction< R: proto::Http1Transaction<
Incoming=proto::RawStatus, Incoming=StatusCode,
Outgoing=proto::RequestLine, Outgoing=proto::RequestLine,
>, >,
{ {
@@ -451,7 +456,7 @@ where
// ===== impl ResponseFuture // ===== impl ResponseFuture
impl Future for ResponseFuture { impl Future for ResponseFuture {
type Item = Response; type Item = Response<Body>;
type Error = ::Error; type Error = ::Error;
#[inline] #[inline]
@@ -497,3 +502,4 @@ impl AssertSendSync for Builder {}
#[doc(hidden)] #[doc(hidden)]
impl AssertSend for ResponseFuture {} impl AssertSend for ResponseFuture {}

View File

@@ -9,11 +9,12 @@ use futures::{Future, Poll, Async};
use futures::future::{Executor, ExecuteError}; use futures::future::{Executor, ExecuteError};
use futures::sync::oneshot; use futures::sync::oneshot;
use futures_cpupool::{Builder as CpuPoolBuilder}; use futures_cpupool::{Builder as CpuPoolBuilder};
use http::Uri;
use http::uri::Scheme;
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use tokio::reactor::Handle; use tokio::reactor::Handle;
use tokio::net::{TcpStream, TcpStreamNew}; use tokio::net::{TcpStream, TcpStreamNew};
use tokio_service::Service; use tokio_service::Service;
use Uri;
use super::dns; use super::dns;
@@ -118,10 +119,10 @@ impl Service for HttpConnector {
trace!("Http::connect({:?})", uri); trace!("Http::connect({:?})", uri);
if self.enforce_http { if self.enforce_http {
if uri.scheme() != Some("http") { if uri.scheme_part() != Some(&Scheme::HTTP) {
return invalid_url(InvalidUrl::NotHttp, &self.handle); 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); return invalid_url(InvalidUrl::MissingScheme, &self.handle);
} }
@@ -131,10 +132,7 @@ impl Service for HttpConnector {
}; };
let port = match uri.port() { let port = match uri.port() {
Some(port) => port, Some(port) => port,
None => match uri.scheme() { None => if uri.scheme_part() == Some(&Scheme::HTTPS) { 443 } else { 80 },
Some("https") => 443,
_ => 80,
},
}; };
HttpConnecting { HttpConnecting {

View File

@@ -9,21 +9,14 @@ use std::time::Duration;
use futures::{Async, Future, Poll, Stream}; use futures::{Async, Future, Poll, Stream};
use futures::future::{self, Executor}; use futures::future::{self, Executor};
#[cfg(feature = "compat")] use http::{Method, Request, Response, Uri, Version};
use http; use http::header::{Entry, HeaderValue, HOST};
use tokio::reactor::Handle; use tokio::reactor::Handle;
pub use tokio_service::Service; pub use tokio_service::Service;
use header::{Host}; use proto::{self, Body};
use proto;
use proto::request;
use method::Method;
use self::pool::Pool; 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}; pub use self::connect::{HttpConnector, Connect};
use self::background::{bg, Background}; use self::background::{bg, Background};
@@ -34,8 +27,6 @@ mod connect;
pub(crate) mod dispatch; pub(crate) mod dispatch;
mod dns; mod dns;
mod pool; mod pool;
#[cfg(feature = "compat")]
pub mod compat;
mod signal; mod signal;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@@ -113,14 +104,29 @@ where C: Connect,
B: Stream<Error=::Error> + 'static, B: Stream<Error=::Error> + 'static,
B::Item: AsRef<[u8]>, B::Item: AsRef<[u8]>,
{ {
/// Send a GET Request using this Client.
#[inline] /// Send a `GET` request to the supplied `Uri`.
pub fn get(&self, url: Uri) -> FutureResponse { ///
self.request(Request::new(Method::Get, url)) /// # 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. /// Send a constructed Request using this Client.
#[inline]
pub fn request(&self, mut req: Request<B>) -> FutureResponse { pub fn request(&self, mut req: Request<B>) -> FutureResponse {
// TODO(0.12): do this at construction time. // TODO(0.12): do this at construction time.
// //
@@ -131,23 +137,25 @@ where C: Connect,
self.schedule_pool_timer(); self.schedule_pool_timer();
match req.version() { match req.version() {
HttpVersion::Http10 | Version::HTTP_10 |
HttpVersion::Http11 => (), Version::HTTP_11 => (),
other => { other => {
error!("Request has unsupported version \"{}\"", other); error!("Request has unsupported version \"{:?}\"", other);
return FutureResponse(Box::new(future::err(::Error::Version))); 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"); debug!("Client does not support CONNECT requests");
return FutureResponse(Box::new(future::err(::Error::Method))); return FutureResponse(Box::new(future::err(::Error::Method)));
} }
let domain = match uri::scheme_and_authority(req.uri()) { let uri = req.uri().clone();
Some(uri) => uri, let domain = match (uri.scheme_part(), uri.authority_part()) {
None => { (Some(scheme), Some(auth)) => {
debug!("request uri does not include scheme and authority"); format!("{}://{}", scheme, auth)
}
_ => {
return FutureResponse(Box::new(future::err(::Error::Io( return FutureResponse(Box::new(future::err(::Error::Io(
io::Error::new( io::Error::new(
io::ErrorKind::InvalidInput, io::ErrorKind::InvalidInput,
@@ -156,45 +164,51 @@ where C: Connect,
)))); ))));
} }
}; };
if self.set_host && !req.headers().has::<Host>() {
let host = Host::new( if self.set_host {
domain.host().expect("authority implies host").to_owned(), if let Entry::Vacant(entry) = req.headers_mut().entry(HOST).expect("HOST is always valid header name") {
domain.port(), let hostname = uri.host().expect("authority implies host");
); let host = if let Some(port) = uri.port() {
req.headers_mut().set_pos(0, host); 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 client = self.clone();
let is_proxy = req.is_proxy(); //TODO: let is_proxy = req.is_proxy();
let uri = req.uri().clone(); //let uri = req.uri().clone();
let fut = RetryableSendRequest { let fut = RetryableSendRequest {
client: client, client: client,
future: self.send_request(req, &domain), future: self.send_request(req, &domain),
domain: domain, domain: domain,
is_proxy: is_proxy, //is_proxy: is_proxy,
uri: uri, //uri: uri,
}; };
FutureResponse(Box::new(fut)) FutureResponse(Box::new(fut))
} }
/// Send an `http::Request` using this Client.
#[inline]
#[cfg(feature = "compat")]
pub fn request_compat(&self, req: http::Request<B>) -> 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<C, B> {
self::compat::client(self)
}
//TODO: replace with `impl Future` when stable //TODO: replace with `impl Future` when stable
fn send_request(&self, req: Request<B>, domain: &Uri) -> Box<Future<Item=Response, Error=ClientError<B>>> { fn send_request(&self, mut req: Request<B>, domain: &str) -> Box<Future<Item=Response<Body>, Error=ClientError<B>>> {
//fn send_request(&self, req: Request<B>, domain: &Uri) -> Box<Future<Item=Response, Error=::Error>> {
let url = req.uri().clone(); 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 connect = {
let executor = self.executor.clone(); let executor = self.executor.clone();
let pool = self.pool.clone(); let pool = self.pool.clone();
@@ -228,7 +242,6 @@ where C: Connect,
ClientError::Normal(e) ClientError::Normal(e)
}); });
let executor = self.executor.clone(); let executor = self.executor.clone();
let resp = race.and_then(move |mut pooled| { let resp = race.and_then(move |mut pooled| {
let conn_reused = pooled.is_reused(); let conn_reused = pooled.is_reused();
@@ -284,7 +297,7 @@ where C: Connect,
B::Item: AsRef<[u8]>, B::Item: AsRef<[u8]>,
{ {
type Request = Request<B>; type Request = Request<B>;
type Response = Response; type Response = Response<Body>;
type Error = ::Error; type Error = ::Error;
type Future = FutureResponse; type Future = FutureResponse;
@@ -315,7 +328,7 @@ impl<C, B> fmt::Debug for Client<C, B> {
/// A `Future` that will resolve to an HTTP Response. /// A `Future` that will resolve to an HTTP Response.
#[must_use = "futures do nothing unless polled"] #[must_use = "futures do nothing unless polled"]
pub struct FutureResponse(Box<Future<Item=Response, Error=::Error> + 'static>); pub struct FutureResponse(Box<Future<Item=Response<Body>, Error=::Error> + 'static>);
impl fmt::Debug for FutureResponse { impl fmt::Debug for FutureResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -324,7 +337,7 @@ impl fmt::Debug for FutureResponse {
} }
impl Future for FutureResponse { impl Future for FutureResponse {
type Item = Response; type Item = Response<Body>;
type Error = ::Error; type Error = ::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
@@ -334,10 +347,10 @@ impl Future for FutureResponse {
struct RetryableSendRequest<C, B> { struct RetryableSendRequest<C, B> {
client: Client<C, B>, client: Client<C, B>,
domain: Uri, domain: String,
future: Box<Future<Item=Response, Error=ClientError<B>>>, future: Box<Future<Item=Response<Body>, Error=ClientError<B>>>,
is_proxy: bool, //is_proxy: bool,
uri: Uri, //uri: Uri,
} }
impl<C, B> Future for RetryableSendRequest<C, B> impl<C, B> Future for RetryableSendRequest<C, B>
@@ -346,7 +359,7 @@ where
B: Stream<Error=::Error> + 'static, B: Stream<Error=::Error> + 'static,
B::Item: AsRef<[u8]>, B::Item: AsRef<[u8]>,
{ {
type Item = Response; type Item = Response<Body>;
type Error = ::Error; type Error = ::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
@@ -367,9 +380,6 @@ where
} }
trace!("unstarted request canceled, trying again (reason={:?})", reason); 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); self.future = self.client.send_request(req, &self.domain);
} }
} }
@@ -394,7 +404,7 @@ pub(crate) enum ClientError<B> {
Normal(::Error), Normal(::Error),
Canceled { Canceled {
connection_reused: bool, connection_reused: bool,
req: (::proto::RequestHead, Option<B>), req: Request<B>,
reason: ::Error, reason: ::Error,
} }
} }

View File

@@ -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(|| { let srv1 = poll_fn(|| {
try_ready!(sock1.read(&mut [0u8; 512])); 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")); 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); 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| { .map(|res| {
assert_eq!(res.status().as_u16(), 222); 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(|| { let srv1 = poll_fn(|| {
try_ready!(sock1.read(&mut [0u8; 512])); 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")); 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"); 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 mut sock1 = Some(sock1);
let srv2 = poll_fn(|| { let srv2 = poll_fn(|| {
// We purposefully keep the socket open until the client // We purposefully keep the socket open until the client

View File

@@ -1,6 +1,2 @@
pub use self::str::ByteStr;
mod str;
#[derive(Debug)] #[derive(Debug)]
pub enum Never {} pub enum Never {}

View File

@@ -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))
}
}

View File

@@ -6,13 +6,12 @@ use std::str::Utf8Error;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
use httparse; use httparse;
use http;
pub use uri::UriError;
use self::Error::{ use self::Error::{
Method, Method,
Uri,
Version, Version,
Uri,
Header, Header,
Status, Status,
Timeout, Timeout,
@@ -33,10 +32,10 @@ pub type Result<T> = ::std::result::Result<T, Error>;
pub enum Error { pub enum Error {
/// An invalid `Method`, such as `GE,T`. /// An invalid `Method`, such as `GE,T`.
Method, Method,
/// An invalid `Uri`, such as `exam ple.domain`.
Uri(UriError),
/// An invalid `HttpVersion`, such as `HTP/1.1` /// An invalid `HttpVersion`, such as `HTP/1.1`
Version, Version,
/// Uri Errors
Uri,
/// An invalid `Header`. /// An invalid `Header`.
Header, Header,
/// A message head is too large to be reasonable. /// A message head is too large to be reasonable.
@@ -105,7 +104,6 @@ impl fmt::Debug for Void {
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
Uri(ref e) => fmt::Display::fmt(e, f),
Io(ref e) => fmt::Display::fmt(e, f), Io(ref e) => fmt::Display::fmt(e, f),
Utf8(ref e) => fmt::Display::fmt(e, f), Utf8(ref e) => fmt::Display::fmt(e, f),
ref e => f.write_str(e.description()), ref e => f.write_str(e.description()),
@@ -118,6 +116,7 @@ impl StdError for Error {
match *self { match *self {
Method => "invalid Method specified", Method => "invalid Method specified",
Version => "invalid HTTP version specified", Version => "invalid HTTP version specified",
Uri => "invalid URI",
Header => "invalid Header provided", Header => "invalid Header provided",
TooLarge => "message head is too large", TooLarge => "message head is too large",
Status => "invalid Status provided", Status => "invalid Status provided",
@@ -126,7 +125,6 @@ impl StdError for Error {
Upgrade => "unsupported protocol upgrade", Upgrade => "unsupported protocol upgrade",
Closed => "connection is closed", Closed => "connection is closed",
Cancel(ref e) => e.description(), Cancel(ref e) => e.description(),
Uri(ref e) => e.description(),
Io(ref e) => e.description(), Io(ref e) => e.description(),
Utf8(ref e) => e.description(), Utf8(ref e) => e.description(),
Error::__Nonexhaustive(..) => unreachable!(), Error::__Nonexhaustive(..) => unreachable!(),
@@ -136,7 +134,6 @@ impl StdError for Error {
fn cause(&self) -> Option<&StdError> { fn cause(&self) -> Option<&StdError> {
match *self { match *self {
Io(ref error) => Some(error), Io(ref error) => Some(error),
Uri(ref error) => Some(error),
Utf8(ref error) => Some(error), Utf8(ref error) => Some(error),
Cancel(ref e) => e.cause.as_ref().map(|e| &**e as &StdError), Cancel(ref e) => e.cause.as_ref().map(|e| &**e as &StdError),
Error::__Nonexhaustive(..) => unreachable!(), Error::__Nonexhaustive(..) => unreachable!(),
@@ -145,12 +142,6 @@ impl StdError for Error {
} }
} }
impl From<UriError> for Error {
fn from(err: UriError) -> Error {
Uri(err)
}
}
impl From<IoError> for Error { impl From<IoError> for Error {
fn from(err: IoError) -> Error { fn from(err: IoError) -> Error {
Io(err) Io(err)
@@ -183,6 +174,18 @@ impl From<httparse::Error> for Error {
} }
} }
impl From<http::method::InvalidMethod> for Error {
fn from(_: http::method::InvalidMethod) -> Error {
Error::Method
}
}
impl From<http::uri::InvalidUriBytes> for Error {
fn from(_: http::uri::InvalidUriBytes) -> Error {
Error::Uri
}
}
#[doc(hidden)] #[doc(hidden)]
trait AssertSendSync: Send + Sync + 'static {} trait AssertSendSync: Send + Sync + 'static {}
#[doc(hidden)] #[doc(hidden)]

View File

@@ -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<Mime>)+
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()] });

View File

@@ -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<Charset>)+
test_accept_charset {
/// Testcase from RFC
test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]);
}
}

View File

@@ -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<Encoding>)*
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"]);
}
}

View File

@@ -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 = <language-range, see [RFC4647], Section 2.1>
/// ```
///
/// # 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<LanguageTag>)+
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()] });

View File

@@ -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<Self> {
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),
}
}
}

View File

@@ -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<AccessControlAllowCredentials> {
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);
}

View File

@@ -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<String>)*
test_access_control_allow_headers {
test_header!(test1, vec![b"accept-language, date"]);
}
}

View File

@@ -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"]);
}
}

View File

@@ -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<AccessControlAllowOrigin> {
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/"]);
}

View File

@@ -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<String>)*
test_access_control_expose_headers {
test_header!(test1, vec![b"etag, content-length"]);
}
}

View File

@@ -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"]);
}
}

View File

@@ -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<String>)*
test_access_control_request_headers {
test_header!(test1, vec![b"accept-language, date"]);
}
}

View File

@@ -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"]);
}
}

View File

@@ -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::<Method>::new())));
}
}
bench_header!(bench,
Allow, { vec![b"OPTIONS,GET,PUT,POST,DELETE,HEAD,TRACE,CONNECT,PATCH,fOObAr".to_vec()] });

View File

@@ -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<S: Scheme>(pub S);
impl<S: Scheme> Deref for Authorization<S> {
type Target = S;
fn deref(&self) -> &S {
&self.0
}
}
impl<S: Scheme> DerefMut for Authorization<S> {
fn deref_mut(&mut self) -> &mut S {
&mut self.0
}
}
impl<S: Scheme + Any> Header for Authorization<S> where <S as FromStr>::Err: 'static {
fn header_name() -> &'static str {
static NAME: &'static str = "Authorization";
NAME
}
fn parse_header(raw: &Raw) -> ::Result<Authorization<S>> {
if let Some(line) = raw.one() {
let header = try!(from_utf8(line));
if let Some(scheme) = <S as Scheme>::scheme() {
if header.starts_with(scheme) && header.len() > scheme.len() + 1 {
match header[scheme.len() + 1..].parse::<S>().map(Authorization) {
Ok(h) => Ok(h),
Err(_) => Err(::Error::Header)
}
} else {
Err(::Error::Header)
}
} else {
match header.parse::<S>().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<S: Scheme> fmt::Display for Authorization<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(scheme) = <S as 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<String>
}
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<Basic> {
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<Bearer> {
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<String> = 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<Basic> = 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<Basic> = 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<Bearer> = Header::parse_header(
&b"Bearer fpKL54jvWmEGVoRdCNjG".as_ref().into()).unwrap();
assert_eq!(auth.0.token, "fpKL54jvWmEGVoRdCNjG");
}
}
bench_header!(raw, Authorization<String>, { vec![b"foo bar baz".to_vec()] });
bench_header!(basic, Authorization<Basic>, { vec![b"Basic QWxhZGRpbjpuIHNlc2FtZQ==".to_vec()] });
bench_header!(bearer, Authorization<Bearer>, { vec![b"Bearer fpKL54jvWmEGVoRdCNjG".to_vec()] });

View File

@@ -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<CacheDirective>);
__hyper__deref!(CacheControl => Vec<CacheDirective>);
//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<CacheControl> {
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<String>)
}
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<<u32 as FromStr>::Err>;
fn from_str(s: &str) -> Result<CacheDirective, Option<<u32 as FromStr>::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<CacheControl> = 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()] });

View File

@@ -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<String>),
}
impl FromStr for ConnectionOption {
type Err = ();
fn from_str(s: &str) -> Result<ConnectionOption, ()> {
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<u8>) -> 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()));
}
}

View File

@@ -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<LanguageTag>, Vec<u8>),
/// 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 = <the characters in token, followed by "*">
/// ```
///
/// # 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<DispositionParam>,
}
impl Header for ContentDisposition {
fn header_name() -> &'static str {
static NAME: &'static str = "Content-Disposition";
NAME
}
fn parse_header(raw: &Raw) -> ::Result<ContentDisposition> {
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);
}
}

View File

@@ -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()] });

View File

@@ -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<LanguageTag>)+
test_content_language {
test_header!(test1, vec![b"da"]);
test_header!(test2, vec![b"mi, en"]);
}
}

View File

@@ -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<ContentLength> {
// 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()] });

View File

@@ -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"]);
}
}

View File

@@ -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::<ContentRange>);
test_header!(test_only_unit,
vec![b"bytes"],
None::<ContentRange>);
test_header!(test_end_less_than_start,
vec![b"bytes 499-0/500"],
None::<ContentRange>);
test_header!(test_blank,
vec![b""],
None::<ContentRange>);
test_header!(test_bytes_many_spaces,
vec![b"bytes 1-2/500 3"],
None::<ContentRange>);
test_header!(test_bytes_many_slashes,
vec![b"bytes 1-2/500/600"],
None::<ContentRange>);
test_header!(test_bytes_many_dashes,
vec![b"bytes 1-2-3/500"],
None::<ContentRange>);
}
}
/// 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<u64>
},
/// 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<Self> {
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)
}
}
}
}

View File

@@ -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()] });

View File

@@ -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>, 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<K, V>(&mut self, key: K, value: V)
where K: Into<Cow<'static, str>>,
V: Into<Cow<'static, str>>
{
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<K, V>(&mut self, key: K, value: V)
where K: Into<Cow<'static, str>>,
V: Into<Cow<'static, str>>
{
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<Cookie> {
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::Item> {
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()]
});

View File

@@ -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()] });

View File

@@ -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::<ETag>);
test_header!(test10,
vec![b"w/\"the-first-w-is-case-sensitive\""],
None::<ETag>);
test_header!(test11,
vec![b""],
None::<ETag>);
test_header!(test12,
vec![b"\"unmatched-dquotes1"],
None::<ETag>);
test_header!(test13,
vec![b"unmatched-dquotes2\""],
None::<ETag>);
test_header!(test14,
vec![b"matched-\"dquotes\""],
None::<ETag>);
test_header!(test15,
vec![b"\""],
None::<ETag>);
}
}
bench_header!(bench, ETag, { vec![b"W/\"nonemptytag\"".to_vec()] });

View File

@@ -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<Expect> {
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")
}
}

View File

@@ -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()] });

View File

@@ -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 = <mailbox, see [RFC5322], Section 3.4>
/// ```
///
/// # 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"]);
}
}

View File

@@ -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<u16>
}
impl Host {
/// Create a `Host` header, providing the hostname and optional port.
pub fn new<H, P>(hostname: H, port: P) -> Host
where H: Into<Cow<'static, str>>,
P: Into<Option<u16>>
{
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<u16> {
self.port
}
}
impl Header for Host {
fn header_name() -> &'static str {
static NAME: &'static str = "Host";
NAME
}
fn parse_header(raw: &Raw) -> ::Result<Host> {
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<Host> {
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()] });

View File

@@ -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()] });

View File

@@ -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()] });

View File

@@ -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<IfNoneMatch>;
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<EntityTag> = 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()] });

View File

@@ -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<IfRange> {
let etag: ::Result<EntityTag> = header::parsing::from_one_raw_str(raw);
if let Ok(etag) = etag {
return Ok(IfRange::EntityTag(etag));
}
let date: ::Result<HttpDate> = 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::<IfRange>);
}

View File

@@ -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()] });

View File

@@ -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<Self> {
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())));
});

View File

@@ -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()] });

File diff suppressed because it is too large Load Diff

View File

@@ -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()] });

View File

@@ -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<Vec<u8>> = 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<String> = result
.to_ascii_lowercase()
.split(' ')
.map(|x| x.to_owned())
.collect();
let expected_cmp: Vec<String> = 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<Vec<u8>> = $raw.iter().map(|x| x.to_vec()).collect();
let a = a.into();
let val = HeaderField::parse_header(&a);
let typed: Option<HeaderField> = $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<Self> {
$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<Self> {
$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<Self> {
$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<Self> {
$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<I: Into<::std::borrow::Cow<'static,$value>>>(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<Self> {
$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<Self> {
// 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;

View File

@@ -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<S: Into<Cow<'static,str>>, H: Into<Cow<'static,str>>>(scheme: S, hostname: H, port: Option<u16>) -> 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<Origin> {
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<Origin> {
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()] });

View File

@@ -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<Pragma> {
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<Pragma> = Header::parse_header(&"".into());
assert_eq!(e.ok(), None);
}

View File

@@ -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<Preference>);
__hyper__deref!(Prefer => Vec<Preference>);
impl Header for Prefer {
fn header_name() -> &'static str {
static NAME: &'static str = "Prefer";
NAME
}
fn parse_header(raw: &Raw) -> ::Result<Prefer> {
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<<u32 as FromStr>::Err>;
fn from_str(s: &str) -> Result<Preference, Option<<u32 as FromStr>::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<Prefer> = 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()] });

View File

@@ -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<Preference>);
__hyper__deref!(PreferenceApplied => Vec<Preference>);
impl Header for PreferenceApplied {
fn header_name() -> &'static str {
static NAME: &'static str = "Preference-Applied";
NAME
}
fn parse_header(raw: &Raw) -> ::Result<PreferenceApplied> {
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()] });

View File

@@ -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<S: Scheme>(pub S);
impl<S: Scheme> Deref for ProxyAuthorization<S> {
type Target = S;
fn deref(&self) -> &S {
&self.0
}
}
impl<S: Scheme> DerefMut for ProxyAuthorization<S> {
fn deref_mut(&mut self) -> &mut S {
&mut self.0
}
}
impl<S: Scheme + Any> Header for ProxyAuthorization<S> where <S as FromStr>::Err: 'static {
fn header_name() -> &'static str {
static NAME: &'static str = "Proxy-Authorization";
NAME
}
fn parse_header(raw: &Raw) -> ::Result<ProxyAuthorization<S>> {
if let Some(line) = raw.one() {
let header = try!(from_utf8(line));
if let Some(scheme) = <S as Scheme>::scheme() {
if header.starts_with(scheme) && header.len() > scheme.len() + 1 {
match header[scheme.len() + 1..].parse::<S>().map(ProxyAuthorization) {
Ok(h) => Ok(h),
Err(_) => Err(::Error::Header)
}
} else {
Err(::Error::Header)
}
} else {
match header.parse::<S>().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<S: Scheme> fmt::Display for ProxyAuthorization<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(scheme) = <S as 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<String> = 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<Basic> = 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<Basic> = 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<Bearer> = 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<String>, { vec![b"foo bar baz".to_vec()] });
bench_header!(basic, ProxyAuthorization<Basic>, { vec![b"Basic QWxhZGRpbjpuIHNlc2FtZQ==".to_vec()] });
bench_header!(bearer, ProxyAuthorization<Bearer>, { vec![b"Bearer fpKL54jvWmEGVoRdCNjG".to_vec()] });
}

View File

@@ -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<ByteRangeSpec>),
/// 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<Range> {
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<ByteRangeSpec> {
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<T: FromStr>(s: &str) -> Vec<T> {
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<Range> {
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<Range> = Header::parse_header(&"bytes=1-a,-".into());
assert_eq!(r.ok(), None);
let r: ::Result<Range> = Header::parse_header(&"bytes=1-2-3".into());
assert_eq!(r.ok(), None);
let r: ::Result<Range> = Header::parse_header(&"abc".into());
assert_eq!(r.ok(), None);
let r: ::Result<Range> = Header::parse_header(&"bytes=1-100=".into());
assert_eq!(r.ok(), None);
let r: ::Result<Range> = Header::parse_header(&"bytes=".into());
assert_eq!(r.ok(), None);
let r: ::Result<Range> = Header::parse_header(&"custom=".into());
assert_eq!(r.ok(), None);
let r: ::Result<Range> = 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()]});

View File

@@ -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()] });

View File

@@ -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<ReferrerPolicy> {
use self::ReferrerPolicy::*;
// See https://www.w3.org/TR/referrer-policy/#determine-policy-for-token
let headers: Vec<String> = 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<ReferrerPolicy> = 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);
}

View File

@@ -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<RetryAfter> {
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::<HttpDate>() {
return Ok(RetryAfter::DateTime(datetime))
}
if let Ok(seconds) = utf8_str.parse::<u64>() {
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::<HttpDate>().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::<HttpDate>().unwrap();
assert_eq!(retry_after, RetryAfter::DateTime(expected));
}
}

View File

@@ -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()] });

View File

@@ -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 = <token, defined in [RFC2616], Section 2.2>
///
/// 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 = <rfc1123-date, defined in [RFC2616], Section 3.3.1>
/// 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 = <subdomain>
/// ; defined in [RFC1034], Section 3.5, as
/// ; enhanced by [RFC1123], Section 2.1
/// path-av = "Path=" path-value
/// path-value = <any CHAR except CTLs or ";">
/// secure-av = "Secure"
/// httponly-av = "HttpOnly"
/// extension-av = <any CHAR except CTLs or ";">
/// ```
///
/// # 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<String>);
__hyper__deref!(SetCookie => Vec<String>);
impl Header for SetCookie {
fn header_name() -> &'static str {
static NAME: &'static str = "Set-Cookie";
NAME
}
fn parse_header(raw: &Raw) -> ::Result<SetCookie> {
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");
}

View File

@@ -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<StrictTransportSecurity> {
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<StrictTransportSecurity> {
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<StrictTransportSecurity> = 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<StrictTransportSecurity> = Header::parse_header(&"includeSubDomains".into());
assert!(h.is_err());
}
#[test]
fn test_parse_max_age_nan() {
let h: ::Result<StrictTransportSecurity> = 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()] });

View File

@@ -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<Encoding>)*
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""]);
}
}

View File

@@ -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()] });

View File

@@ -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<Upgrade> = 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<ProtocolName, ()> {
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<String>,
}
impl Protocol {
/// Creates a new Protocol with the given name and version
pub fn new(name: ProtocolName, version: Option<String>) -> Protocol {
Protocol { name: name, version: version }
}
}
impl FromStr for Protocol {
type Err =();
fn from_str(s: &str) -> Result<Protocol, ()> {
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()] });

View File

@@ -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")));
}
}

View File

@@ -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<String>)+}
test_vary {
test_header!(test1, vec![b"accept-encoding, accept-language"]);
#[test]
fn test2() {
let mut vary: ::Result<Vary>;
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(),])));
}
}
}

View File

@@ -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::<HttpDate>().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<HttpDate>
}
impl Header for Warning {
fn header_name() -> &'static str {
static NAME: &'static str = "Warning";
NAME
}
fn parse_header(raw: &Raw) -> ::Result<Warning> {
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<Warning> {
let mut warning_split = s.split_whitespace();
let code = match warning_split.next() {
Some(c) => match c.parse::<u16>() {
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::<HttpDate>().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::<HttpDate>().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::<HttpDate>().ok()
}));
}
}

View File

@@ -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<T>(UnsafeCell<Option<T>>);
impl<T> OptCell<T> {
#[inline]
pub fn new(val: Option<T>) -> OptCell<T> {
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<T> Deref for OptCell<T> {
type Target = Option<T>;
#[inline]
fn deref(&self) -> &Option<T> {
unsafe { &*self.0.get() }
}
}
impl<T: Clone> Clone for OptCell<T> {
#[inline]
fn clone(&self) -> OptCell<T> {
OptCell::new((**self).clone())
}
}
pub struct PtrMapCell<V: ?Sized>(UnsafeCell<PtrMap<Box<V>>>);
#[derive(Clone, Debug)]
enum PtrMap<T> {
Empty,
One(TypeId, T),
Many(HashMap<TypeId, T>)
}
impl<V: ?Sized + Any + 'static> PtrMapCell<V> {
#[inline]
pub fn new() -> PtrMapCell<V> {
PtrMapCell(UnsafeCell::new(PtrMap::Empty))
}
#[inline]
pub fn with_one(key: TypeId, val: Box<V>) -> PtrMapCell<V> {
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<Box<V>> {
// 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<V>) {
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<V: ?Sized + Any + 'static> Clone for PtrMapCell<V> where Box<V>: Clone {
#[inline]
fn clone(&self) -> PtrMapCell<V> {
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<u32> = OptCell::new(None);
one.set(1);
assert_eq!(*one,Some(1));
}
#[test]
fn test_opt_cell_clone() {
let one:OptCell<u32> = 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::<u32>();
let pm:PtrMapCell<u32> = PtrMapCell::new();
assert_eq!(pm.get(type_id),None);
}
#[test]
fn test_ptr_map_cell_one() {
let type_id = TypeId::of::<String>();
let pm:PtrMapCell<String> = 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::<String>();
let type_id2 = TypeId::of::<Vec<u8>>();
let pm:PtrMapCell<String> = 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::<String>();
let id2 = TypeId::of::<Vec<u8>>();
let id3 = TypeId::of::<OptCell<String>>();
let pm:PtrMapCell<String> = 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::<String>();
let pm:PtrMapCell<String> = 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()));
}
}

View File

@@ -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<Raw>,
typed: PtrMapCell<Header + Send + Sync>
}
impl Item {
#[inline]
pub fn new_raw(data: Raw) -> Item {
Item {
raw: OptCell::new(Some(data)),
typed: PtrMapCell::new(),
}
}
#[inline]
pub fn new_typed<H: Header>(val: H) -> Item {
Item {
raw: OptCell::new(None),
typed: PtrMapCell::with_one(TypeId::of::<H>(), 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<H: Header + Any>(&self) -> Option<&H> {
let tid = TypeId::of::<H>();
match self.typed.get(tid) {
Some(val) => Some(val),
None => {
parse::<H>(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<H: Header>(&mut self) -> Option<&mut H> {
let tid = TypeId::of::<H>();
if self.typed.get_mut(tid).is_none() {
parse::<H>(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<H: Header>(self) -> Option<H> {
let tid = TypeId::of::<H>();
let Item { typed, raw } = self;
typed.into_value(tid)
.or_else(|| raw.as_ref().and_then(parse::<H>))
.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<H: Header>(raw: &Raw) -> Option<Box<Header + Send + Sync>> {
H::parse_header(raw).map(|h| {
let h: Box<Header + Send + Sync> = Box::new(h);
h
}).ok()
}

View File

@@ -1,6 +0,0 @@
pub use self::item::Item;
pub use self::vec_map::{VecMap, Entry};
mod cell;
mod item;
mod vec_map;

View File

@@ -1,146 +0,0 @@
#[derive(Clone)]
pub struct VecMap<K, V> {
vec: Vec<(K, V)>,
}
impl<K: PartialEq, V> VecMap<K, V> {
#[inline]
pub fn with_capacity(cap: usize) -> VecMap<K, V> {
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<K, V> {
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<K2: PartialEq<K> + ?Sized>(&self, key: &K2) -> Option<&V> {
self.find(key).map(|entry| &entry.1)
}
#[inline]
pub fn get_mut<K2: PartialEq<K> + ?Sized>(&mut self, key: &K2) -> Option<&mut V> {
self.find_mut(key).map(|entry| &mut entry.1)
}
#[inline]
pub fn contains_key<K2: PartialEq<K> + ?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<K2: PartialEq<K> + ?Sized>(&mut self, key: &K2) -> Option<V> {
self.pos(key).map(|pos| self.vec.remove(pos)).map(|(_, v)| v)
}
#[inline]
pub fn remove_all<K2: PartialEq<K> + ?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<K2: PartialEq<K> + ?Sized>(&self, key: &K2) -> Option<&(K, V)> {
for entry in &self.vec {
if key == &entry.0 {
return Some(entry);
}
}
None
}
#[inline]
fn find_mut<K2: PartialEq<K> + ?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<K2: PartialEq<K> + ?Sized>(&self, key: &K2) -> Option<usize> {
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
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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<T: str::FromStr>(raw: &Raw) -> ::Result<T> {
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<T: str::FromStr>(raw: &[u8]) -> ::Result<T> {
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<T: str::FromStr>(raw: &Raw) -> ::Result<Vec<T>> {
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<T: Display>(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<LanguageTag>,
/// The parameter value, as expressed in octets.
pub value: Vec<u8>,
}
/// 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 <extended-initial-value>
/// ; (see [RFC2231], Section 7)
///
/// charset = "UTF-8" / "ISO-8859-1" / mime-charset
///
/// mime-charset = 1*mime-charsetc
/// mime-charsetc = ALPHA / DIGIT
/// / "!" / "#" / "$" / "%" / "&"
/// / "+" / "-" / "^" / "_" / "`"
/// / "{" / "}" / "~"
/// ; as <mime-charset> in Section 2.3 of [RFC2978]
/// ; except that the single quote is not included
/// ; SHOULD be registered in the IANA charset registry
///
/// language = <Language-Tag, defined in [RFC5646], Section 2.1>
///
/// 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<ExtendedValue> {
// 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<LanguageTag> = 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<u8> = 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::<LanguageTag>().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));
}
}

View File

@@ -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<V: Into<Raw>>(&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<Bytes>),
}
fn eq_many<A: AsRef<[u8]>, 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<B: AsRef<[u8]>>(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<u8>]> for Raw {
fn eq(&self, bytes: &[Vec<u8>]) -> 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<str> for Raw {
fn eq(&self, s: &str) -> bool {
self == s.as_bytes()
}
}
impl From<Vec<Vec<u8>>> for Raw {
#[inline]
fn from(val: Vec<Vec<u8>>) -> Raw {
Raw(Lines::Many(
val.into_iter()
.map(|vec| maybe_literal(vec.into()))
.collect()
))
}
}
impl From<String> for Raw {
#[inline]
fn from(val: String) -> Raw {
Raw::from(val.into_bytes())
}
}
impl From<Vec<u8>> for Raw {
#[inline]
fn from(val: Vec<u8>) -> 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<Bytes> 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<usize> 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()),
}
}
}

View File

@@ -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<Charset> {
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())));
}

View File

@@ -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<Encoding> {
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()))
}
}
}

View File

@@ -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<EntityTag> {
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::<EntityTag>().unwrap(),
EntityTag::strong("foobar".to_owned()));
assert_eq!("\"\"".parse::<EntityTag>().unwrap(),
EntityTag::strong("".to_owned()));
assert_eq!("W/\"weaktag\"".parse::<EntityTag>().unwrap(),
EntityTag::weak("weaktag".to_owned()));
assert_eq!("W/\"\x65\x62\"".parse::<EntityTag>().unwrap(),
EntityTag::weak("\x65\x62".to_owned()));
assert_eq!("W/\"\"".parse::<EntityTag>().unwrap(), EntityTag::weak("".to_owned()));
}
#[test]
fn test_etag_parse_failures() {
// Expected failures
assert!("no-dquotes".parse::<EntityTag>().is_err());
assert!("w/\"the-first-w-is-case-sensitive\"".parse::<EntityTag>().is_err());
assert!("".parse::<EntityTag>().is_err());
assert!("\"unmatched-dquotes1".parse::<EntityTag>().is_err());
assert!("unmatched-dquotes2\"".parse::<EntityTag>().is_err());
assert!("matched-\"dquotes\"".parse::<EntityTag>().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));
}
}

View File

@@ -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<HttpDate> {
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<SystemTime> 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<HttpDate> 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::<HttpDate>().unwrap(), NOV_07);
}
#[test]
fn test_rfc_850() {
assert_eq!("Sunday, 07-Nov-94 08:48:37 GMT".parse::<HttpDate>().unwrap(), NOV_07);
}
#[test]
fn test_asctime() {
assert_eq!("Sun Nov 7 08:48:37 1994".parse::<HttpDate>().unwrap(), NOV_07);
}
#[test]
fn test_no_date() {
assert!("this-is-no-date".parse::<HttpDate>().is_err());
}
}

View File

@@ -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;

View File

@@ -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<T> {
/// The actual contents of the field.
pub item: T,
/// The quality (client or server preference) for the value.
pub quality: Quality,
}
impl<T> QualityItem<T> {
/// 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<T> {
QualityItem {
item: item,
quality: quality
}
}
}
impl<T: PartialEq> cmp::PartialOrd for QualityItem<T> {
fn partial_cmp(&self, other: &QualityItem<T>) -> Option<cmp::Ordering> {
self.quality.partial_cmp(&other.quality)
}
}
impl<T: fmt::Display> fmt::Display for QualityItem<T> {
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<T: str::FromStr> str::FromStr for QualityItem<T> {
type Err = ::Error;
fn from_str(s: &str) -> ::Result<QualityItem<T>> {
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::<f32>() {
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::<T>() {
// 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<T>(item: T) -> QualityItem<T> {
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<T: IntoQuality>(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<QualityItem<Encoding>> = "chunked".parse();
assert_eq!(x.unwrap(), QualityItem{ item: Chunked, quality: Quality(1000), });
}
#[test]
fn test_quality_item_from_str2() {
let x: ::Result<QualityItem<Encoding>> = "chunked; q=1".parse();
assert_eq!(x.unwrap(), QualityItem{ item: Chunked, quality: Quality(1000), });
}
#[test]
fn test_quality_item_from_str3() {
let x: ::Result<QualityItem<Encoding>> = "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<QualityItem<Encoding>> = "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<QualityItem<Encoding>> = "gzip; q=0.2739999".parse();
assert!(x.is_err());
}
#[test]
fn test_quality_item_from_str6() {
let x: ::Result<QualityItem<Encoding>> = "gzip; q=2".parse();
assert!(x.is_err());
}
#[test]
fn test_quality_item_ordering() {
let x: QualityItem<Encoding> = "gzip; q=0.5".parse().ok().unwrap();
let y: QualityItem<Encoding> = "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::<QualityItem<String>>().is_err());
assert!("\x0d;;;=\u{d6aa}==".parse::<QualityItem<String>>().is_err())
}
}

85
src/headers.rs Normal file
View File

@@ -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<u64> {
// 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
}

View File

@@ -11,24 +11,18 @@
//! layer over "stringly-typed" HTTP. //! layer over "stringly-typed" HTTP.
//! //!
//! Hyper provides both a [Client](client/index.html) and a //! Hyper provides both a [Client](client/index.html) and a
//! [Server](server/index.html), along with a //! [Server](server/index.html).
//! [typed Headers system](header/index.html).
//! //!
//! If just starting out, **check out the [Guides](https://hyper.rs/guides) //! If just starting out, **check out the [Guides](https://hyper.rs/guides)
//! first.** //! first.**
extern crate base64;
extern crate bytes; extern crate bytes;
#[macro_use] extern crate futures; #[macro_use] extern crate futures;
extern crate futures_cpupool; extern crate futures_cpupool;
#[cfg(feature = "compat")]
extern crate http; extern crate http;
extern crate httparse; extern crate httparse;
extern crate iovec; extern crate iovec;
extern crate language_tags;
#[macro_use] extern crate log; #[macro_use] extern crate log;
pub extern crate mime;
#[macro_use] extern crate percent_encoding;
extern crate relay; extern crate relay;
extern crate time; extern crate time;
extern crate tokio_core as tokio; extern crate tokio_core as tokio;
@@ -39,29 +33,25 @@ extern crate unicase;
#[cfg(all(test, feature = "nightly"))] #[cfg(all(test, feature = "nightly"))]
extern crate test; extern crate test;
pub use uri::Uri; pub use http::{
Method,
Request,
Response,
StatusCode,
Uri,
Version,
};
pub use client::Client; pub use client::Client;
pub use error::{Result, Error}; pub use error::{Result, Error};
pub use header::Headers;
pub use proto::{Body, Chunk}; 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 server::Server;
pub use version::HttpVersion;
#[cfg(feature = "raw_status")]
pub use proto::RawStatus;
mod common; mod common;
#[cfg(test)] #[cfg(test)]
mod mock; mod mock;
pub mod client; pub mod client;
pub mod error; pub mod error;
mod method; mod headers;
pub mod header;
mod proto; mod proto;
pub mod server; pub mod server;
mod status;
mod uri;
mod version;

View File

@@ -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<str> 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<Method, Error> {
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<http::Method> 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<Method> 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<Method, _> = 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<Method,usize> = 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);
}
}
}

View File

@@ -418,11 +418,11 @@ impl Service for MockConnector {
fn call(&self, uri: Uri) -> Self::Future { fn call(&self, uri: Uri) -> Self::Future {
use futures::future; use futures::future;
trace!("mock connect: {:?}", uri.as_ref()); trace!("mock connect: {}", uri);
let mut mocks = self.mocks.borrow_mut(); let mut mocks = self.mocks.borrow_mut();
let mocks = mocks.get_mut(uri.as_ref()) let mocks = mocks.get_mut(&uri.to_string())
.expect(&format!("unknown mocks uri: {:?}", uri.as_ref())); .expect(&format!("unknown mocks uri: {}", uri));
assert!(!mocks.is_empty(), "no additional mocks for {:?}", uri.as_ref()); assert!(!mocks.is_empty(), "no additional mocks for {}", uri);
future::ok(mocks.remove(0)) future::ok(mocks.remove(0))
} }
} }

View File

@@ -5,13 +5,12 @@ use std::marker::PhantomData;
use bytes::Bytes; use bytes::Bytes;
use futures::{Async, AsyncSink, Poll, StartSend}; use futures::{Async, AsyncSink, Poll, StartSend};
use futures::task::Task; use futures::task::Task;
use http::{Method, Version};
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use proto::{Chunk, Decode, Http1Transaction, MessageHead}; use proto::{Chunk, Decode, Http1Transaction, MessageHead};
use super::io::{Cursor, Buffered}; use super::io::{Cursor, Buffered};
use super::{EncodedBuf, Encoder, Decoder}; use super::{EncodedBuf, Encoder, Decoder};
use method::Method;
use version::HttpVersion;
/// This handles a connection, which will have been established over an /// This handles a connection, which will have been established over an
@@ -55,7 +54,7 @@ where I: AsyncRead + AsyncWrite,
writing: Writing::Init, writing: Writing::Init,
// We assume a modern world where the remote speaks HTTP/1.1. // We assume a modern world where the remote speaks HTTP/1.1.
// If they tell us otherwise, we'll downgrade in `read_head`. // If they tell us otherwise, we'll downgrade in `read_head`.
version: Version::Http11, version: Version::HTTP_11,
}, },
_marker: PhantomData, _marker: PhantomData,
} }
@@ -141,15 +140,16 @@ where I: AsyncRead + AsyncWrite,
} }
}; };
self.state.version = match version { match version {
HttpVersion::Http10 => Version::Http10, Version::HTTP_10 |
HttpVersion::Http11 => Version::Http11, Version::HTTP_11 => {},
_ => { _ => {
error!("unimplemented HTTP Version = {:?}", version); error!("unimplemented HTTP Version = {:?}", version);
self.state.close_read(); self.state.close_read();
return Err(::Error::Version); return Err(::Error::Version);
} }
}; };
self.state.version = version;
let decoder = match T::decoder(&head, &mut self.state.method) { let decoder = match T::decoder(&head, &mut self.state.method) {
Ok(Decode::Normal(d)) => { 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 // If we know the remote speaks an older version, we try to fix up any messages
// to work with our older peer. // to work with our older peer.
fn enforce_version(&mut self, head: &mut MessageHead<T::Outgoing>) { fn enforce_version(&mut self, head: &mut MessageHead<T::Outgoing>) {
use header::Connection; //use header::Connection;
let wants_keep_alive = if self.state.wants_keep_alive() { let wants_keep_alive = if self.state.wants_keep_alive() {
let ka = head.should_keep_alive(); let ka = head.should_keep_alive();
@@ -459,15 +459,15 @@ where I: AsyncRead + AsyncWrite,
}; };
match self.state.version { match self.state.version {
Version::Http10 => { Version::HTTP_10 => {
// If the remote only knows HTTP/1.0, we should force ourselves // If the remote only knows HTTP/1.0, we should force ourselves
// to do only speak HTTP/1.0 as well. // to do only speak HTTP/1.0 as well.
head.version = HttpVersion::Http10; head.version = Version::HTTP_10;
if wants_keep_alive { 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 // 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 // both HTTP/1.0 and HTTP/1.1 from us. So again, we just let
// the user's headers be. // the user's headers be.
@@ -789,12 +789,6 @@ impl State {
} }
} }
#[derive(Debug, Clone, Copy)]
enum Version {
Http10,
Http11,
}
#[cfg(test)] #[cfg(test)]
//TODO: rewrite these using dispatch //TODO: rewrite these using dispatch
mod tests { mod tests {

View File

@@ -2,11 +2,11 @@ use std::io;
use bytes::Bytes; use bytes::Bytes;
use futures::{Async, AsyncSink, Future, Poll, Stream}; use futures::{Async, AsyncSink, Future, Poll, Stream};
use http::{Request, Response, StatusCode};
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use tokio_service::Service; use tokio_service::Service;
use proto::{Body, Conn, Http1Transaction, MessageHead, RequestHead, ResponseHead}; use proto::{Body, Conn, Http1Transaction, MessageHead, RequestHead, RequestLine, ResponseHead};
use ::StatusCode;
pub struct Dispatcher<D, Bs, I, B, T> { pub struct Dispatcher<D, Bs, I, B, T> {
conn: Conn<I, B, T>, conn: Conn<I, B, T>,
@@ -21,7 +21,7 @@ pub trait Dispatch {
type PollBody; type PollBody;
type RecvItem; type RecvItem;
fn poll_msg(&mut self) -> Poll<Option<(Self::PollItem, Option<Self::PollBody>)>, ::Error>; fn poll_msg(&mut self) -> Poll<Option<(Self::PollItem, Option<Self::PollBody>)>, ::Error>;
fn recv_msg(&mut self, msg: ::Result<(Self::RecvItem, Option<Body>)>) -> ::Result<()>; fn recv_msg(&mut self, msg: ::Result<(Self::RecvItem, Body)>) -> ::Result<()>;
fn poll_ready(&mut self) -> Poll<(), ()>; fn poll_ready(&mut self) -> Poll<(), ()>;
fn should_poll(&self) -> bool; fn should_poll(&self) -> bool;
} }
@@ -32,13 +32,13 @@ pub struct Server<S: Service> {
} }
pub struct Client<B> { pub struct Client<B> {
callback: Option<::client::dispatch::Callback<ClientMsg<B>, ::Response>>, callback: Option<::client::dispatch::Callback<ClientMsg<B>, Response<Body>>>,
rx: ClientRx<B>, rx: ClientRx<B>,
} }
pub type ClientMsg<B> = (RequestHead, Option<B>); pub type ClientMsg<B> = Request<B>;
type ClientRx<B> = ::client::dispatch::Receiver<ClientMsg<B>, ::Response>; type ClientRx<B> = ::client::dispatch::Receiver<ClientMsg<B>, Response<Body>>;
impl<D, Bs, I, B, T> Dispatcher<D, Bs, I, B, T> impl<D, Bs, I, B, T> Dispatcher<D, Bs, I, B, T>
where where
@@ -184,9 +184,9 @@ where
let (mut tx, rx) = ::proto::body::channel(); let (mut tx, rx) = ::proto::body::channel();
let _ = tx.poll_ready(); // register this task if rx is dropped let _ = tx.poll_ready(); // register this task if rx is dropped
self.body_tx = Some(tx); self.body_tx = Some(tx);
Some(rx) rx
} else { } else {
None Body::empty()
}; };
self.dispatch.recv_msg(Ok((head, body)))?; self.dispatch.recv_msg(Ok((head, body)))?;
Ok(Async::Ready(())) Ok(Async::Ready(()))
@@ -315,7 +315,7 @@ impl<S> Server<S> where S: Service {
impl<S, Bs> Dispatch for Server<S> impl<S, Bs> Dispatch for Server<S>
where where
S: Service<Request=::Request, Response=::Response<Bs>, Error=::Error>, S: Service<Request=Request<Body>, Response=Response<Bs>, Error=::Error>,
Bs: Stream<Error=::Error>, Bs: Stream<Error=::Error>,
Bs::Item: AsRef<[u8]>, Bs::Item: AsRef<[u8]>,
{ {
@@ -332,16 +332,25 @@ where
return Ok(Async::NotReady); return Ok(Async::NotReady);
} }
}; };
let (head, body) = ::proto::response::split(resp); let (parts, body) = resp.into_parts();
Ok(Async::Ready(Some((head.into(), body)))) let head = MessageHead {
version: parts.version,
subject: parts.status,
headers: parts.headers,
};
Ok(Async::Ready(Some((head, Some(body)))))
} else { } else {
unreachable!("poll_msg shouldn't be called if no inflight"); unreachable!("poll_msg shouldn't be called if no inflight");
} }
} }
fn recv_msg(&mut self, msg: ::Result<(Self::RecvItem, Option<Body>)>) -> ::Result<()> { fn recv_msg(&mut self, msg: ::Result<(Self::RecvItem, Body)>) -> ::Result<()> {
let (msg, body) = msg?; 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)); self.in_flight = Some(self.service.call(req));
Ok(()) Ok(())
} }
@@ -382,7 +391,7 @@ where
fn poll_msg(&mut self) -> Poll<Option<(Self::PollItem, Option<Self::PollBody>)>, ::Error> { fn poll_msg(&mut self) -> Poll<Option<(Self::PollItem, Option<Self::PollBody>)>, ::Error> {
match self.rx.poll() { 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 // check that future hasn't been canceled already
match cb.poll_cancel().expect("poll_cancel cannot error") { match cb.poll_cancel().expect("poll_cancel cannot error") {
Async::Ready(()) => { Async::Ready(()) => {
@@ -390,8 +399,14 @@ where
Ok(Async::Ready(None)) Ok(Async::Ready(None))
}, },
Async::NotReady => { 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); 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<Body>)>) -> ::Result<()> { fn recv_msg(&mut self, msg: ::Result<(Self::RecvItem, Body)>) -> ::Result<()> {
match msg { match msg {
Ok((msg, body)) => { Ok((msg, body)) => {
if let Some(cb) = self.callback.take() { 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)); let _ = cb.send(Ok(res));
Ok(()) Ok(())
} else { } else {
@@ -469,12 +487,7 @@ mod tests {
let conn = Conn::<_, ::Chunk, ClientTransaction>::new(io); let conn = Conn::<_, ::Chunk, ClientTransaction>::new(io);
let mut dispatcher = Dispatcher::new(Client::new(rx), conn); let mut dispatcher = Dispatcher::new(Client::new(rx), conn);
let req = RequestHead { let res_rx = tx.try_send(::Request::new(::Body::empty())).unwrap();
version: ::HttpVersion::Http11,
subject: ::proto::RequestLine::default(),
headers: Default::default(),
};
let res_rx = tx.try_send((req, None::<::Body>)).unwrap();
let a1 = dispatcher.poll().expect("error should be sent on channel"); let a1 = dispatcher.poll().expect("error should be sent on channel");
assert!(a1.is_ready(), "dispatcher should be closed"); assert!(a1.is_ready(), "dispatcher should be closed");

View File

@@ -1,16 +1,13 @@
use std::borrow::Cow;
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use httparse;
use bytes::{BytesMut, Bytes}; 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 headers;
use proto::{Decode, MessageHead, RawStatus, Http1Transaction, ParseResult, use proto::{Decode, MessageHead, Http1Transaction, ParseResult, RequestLine, RequestHead};
RequestLine, RequestHead};
use proto::h1::{Encoder, Decoder, date}; use proto::h1::{Encoder, Decoder, date};
use method::Method;
use status::StatusCode;
use version::HttpVersion::{Http10, Http11};
const MAX_HEADERS: usize = 100; const MAX_HEADERS: usize = 100;
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
@@ -46,13 +43,17 @@ where
match try!(req.parse(&buf)) { match try!(req.parse(&buf)) {
httparse::Status::Complete(len) => { httparse::Status::Complete(len) => {
trace!("Request.parse 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 path = req.path.unwrap();
let bytes_ptr = buf.as_ref().as_ptr() as usize; let bytes_ptr = buf.as_ref().as_ptr() as usize;
let path_start = path.as_ptr() as usize - bytes_ptr; let path_start = path.as_ptr() as usize - bytes_ptr;
let path_end = path_start + path.len(); let path_end = path_start + path.len();
let path = (path_start, path_end); 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); record_header_indices(buf.as_ref(), &req.headers, &mut headers_indices);
let headers_len = req.headers.len(); 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 slice = buf.split_to(len).freeze();
let path = slice.slice(path.0, path.1); let path = slice.slice(path.0, path.1);
// path was found to be utf8 by httparse // 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( let subject = RequestLine(
method, method,
path, path,
@@ -85,8 +86,6 @@ where
} }
fn decoder(head: &MessageHead<Self::Incoming>, method: &mut Option<Method>) -> ::Result<Decode> { fn decoder(head: &MessageHead<Self::Incoming>, method: &mut Option<Method>) -> ::Result<Decode> {
use ::header;
*method = Some(head.subject.0.clone()); *method = Some(head.subject.0.clone());
// According to https://tools.ietf.org/html/rfc7230#section-3.3.3 // According to https://tools.ietf.org/html/rfc7230#section-3.3.3
@@ -98,24 +97,24 @@ where
// 6. Length 0. // 6. Length 0.
// 7. (irrelevant to Request) // 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 // https://tools.ietf.org/html/rfc7230#section-3.3.3
// If Transfer-Encoding header is present, and 'chunked' is // If Transfer-Encoding header is present, and 'chunked' is
// not the final encoding, and this is a Request, then it is // not the final encoding, and this is a Request, then it is
// mal-formed. A server should respond with 400 Bad Request. // mal-formed. A server should respond with 400 Bad Request.
if head.version == Http10 { if head.version == Version::HTTP_10 {
debug!("HTTP/1.0 has Transfer-Encoding header"); debug!("HTTP/1.0 cannot have Transfer-Encoding header");
Err(::Error::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())) Ok(Decode::Normal(Decoder::chunked()))
} else { } else {
debug!("request with transfer-encoding header, but not chunked, bad request"); debug!("request with transfer-encoding header, but not chunked, bad request");
Err(::Error::Header) 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))) Ok(Decode::Normal(Decoder::length(len)))
} else if head.headers.has::<header::ContentLength>() { } else if head.headers.contains_key(CONTENT_LENGTH) {
debug!("illegal Content-Length: {:?}", head.headers.get_raw("Content-Length")); debug!("illegal Content-Length header");
Err(::Error::Header) Err(::Error::Header)
} else { } else {
Ok(Decode::Normal(Decoder::length(0))) Ok(Decode::Normal(Decoder::length(0)))
@@ -130,7 +129,7 @@ where
// This is because Service only allows returning a single Response, and // 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 // so if you try to reply with a e.g. 100 Continue, you have no way of
// replying with the latter status code response. // 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) T::on_encode_upgrade(&mut head)
.map(|_| { .map(|_| {
let mut enc = Server::set_length(&mut head, has_body, method.as_ref()); let mut enc = Server::set_length(&mut head, has_body, method.as_ref());
@@ -140,8 +139,8 @@ where
} else if head.subject.is_informational() { } else if head.subject.is_informational() {
error!("response with 1xx status code not supported"); error!("response with 1xx status code not supported");
head = MessageHead::default(); head = MessageHead::default();
head.subject = ::StatusCode::InternalServerError; head.subject = StatusCode::INTERNAL_SERVER_ERROR;
head.headers.set(ContentLength(0)); headers::content_length_zero(&mut head.headers);
Err(::Error::Status) Err(::Error::Status)
} else { } else {
Ok(Server::set_length(&mut head, has_body, method.as_ref())) 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; let init_cap = 30 + head.headers.len() * AVERAGE_HEADER_SIZE;
dst.reserve(init_cap); 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"); extend(dst, b"HTTP/1.1 200 OK\r\n");
let _ = write!(FastWrite(dst), "{}", head.headers);
} else { } 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("<none>").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 // using http::h1::date is quite a lot faster than generating a unique Date header each time
// like req/s goes up about 10% // like req/s goes up about 10%
if !head.headers.has::<header::Date>() { if !head.headers.contains_key(DATE) {
dst.reserve(date::DATE_VALUE_LENGTH + 8); dst.reserve(date::DATE_VALUE_LENGTH + 8);
extend(dst, b"Date: "); extend(dst, b"Date: ");
date::extend(dst); date::extend(dst);
@@ -173,12 +181,12 @@ where
let status = match err { let status = match err {
&::Error::Method | &::Error::Method |
&::Error::Version | &::Error::Version |
&::Error::Header | &::Error::Header /*|
&::Error::Uri(_) => { &::Error::Uri(_)*/ => {
StatusCode::BadRequest StatusCode::BAD_REQUEST
}, },
&::Error::TooLarge => { &::Error::TooLarge => {
StatusCode::RequestHeaderFieldsTooLarge StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE
} }
_ => return None, _ => return None,
}; };
@@ -202,8 +210,8 @@ impl Server<()> {
fn set_length(head: &mut MessageHead<StatusCode>, has_body: bool, method: Option<&Method>) -> Encoder { fn set_length(head: &mut MessageHead<StatusCode>, has_body: bool, method: Option<&Method>) -> Encoder {
// these are here thanks to borrowck // these are here thanks to borrowck
// `if method == Some(&Method::Get)` says the RHS doesn't live long enough // `if method == Some(&Method::Get)` says the RHS doesn't live long enough
const HEAD: Option<&'static Method> = Some(&Method::Head); const HEAD: Option<&'static Method> = Some(&Method::HEAD);
const CONNECT: Option<&'static Method> = Some(&Method::Connect); const CONNECT: Option<&'static Method> = Some(&Method::CONNECT);
let can_have_body = { let can_have_body = {
if method == HEAD { if method == HEAD {
@@ -214,20 +222,20 @@ impl Server<()> {
match head.subject { match head.subject {
// TODO: support for 1xx codes needs improvement everywhere // TODO: support for 1xx codes needs improvement everywhere
// would be 100...199 => false // would be 100...199 => false
StatusCode::SwitchingProtocols | StatusCode::SWITCHING_PROTOCOLS |
StatusCode::NoContent | StatusCode::NO_CONTENT |
StatusCode::NotModified => false, StatusCode::NOT_MODIFIED => false,
_ => true, _ => true,
} }
} }
}; };
if has_body && can_have_body { 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 { } else {
head.headers.remove::<TransferEncoding>(); head.headers.remove(TRANSFER_ENCODING);
if can_have_body { if can_have_body {
head.headers.set(ContentLength(0)); headers::content_length_zero(&mut head.headers);
} }
Encoder::length(0) Encoder::length(0)
} }
@@ -238,10 +246,10 @@ impl<T> Http1Transaction for Client<T>
where where
T: OnUpgrade, T: OnUpgrade,
{ {
type Incoming = RawStatus; type Incoming = StatusCode;
type Outgoing = RequestLine; type Outgoing = RequestLine;
fn parse(buf: &mut BytesMut) -> ParseResult<RawStatus> { fn parse(buf: &mut BytesMut) -> ParseResult<StatusCode> {
if buf.len() == 0 { if buf.len() == 0 {
return Ok(None); return Ok(None);
} }
@@ -249,7 +257,7 @@ where
name: (0, 0), name: (0, 0),
value: (0, 0) value: (0, 0)
}; MAX_HEADERS]; }; MAX_HEADERS];
let (len, code, reason, version, headers_len) = { let (len, status, version, headers_len) = {
let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS]; let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS];
trace!("Response.parse([Header; {}], [u8; {}])", headers.len(), buf.len()); trace!("Response.parse([Header; {}], [u8; {}])", headers.len(), buf.len());
let mut res = httparse::Response::new(&mut headers); let mut res = httparse::Response::new(&mut headers);
@@ -257,22 +265,21 @@ where
match try!(res.parse(bytes)) { match try!(res.parse(bytes)) {
httparse::Status::Complete(len) => { httparse::Status::Complete(len) => {
trace!("Response.parse Complete({})", len); trace!("Response.parse Complete({})", len);
let code = res.code.unwrap(); let status = try!(StatusCode::from_u16(res.code.unwrap()).map_err(|_| ::Error::Status));
let status = try!(StatusCode::try_from(code).map_err(|_| ::Error::Status)); let version = if res.version.unwrap() == 1 {
let reason = match status.canonical_reason() { Version::HTTP_11
Some(reason) if reason == res.reason.unwrap() => Cow::Borrowed(reason), } else {
_ => Cow::Owned(res.reason.unwrap().to_owned()) Version::HTTP_10
}; };
let version = if res.version.unwrap() == 1 { Http11 } else { Http10 };
record_header_indices(bytes, &res.headers, &mut headers_indices); record_header_indices(bytes, &res.headers, &mut headers_indices);
let headers_len = res.headers.len(); let headers_len = res.headers.len();
(len, code, reason, version, headers_len) (len, status, version, headers_len)
}, },
httparse::Status::Partial => return Ok(None), 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(); let slice = buf.split_to(len).freeze();
headers.extend(HeadersAsBytesIter { headers.extend(HeadersAsBytesIter {
headers: headers_indices[..headers_len].iter(), headers: headers_indices[..headers_len].iter(),
@@ -280,7 +287,7 @@ where
}); });
Ok(Some((MessageHead { Ok(Some((MessageHead {
version: version, version: version,
subject: RawStatus(code, reason), subject: status,
headers: headers, headers: headers,
}, len))) }, len)))
} }
@@ -295,12 +302,12 @@ where
// 6. (irrelevant to Response) // 6. (irrelevant to Response)
// 7. Read till EOF. // 7. Read till EOF.
match inc.subject.0 { match inc.subject.as_u16() {
101 => { 101 => {
return T::on_decode_upgrade().map(Decode::Final); return T::on_decode_upgrade().map(Decode::Final);
}, },
100...199 => { 100...199 => {
trace!("ignoring informational response: {}", inc.subject.0); trace!("ignoring informational response: {}", inc.subject.as_u16());
return Ok(Decode::Ignore); return Ok(Decode::Ignore);
}, },
204 | 204 |
@@ -308,10 +315,10 @@ where
_ => (), _ => (),
} }
match *method { match *method {
Some(Method::Head) => { Some(Method::HEAD) => {
return Ok(Decode::Normal(Decoder::length(0))); return Ok(Decode::Normal(Decoder::length(0)));
} }
Some(Method::Connect) => match inc.subject.0 { Some(Method::CONNECT) => match inc.subject.as_u16() {
200...299 => { 200...299 => {
return Ok(Decode::Final(Decoder::length(0))); return Ok(Decode::Final(Decoder::length(0)));
}, },
@@ -323,21 +330,24 @@ where
} }
} }
if inc.headers.contains_key(TRANSFER_ENCODING) {
if let Some(&header::TransferEncoding(ref codings)) = inc.headers.get() { // https://tools.ietf.org/html/rfc7230#section-3.3.3
if inc.version == Http10 { // If Transfer-Encoding header is present, and 'chunked' is
debug!("HTTP/1.0 has Transfer-Encoding header"); // 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) 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())) Ok(Decode::Normal(Decoder::chunked()))
} else { } else {
trace!("not chunked. read till eof"); trace!("not chunked, read till eof");
Ok(Decode::Normal(Decoder::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))) Ok(Decode::Normal(Decoder::length(len)))
} else if inc.headers.has::<header::ContentLength>() { } else if inc.headers.contains_key(CONTENT_LENGTH) {
debug!("illegal Content-Length: {:?}", inc.headers.get_raw("Content-Length")); debug!("illegal Content-Length header");
Err(::Error::Header) Err(::Error::Header)
} else { } else {
trace!("neither Transfer-Encoding nor Content-Length"); trace!("neither Transfer-Encoding nor Content-Length");
@@ -354,7 +364,22 @@ where
let init_cap = 30 + head.headers.len() * AVERAGE_HEADER_SIZE; let init_cap = 30 + head.headers.len() * AVERAGE_HEADER_SIZE;
dst.reserve(init_cap); 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) Ok(body)
} }
@@ -376,41 +401,30 @@ where
impl Client<()> { impl Client<()> {
fn set_length(head: &mut RequestHead, has_body: bool) -> Encoder { fn set_length(head: &mut RequestHead, has_body: bool) -> Encoder {
if has_body { if has_body {
let can_chunked = head.version == Http11 let can_chunked = head.version == Version::HTTP_11
&& (head.subject.0 != Method::Head) && (head.subject.0 != Method::HEAD)
&& (head.subject.0 != Method::Get) && (head.subject.0 != Method::GET)
&& (head.subject.0 != Method::Connect); && (head.subject.0 != Method::CONNECT);
set_length(&mut head.headers, can_chunked) set_length(&mut head.headers, can_chunked)
} else { } else {
head.headers.remove::<ContentLength>(); head.headers.remove(CONTENT_LENGTH);
head.headers.remove::<TransferEncoding>(); head.headers.remove(TRANSFER_ENCODING);
Encoder::length(0) Encoder::length(0)
} }
} }
} }
fn set_length(headers: &mut Headers, can_chunked: bool) -> Encoder { fn set_length(headers: &mut HeaderMap, can_chunked: bool) -> Encoder {
let len = headers.get::<header::ContentLength>().map(|n| **n); let len = headers::content_length_parse(&headers);
if let Some(len) = len { if let Some(len) = len {
Encoder::length(len) Encoder::length(len)
} else if can_chunked { } else if can_chunked {
let encodings = match headers.get_mut::<header::TransferEncoding>() { //TODO: maybe not overwrite existing transfer-encoding
Some(&mut header::TransferEncoding(ref mut encodings)) => { headers.insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
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]));
}
Encoder::chunked() Encoder::chunked()
} else { } else {
headers.remove::<TransferEncoding>(); headers.remove(TRANSFER_ENCODING);
Encoder::eof() Encoder::eof()
} }
} }
@@ -440,8 +454,8 @@ impl OnUpgrade for NoUpgrades {
fn on_encode_upgrade(head: &mut MessageHead<StatusCode>) -> ::Result<()> { fn on_encode_upgrade(head: &mut MessageHead<StatusCode>) -> ::Result<()> {
error!("response with 101 status code not supported"); error!("response with 101 status code not supported");
*head = MessageHead::default(); *head = MessageHead::default();
head.subject = ::StatusCode::InternalServerError; head.subject = ::StatusCode::INTERNAL_SERVER_ERROR;
head.headers.set(ContentLength(0)); headers::content_length_zero(&mut head.headers);
Err(::Error::Status) Err(::Error::Status)
} }
@@ -475,7 +489,7 @@ struct HeadersAsBytesIter<'a> {
} }
impl<'a> Iterator for HeadersAsBytesIter<'a> { impl<'a> Iterator for HeadersAsBytesIter<'a> {
type Item = (&'a str, Bytes); type Item = (HeaderName, HeaderValue);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
self.headers.next().map(|header| { self.headers.next().map(|header| {
let name = unsafe { let name = unsafe {
@@ -485,11 +499,27 @@ impl<'a> Iterator for HeadersAsBytesIter<'a> {
); );
::std::str::from_utf8_unchecked(bytes) ::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<u8>) {
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<u8>); struct FastWrite<'a>(&'a mut Vec<u8>);
impl<'a> fmt::Write for FastWrite<'a> { impl<'a> fmt::Write for FastWrite<'a> {
@@ -516,7 +546,6 @@ mod tests {
use proto::{Decode, MessageHead}; use proto::{Decode, MessageHead};
use super::{Decoder, Server as S, Client as C, NoUpgrades, Http1Transaction}; use super::{Decoder, Server as S, Client as C, NoUpgrades, Http1Transaction};
use header::{ContentLength, TransferEncoding};
type Server = S<NoUpgrades>; type Server = S<NoUpgrades>;
type Client = C<NoUpgrades>; type Client = C<NoUpgrades>;
@@ -552,11 +581,11 @@ mod tests {
let expected_len = raw.len(); let expected_len = raw.len();
let (req, len) = Server::parse(&mut raw).unwrap().unwrap(); let (req, len) = Server::parse(&mut raw).unwrap().unwrap();
assert_eq!(len, expected_len); 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.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.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 expected_len = raw.len();
let (req, len) = Client::parse(&mut raw).unwrap().unwrap(); let (req, len) = Client::parse(&mut raw).unwrap().unwrap();
assert_eq!(len, expected_len); assert_eq!(len, expected_len);
assert_eq!(req.subject.0, 200); assert_eq!(req.subject, ::StatusCode::OK);
assert_eq!(req.subject.1, "OK"); assert_eq!(req.version, ::Version::HTTP_11);
assert_eq!(req.version, ::HttpVersion::Http11);
assert_eq!(req.headers.len(), 1); 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] #[test]
@@ -581,18 +609,6 @@ mod tests {
Server::parse(&mut raw).unwrap_err(); 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] #[test]
fn test_decoder_request() { fn test_decoder_request() {
use super::Decoder; use super::Decoder;
@@ -600,47 +616,49 @@ mod tests {
let method = &mut None; let method = &mut None;
let mut head = MessageHead::<::proto::RequestLine>::default(); 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!(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!(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()); assert_eq!(Decoder::chunked(), Server::decoder(&head, method).unwrap().normal());
// transfer-encoding and content-length = chunked // 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()); assert_eq!(Decoder::chunked(), Server::decoder(&head, method).unwrap().normal());
head.headers.remove::<TransferEncoding>(); head.headers.remove("transfer-encoding");
assert_eq!(Decoder::length(10), Server::decoder(&head, method).unwrap().normal()); 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()); 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(); Server::decoder(&head, method).unwrap_err();
head.headers.remove::<ContentLength>(); 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(); Server::decoder(&head, method).unwrap_err();
// http/1.0 // http/1.0
head.version = ::HttpVersion::Http10; head.version = ::Version::HTTP_10;
head.headers.clear(); head.headers.clear();
// 1.0 requests can only have bodies if content-length is set // 1.0 requests can only have bodies if content-length is set
assert_eq!(Decoder::length(0), Server::decoder(&head, method).unwrap().normal()); 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(); Server::decoder(&head, method).unwrap_err();
head.headers.remove::<TransferEncoding>(); 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()); assert_eq!(Decoder::length(15), Server::decoder(&head, method).unwrap().normal());
} }
@@ -648,67 +666,69 @@ mod tests {
fn test_decoder_response() { fn test_decoder_response() {
use super::Decoder; use super::Decoder;
let method = &mut Some(::Method::Get); let method = &mut Some(::Method::GET);
let mut head = MessageHead::<::proto::RawStatus>::default(); 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()); 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()); 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()); 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()); 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_()); assert_eq!(Decoder::length(0), Client::decoder(&head, method).unwrap().final_());
// CONNECT receiving non 200 can have a body // CONNECT receiving non 200 can have a body
head.subject.0 = 404; head.subject = ::StatusCode::NOT_FOUND;
head.headers.set(ContentLength(10)); head.headers.insert("content-length", ::http::header::HeaderValue::from_static("10"));
assert_eq!(Decoder::length(10), Client::decoder(&head, method).unwrap().normal()); assert_eq!(Decoder::length(10), Client::decoder(&head, method).unwrap().normal());
head.headers.remove::<ContentLength>(); head.headers.remove("content-length");
*method = Some(::Method::Get); *method = Some(::Method::GET);
head.headers.set(TransferEncoding::chunked()); head.headers.insert("transfer-encoding", ::http::header::HeaderValue::from_static("chunked"));
assert_eq!(Decoder::chunked(), Client::decoder(&head, method).unwrap().normal()); assert_eq!(Decoder::chunked(), Client::decoder(&head, method).unwrap().normal());
// transfer-encoding and content-length = chunked // 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()); assert_eq!(Decoder::chunked(), Client::decoder(&head, method).unwrap().normal());
head.headers.remove::<TransferEncoding>(); head.headers.remove("transfer-encoding");
assert_eq!(Decoder::length(10), Client::decoder(&head, method).unwrap().normal()); 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()); 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(); Client::decoder(&head, method).unwrap_err();
head.headers.clear(); head.headers.clear();
// 1xx status codes // 1xx status codes
head.subject.0 = 100; head.subject = ::StatusCode::CONTINUE;
Client::decoder(&head, method).unwrap().ignore(); Client::decoder(&head, method).unwrap().ignore();
head.subject.0 = 103; head.subject = ::StatusCode::from_u16(103).unwrap();
Client::decoder(&head, method).unwrap().ignore(); Client::decoder(&head, method).unwrap().ignore();
// 101 upgrade not supported yet // 101 upgrade not supported yet
head.subject.0 = 101; head.subject = ::StatusCode::SWITCHING_PROTOCOLS;
Client::decoder(&head, method).unwrap_err(); Client::decoder(&head, method).unwrap_err();
head.subject.0 = 200; head.subject = ::StatusCode::OK;
// http/1.0 // http/1.0
head.version = ::HttpVersion::Http10; head.version = ::Version::HTTP_10;
assert_eq!(Decoder::eof(), Client::decoder(&head, method).unwrap().normal()); 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(); Client::decoder(&head, method).unwrap_err();
} }
@@ -757,23 +777,19 @@ mod tests {
#[cfg(feature = "nightly")] #[cfg(feature = "nightly")]
#[bench] #[bench]
fn bench_server_transaction_encode(b: &mut Bencher) { fn bench_server_transaction_encode(b: &mut Bencher) {
use header::{Headers, ContentLength, ContentType}; use http::header::HeaderValue;
use ::{StatusCode, HttpVersion}; use proto::BodyLength;
let len = 108; let len = 108;
b.bytes = len as u64; b.bytes = len as u64;
let mut head = MessageHead { let mut head = MessageHead::default();
subject: StatusCode::Ok, head.headers.insert("content-length", HeaderValue::from_static("10"));
headers: Headers::new(), head.headers.insert("content-type", HeaderValue::from_static("application/json"));
version: HttpVersion::Http11,
};
head.headers.set(ContentLength(10));
head.headers.set(ContentType::json());
b.iter(|| { b.iter(|| {
let mut vec = Vec::new(); 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); assert_eq!(vec.len(), len);
::test::black_box(vec); ::test::black_box(vec);
}) })

View File

@@ -1,16 +1,8 @@
//! Pieces pertaining to the HTTP message protocol. //! Pieces pertaining to the HTTP message protocol.
use std::borrow::Cow;
use std::fmt;
use bytes::BytesMut; use bytes::BytesMut;
use http::{HeaderMap, Method, StatusCode, Uri, Version};
use header::{Connection, ConnectionOption, Expect}; use headers;
use header::Headers;
use method::Method;
use status::StatusCode;
use uri::Uri;
use version::HttpVersion;
use version::HttpVersion::{Http10, Http11};
pub use self::body::Body; pub use self::body::Body;
pub use self::chunk::Chunk; pub use self::chunk::Chunk;
@@ -20,19 +12,17 @@ mod body;
mod chunk; mod chunk;
mod h1; mod h1;
//mod h2; //mod h2;
pub mod request;
pub mod response;
/// An Incoming Message head. Includes request/status line, and headers. /// An Incoming Message head. Includes request/status line, and headers.
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, PartialEq)]
pub struct MessageHead<S> { pub struct MessageHead<S> {
/// HTTP version of the message. /// HTTP version of the message.
pub version: HttpVersion, pub version: Version,
/// Subject (request line or status line) of Incoming message. /// Subject (request line or status line) of Incoming message.
pub subject: S, pub subject: S,
/// Headers of the Incoming message. /// Headers of the Incoming message.
pub headers: Headers pub headers: HeaderMap,
} }
/// An incoming request message. /// An incoming request message.
@@ -41,14 +31,8 @@ pub type RequestHead = MessageHead<RequestLine>;
#[derive(Debug, Default, PartialEq)] #[derive(Debug, Default, PartialEq)]
pub struct RequestLine(pub Method, pub Uri); 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. /// An incoming response message.
pub type ResponseHead = MessageHead<RawStatus>; pub type ResponseHead = MessageHead<StatusCode>;
impl<S> MessageHead<S> { impl<S> MessageHead<S> {
pub fn should_keep_alive(&self) -> bool { pub fn should_keep_alive(&self) -> bool {
@@ -60,76 +44,20 @@ impl<S> MessageHead<S> {
} }
} }
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<StatusCode> 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<MessageHead<::StatusCode>> for MessageHead<RawStatus> {
fn from(head: MessageHead<::StatusCode>) -> MessageHead<RawStatus> {
MessageHead {
subject: head.subject.into(),
version: head.version,
headers: head.headers,
}
}
}
/// Checks if a connection should be kept alive. /// Checks if a connection should be kept alive.
#[inline] #[inline]
pub fn should_keep_alive(version: HttpVersion, headers: &Headers) -> bool { pub fn should_keep_alive(version: Version, headers: &HeaderMap) -> bool {
let ret = match (version, headers.get::<Connection>()) { if version == Version::HTTP_10 {
(Http10, None) => false, headers::connection_keep_alive(headers)
(Http10, Some(conn)) if !conn.contains(&ConnectionOption::KeepAlive) => false, } else {
(Http11, Some(conn)) if conn.contains(&ConnectionOption::Close) => false, !headers::connection_close(headers)
_ => true }
};
trace!("should_keep_alive(version={:?}, header={:?}) = {:?}", version, headers.get::<Connection>(), ret);
ret
} }
/// Checks if a connection is expecting a `100 Continue` before sending its body. /// Checks if a connection is expecting a `100 Continue` before sending its body.
#[inline] #[inline]
pub fn expecting_continue(version: HttpVersion, headers: &Headers) -> bool { pub fn expecting_continue(version: Version, headers: &HeaderMap) -> bool {
let ret = match (version, headers.get::<Expect>()) { version == Version::HTTP_11 && headers::expect_continue(headers)
(Http11, Some(&Expect::Continue)) => true,
_ => false
};
trace!("expecting_continue(version={:?}, header={:?}) = {:?}", version, headers.get::<Expect>(), ret);
ret
} }
pub type ServerTransaction = h1::role::Server<h1::role::YesUpgrades>; pub type ServerTransaction = h1::role::Server<h1::role::YesUpgrades>;
@@ -143,7 +71,7 @@ pub trait Http1Transaction {
type Incoming; type Incoming;
type Outgoing: Default; type Outgoing: Default;
fn parse(bytes: &mut BytesMut) -> ParseResult<Self::Incoming>; fn parse(bytes: &mut BytesMut) -> ParseResult<Self::Incoming>;
fn decoder(head: &MessageHead<Self::Incoming>, method: &mut Option<::Method>) -> ::Result<Decode>; fn decoder(head: &MessageHead<Self::Incoming>, method: &mut Option<Method>) -> ::Result<Decode>;
fn encode(head: MessageHead<Self::Outgoing>, has_body: bool, method: &mut Option<Method>, dst: &mut Vec<u8>) -> ::Result<h1::Encoder>; fn encode(head: MessageHead<Self::Outgoing>, has_body: bool, method: &mut Option<Method>, dst: &mut Vec<u8>) -> ::Result<h1::Encoder>;
fn on_error(err: &::Error) -> Option<MessageHead<Self::Outgoing>>; fn on_error(err: &::Error) -> Option<MessageHead<Self::Outgoing>>;
@@ -165,28 +93,28 @@ pub enum Decode {
#[test] #[test]
fn test_should_keep_alive() { 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(Version::HTTP_10, &headers));
assert!(should_keep_alive(Http11, &headers)); assert!(should_keep_alive(Version::HTTP_11, &headers));
headers.set(Connection::close()); headers.insert("connection", ::http::header::HeaderValue::from_static("close"));
assert!(!should_keep_alive(Http10, &headers)); assert!(!should_keep_alive(Version::HTTP_10, &headers));
assert!(!should_keep_alive(Http11, &headers)); assert!(!should_keep_alive(Version::HTTP_11, &headers));
headers.set(Connection::keep_alive()); headers.insert("connection", ::http::header::HeaderValue::from_static("keep-alive"));
assert!(should_keep_alive(Http10, &headers)); assert!(should_keep_alive(Version::HTTP_10, &headers));
assert!(should_keep_alive(Http11, &headers)); assert!(should_keep_alive(Version::HTTP_11, &headers));
} }
#[test] #[test]
fn test_expecting_continue() { fn test_expecting_continue() {
let mut headers = Headers::new(); let mut headers = HeaderMap::new();
assert!(!expecting_continue(Http10, &headers)); assert!(!expecting_continue(Version::HTTP_10, &headers));
assert!(!expecting_continue(Http11, &headers)); assert!(!expecting_continue(Version::HTTP_11, &headers));
headers.set(Expect::Continue); headers.insert("expect", ::http::header::HeaderValue::from_static("100-continue"));
assert!(!expecting_continue(Http10, &headers)); assert!(!expecting_continue(Version::HTTP_10, &headers));
assert!(expecting_continue(Http11, &headers)); assert!(expecting_continue(Version::HTTP_11, &headers));
} }

View File

@@ -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<B = Body> {
method: Method,
uri: Uri,
version: HttpVersion,
headers: Headers,
body: Option<B>,
is_proxy: bool,
remote_addr: Option<SocketAddr>,
}
impl<B> Request<B> {
/// Construct a new Request.
#[inline]
pub fn new(method: Method, uri: Uri) -> Request<B> {
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<B> { &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<SocketAddr> { 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<T: Into<B>>(&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<Body> {
/// 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<B> fmt::Debug for Request<B> {
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<Request> for http::Request<Body> {
fn from(from_req: Request) -> http::Request<Body> {
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<B> From<http::Request<B>> for Request<B> {
fn from(from_req: http::Request<B>) -> Request<B> {
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<SocketAddr>, incoming: RequestHead, body: Option<Body>) -> Request<Body> {
Request {
remote_addr: addr,
..join((incoming, body))
}
}
pub fn split<B>(req: Request<B>) -> (RequestHead, Option<B>) {
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<B>((head, body): (RequestHead, Option<B>)) -> Request<B> {
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<B>(req: &mut Request<B>, 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<Fresh>) -> Vec<u8> {
let req = req.start().unwrap();
let message = req.message;
let mut message = message.downcast::<Http11Message>().ok().unwrap();
message.flush_outgoing().unwrap();
let stream = *message
.into_inner().downcast::<MockStream>().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"));
}
*/
}

Some files were not shown because too many files have changed in this diff Show More