blob: 847138c380229acdf661aed8ec076186830f03a9 [file] [log] [blame]
// Copyright 2015 The tiny-http Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use ascii::{AsciiString, AsciiStr, FromAsciiError};
use std::fmt::{self, Display, Formatter};
use std::str::{FromStr};
use std::cmp::Ordering;
use chrono::*;
/// Status code of a request or response.
#[derive(Eq, PartialEq, Clone, Debug, Ord, PartialOrd)]
pub struct StatusCode(pub u16);
impl StatusCode {
/// Returns the default reason phrase for this status code.
/// For example the status code 404 corresponds to "Not Found".
pub fn default_reason_phrase(&self) -> &'static str {
match self.0 {
100 => "Continue",
101 => "Switching Protocols",
102 => "Processing",
103 => "Early Hints",
200 => "OK",
201 => "Created",
202 => "Accepted",
203 => "Non-Authoritative Information",
204 => "No Content",
205 => "Reset Content",
206 => "Partial Content",
207 => "Multi-Status",
208 => "Already Reported",
226 => "IM Used",
300 => "Multiple Choices",
301 => "Moved Permanently",
302 => "Found",
303 => "See Other",
304 => "Not Modified",
305 => "Use Proxy",
307 => "Temporary Redirect",
308 => "Permanent Redirect",
400 => "Bad Request",
401 => "Unauthorized",
402 => "Payment Required",
403 => "Forbidden",
404 => "Not Found",
405 => "Method Not Allowed",
406 => "Not Acceptable",
407 => "Proxy Authentication Required",
408 => "Request Timeout",
409 => "Conflict",
410 => "Gone",
411 => "Length Required",
412 => "Precondition Failed",
413 => "Payload Too Large",
414 => "URI Too Long",
415 => "Unsupported Media Type",
416 => "Range Not Satisfiable",
417 => "Expectation Failed",
421 => "Misdirected Request",
422 => "Unprocessable Entity",
423 => "Locked",
424 => "Failed Dependency",
426 => "Upgrade Required",
428 => "Precondition Required",
429 => "Too Many Requests",
431 => "Request Header Fields Too Large",
451 => "Unavailable For Legal Reasons",
500 => "Internal Server Error",
501 => "Not Implemented",
502 => "Bad Gateway",
503 => "Service Unavailable",
504 => "Gateway Timeout",
505 => "HTTP Version Not Supported",
506 => "Variant Also Negotiates",
507 => "Insufficient Storage",
508 => "Loop Detected",
510 => "Not Extended",
511 => "Network Authentication Required",
_ => "Unknown"
}
}
}
impl From<i8> for StatusCode {
fn from(in_code: i8) -> StatusCode {
StatusCode(in_code as u16)
}
}
impl From<u8> for StatusCode {
fn from(in_code: u8) -> StatusCode {
StatusCode(in_code as u16)
}
}
impl From<i16> for StatusCode {
fn from(in_code: i16) -> StatusCode {
StatusCode(in_code as u16)
}
}
impl From<u16> for StatusCode {
fn from(in_code: u16) -> StatusCode {
StatusCode(in_code)
}
}
impl From<i32> for StatusCode {
fn from(in_code: i32) -> StatusCode {
StatusCode(in_code as u16)
}
}
impl From<u32> for StatusCode {
fn from(in_code: u32) -> StatusCode {
StatusCode(in_code as u16)
}
}
impl AsRef<u16> for StatusCode {
fn as_ref(&self) -> &u16 {
&self.0
}
}
impl PartialEq<u16> for StatusCode {
fn eq(&self, other: &u16) -> bool {
&self.0 == other
}
}
impl PartialEq<StatusCode> for u16 {
fn eq(&self, other: &StatusCode) -> bool {
self == &other.0
}
}
impl PartialOrd<u16> for StatusCode {
fn partial_cmp(&self, other: &u16) -> Option<Ordering> {
self.0.partial_cmp(other)
}
}
impl PartialOrd<StatusCode> for u16 {
fn partial_cmp(&self, other: &StatusCode) -> Option<Ordering> {
self.partial_cmp(&other.0)
}
}
/// Represents a HTTP header.
#[derive(Debug, Clone)]
pub struct Header {
pub field: HeaderField,
pub value: AsciiString,
}
impl Header {
/// Builds a `Header` from two `Vec<u8>`s or two `&[u8]`s.
///
/// Example:
///
/// ```
/// let header = tiny_http::Header::from_bytes(&b"Content-Type"[..], &b"text/plain"[..]).unwrap();
/// ```
pub fn from_bytes<B1, B2>(header: B1, value: B2) -> Result<Header, ()>
where B1: Into<Vec<u8>> + AsRef<[u8]>,
B2: Into<Vec<u8>> + AsRef<[u8]>
{
let header = try!(HeaderField::from_bytes(header).or(Err(())));
let value = try!(AsciiString::from_ascii(value).or(Err(())));
Ok(Header { field: header, value: value })
}
}
impl FromStr for Header {
type Err = ();
fn from_str(input: &str) -> Result<Header, ()> {
let mut elems = input.splitn(2, ':');
let field = elems.next();
let value = elems.next();
let (field, value) = match (field, value) {
(Some(f), Some(v)) => (f, v),
_ => return Err(())
};
let field = match FromStr::from_str(field) {
Ok(f) => f,
_ => return Err(())
};
let value = try!(AsciiString::from_ascii(value.trim()).map_err(|_| () ));
Ok(Header {
field: field,
value: value,
})
}
}
impl Display for Header {
fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
write!(formatter, "{}: {}", self.field, self.value.as_str())
}
}
/// Field of a header (eg. `Content-Type`, `Content-Length`, etc.)
///
/// Comparaison between two `HeaderField`s ignores case.
#[derive(Debug, Clone)]
pub struct HeaderField(AsciiString);
impl HeaderField {
pub fn from_bytes<B>(bytes: B) -> Result<HeaderField, FromAsciiError<B>> where B: Into<Vec<u8>> + AsRef<[u8]> {
AsciiString::from_ascii(bytes).map(HeaderField)
}
pub fn as_str<'a>(&'a self) -> &'a AsciiStr {
match self { &HeaderField(ref s) => s }
}
pub fn equiv(&self, other: &'static str) -> bool {
other.eq_ignore_ascii_case(self.as_str().as_str())
}
}
impl FromStr for HeaderField {
type Err = ();
fn from_str(s: &str) -> Result<HeaderField, ()> {
AsciiString::from_ascii(s.trim()).map(HeaderField).map_err(|_| () )
}
}
impl Display for HeaderField {
fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
let method = self.as_str();
write!(formatter, "{}", method.as_str())
}
}
impl PartialEq for HeaderField {
fn eq(&self, other: &HeaderField) -> bool {
let self_str: &str = self.as_str().as_ref();
let other_str = other.as_str().as_ref();
self_str.eq_ignore_ascii_case(other_str)
}
}
impl Eq for HeaderField {}
/// HTTP request methods
///
/// As per [RFC 7231](https://tools.ietf.org/html/rfc7231#section-4.1) and
/// [RFC 5789](https://tools.ietf.org/html/rfc5789)
#[derive(Debug, Clone)]
pub enum Method {
/// `GET`
Get,
/// `HEAD`
Head,
/// `POST`
Post,
/// `PUT`
Put,
/// `DELETE`
Delete,
/// `CONNECT`
Connect,
/// `OPTIONS`
Options,
/// `TRACE`
Trace,
/// `PATCH`
Patch,
/// Request methods not standardized by the IETF
NonStandard(AsciiString),
}
impl Method {
pub fn as_str(&self) -> &str {
match *self {
Method::Get => "GET",
Method::Head => "HEAD",
Method::Post => "POST",
Method::Put => "PUT",
Method::Delete => "DELETE",
Method::Connect => "CONNECT",
Method::Options => "OPTIONS",
Method::Trace => "TRACE",
Method::Patch => "PATCH",
Method::NonStandard(ref s) => s.as_str(),
}
}
}
impl FromStr for Method {
type Err = ();
fn from_str(s: &str) -> Result<Method, ()> {
Ok(match s {
s if s.eq_ignore_ascii_case("GET") => Method::Get,
s if s.eq_ignore_ascii_case("HEAD") => Method::Head,
s if s.eq_ignore_ascii_case("POST") => Method::Post,
s if s.eq_ignore_ascii_case("PUT") => Method::Put,
s if s.eq_ignore_ascii_case("DELETE") => Method::Delete,
s if s.eq_ignore_ascii_case("CONNECT") => Method::Connect,
s if s.eq_ignore_ascii_case("OPTIONS") => Method::Options,
s if s.eq_ignore_ascii_case("TRACE") => Method::Trace,
s if s.eq_ignore_ascii_case("PATCH") => Method::Patch,
s => {
let ascii_string = try!(AsciiString::from_ascii(s).map_err(|_| () ));
Method::NonStandard(ascii_string)
}
})
}
}
impl Display for Method {
fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
write!(formatter, "{}", self.as_str())
}
}
impl PartialEq for Method {
fn eq(&self, other: &Method) -> bool {
match (self, other) {
(&Method::NonStandard(ref s1), &Method::NonStandard(ref s2)) =>
s1.as_str().eq_ignore_ascii_case(s2.as_str()),
(&Method::Get, &Method::Get) => true,
(&Method::Head, &Method::Head) => true,
(&Method::Post, &Method::Post) => true,
(&Method::Put, &Method::Put) => true,
(&Method::Delete, &Method::Delete) => true,
(&Method::Connect, &Method::Connect) => true,
(&Method::Options, &Method::Options) => true,
(&Method::Trace, &Method::Trace) => true,
(&Method::Patch, &Method::Patch) => true,
_ => false,
}
}
}
impl Eq for Method {}
/// HTTP version (usually 1.0 or 1.1).
#[derive(Debug, Clone, PartialEq, Eq, Ord)]
pub struct HTTPVersion(pub u8, pub u8);
impl Display for HTTPVersion {
fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
let (major, minor) = match self { &HTTPVersion(m, n) => (m, n) };
write!(formatter, "{}.{}", major, minor)
}
}
impl PartialOrd for HTTPVersion {
fn partial_cmp(&self, other: &HTTPVersion) -> Option<Ordering> {
let (my_major, my_minor) = match self { &HTTPVersion(m, n) => (m, n) };
let (other_major, other_minor) = match other { &HTTPVersion(m, n) => (m, n) };
if my_major != other_major {
return my_major.partial_cmp(&other_major)
}
my_minor.partial_cmp(&other_minor)
}
}
impl PartialEq<(u8, u8)> for HTTPVersion {
fn eq(&self, &(major, minor): &(u8, u8)) -> bool {
self.eq(&HTTPVersion(major, minor))
}
}
impl PartialEq<HTTPVersion> for (u8, u8) {
fn eq(&self, other: &HTTPVersion) -> bool {
let &(major, minor) = self;
HTTPVersion(major, minor).eq(other)
}
}
impl PartialOrd<(u8, u8)> for HTTPVersion {
fn partial_cmp(&self, &(major, minor): &(u8, u8)) -> Option<Ordering> {
self.partial_cmp(&HTTPVersion(major, minor))
}
}
impl PartialOrd<HTTPVersion> for (u8, u8) {
fn partial_cmp(&self, other: &HTTPVersion) -> Option<Ordering> {
let &(major, minor) = self;
HTTPVersion(major, minor).partial_cmp(other)
}
}
impl From<(u8, u8)> for HTTPVersion {
fn from((major, minor): (u8, u8)) -> HTTPVersion {
HTTPVersion(major, minor)
}
}
/// Represents the current date, expressed in RFC 1123 format, e.g. Sun, 06 Nov 1994 08:49:37 GMT
pub struct HTTPDate {
d: DateTime<Utc>
}
impl HTTPDate {
pub fn new() -> HTTPDate {
HTTPDate {d: Utc::now(),}
}
}
impl ToString for HTTPDate {
fn to_string(&self) -> String {
self.d.format("%a, %e %b %Y %H:%M:%S GMT").to_string()
}
}
#[cfg(test)]
mod test {
use super::Header;
#[test]
fn test_parse_header() {
let header: Header = "Content-Type: text/html".parse().unwrap();
assert!(header.field.equiv(&"content-type"));
assert!(header.value.as_str() == "text/html");
assert!("hello world".parse::<Header>().is_err());
}
#[test]
fn test_parse_header_with_doublecolon() {
let header: Header = "Time: 20: 34".parse().unwrap();
assert!(header.field.equiv(&"time"));
assert!(header.value.as_str() == "20: 34");
}
}