diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 848cdcd09..b69e67899 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -155,7 +155,37 @@ Full example is available in ## Urlencoded body -[WIP] +Actix provides support for *application/x-www-form-urlencoded* encoded body. +`HttpResponse::urlencoded()` method returns +[*UrlEncoded*](../actix_web/dev/struct.UrlEncoded.html) future, it resolves +into `HashMap` which contains decoded parameters. +*UrlEncoded* future can resolve into a error in several cases: + +* content type is not `application/x-www-form-urlencoded` +* transfer encoding is `chunked`. +* content-length is greater than 256k +* payload terminates with error. + + +```rust +# extern crate actix_web; +# extern crate futures; +use actix_web::*; +use futures::future::{Future, ok}; + +fn index(mut req: HttpRequest) -> Box> { + Box::new( + req.urlencoded() // <- get UrlEncoded future + .and_then(|params| { // <- url encoded parameters + println!("==== BODY ==== {:?}", params); + ok(httpcodes::HTTPOk.response()) + }) + .map_err(Error::from) + ) +} +# fn main() {} +``` + ## Streaming request diff --git a/src/error.rs b/src/error.rs index 98f26e4cb..275485603 100644 --- a/src/error.rs +++ b/src/error.rs @@ -360,7 +360,7 @@ impl ResponseError for WsHandshakeError { } /// A set of errors that can occur during parsing urlencoded payloads -#[derive(Fail, Debug, PartialEq)] +#[derive(Fail, Debug)] pub enum UrlencodedError { /// Can not decode chunked transfer encoding #[fail(display="Can not decode chunked transfer encoding")] @@ -374,6 +374,9 @@ pub enum UrlencodedError { /// Content type error #[fail(display="Content type error")] ContentType, + /// Payload error + #[fail(display="Error that occur during reading payload")] + Payload(PayloadError), } /// Return `BadRequest` for `UrlencodedError` @@ -384,6 +387,12 @@ impl ResponseError for UrlencodedError { } } +impl From for UrlencodedError { + fn from(err: PayloadError) -> UrlencodedError { + UrlencodedError::Payload(err) + } +} + /// Errors which can occur when attempting to interpret a segment string as a /// valid path segment. #[derive(Fail, Debug, PartialEq)] diff --git a/src/h1.rs b/src/h1.rs index bfd187e19..9af1d8cdd 100644 --- a/src/h1.rs +++ b/src/h1.rs @@ -107,6 +107,8 @@ impl Http1 } } + // TODO: refacrtor + #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] pub fn poll(&mut self) -> Poll { // keep-alive timer if self.keepalive_timer.is_some() { diff --git a/src/httprequest.rs b/src/httprequest.rs index 660d172a1..e48cfcd85 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -17,8 +17,7 @@ use router::Router; use payload::Payload; use multipart::Multipart; use helpers::SharedHttpMessage; -use error::{ParseError, PayloadError, UrlGenerationError, - CookieParseError, HttpRangeError, UrlencodedError}; +use error::{ParseError, UrlGenerationError, CookieParseError, HttpRangeError, UrlencodedError}; pub struct HttpMessage { @@ -447,41 +446,27 @@ impl HttpRequest { /// * content type is not `application/x-www-form-urlencoded` /// * transfer encoding is `chunked`. /// * content-length is greater than 256k - pub fn urlencoded(&mut self) -> Result { - if let Ok(true) = self.chunked() { - return Err(UrlencodedError::Chunked) - } - - if let Some(len) = self.headers().get(header::CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - if len > 262_144 { - return Err(UrlencodedError::Overflow) - } - } else { - return Err(UrlencodedError::UnknownLength) - } - } else { - return Err(UrlencodedError::UnknownLength) - } - } - - // check content type - let t = if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { - if let Ok(content_type) = content_type.to_str() { - content_type.to_lowercase() == "application/x-www-form-urlencoded" - } else { - false - } - } else { - false - }; - - if t { - Ok(UrlEncoded{pl: self.payload().clone(), body: BytesMut::new()}) - } else { - Err(UrlencodedError::ContentType) - } + /// + /// ```rust + /// # extern crate actix_web; + /// # extern crate futures; + /// use actix_web::*; + /// use futures::future::{Future, ok}; + /// + /// fn index(mut req: HttpRequest) -> Box> { + /// Box::new( + /// req.urlencoded() // <- get UrlEncoded future + /// .and_then(|params| { // <- url encoded parameters + /// println!("==== BODY ==== {:?}", params); + /// ok(httpcodes::HTTPOk.response()) + /// }) + /// .map_err(Error::from) + /// ) + /// } + /// # fn main() {} + /// ``` + pub fn urlencoded(&mut self) -> UrlEncoded { + UrlEncoded::from_request(self) } } @@ -526,13 +511,59 @@ impl fmt::Debug for HttpRequest { pub struct UrlEncoded { pl: Payload, body: BytesMut, + error: Option, +} + +impl UrlEncoded { + pub fn from_request(req: &mut HttpRequest) -> UrlEncoded { + let mut encoded = UrlEncoded { + pl: req.payload_mut().clone(), + body: BytesMut::new(), + error: None + }; + + if let Ok(true) = req.chunked() { + encoded.error = Some(UrlencodedError::Chunked); + } else if let Some(len) = req.headers().get(header::CONTENT_LENGTH) { + if let Ok(s) = len.to_str() { + if let Ok(len) = s.parse::() { + if len > 262_144 { + encoded.error = Some(UrlencodedError::Overflow); + } + } else { + encoded.error = Some(UrlencodedError::UnknownLength); + } + } else { + encoded.error = Some(UrlencodedError::UnknownLength); + } + } + + // check content type + if encoded.error.is_none() { + if let Some(content_type) = req.headers().get(header::CONTENT_TYPE) { + if let Ok(content_type) = content_type.to_str() { + if content_type.to_lowercase() == "application/x-www-form-urlencoded" { + return encoded + } + } + } + encoded.error = Some(UrlencodedError::ContentType); + return encoded + } + + encoded + } } impl Future for UrlEncoded { type Item = HashMap; - type Error = PayloadError; + type Error = UrlencodedError; fn poll(&mut self) -> Poll { + if let Some(err) = self.error.take() { + return Err(err) + } + loop { return match self.pl.poll() { Ok(Async::NotReady) => Ok(Async::NotReady), @@ -547,7 +578,7 @@ impl Future for UrlEncoded { self.body.extend_from_slice(&item); continue }, - Err(err) => Err(err), + Err(err) => Err(err.into()), } } } @@ -673,6 +704,30 @@ mod tests { assert!(req.chunked().is_err()); } + impl PartialEq for UrlencodedError { + fn eq(&self, other: &UrlencodedError) -> bool { + match *self { + UrlencodedError::Chunked => match *other { + UrlencodedError::Chunked => true, + _ => false, + }, + UrlencodedError::Overflow => match *other { + UrlencodedError::Overflow => true, + _ => false, + }, + UrlencodedError::UnknownLength => match *other { + UrlencodedError::UnknownLength => true, + _ => false, + }, + UrlencodedError::ContentType => match *other { + UrlencodedError::ContentType => true, + _ => false, + }, + _ => false, + } + } + } + #[test] fn test_urlencoded_error() { let mut headers = HeaderMap::new(); @@ -681,7 +736,7 @@ mod tests { let mut req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - assert_eq!(req.urlencoded().err().unwrap(), UrlencodedError::Chunked); + assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Chunked); let mut headers = HeaderMap::new(); headers.insert(header::CONTENT_TYPE, @@ -691,7 +746,7 @@ mod tests { let mut req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - assert_eq!(req.urlencoded().err().unwrap(), UrlencodedError::UnknownLength); + assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::UnknownLength); let mut headers = HeaderMap::new(); headers.insert(header::CONTENT_TYPE, @@ -701,7 +756,7 @@ mod tests { let mut req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - assert_eq!(req.urlencoded().err().unwrap(), UrlencodedError::Overflow); + assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::Overflow); let mut headers = HeaderMap::new(); headers.insert(header::CONTENT_TYPE, @@ -711,7 +766,7 @@ mod tests { let mut req = HttpRequest::new( Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None); - assert_eq!(req.urlencoded().err().unwrap(), UrlencodedError::ContentType); + assert_eq!(req.urlencoded().poll().err().unwrap(), UrlencodedError::ContentType); } #[test]