blob: 5fa1e9bb067537772ae65b8ecee9fb2309c743c7 [file] [log] [blame]
// Deprecated in 1.26, needed until our minimum version is >=1.23.
#[allow(unused, deprecated)]
use std::ascii::AsciiExt;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::str::FromStr;
use bytes::Bytes;
use byte_str::ByteStr;
use super::{ErrorKind, InvalidUri, InvalidUriBytes};
/// Represents the scheme component of a URI
#[derive(Clone)]
pub struct Scheme {
pub(super) inner: Scheme2,
}
#[derive(Clone, Debug)]
pub(super) enum Scheme2<T = Box<ByteStr>> {
None,
Standard(Protocol),
Other(T),
}
#[derive(Copy, Clone, Debug)]
pub(super) enum Protocol {
Http,
Https,
}
impl Scheme {
/// HTTP protocol scheme
pub const HTTP: Scheme = Scheme {
inner: Scheme2::Standard(Protocol::Http),
};
/// HTTP protocol over TLS.
pub const HTTPS: Scheme = Scheme {
inner: Scheme2::Standard(Protocol::Https),
};
/// Attempt to convert a `Scheme` from `Bytes`
///
/// This function will be replaced by a `TryFrom` implementation once the
/// trait lands in stable.
///
/// # Examples
///
/// ```
/// # extern crate http;
/// # use http::uri::*;
/// extern crate bytes;
///
/// use bytes::Bytes;
///
/// # pub fn main() {
/// let bytes = Bytes::from("http");
/// let scheme = Scheme::from_shared(bytes).unwrap();
///
/// assert_eq!(scheme.as_str(), "http");
/// # }
/// ```
pub fn from_shared(s: Bytes) -> Result<Self, InvalidUriBytes> {
use self::Scheme2::*;
match Scheme2::parse_exact(&s[..]).map_err(InvalidUriBytes)? {
None => Err(ErrorKind::InvalidScheme.into()),
Standard(p) => Ok(Standard(p).into()),
Other(_) => {
let b = unsafe { ByteStr::from_utf8_unchecked(s) };
Ok(Other(Box::new(b)).into())
}
}
}
pub(super) fn empty() -> Self {
Scheme {
inner: Scheme2::None,
}
}
/// Return a str representation of the scheme
///
/// # Examples
///
/// ```
/// # use http::uri::*;
/// let scheme: Scheme = "http".parse().unwrap();
/// assert_eq!(scheme.as_str(), "http");
/// ```
#[inline]
pub fn as_str(&self) -> &str {
use self::Scheme2::*;
use self::Protocol::*;
match self.inner {
Standard(Http) => "http",
Standard(Https) => "https",
Other(ref v) => &v[..],
None => unreachable!(),
}
}
/// Converts this `Scheme` back to a sequence of bytes
#[inline]
pub fn into_bytes(self) -> Bytes {
self.into()
}
}
impl FromStr for Scheme {
type Err = InvalidUri;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use self::Scheme2::*;
match Scheme2::parse_exact(s.as_bytes())? {
None => Err(ErrorKind::InvalidScheme.into()),
Standard(p) => Ok(Standard(p).into()),
Other(_) => {
Ok(Other(Box::new(s.into())).into())
}
}
}
}
impl From<Scheme> for Bytes {
#[inline]
fn from(src: Scheme) -> Self {
use self::Scheme2::*;
use self::Protocol::*;
match src.inner {
None => Bytes::new(),
Standard(Http) => Bytes::from_static(b"http"),
Standard(Https) => Bytes::from_static(b"https"),
Other(v) => (*v).into(),
}
}
}
impl fmt::Debug for Scheme {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), f)
}
}
impl fmt::Display for Scheme {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl AsRef<str> for Scheme {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl PartialEq for Scheme {
fn eq(&self, other: &Scheme) -> bool {
use self::Protocol::*;
use self::Scheme2::*;
match (&self.inner, &other.inner) {
(&Standard(Http), &Standard(Http)) => true,
(&Standard(Https), &Standard(Https)) => true,
(&Other(ref a), &Other(ref b)) => a.eq_ignore_ascii_case(b),
(&None, _) | (_, &None) => unreachable!(),
_ => false,
}
}
}
impl Eq for Scheme {}
/// Case-insensitive equality
///
/// # Examples
///
/// ```
/// # use http::uri::Scheme;
/// let scheme: Scheme = "HTTP".parse().unwrap();
/// assert_eq!(scheme, *"http");
/// ```
impl PartialEq<str> for Scheme {
fn eq(&self, other: &str) -> bool {
self.as_str().eq_ignore_ascii_case(other)
}
}
/// Case-insensitive equality
impl PartialEq<Scheme> for str {
fn eq(&self, other: &Scheme) -> bool {
other == self
}
}
/// Case-insensitive hashing
impl Hash for Scheme {
fn hash<H>(&self, state: &mut H) where H: Hasher {
match self.inner {
Scheme2::None => (),
Scheme2::Standard(Protocol::Http) => state.write_u8(1),
Scheme2::Standard(Protocol::Https) => state.write_u8(2),
Scheme2::Other(ref other) => {
other.len().hash(state);
for &b in other.as_bytes() {
state.write_u8(b.to_ascii_lowercase());
}
}
}
}
}
impl<T> Scheme2<T> {
pub(super) fn is_none(&self) -> bool {
match *self {
Scheme2::None => true,
_ => false,
}
}
}
// Require the scheme to not be too long in order to enable further
// optimizations later.
const MAX_SCHEME_LEN: usize = 64;
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
//
const SCHEME_CHARS: [u8; 256] = [
// 0 1 2 3 4 5 6 7 8 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3x
0, 0, 0, b'+', 0, b'-', b'.', 0, b'0', b'1', // 4x
b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b':', 0, // 5x
0, 0, 0, 0, 0, b'A', b'B', b'C', b'D', b'E', // 6x
b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', // 7x
b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', // 8x
b'Z', 0, 0, 0, 0, 0, 0, b'a', b'b', b'c', // 9x
b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', // 10x
b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', // 11x
b'x', b'y', b'z', 0, 0, 0, b'~', 0, 0, 0, // 12x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 13x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 14x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 15x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 17x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 18x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 19x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 21x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 22x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 23x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 24x
0, 0, 0, 0, 0, 0 // 25x
];
impl Scheme2<usize> {
fn parse_exact(s: &[u8]) -> Result<Scheme2<()>, InvalidUri> {
match s {
b"http" => Ok(Protocol::Http.into()),
b"https" => Ok(Protocol::Https.into()),
_ => {
if s.len() > MAX_SCHEME_LEN {
return Err(ErrorKind::SchemeTooLong.into());
}
for &b in s {
match SCHEME_CHARS[b as usize] {
b':' => {
// Don't want :// here
return Err(ErrorKind::InvalidScheme.into());
}
0 => {
return Err(ErrorKind::InvalidScheme.into());
}
_ => {}
}
}
Ok(Scheme2::Other(()))
}
}
}
pub(super) fn parse(s: &[u8]) -> Result<Scheme2<usize>, InvalidUri> {
if s.len() >= 7 {
// Check for HTTP
if s[..7].eq_ignore_ascii_case(b"http://") {
// Prefix will be striped
return Ok(Protocol::Http.into());
}
}
if s.len() >= 8 {
// Check for HTTPs
if s[..8].eq_ignore_ascii_case(b"https://") {
return Ok(Protocol::Https.into());
}
}
if s.len() > 3 {
for i in 0..s.len() {
let b = s[i];
if i == MAX_SCHEME_LEN {
return Err(ErrorKind::SchemeTooLong.into());
}
match SCHEME_CHARS[b as usize] {
b':' => {
// Not enough data remaining
if s.len() < i + 3 {
break;
}
// Not a scheme
if &s[i+1..i+3] != b"//" {
break;
}
// Return scheme
return Ok(Scheme2::Other(i));
}
// Invald scheme character, abort
0 => break,
_ => {}
}
}
}
Ok(Scheme2::None)
}
}
impl Protocol {
pub(super) fn len(&self) -> usize {
match *self {
Protocol::Http => 4,
Protocol::Https => 5,
}
}
}
impl<T> From<Protocol> for Scheme2<T> {
fn from(src: Protocol) -> Self {
Scheme2::Standard(src)
}
}
#[doc(hidden)]
impl From<Scheme2> for Scheme {
fn from(src: Scheme2) -> Self {
Scheme { inner: src }
}
}