#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(test, deny(warnings))]
#![deny(missing_docs)]
#[cfg(feature = "std")] extern crate std as core;
use core::{fmt, result, str, slice};
use std::collections::HashMap;
use iter::Bytes;
mod iter;
mod test;
macro_rules! next {
($bytes:ident) => ({
match $bytes.next() {
Some(b) => b,
None => return Ok(Status::Partial)
}
})
}
macro_rules! expect {
($bytes:ident.next() == $pat:pat => $ret:expr) => {
expect!(next!($bytes) => $pat |? $ret)
};
($e:expr => $pat:pat |? $ret:expr) => {
match $e {
v@$pat => v,
_ => return $ret
}
};
}
macro_rules! complete {
($e:expr) => {
match try!($e) {
Status::Complete(v) => v,
Status::Partial => return Ok(Status::Partial)
}
}
}
#[inline]
fn shrink<T>(slice: &mut &mut [T], len: usize) {
debug_assert!(slice.len() >= len);
let ptr = slice.as_mut_ptr();
*slice = unsafe { slice::from_raw_parts_mut(ptr, len) };
}
#[inline]
fn is_token(b: u8) -> bool {
b > 0x1F && b < 0x7F
}
macro_rules! byte_map {
($($flag:expr,)*) => ([
$($flag != 0,)*
])
}
static HEADER_NAME_MAP: [bool; 256] = byte_map![
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
#[inline]
fn is_header_name_token(b: u8) -> bool {
HEADER_NAME_MAP[b as usize]
}
static HEADER_VALUE_MAP: [bool; 256] = byte_map![
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
];
#[inline]
fn is_header_value_token(b: u8) -> bool {
HEADER_VALUE_MAP[b as usize]
}
macro_rules! space {
($bytes:ident or $err:expr) => ({
expect!($bytes.next() == b' ' => Err($err));
$bytes.slice();
})
}
macro_rules! newline {
($bytes:ident) => ({
match next!($bytes) {
b'\r' => {
expect!($bytes.next() == b'\n' => Err(Error::NewLine));
$bytes.slice();
},
b'\n' => {
$bytes.slice();
},
_ => return Err(Error::NewLine)
}
})
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Error {
HeaderName,
HeaderValue,
NewLine,
Status,
Token,
TooManyHeaders,
Version,
MissingEncapsulated
}
impl Error {
#[inline]
fn description_str(&self) -> &'static str {
match *self {
Error::HeaderName => "invalid header name",
Error::HeaderValue => "invalid header value",
Error::NewLine => "invalid new line",
Error::Status => "invalid response status",
Error::Token => "invalid token",
Error::TooManyHeaders => "too many headers",
Error::Version => "invalid ICAP version",
Error::MissingEncapsulated => "missing encapsulated ICAP header",
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.description_str())
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn description(&self) -> &str {
self.description_str()
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct InvalidChunkSize;
impl fmt::Display for InvalidChunkSize {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("invalid chunk size")
}
}
pub type Result<T> = result::Result<Status<T>, Error>;
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Status<T> {
Complete(T),
Partial
}
impl<T> Status<T> {
#[inline]
pub fn is_complete(&self) -> bool {
match *self {
Status::Complete(..) => true,
Status::Partial => false
}
}
#[inline]
pub fn is_partial(&self) -> bool {
match *self {
Status::Complete(..) => false,
Status::Partial => true
}
}
#[inline]
pub fn unwrap(self) -> T {
match self {
Status::Complete(t) => t,
Status::Partial => panic!("Tried to unwrap Status::Partial")
}
}
}
#[derive(Debug, PartialEq)]
pub struct Request<'headers, 'buf: 'headers> {
pub method: Option<&'buf str>,
pub path: Option<&'buf str>,
pub version: Option<u8>,
pub headers: &'headers mut [Header<'buf>],
pub encapsulated_sections: Option<HashMap<SectionType, Vec<u8>>>
}
impl<'h, 'b> Request<'h, 'b> {
#[inline]
pub fn new(headers: &'h mut [Header<'b>]) -> Request<'h, 'b> {
Request {
method: None,
path: None,
version: None,
headers: headers,
encapsulated_sections: None,
}
}
pub fn parse(&mut self, buf: &'b [u8]) -> Result<usize> {
let orig_len = buf.len();
let mut bytes = Bytes::new(buf);
complete!(skip_empty_lines(&mut bytes));
self.method = Some(complete!(parse_token(&mut bytes)));
self.path = Some(complete!(parse_token(&mut bytes)));
self.version = Some(complete!(parse_version(&mut bytes)));
newline!(bytes);
let len = orig_len - bytes.len();
let headers_len = complete!(parse_headers_iter(&mut self.headers, &mut bytes));
match self.headers.iter().find(|&h| h.name == "Encapsulated") {
Some(h) => {
self.encapsulated_sections = Some(parse_encapsulated(&h.value, &buf[len+headers_len..buf.len()]));
Ok(Status::Complete(orig_len + len - len + headers_len - headers_len))
},
None => {
match self.method {
Some("OPTIONS") => Ok(Status::Complete(orig_len)),
_ => Err(Error::MissingEncapsulated)
}
}
}
}
}
#[inline]
fn skip_empty_lines(bytes: &mut Bytes) -> Result<()> {
loop {
let b = bytes.peek();
match b {
Some(b'\r') => {
bytes.bump();
expect!(bytes.next() == b'\n' => Err(Error::NewLine));
},
Some(b'\n') => {
bytes.bump();
},
Some(..) => {
bytes.slice();
return Ok(Status::Complete(()));
},
None => return Ok(Status::Partial)
}
}
}
#[derive(Debug, PartialEq)]
pub struct Response<'headers, 'buf: 'headers> {
pub version: Option<u8>,
pub code: Option<u16>,
pub reason: Option<&'buf str>,
pub headers: &'headers mut [Header<'buf>]
}
impl<'h, 'b> Response<'h, 'b> {
#[inline]
pub fn new(headers: &'h mut [Header<'b>]) -> Response<'h, 'b> {
Response {
version: None,
code: None,
reason: None,
headers: headers,
}
}
pub fn parse(&mut self, buf: &'b [u8]) -> Result<usize> {
let orig_len = buf.len();
let mut bytes = Bytes::new(buf);
complete!(skip_empty_lines(&mut bytes));
self.version = Some(complete!(parse_version(&mut bytes)));
space!(bytes or Error::Version);
self.code = Some(complete!(parse_code(&mut bytes)));
match next!(bytes) {
b' ' => {
bytes.slice();
self.reason = Some(complete!(parse_reason(&mut bytes)));
},
b'\r' => {
expect!(bytes.next() == b'\n' => Err(Error::Status));
self.reason = Some("");
},
b'\n' => self.reason = Some(""),
_ => return Err(Error::Status),
}
let len = orig_len - bytes.len();
let headers_len = complete!(parse_headers_iter(&mut self.headers, &mut bytes));
Ok(Status::Complete(len + headers_len))
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct Header<'a> {
pub name: &'a str,
pub value: &'a [u8],
}
pub const EMPTY_HEADER: Header<'static> = Header { name: "", value: b"" };
#[inline]
fn parse_version(bytes: &mut Bytes) -> Result<u8> {
if let Some(mut eight) = bytes.next_8() {
expect!(eight._0() => b'I' |? Err(Error::Version));
expect!(eight._1() => b'C' |? Err(Error::Version));
expect!(eight._2() => b'A' |? Err(Error::Version));
expect!(eight._3() => b'P' |? Err(Error::Version));
expect!(eight._4() => b'/' |? Err(Error::Version));
expect!(eight._5() => b'1' |? Err(Error::Version));
expect!(eight._6() => b'.' |? Err(Error::Version));
let v = match eight._7() {
b'0' => 0,
b'1' => 1,
_ => return Err(Error::Version)
};
Ok(Status::Complete(v))
} else {
Ok(Status::Partial)
}
}
fn find_section_start(encapsulated: &str, section: &str) -> Option<usize> {
if let Some(x) = encapsulated.find(section) {
let start = x + section.len();
let end = match encapsulated[start..encapsulated.len()].find(",") {
Some(i) => start + i,
None => encapsulated.len()
};
encapsulated[start..end].parse::<usize>().ok()
}
else {
None
}
}
#[derive(Debug, Hash, PartialEq, Eq)]
pub struct EncapsulationSection {
name: SectionType,
start: usize
}
impl EncapsulationSection {
pub fn new(name: SectionType, start: usize) -> EncapsulationSection {
EncapsulationSection {
name: name,
start: start
}
}
}
impl fmt::Display for EncapsulationSection {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "found {:?} starting at {}", self.name, self.start)
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum SectionType {
NullBody,
RequestHeader,
RequestBody,
ResponseHeader,
ResponseBody,
OptionsBody
}
fn parse_encapsulated(header: &[u8], encapsulated: &[u8]) -> HashMap<SectionType, Vec<u8>> {
let mut sections = Vec::new();
{
let mut add_section = |section, start| sections.push(EncapsulationSection::new(section, start));
if let Ok(encapsulated) = str::from_utf8(header) {
if let Some(section_start) = find_section_start(encapsulated, "req-hdr=") {
add_section(SectionType::RequestHeader, section_start);
}
if let Some(section_start) = find_section_start(encapsulated, "req-body=") {
add_section(SectionType::ResponseBody, section_start);
}
if let Some(section_start) = find_section_start(encapsulated, "null-body=") {
add_section(SectionType::NullBody, section_start);
}
if let Some(section_start) = find_section_start(encapsulated, "res-hdr=") {
add_section(SectionType::ResponseHeader, section_start);
}
if let Some(section_start) = find_section_start(encapsulated, "res-body=") {
add_section(SectionType::ResponseBody, section_start);
}
if let Some(section_start) = find_section_start(encapsulated, "opt-body=") {
add_section(SectionType::OptionsBody, section_start);
}
}
}
sections.sort_by(|a, b| a.start.cmp(&b.start));
let mut hm = HashMap::new();
let mut iter = sections.iter().peekable();
loop {
let x = iter.next();
let y = iter.peek();
if x.is_some() {
let x = x.unwrap();
if y.is_some() {
let y = y.unwrap();
hm.insert(x.name.clone(), encapsulated[x.start..y.start].to_vec());
}
else {
hm.insert(x.name.clone(), encapsulated[x.start..encapsulated.len()].to_vec());
}
}
else {
break;
}
}
hm
}
#[inline]
fn parse_reason<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> {
loop {
let b = next!(bytes);
if b == b'\r' {
expect!(bytes.next() == b'\n' => Err(Error::Status));
return Ok(Status::Complete(unsafe {
str::from_utf8_unchecked(bytes.slice_skip(2))
}));
} else if b == b'\n' {
return Ok(Status::Complete(unsafe {
str::from_utf8_unchecked(bytes.slice_skip(1))
}));
} else if !((b >= 0x20 && b <= 0x7E) || b == b'\t') {
return Err(Error::Status);
}
}
}
#[inline]
fn parse_token<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> {
loop {
let b = next!(bytes);
if b == b' ' {
return Ok(Status::Complete(unsafe {
str::from_utf8_unchecked(bytes.slice_skip(1))
}));
} else if !is_token(b) {
return Err(Error::Token);
}
}
}
#[inline]
fn parse_code(bytes: &mut Bytes) -> Result<u16> {
let hundreds = expect!(bytes.next() == b'0'...b'9' => Err(Error::Status));
let tens = expect!(bytes.next() == b'0'...b'9' => Err(Error::Status));
let ones = expect!(bytes.next() == b'0'...b'9' => Err(Error::Status));
Ok(Status::Complete((hundreds - b'0') as u16 * 100 +
(tens - b'0') as u16 * 10 +
(ones - b'0') as u16))
}
pub fn parse_headers<'b: 'h, 'h>(src: &'b [u8], mut dst: &'h mut [Header<'b>])
-> Result<(usize, &'h [Header<'b>])> {
let mut iter = Bytes::new(src);
let pos = complete!(parse_headers_iter(&mut dst, &mut iter));
Ok(Status::Complete((pos, dst)))
}
#[inline]
fn parse_headers_iter<'a, 'b>(headers: &mut &mut [Header<'a>], bytes: &'b mut Bytes<'a>)
-> Result<usize> {
let mut num_headers: usize = 0;
let mut count: usize = 0;
let mut result = Err(Error::TooManyHeaders);
{
let mut iter = headers.iter_mut();
'headers: loop {
let b = next!(bytes);
if b == b'\r' {
expect!(bytes.next() == b'\n' => Err(Error::NewLine));
result = Ok(Status::Complete(count + bytes.pos()));
break;
} else if b == b'\n' {
result = Ok(Status::Complete(count + bytes.pos()));
break;
} else if !is_header_name_token(b) {
return Err(Error::HeaderName);
}
let header = match iter.next() {
Some(header) => header,
None => break 'headers
};
num_headers += 1;
'name: loop {
let b = next!(bytes);
if b == b':' {
count += bytes.pos();
header.name = unsafe {
str::from_utf8_unchecked(bytes.slice_skip(1))
};
break 'name;
} else if !is_header_name_token(b) {
return Err(Error::HeaderName);
}
}
let mut b;
'value: loop {
'whitespace: loop {
b = next!(bytes);
if b == b' ' || b == b'\t' {
count += bytes.pos();
bytes.slice();
continue 'whitespace;
} else {
if !is_header_value_token(b) {
break 'value;
}
break 'whitespace;
}
}
macro_rules! check {
($bytes:ident, $i:ident) => ({
b = $bytes.$i();
if !is_header_value_token(b) {
break 'value;
}
});
($bytes:ident) => ({
check!($bytes, _0);
check!($bytes, _1);
check!($bytes, _2);
check!($bytes, _3);
check!($bytes, _4);
check!($bytes, _5);
check!($bytes, _6);
check!($bytes, _7);
})
}
while let Some(mut bytes8) = bytes.next_8() {
check!(bytes8);
}
loop {
b = next!(bytes);
if !is_header_value_token(b) {
break 'value;
}
}
}
if b == b'\r' {
expect!(bytes.next() == b'\n' => Err(Error::HeaderValue));
count += bytes.pos();
header.value = bytes.slice_skip(2);
} else if b == b'\n' {
count += bytes.pos();
header.value = bytes.slice_skip(1);
} else {
return Err(Error::HeaderValue);
}
}
}
shrink(headers, num_headers);
result
}
pub fn parse_chunk_size(buf: &[u8])
-> result::Result<Status<(usize, u64)>, InvalidChunkSize> {
const RADIX: u64 = 16;
let mut bytes = Bytes::new(buf);
let mut size = 0;
let mut in_chunk_size = true;
let mut in_ext = false;
let mut count = 0;
loop {
let b = next!(bytes);
match b {
b'0'...b'9' if in_chunk_size => {
if count > 15 {
return Err(InvalidChunkSize);
}
count += 1;
size *= RADIX;
size += (b - b'0') as u64;
},
b'a'...b'f' if in_chunk_size => {
if count > 15 {
return Err(InvalidChunkSize);
}
count += 1;
size *= RADIX;
size += (b + 10 - b'a') as u64;
}
b'A'...b'F' if in_chunk_size => {
if count > 15 {
return Err(InvalidChunkSize);
}
count += 1;
size *= RADIX;
size += (b + 10 - b'A') as u64;
}
b'\r' => {
match next!(bytes) {
b'\n' => break,
_ => return Err(InvalidChunkSize),
}
}
b';' if !in_ext => {
in_ext = true;
in_chunk_size = false;
}
b'\t' | b' ' if !in_ext & !in_chunk_size => {}
b'\t' | b' ' if in_chunk_size => in_chunk_size = false,
_ if in_ext => {}
_ => return Err(InvalidChunkSize),
}
}
Ok(Status::Complete((bytes.pos(), size)))
}