├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── bodyparser.rs └── src ├── errors.rs ├── lib.rs └── limit_reader.rs /.gitignore: -------------------------------------------------------------------------------- 1 | deps/ 2 | .DS_Store 3 | *~ 4 | *# 5 | *.o 6 | *.so 7 | *.swp 8 | *.dylib 9 | *.dSYM 10 | *.dll 11 | *.rlib 12 | *.dummy 13 | *.exe 14 | *-test 15 | /bin/main 16 | /bin/test-internal 17 | /bin/test-external 18 | /doc/ 19 | /target/ 20 | /build/ 21 | /.rust/ 22 | rusti.sh 23 | /examples/* 24 | !/examples/*.rs 25 | Cargo.lock 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - nightly 4 | - beta 5 | - stable 6 | sudo: false 7 | env: 8 | global: 9 | secure: MFJHBZPW7DNtZ+lNRZU6g/l5T0Khc2J0kbQj7BZ/QtdyGLhkW4XcrS8lJ5Fbm/i96hc2sX+MXbrWRYDkuyE6u5dgIXIFL4X+KzAf19JpIBWfeRnZKL9b8/lOB7PN0WqPP0wNmB7mBi/2rgz31wt8yGU6pnk2UiWlHX77sT14PWE= 10 | after_success: 'curl https://raw.githubusercontent.com/iron/build-doc/master/build-doc.sh 11 | | sh ' 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | **body-parser** uses the same conventions as **[Iron](https://github.com/iron/iron)**. 4 | 5 | ### Overview 6 | 7 | * Fork body-parser to your own account 8 | * Create a feature branch, namespaced by. 9 | * bug/... 10 | * feat/... 11 | * test/... 12 | * doc/... 13 | * refactor/... 14 | * Make commits to your feature branch. Prefix each commit like so: 15 | * (feat) Added a new feature 16 | * (fix) Fixed inconsistent tests [Fixes #0] 17 | * (refactor) ... 18 | * (cleanup) ... 19 | * (test) ... 20 | * (doc) ... 21 | * Make a pull request with your changes directly to master. Include a 22 | description of your changes. 23 | * Wait for one of the reviewers to look at your code and either merge it or 24 | give feedback which you should adapt to. 25 | 26 | #### Thank you for contributing! 27 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "bodyparser" 4 | version = "0.8.0" 5 | license = "MIT" 6 | description = "Body parsing middleware for Iron." 7 | repository = "https://github.com/iron/body-parser" 8 | documentation = "https://docs.rs/bodyparser/" 9 | authors = ["Patrick Tran ", 10 | "Jonathan Reem "] 11 | keywords = ["iron", "web", "http", "parsing", "parser"] 12 | 13 | [dependencies] 14 | iron = ">= 0.5, < 0.7" 15 | plugin = "0.2" 16 | persistent = "0.4" 17 | serde = "1.0" 18 | serde_json = "1.0" 19 | 20 | [dev-dependencies] 21 | serde_derive = "1.0" 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 iron 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | body-parser 2 | ==== 3 | 4 | [![Build Status](https://secure.travis-ci.org/iron/body-parser.png?branch=master)](https://travis-ci.org/iron/body-parser) 5 | [![Crates.io](https://img.shields.io/crates/v/bodyparser.svg)](https://crates.io/crates/bodyparser) 6 | 7 | > Body parsing plugins for the [Iron](https://github.com/iron/iron) web framework. 8 | 9 | ## Example 10 | 11 | ```rust 12 | extern crate iron; 13 | extern crate bodyparser; 14 | extern crate persistent; 15 | #[macro_use] 16 | extern crate serde_derive; 17 | 18 | use persistent::Read; 19 | use iron::status; 20 | use iron::prelude::*; 21 | 22 | #[derive(Debug, Clone, Deserialize)] 23 | struct MyStructure { 24 | a: String, 25 | b: Option, 26 | } 27 | 28 | fn log_body(req: &mut Request) -> IronResult { 29 | let body = req.get::(); 30 | match body { 31 | Ok(Some(body)) => println!("Read body:\n{}", body), 32 | Ok(None) => println!("No body"), 33 | Err(err) => println!("Error: {:?}", err) 34 | } 35 | 36 | let json_body = req.get::(); 37 | match json_body { 38 | Ok(Some(json_body)) => println!("Parsed body:\n{:?}", json_body), 39 | Ok(None) => println!("No body"), 40 | Err(err) => println!("Error: {:?}", err) 41 | } 42 | 43 | let struct_body = req.get::>(); 44 | match struct_body { 45 | Ok(Some(struct_body)) => println!("Parsed body:\n{:?}", struct_body), 46 | Ok(None) => println!("No body"), 47 | Err(err) => println!("Error: {:?}", err) 48 | } 49 | 50 | Ok(Response::with(status::Ok)) 51 | } 52 | 53 | const MAX_BODY_LENGTH: usize = 1024 * 1024 * 10; 54 | 55 | // While the example is running, try the following curl commands and see how they are 56 | // logged by the Rust server process: 57 | // 58 | // `curl -i "localhost:3000/" -H "application/json" -d '{"name":"jason","age":"2"}'` 59 | // `curl -i "localhost:3000/" -H "application/json" -d '{"a":"jason","b":"2"}'` 60 | // `curl -i "localhost:3000/" -H "application/json" -d '{"a":"jason"}'` 61 | fn main() { 62 | let mut chain = Chain::new(log_body); 63 | chain.link_before(Read::::one(MAX_BODY_LENGTH)); 64 | Iron::new(chain).http("localhost:3000").unwrap(); 65 | } 66 | ``` 67 | 68 | ## Overview 69 | 70 | body-parser is a part of Iron's [core bundle](https://github.com/iron/core). It contains: 71 | 72 | * **Raw** - performs body parsing to string with limiting. 73 | * **Json** - parses body into Json. 74 | * **Struct** - parses body into a struct using Serde. 75 | 76 | ## Installation 77 | 78 | If you're using a `Cargo.toml` to manage dependencies, just add body-parser to the toml: 79 | 80 | ```toml 81 | [dependencies.bodyparser] 82 | 83 | git = "https://github.com/iron/body-parser.git" 84 | ``` 85 | 86 | Otherwise, `cargo build`, and the rlib will be in your `target` directory. 87 | 88 | ## [Documentation](https://docs.rs/bodyparser/) 89 | 90 | Along with the [online documentation](https://docs.rs/bodyparser/), 91 | you can build a local copy with `make doc`. 92 | 93 | ## [Examples](/examples) 94 | 95 | ## Get Help 96 | 97 | One of us ([@reem](https://github.com/reem/), [@zzmp](https://github.com/zzmp/), 98 | [@theptrk](https://github.com/theptrk/), [@mcreinhard](https://github.com/mcreinhard)) 99 | is usually on `#iron` on the mozilla irc. Come say hi and ask any questions you might have. 100 | We are also usually on `#rust` and `#rust-webdev`. 101 | -------------------------------------------------------------------------------- /examples/bodyparser.rs: -------------------------------------------------------------------------------- 1 | extern crate iron; 2 | extern crate bodyparser; 3 | extern crate persistent; 4 | extern crate serde; 5 | #[macro_use] 6 | extern crate serde_derive; 7 | 8 | use persistent::Read; 9 | use iron::status; 10 | use iron::prelude::*; 11 | 12 | #[derive(Deserialize, Debug, Clone)] 13 | struct MyStructure { 14 | a: String, 15 | b: Option, 16 | } 17 | 18 | fn log_body(req: &mut Request) -> IronResult { 19 | let body = req.get::(); 20 | match body { 21 | Ok(Some(body)) => println!("Read body:\n{}", body), 22 | Ok(None) => println!("No body"), 23 | Err(err) => println!("Error: {:?}", err) 24 | } 25 | 26 | let json_body = req.get::(); 27 | match json_body { 28 | Ok(Some(json_body)) => println!("Parsed body:\n{:?}", json_body), 29 | Ok(None) => println!("No body"), 30 | Err(err) => println!("Error: {:?}", err) 31 | } 32 | 33 | let struct_body = req.get::>(); 34 | match struct_body { 35 | Ok(Some(struct_body)) => println!("Parsed body:\n{:?}", struct_body), 36 | Ok(None) => println!("No body"), 37 | Err(err) => println!("Error: {:?}", err) 38 | } 39 | 40 | Ok(Response::with(status::Ok)) 41 | } 42 | 43 | const MAX_BODY_LENGTH: usize = 1024 * 1024 * 10; 44 | 45 | // While the example is running, try the following curl commands and see how they are 46 | // logged by the Rust server process: 47 | // 48 | // `curl -i "localhost:3000/" -H "application/json" -d '{"name":"jason","age":"2"}'` 49 | // `curl -i "localhost:3000/" -H "application/json" -d '{"a":"jason","b":"2"}'` 50 | // `curl -i "localhost:3000/" -H "application/json" -d '{"a":"jason"}'` 51 | fn main() { 52 | let mut chain = Chain::new(log_body); 53 | chain.link_before(Read::::one(MAX_BODY_LENGTH)); 54 | Iron::new(chain).http("localhost:3000").unwrap(); 55 | } 56 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as StdError; 2 | use std::fmt; 3 | use std::io; 4 | use std::str; 5 | 6 | use serde_json; 7 | 8 | #[derive(Debug)] 9 | pub enum BodyErrorCause { 10 | Utf8Error(str::Utf8Error), 11 | IoError(io::Error), 12 | JsonError(serde_json::Error), 13 | } 14 | 15 | #[derive(Debug)] 16 | pub struct BodyError { 17 | pub detail: String, 18 | pub cause: BodyErrorCause 19 | } 20 | 21 | impl StdError for BodyError { 22 | fn description(&self) -> &str { 23 | &self.detail[..] 24 | } 25 | 26 | fn cause(&self) -> Option<&StdError> { 27 | use BodyErrorCause::*; 28 | 29 | match self.cause { 30 | Utf8Error(ref err) => Some(err), 31 | IoError(ref err) => Some(err), 32 | JsonError(ref err) => Some(err), 33 | } 34 | } 35 | } 36 | 37 | impl fmt::Display for BodyError { 38 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 39 | self.description().fmt(formatter) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![crate_name = "bodyparser"] 2 | 3 | //! Body Parser Plugin for Iron 4 | //! 5 | //! This plugin parses JSON out of an incoming Request. 6 | 7 | extern crate iron; 8 | extern crate plugin; 9 | extern crate persistent; 10 | extern crate serde; 11 | extern crate serde_json; 12 | 13 | use serde::Deserialize; 14 | use serde_json::{from_str, from_value}; 15 | 16 | use iron::mime; 17 | use iron::prelude::*; 18 | use iron::headers; 19 | use iron::typemap::{Key}; 20 | use std::io::Read; 21 | use std::any::Any; 22 | use std::marker; 23 | 24 | pub use self::errors::{BodyError, BodyErrorCause}; 25 | pub use self::limit_reader::{LimitReader}; 26 | 27 | mod errors; 28 | mod limit_reader; 29 | 30 | fn read_body_as_utf8(req: &mut Request, limit: usize) -> Result { 31 | let mut bytes = Vec::new(); 32 | match LimitReader::new(req.body.by_ref(), limit).read_to_end(&mut bytes) { 33 | Ok(_) => { 34 | match String::from_utf8(bytes) { 35 | Ok(e) => Ok(e), 36 | Err(err) => Err(errors::BodyError { 37 | detail: "Invalid UTF-8 sequence".to_string(), 38 | cause: errors::BodyErrorCause::Utf8Error(err.utf8_error()) 39 | }) 40 | } 41 | }, 42 | Err(err) => Err(errors::BodyError { 43 | detail: "Can't read request body".to_string(), 44 | cause: errors::BodyErrorCause::IoError(err) 45 | }) 46 | } 47 | } 48 | 49 | /// Use this key to modify the default body limit. 50 | pub struct MaxBodyLength; 51 | impl Key for MaxBodyLength { 52 | type Value = usize; 53 | } 54 | 55 | /// Raw is a plugin to read a request body into UTF-8 String. 56 | /// Doesn't read `multipart/form-data`. 57 | pub struct Raw; 58 | 59 | impl Key for Raw { 60 | type Value = Option; 61 | } 62 | 63 | const DEFAULT_BODY_LIMIT: usize = 1024 * 1024 * 100; 64 | 65 | impl<'a, 'b> plugin::Plugin> for Raw { 66 | type Error = BodyError; 67 | 68 | fn eval(req: &mut Request) -> Result, BodyError> { 69 | let need_read = req.headers.get::().map(|header| { 70 | match **header { 71 | mime::Mime(mime::TopLevel::Multipart, mime::SubLevel::FormData, _) => false, 72 | _ => true 73 | } 74 | }).unwrap_or(false); 75 | 76 | if need_read { 77 | let max_length = req 78 | .get::>() 79 | .ok() 80 | .map(|x| *x) 81 | .unwrap_or(DEFAULT_BODY_LIMIT); 82 | 83 | let body = try!(read_body_as_utf8(req, max_length)); 84 | Ok(Some(body)) 85 | } else { 86 | Ok(None) 87 | } 88 | } 89 | } 90 | 91 | /// Json is a plugin to parse a request body into JSON. 92 | /// Uses Raw plugin to parse the body with limit. 93 | #[derive(Clone)] 94 | pub struct Json; 95 | impl Key for Json { 96 | type Value = Option; 97 | } 98 | 99 | impl<'a, 'b> plugin::Plugin> for Json { 100 | type Error = BodyError; 101 | 102 | fn eval(req: &mut Request) -> Result, BodyError> { 103 | req.get::() 104 | .and_then(|maybe_body| { 105 | reverse_option(maybe_body.map(|body| from_str(&body))) 106 | .map_err(|err| { 107 | BodyError { 108 | detail: "Can't parse body to JSON".to_string(), 109 | cause: BodyErrorCause::JsonError(err) 110 | } 111 | }) 112 | }) 113 | } 114 | } 115 | 116 | /// Struct is a plugin to parse a request body into a struct. 117 | /// Uses Raw plugin to parse the body with limit. 118 | pub struct Struct where T: for<'a> Deserialize<'a> { 119 | marker: marker::PhantomData 120 | } 121 | impl Key for Struct where T: for<'a> Deserialize<'a> + Any { 122 | type Value = Option; 123 | } 124 | 125 | impl<'a, 'b, T> plugin::Plugin> for Struct 126 | where T: for<'c> Deserialize<'c> + Any { 127 | type Error = BodyError; 128 | 129 | fn eval(req: &mut Request) -> Result, BodyError> { 130 | req.get::() 131 | .and_then(|maybe_body| { 132 | reverse_option(maybe_body.map(|body| from_value(body))) 133 | .map_err(|err| BodyError { 134 | detail: "Can't parse body to the struct".to_string(), 135 | cause: BodyErrorCause::JsonError(err) 136 | }) 137 | }) 138 | } 139 | } 140 | 141 | fn reverse_option(value: Option>) -> Result, E> { 142 | match value { 143 | Some(Ok(val)) => Ok(Some(val)), 144 | Some(Err(err)) => Err(err), 145 | None => Ok(None), 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/limit_reader.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::cmp; 3 | 4 | /// [Original impl](https://github.com/rust-lang/rust/blob/17bc7d8d5be3be9674d702ccad2fa88c487d23b0/src/libstd/old_io/util.rs#L20) 5 | /// 6 | /// The LimitReader from the `std` just stops to read when reaches a limit, but we don't want 7 | /// to return partially readed body to the client code because it is useless. This modified LimitReader 8 | /// returns `IoError` with `IoErrorKind::InvalidInput` when it reaches the limit. 9 | #[derive(Debug)] 10 | pub struct LimitReader { 11 | limit: usize, 12 | inner: R 13 | } 14 | 15 | impl LimitReader { 16 | pub fn new(r: R, limit: usize) -> LimitReader { 17 | LimitReader { limit: limit, inner: r } 18 | } 19 | 20 | pub fn into_inner(self) -> R { self.inner } 21 | pub fn limit(&self) -> usize { self.limit } 22 | } 23 | 24 | impl io::Read for LimitReader { 25 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 26 | if self.limit == 0 { 27 | return Err(io::Error::new( 28 | io::ErrorKind::InvalidInput, "Body is too big")) 29 | } 30 | 31 | let len = cmp::min(self.limit, buf.len()); 32 | let res = self.inner.read(&mut buf[..len]); 33 | match res { 34 | Ok(len) => self.limit -= len, 35 | _ => {} 36 | } 37 | res 38 | } 39 | } --------------------------------------------------------------------------------