feat(lib): remove extern Url type usage
BREAKING CHANGE: The `Url` type is no longer used. Any instance in the `Client` API has had it replaced with `hyper::Uri`. This also means `Error::Uri` has changed types to `hyper::error::UriError`. The type `hyper::header::parsing::HTTP_VALUE` has been made private, as an implementation detail. The function `http_percent_encoding` should be used instead.
This commit is contained in:
@@ -28,7 +28,7 @@ fn get_one_at_a_time(b: &mut test::Bencher) {
|
||||
|
||||
let client = hyper::Client::new(&handle);
|
||||
|
||||
let url: hyper::Url = format!("http://{}/get", addr).parse().unwrap();
|
||||
let url: hyper::Uri = format!("http://{}/get", addr).parse().unwrap();
|
||||
|
||||
b.bytes = 160 * 2 + PHRASE.len() as u64;
|
||||
b.iter(move || {
|
||||
@@ -51,7 +51,7 @@ fn post_one_at_a_time(b: &mut test::Bencher) {
|
||||
|
||||
let client = hyper::Client::new(&handle);
|
||||
|
||||
let url: hyper::Url = format!("http://{}/get", addr).parse().unwrap();
|
||||
let url: hyper::Uri = format!("http://{}/get", addr).parse().unwrap();
|
||||
|
||||
let post = "foo bar baz quux";
|
||||
b.bytes = 180 * 2 + post.len() as u64 + PHRASE.len() as u64;
|
||||
|
||||
@@ -24,8 +24,8 @@ fn main() {
|
||||
}
|
||||
};
|
||||
|
||||
let url = hyper::Url::parse(&url).unwrap();
|
||||
if url.scheme() != "http" {
|
||||
let url = url.parse::<hyper::Uri>().unwrap();
|
||||
if url.scheme() != Some("http") {
|
||||
println!("This example only works with 'http' URLs.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use tokio::reactor::Handle;
|
||||
use tokio::net::{TcpStream, TcpStreamNew};
|
||||
use tokio_service::Service;
|
||||
use Url;
|
||||
use Uri;
|
||||
|
||||
use super::dns;
|
||||
|
||||
@@ -15,25 +15,25 @@ use super::dns;
|
||||
///
|
||||
/// This trait is not implemented directly, and only exists to make
|
||||
/// the intent clearer. A connector should implement `Service` with
|
||||
/// `Request=Url` and `Response: Io` instead.
|
||||
pub trait Connect: Service<Request=Url, Error=io::Error> + 'static {
|
||||
/// `Request=Uri` and `Response: Io` instead.
|
||||
pub trait Connect: Service<Request=Uri, Error=io::Error> + 'static {
|
||||
/// The connected Io Stream.
|
||||
type Output: AsyncRead + AsyncWrite + 'static;
|
||||
/// A Future that will resolve to the connected Stream.
|
||||
type Future: Future<Item=Self::Output, Error=io::Error> + 'static;
|
||||
/// Connect to a remote address.
|
||||
fn connect(&self, Url) -> <Self as Connect>::Future;
|
||||
fn connect(&self, Uri) -> <Self as Connect>::Future;
|
||||
}
|
||||
|
||||
impl<T> Connect for T
|
||||
where T: Service<Request=Url, Error=io::Error> + 'static,
|
||||
where T: Service<Request=Uri, Error=io::Error> + 'static,
|
||||
T::Response: AsyncRead + AsyncWrite,
|
||||
T::Future: Future<Error=io::Error>,
|
||||
{
|
||||
type Output = T::Response;
|
||||
type Future = T::Future;
|
||||
|
||||
fn connect(&self, url: Url) -> <Self as Connect>::Future {
|
||||
fn connect(&self, url: Uri) -> <Self as Connect>::Future {
|
||||
self.call(url)
|
||||
}
|
||||
}
|
||||
@@ -66,21 +66,21 @@ impl fmt::Debug for HttpConnector {
|
||||
}
|
||||
|
||||
impl Service for HttpConnector {
|
||||
type Request = Url;
|
||||
type Request = Uri;
|
||||
type Response = TcpStream;
|
||||
type Error = io::Error;
|
||||
type Future = HttpConnecting;
|
||||
|
||||
fn call(&self, url: Url) -> Self::Future {
|
||||
fn call(&self, url: Uri) -> Self::Future {
|
||||
debug!("Http::connect({:?})", url);
|
||||
let host = match url.host_str() {
|
||||
let host = match url.host() {
|
||||
Some(s) => s,
|
||||
None => return HttpConnecting {
|
||||
state: State::Error(Some(io::Error::new(io::ErrorKind::InvalidInput, "invalid url"))),
|
||||
handle: self.handle.clone(),
|
||||
},
|
||||
};
|
||||
let port = url.port_or_known_default().unwrap_or(80);
|
||||
let port = url.port().unwrap_or(80);
|
||||
|
||||
HttpConnecting {
|
||||
state: State::Resolving(self.dns.resolve(host.into(), port)),
|
||||
@@ -185,13 +185,12 @@ impl<S: SslClient> HttpsConnector<S> {
|
||||
mod tests {
|
||||
use std::io;
|
||||
use tokio::reactor::Core;
|
||||
use Url;
|
||||
use super::{Connect, HttpConnector};
|
||||
|
||||
#[test]
|
||||
fn test_non_http_url() {
|
||||
let mut core = Core::new().unwrap();
|
||||
let url = Url::parse("file:///home/sean/foo.txt").unwrap();
|
||||
let url = "/foo/bar?baz".parse().unwrap();
|
||||
let connector = HttpConnector::new(1, &core.handle());
|
||||
|
||||
assert_eq!(core.run(connector.connect(url)).unwrap_err().kind(), io::ErrorKind::InvalidInput);
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::{Poll, Async, Future, Stream};
|
||||
use futures::{future, Poll, Async, Future, Stream};
|
||||
use futures::unsync::oneshot;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use tokio::reactor::Handle;
|
||||
@@ -24,7 +24,7 @@ use header::{Headers, Host};
|
||||
use http::{self, TokioBody};
|
||||
use method::Method;
|
||||
use self::pool::{Pool, Pooled};
|
||||
use Url;
|
||||
use uri::{self, Uri};
|
||||
|
||||
pub use self::connect::{HttpConnector, Connect};
|
||||
pub use self::request::Request;
|
||||
@@ -95,7 +95,7 @@ where C: Connect,
|
||||
{
|
||||
/// Send a GET Request using this Client.
|
||||
#[inline]
|
||||
pub fn get(&self, url: Url) -> FutureResponse {
|
||||
pub fn get(&self, url: Uri) -> FutureResponse {
|
||||
self.request(Request::new(Method::Get, url))
|
||||
}
|
||||
|
||||
@@ -135,18 +135,30 @@ where C: Connect,
|
||||
type Future = FutureResponse;
|
||||
|
||||
fn call(&self, req: Self::Request) -> Self::Future {
|
||||
let url = req.url().clone();
|
||||
let url = req.uri().clone();
|
||||
let domain = match uri::scheme_and_authority(&url) {
|
||||
Some(uri) => uri,
|
||||
None => {
|
||||
return FutureResponse(Box::new(future::err(::Error::Io(
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"invalid URI for Client Request"
|
||||
)
|
||||
))));
|
||||
}
|
||||
};
|
||||
let host = Host::new(domain.host().expect("authority implies host").to_owned(), domain.port());
|
||||
let (mut head, body) = request::split(req);
|
||||
let mut headers = Headers::new();
|
||||
headers.set(Host::new(url.host_str().unwrap().to_owned(), url.port()));
|
||||
headers.set(host);
|
||||
headers.extend(head.headers.iter());
|
||||
head.headers = headers;
|
||||
|
||||
let checkout = self.pool.checkout(&url[..::url::Position::BeforePath]);
|
||||
let checkout = self.pool.checkout(domain.as_ref());
|
||||
let connect = {
|
||||
let handle = self.handle.clone();
|
||||
let pool = self.pool.clone();
|
||||
let pool_key = Rc::new(url[..::url::Position::BeforePath].to_owned());
|
||||
let pool_key = Rc::new(domain.to_string());
|
||||
self.connector.connect(url)
|
||||
.map(move |io| {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
use std::fmt;
|
||||
|
||||
use Url;
|
||||
|
||||
use header::Headers;
|
||||
use http::{Body, RequestHead};
|
||||
use method::Method;
|
||||
use uri::Uri;
|
||||
use uri::{self, Uri};
|
||||
use version::HttpVersion;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// A client request to a remote server.
|
||||
pub struct Request<B = Body> {
|
||||
method: Method,
|
||||
url: Url,
|
||||
uri: Uri,
|
||||
version: HttpVersion,
|
||||
headers: Headers,
|
||||
body: Option<B>,
|
||||
@@ -22,10 +19,10 @@ pub struct Request<B = Body> {
|
||||
impl<B> Request<B> {
|
||||
/// Construct a new Request.
|
||||
#[inline]
|
||||
pub fn new(method: Method, url: Url) -> Request<B> {
|
||||
pub fn new(method: Method, uri: Uri) -> Request<B> {
|
||||
Request {
|
||||
method: method,
|
||||
url: url,
|
||||
uri: uri,
|
||||
version: HttpVersion::default(),
|
||||
headers: Headers::new(),
|
||||
body: None,
|
||||
@@ -33,9 +30,9 @@ impl<B> Request<B> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Read the Request Url.
|
||||
/// Read the Request Uri.
|
||||
#[inline]
|
||||
pub fn url(&self) -> &Url { &self.url }
|
||||
pub fn uri(&self) -> &Uri { &self.uri }
|
||||
|
||||
/// Read the Request Version.
|
||||
#[inline]
|
||||
@@ -57,9 +54,9 @@ impl<B> Request<B> {
|
||||
#[inline]
|
||||
pub fn headers_mut(&mut self) -> &mut Headers { &mut self.headers }
|
||||
|
||||
/// Set the `Url` of this request.
|
||||
/// Set the `Uri` of this request.
|
||||
#[inline]
|
||||
pub fn set_url(&mut self, url: Url) { self.url = url; }
|
||||
pub fn set_uri(&mut self, uri: Uri) { self.uri = uri; }
|
||||
|
||||
/// Set the `HttpVersion` of this request.
|
||||
#[inline]
|
||||
@@ -81,7 +78,7 @@ 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("url", &self.url)
|
||||
.field("uri", &self.uri)
|
||||
.field("version", &self.version)
|
||||
.field("headers", &self.headers)
|
||||
.finish()
|
||||
@@ -90,9 +87,9 @@ impl<B> fmt::Debug for Request<B> {
|
||||
|
||||
pub fn split<B>(req: Request<B>) -> (RequestHead, Option<B>) {
|
||||
let uri = if req.is_proxy {
|
||||
Uri::from(req.url)
|
||||
req.uri
|
||||
} else {
|
||||
Uri::from_str(&req.url[::url::Position::BeforePath..::url::Position::AfterQuery]).expect("url is not uri")
|
||||
uri::origin_form(&req.uri)
|
||||
};
|
||||
let head = RequestHead {
|
||||
subject: ::http::RequestLine(req.method, uri),
|
||||
|
||||
31
src/error.rs
31
src/error.rs
@@ -6,7 +6,8 @@ use std::str::Utf8Error;
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
use httparse;
|
||||
use url;
|
||||
|
||||
pub use uri::UriError;
|
||||
|
||||
use self::Error::{
|
||||
Method,
|
||||
@@ -21,8 +22,6 @@ use self::Error::{
|
||||
Utf8
|
||||
};
|
||||
|
||||
pub use url::ParseError;
|
||||
|
||||
/// Result type often returned from methods that can have hyper `Error`s.
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
|
||||
@@ -32,7 +31,7 @@ pub enum Error {
|
||||
/// An invalid `Method`, such as `GE,T`.
|
||||
Method,
|
||||
/// An invalid `Uri`, such as `exam ple.domain`.
|
||||
Uri(url::ParseError),
|
||||
Uri(UriError),
|
||||
/// An invalid `HttpVersion`, such as `HTP/1.1`
|
||||
Version,
|
||||
/// An invalid `Header`.
|
||||
@@ -102,15 +101,15 @@ impl StdError for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IoError> for Error {
|
||||
fn from(err: IoError) -> Error {
|
||||
Io(err)
|
||||
impl From<UriError> for Error {
|
||||
fn from(err: UriError) -> Error {
|
||||
Uri(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<url::ParseError> for Error {
|
||||
fn from(err: url::ParseError) -> Error {
|
||||
Uri(err)
|
||||
impl From<IoError> for Error {
|
||||
fn from(err: IoError) -> Error {
|
||||
Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +144,6 @@ mod tests {
|
||||
use std::error::Error as StdError;
|
||||
use std::io;
|
||||
use httparse;
|
||||
use url;
|
||||
use super::Error;
|
||||
use super::Error::*;
|
||||
|
||||
@@ -185,7 +183,6 @@ mod tests {
|
||||
fn test_from() {
|
||||
|
||||
from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => Io(..));
|
||||
from_and_cause!(url::ParseError::EmptyHost => Uri(..));
|
||||
|
||||
from!(httparse::Error::HeaderName => Header);
|
||||
from!(httparse::Error::HeaderName => Header);
|
||||
@@ -196,14 +193,4 @@ mod tests {
|
||||
from!(httparse::Error::TooManyHeaders => TooLarge);
|
||||
from!(httparse::Error::Version => Version);
|
||||
}
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
#[test]
|
||||
fn test_from_ssl() {
|
||||
use openssl::ssl::error::SslError;
|
||||
|
||||
from!(SslError::StreamError(
|
||||
io::Error::new(io::ErrorKind::Other, "ssl negotiation")) => Io(..));
|
||||
from_and_cause!(SslError::SslSessionClosed => Ssl(..));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,9 @@
|
||||
use language_tags::LanguageTag;
|
||||
use std::fmt;
|
||||
use unicase::UniCase;
|
||||
use url::percent_encoding;
|
||||
|
||||
use header::{Header, Raw, parsing};
|
||||
use header::parsing::{parse_extended_value, HTTP_VALUE};
|
||||
use header::parsing::{parse_extended_value, http_percent_encode};
|
||||
use header::shared::Charset;
|
||||
|
||||
/// The implied disposition of the content of the HTTP body
|
||||
@@ -182,8 +181,7 @@ impl fmt::Display for ContentDisposition {
|
||||
try!(write!(f, "{}", lang));
|
||||
};
|
||||
try!(write!(f, "'"));
|
||||
try!(f.write_str(
|
||||
&percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string()))
|
||||
try!(http_percent_encode(f, bytes))
|
||||
}
|
||||
},
|
||||
DispositionParam::Ext(ref k, ref v) => try!(write!(f, "; {}=\"{}\"", k, v)),
|
||||
|
||||
@@ -9,6 +9,7 @@ use url::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() {
|
||||
@@ -132,25 +133,11 @@ pub fn parse_extended_value(val: &str) -> ::Result<ExtendedValue> {
|
||||
})
|
||||
}
|
||||
|
||||
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] | {
|
||||
' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?',
|
||||
'[', '\\', ']', '{', '}'
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for HTTP_VALUE {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.pad("HTTP_VALUE")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ExtendedValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let encoded_value =
|
||||
percent_encoding::percent_encode(&self.value[..], HTTP_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 {
|
||||
@@ -159,6 +146,33 @@ impl Display for ExtendedValue {
|
||||
}
|
||||
}
|
||||
|
||||
/// Percent encode a sequence of bytes with a character set defined in
|
||||
/// 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 std::fmt;
|
||||
use url::percent_encoding;
|
||||
|
||||
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] | {
|
||||
' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?',
|
||||
'[', '\\', ']', '{', '}'
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for HTTP_VALUE {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.pad("HTTP_VALUE")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use header::shared::Charset;
|
||||
|
||||
@@ -61,7 +61,7 @@ impl Http1Transaction for ServerTransaction {
|
||||
let path = unsafe { ByteStr::from_utf8_unchecked(path) };
|
||||
let subject = RequestLine(
|
||||
method,
|
||||
try!(::uri::from_mem_str(path)),
|
||||
try!(::uri::from_byte_str(path)),
|
||||
);
|
||||
|
||||
headers.extend(HeadersAsBytesIter {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use std::ops::Deref;
|
||||
use std::str;
|
||||
|
||||
use bytes::Bytes;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ByteStr(Bytes);
|
||||
|
||||
impl ByteStr {
|
||||
@@ -10,7 +11,35 @@ impl 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()) }
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![doc(html_root_url = "https://hyperium.github.io/hyper/")]
|
||||
#![deny(missing_docs)]
|
||||
//#![deny(warnings)]
|
||||
#![deny(warnings)]
|
||||
#![deny(missing_debug_implementations)]
|
||||
#![cfg_attr(all(test, feature = "nightly"), feature(test))]
|
||||
|
||||
@@ -35,7 +35,6 @@ extern crate test;
|
||||
|
||||
|
||||
pub use uri::Uri;
|
||||
pub use url::Url;
|
||||
pub use client::Client;
|
||||
pub use error::{Result, Error};
|
||||
pub use header::Headers;
|
||||
|
||||
148
src/uri.rs
148
src/uri.rs
@@ -1,13 +1,8 @@
|
||||
use std::borrow::Cow;
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt::{Display, self};
|
||||
use std::ops::Deref;
|
||||
use std::str::{self, FromStr};
|
||||
|
||||
use http::ByteStr;
|
||||
use Url;
|
||||
use url::ParseError as UrlError;
|
||||
|
||||
use Error;
|
||||
|
||||
/// The Request-URI of a Request's StartLine.
|
||||
///
|
||||
@@ -32,9 +27,9 @@ use Error;
|
||||
/// | | | | |
|
||||
/// scheme authority path query fragment
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Hash)]
|
||||
pub struct Uri {
|
||||
source: InternalUri,
|
||||
source: ByteStr,
|
||||
scheme_end: Option<usize>,
|
||||
authority_end: Option<usize>,
|
||||
query: Option<usize>,
|
||||
@@ -43,20 +38,23 @@ pub struct Uri {
|
||||
|
||||
impl Uri {
|
||||
/// Parse a string into a `Uri`.
|
||||
fn new(s: InternalUri) -> Result<Uri, Error> {
|
||||
fn new(s: ByteStr) -> Result<Uri, UriError> {
|
||||
if s.len() == 0 {
|
||||
Err(Error::Uri(UrlError::RelativeUrlWithoutBase))
|
||||
Err(UriError(ErrorKind::Empty))
|
||||
} else if s.as_bytes() == b"*" {
|
||||
// asterisk-form
|
||||
Ok(Uri {
|
||||
source: InternalUri::Cow("*".into()),
|
||||
source: ByteStr::from_static("*"),
|
||||
scheme_end: None,
|
||||
authority_end: None,
|
||||
query: None,
|
||||
fragment: None,
|
||||
})
|
||||
} else if s.as_bytes() == b"/" {
|
||||
// shortcut for '/'
|
||||
Ok(Uri::default())
|
||||
} else if s.as_bytes()[0] == b'/' {
|
||||
// origin-form
|
||||
let query = parse_query(&s);
|
||||
let fragment = parse_fragment(&s);
|
||||
Ok(Uri {
|
||||
@@ -67,22 +65,14 @@ impl Uri {
|
||||
fragment: fragment,
|
||||
})
|
||||
} else if s.contains("://") {
|
||||
// absolute-form
|
||||
let scheme = parse_scheme(&s);
|
||||
let auth = parse_authority(&s);
|
||||
if let Some(end) = scheme {
|
||||
match &s[..end] {
|
||||
"ftp" | "gopher" | "http" | "https" | "ws" | "wss" => {},
|
||||
"blob" | "file" => return Err(Error::Method),
|
||||
_ => return Err(Error::Method),
|
||||
}
|
||||
match auth {
|
||||
Some(a) => {
|
||||
if (end + 3) == a {
|
||||
return Err(Error::Method);
|
||||
}
|
||||
},
|
||||
None => return Err(Error::Method),
|
||||
}
|
||||
let scheme_end = scheme.expect("just checked for ':' above");
|
||||
let auth_end = auth.expect("just checked for ://");
|
||||
if scheme_end + 3 == auth_end {
|
||||
// authority was empty
|
||||
return Err(UriError(ErrorKind::MissingAuthority));
|
||||
}
|
||||
let query = parse_query(&s);
|
||||
let fragment = parse_fragment(&s);
|
||||
@@ -94,8 +84,10 @@ impl Uri {
|
||||
fragment: fragment,
|
||||
})
|
||||
} else if (s.contains("/") || s.contains("?")) && !s.contains("://") {
|
||||
return Err(Error::Method)
|
||||
// last possibility is authority-form, above are illegal characters
|
||||
return Err(UriError(ErrorKind::Malformed))
|
||||
} else {
|
||||
// authority-form
|
||||
let len = s.len();
|
||||
Ok(Uri {
|
||||
source: s,
|
||||
@@ -220,25 +212,12 @@ fn parse_fragment(s: &str) -> Option<usize> {
|
||||
}
|
||||
|
||||
impl FromStr for Uri {
|
||||
type Err = Error;
|
||||
type Err = UriError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Uri, Error> {
|
||||
fn from_str(s: &str) -> Result<Uri, UriError> {
|
||||
//TODO: refactor such that the to_owned() is only required at the end
|
||||
//of successful parsing, so an Err doesn't needlessly clone the string.
|
||||
Uri::new(InternalUri::Cow(Cow::Owned(s.to_owned())))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Url> for Uri {
|
||||
fn from(url: Url) -> Uri {
|
||||
let s = InternalUri::Cow(Cow::Owned(url.into_string()));
|
||||
// TODO: we could build the indices with some simple subtraction,
|
||||
// instead of re-parsing an already parsed string.
|
||||
// For example:
|
||||
// let scheme_end = url[..url::Position::AfterScheme].as_ptr() as usize
|
||||
// - url.as_str().as_ptr() as usize;
|
||||
// etc
|
||||
Uri::new(s).expect("Uri::From<Url> failed")
|
||||
Uri::new(ByteStr::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +238,7 @@ impl AsRef<str> for Uri {
|
||||
impl Default for Uri {
|
||||
fn default() -> Uri {
|
||||
Uri {
|
||||
source: InternalUri::Cow("/".into()),
|
||||
source: ByteStr::from_static("/"),
|
||||
scheme_end: None,
|
||||
authority_end: None,
|
||||
query: None,
|
||||
@@ -280,34 +259,67 @@ impl Display for Uri {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_mem_str(s: ByteStr) -> Result<Uri, Error> {
|
||||
Uri::new(InternalUri::Shared(s))
|
||||
pub fn from_byte_str(s: ByteStr) -> Result<Uri, UriError> {
|
||||
Uri::new(s)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum InternalUri {
|
||||
Cow(Cow<'static, str>),
|
||||
Shared(ByteStr),
|
||||
pub fn scheme_and_authority(uri: &Uri) -> Option<Uri> {
|
||||
if uri.scheme_end.is_some() {
|
||||
Some(Uri {
|
||||
source: uri.source.slice_to(uri.authority_end.expect("scheme without authority")),
|
||||
scheme_end: uri.scheme_end,
|
||||
authority_end: uri.authority_end,
|
||||
query: None,
|
||||
fragment: None,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl InternalUri {
|
||||
fn as_str(&self) -> &str {
|
||||
match *self {
|
||||
InternalUri::Cow(ref s) => s.as_ref(),
|
||||
InternalUri::Shared(ref m) => m.as_str(),
|
||||
pub fn origin_form(uri: &Uri) -> Uri {
|
||||
let start = uri.authority_end.unwrap_or(uri.scheme_end.unwrap_or(0));
|
||||
let end = if let Some(f) = uri.fragment {
|
||||
uri.source.len() - f - 1
|
||||
} else {
|
||||
uri.source.len()
|
||||
};
|
||||
Uri {
|
||||
source: uri.source.slice(start, end),
|
||||
scheme_end: None,
|
||||
authority_end: None,
|
||||
query: uri.query,
|
||||
fragment: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// An error parsing a `Uri`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UriError(ErrorKind);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum ErrorKind {
|
||||
Empty,
|
||||
Malformed,
|
||||
MissingAuthority,
|
||||
}
|
||||
|
||||
impl fmt::Display for UriError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.pad(self.description())
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for UriError {
|
||||
fn description(&self) -> &str {
|
||||
match self.0 {
|
||||
ErrorKind::Empty => "empty Uri string",
|
||||
ErrorKind::Malformed => "invalid character in Uri authority",
|
||||
ErrorKind::MissingAuthority => "absolute Uri missing authority segment",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for InternalUri {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
macro_rules! test_parse {
|
||||
(
|
||||
$test_name:ident,
|
||||
@@ -443,13 +455,3 @@ fn test_uri_parse_error() {
|
||||
err("localhost/");
|
||||
err("localhost?key=val");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uri_from_url() {
|
||||
let uri = Uri::from(Url::parse("http://test.com/nazghul?test=3").unwrap());
|
||||
assert_eq!(uri.path(), "/nazghul");
|
||||
assert_eq!(uri.authority(), Some("test.com"));
|
||||
assert_eq!(uri.scheme(), Some("http"));
|
||||
assert_eq!(uri.query(), Some("test=3"));
|
||||
assert_eq!(uri.as_ref(), "http://test.com/nazghul?test=3");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user