diff --git a/src/common/mod.rs b/src/common/mod.rs index 00236f6..a5804a1 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -142,7 +142,7 @@ impl Body { } /// Supported HTTP Methods. -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Method { /// GET Method. Get, diff --git a/src/response.rs b/src/response.rs index 2916c99..433bc65 100644 --- a/src/response.rs +++ b/src/response.rs @@ -3,9 +3,10 @@ use std::io::{Error as WriteError, Write}; -use crate::common::ascii::{COLON, CR, LF, SP}; -use crate::common::headers::{Header, MediaType}; -use crate::common::{Body, Version}; +use ascii::{COLON, CR, LF, SP}; +use common::{Body, Version}; +use headers::{Header, MediaType}; +use Method; /// Wrapper over a response status code. /// @@ -23,6 +24,8 @@ pub enum StatusCode { BadRequest, /// 404, Not Found NotFound, + /// 405, Method Not Allowed + MethodNotAllowed, /// 500, Internal Server Error InternalServerError, /// 501, Not Implemented @@ -40,6 +43,7 @@ impl StatusCode { Self::NoContent => b"204", Self::BadRequest => b"400", Self::NotFound => b"404", + Self::MethodNotAllowed => b"405", Self::InternalServerError => b"500", Self::NotImplemented => b"501", Self::ServiceUnavailable => b"503", @@ -77,6 +81,7 @@ pub struct ResponseHeaders { content_length: i32, content_type: MediaType, server: String, + allow: Vec, } impl Default for ResponseHeaders { @@ -85,11 +90,31 @@ impl Default for ResponseHeaders { content_length: Default::default(), content_type: Default::default(), server: String::from("Firecracker API"), + allow: Vec::new(), } } } impl ResponseHeaders { + // The logic pertaining to `Allow` header writing. + fn write_allow_header(&self, buf: &mut T) -> Result<(), WriteError> { + if self.allow.is_empty() { + return Ok(()); + } + + buf.write_all(b"Allow: ")?; + + let delimitator = b", "; + for (idx, method) in self.allow.iter().enumerate() { + buf.write_all(method.raw())?; + if idx < self.allow.len() - 1 { + buf.write_all(delimitator)?; + } + } + + buf.write_all(&[CR, LF]) + } + /// Writes the headers to `buf` using the HTTP specification. pub fn write_all(&self, buf: &mut T) -> Result<(), WriteError> { buf.write_all(Header::Server.raw())?; @@ -100,6 +125,8 @@ impl ResponseHeaders { buf.write_all(b"Connection: keep-alive")?; buf.write_all(&[CR, LF])?; + self.write_allow_header(buf)?; + if self.content_length != 0 { buf.write_all(Header::ContentType.raw())?; buf.write_all(&[COLON, SP])?; @@ -172,6 +199,16 @@ impl Response { self.headers.set_server(server); } + /// Sets the HTTP allowed methods. + pub fn set_allow(&mut self, methods: Vec) { + self.headers.allow = methods; + } + + /// Allows a specific HTTP method. + pub fn allow_method(&mut self, method: Method) { + self.headers.allow.push(method); + } + fn write_body(&self, mut buf: T) -> Result<(), WriteError> { if let Some(ref body) = self.body { buf.write_all(body.raw())?; @@ -216,6 +253,11 @@ impl Response { pub fn http_version(&self) -> Version { self.status_line.http_version } + + /// Returns the allowed HTTP methods. + pub fn allow(&self) -> Vec { + self.headers.allow.clone() + } } #[cfg(test)] @@ -246,6 +288,20 @@ mod tests { assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); assert!(response_buf.as_ref() == expected_response); + // Test response `Allow` header. + let mut response = Response::new(Version::Http10, StatusCode::OK); + let allowed_methods = vec![Method::Get, Method::Patch, Method::Put]; + response.set_allow(allowed_methods.clone()); + assert_eq!(response.allow(), allowed_methods); + + let expected_response: &'static [u8] = b"HTTP/1.0 200 \r\n\ + Server: Firecracker API\r\n\ + Connection: keep-alive\r\n\ + Allow: GET, PATCH, PUT\r\n\r\n"; + let mut response_buf: [u8; 90] = [0; 90]; + assert!(response.write_all(&mut response_buf.as_mut()).is_ok()); + assert_eq!(response_buf.as_ref(), expected_response); + // Test write failed. let mut response_buf: [u8; 1] = [0; 1]; assert!(response.write_all(&mut response_buf.as_mut()).is_err()); @@ -288,8 +344,17 @@ mod tests { assert_eq!(StatusCode::NoContent.raw(), b"204"); assert_eq!(StatusCode::BadRequest.raw(), b"400"); assert_eq!(StatusCode::NotFound.raw(), b"404"); + assert_eq!(StatusCode::MethodNotAllowed.raw(), b"405"); assert_eq!(StatusCode::InternalServerError.raw(), b"500"); assert_eq!(StatusCode::NotImplemented.raw(), b"501"); assert_eq!(StatusCode::ServiceUnavailable.raw(), b"503"); } + + #[test] + fn test_allow_method() { + let mut response = Response::new(Version::Http10, StatusCode::MethodNotAllowed); + response.allow_method(Method::Get); + response.allow_method(Method::Put); + assert_eq!(response.allow(), vec![Method::Get, Method::Put]); + } }