├── fuzz
├── .gitignore
├── fuzzer_dict
├── Cargo.toml
├── fuzzers
│ ├── logger.rs
│ └── server_basic.rs
└── Cargo.lock
├── fuzz_server.sh
├── .gitignore
├── src
├── bin
│ ├── test_form.html
│ ├── form_test.rs
│ └── read_file.rs
├── server
│ ├── tiny_http.rs
│ ├── nickel.rs
│ ├── hyper.rs
│ ├── mod.rs
│ ├── iron.rs
│ ├── boundary.rs
│ └── field.rs
├── client
│ ├── hyper.rs
│ ├── sized.rs
│ ├── mod.rs
│ └── lazy.rs
├── lib.rs
├── mock.rs
└── local_test.rs
├── examples
├── hyper_reqbuilder.rs
├── iron_intercept.rs
├── hyper_client.rs
├── hyper_server.rs
├── nickel.rs
├── iron.rs
├── tiny_http.rs
├── rocket.rs
└── README.md
├── .travis.yml
├── LICENSE
├── LICENSE-MIT
├── lorem_ipsum.txt
├── Cargo.toml
├── README.md
└── LICENSE-APACHE
/fuzz/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | target
3 | libfuzzer
4 | corpus
5 | artifacts
6 |
--------------------------------------------------------------------------------
/fuzz/fuzzer_dict:
--------------------------------------------------------------------------------
1 | "Content-Disposition: form-data; name="
2 | # CR LF
3 | "\x0D\x0A"
4 | "Content-Type:"
5 | "filename="
6 | "--12--34--56"
7 | "--12--34--56--"
--------------------------------------------------------------------------------
/fuzz_server.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 | # pwd
3 | cargo fuzz run server_basic -- -dict=fuzz/fuzzer_dict -only_ascii=1 -timeout=60 ${FUZZ_LEN:+ -max_len=$FUZZ_LEN} $@
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /Cargo.lock
3 | *.swp
4 | *~
5 | # Compiled files
6 | *.o
7 | *.so
8 | *.rlib
9 | *.dll
10 |
11 | # Executables
12 | *.exe
13 |
14 | # Generated by Cargo
15 | target/
16 | .idea/
17 | *.iml
18 |
19 | dump.bin
20 |
--------------------------------------------------------------------------------
/src/bin/test_form.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Multipart-Async Form Test
6 |
7 |
8 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/fuzz/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "multipart-fuzz"
3 | version = "0.0.1"
4 | authors = ["Automatically generated"]
5 | publish = false
6 |
7 | [package.metadata]
8 | cargo-fuzz = true
9 |
10 | [dependencies]
11 | log = "*"
12 |
13 | [dependencies.multipart]
14 | path = ".."
15 | default-features = false
16 | features = ["mock", "client", "server"]
17 |
18 | [dependencies.libfuzzer-sys]
19 | git = "https://github.com/rust-fuzz/libfuzzer-sys.git"
20 |
21 | # Prevent this from interfering with workspaces
22 | [workspace]
23 | members = ["."]
24 |
25 | [[bin]]
26 | name = "server_basic"
27 | path = "fuzzers/server_basic.rs"
28 |
--------------------------------------------------------------------------------
/examples/hyper_reqbuilder.rs:
--------------------------------------------------------------------------------
1 | extern crate hyper;
2 | extern crate multipart;
3 |
4 | use hyper::Client;
5 |
6 | use multipart::client::lazy::Multipart;
7 |
8 | fn main() {
9 | let mut binary = "Hello world in binary!".as_bytes();
10 |
11 | let _response = Multipart::new()
12 | .add_text("text", "Hello, world!")
13 | .add_file("file", "lorem_ipsum.txt")
14 | // A little extra type info needed.
15 | .add_stream("binary", &mut binary, None as Option<&str>, None)
16 | // Request is sent here
17 | .client_request(&Client::new(), "http://localhost:80")
18 | .expect("Error sending multipart request");
19 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: rust
2 | cache: cargo
3 | branches:
4 | except:
5 | - fuzzing
6 | rust:
7 | - 1.33.0
8 | - stable
9 | - beta
10 | - nightly
11 | os:
12 | - linux
13 | - osx
14 | env:
15 | global:
16 | - RUST_LOG=multipart=trace RUST_BACKTRACE=1 ARGS=
17 | matrix:
18 | include:
19 | - rust: stable
20 | env: ARGS+=--no-default-features --features "nickel"
21 | - rust: stable
22 | env: ARGS+=--features "use_arc_str"
23 | - rust: nightly
24 | env: ARGS+=--features "nightly,rocket"
25 | script:
26 | - cargo build --verbose $ARGS;
27 | - cargo test --verbose $ARGS -- --test-threads=1;
28 | - cargo doc --verbose $ARGS;
29 |
--------------------------------------------------------------------------------
/fuzz/fuzzers/logger.rs:
--------------------------------------------------------------------------------
1 | extern crate log;
2 |
3 | use self::log::{LogLevelFilter, Log, LogMetadata, LogRecord};
4 |
5 | const MAX_LOG_LEVEL: LogLevelFilter = LogLevelFilter::Off;
6 |
7 | struct Logger;
8 |
9 | impl Log for Logger {
10 | fn enabled(&self, metadata: &LogMetadata) -> bool {
11 | metadata.level() <= MAX_LOG_LEVEL
12 | }
13 |
14 | fn log(&self, record: &LogRecord) {
15 | println!("{}: {}", record.level(), record.args());
16 | }
17 | }
18 |
19 | static LOGGER: Logger = Logger;
20 |
21 | pub fn init() {
22 | let _ = unsafe {
23 | log::set_logger_raw(|max_lvl| {
24 | max_lvl.set(MAX_LOG_LEVEL);
25 | &LOGGER
26 | })
27 | };
28 | }
29 |
--------------------------------------------------------------------------------
/examples/iron_intercept.rs:
--------------------------------------------------------------------------------
1 | extern crate iron;
2 | extern crate multipart;
3 |
4 | use iron::prelude::*;
5 |
6 | use multipart::server::Entries;
7 | use multipart::server::iron::Intercept;
8 |
9 | fn main() {
10 | // We start with a basic request handler chain.
11 | let mut chain = Chain::new(|req: &mut Request|
12 | if let Some(entries) = req.extensions.get::() {
13 | Ok(Response::with(format!("{:?}", entries)))
14 | } else {
15 | Ok(Response::with("Not a multipart request"))
16 | }
17 | );
18 |
19 | // `Intercept` will read out the entries and place them as an extension in the request.
20 | // It has various builder-style methods for changing how it will behave, but has sane settings
21 | // by default.
22 | chain.link_before(Intercept::default());
23 |
24 | Iron::new(chain).http("localhost:80").unwrap();
25 | }
--------------------------------------------------------------------------------
/src/bin/form_test.rs:
--------------------------------------------------------------------------------
1 | extern crate hyper;
2 | extern crate multipart;
3 |
4 | use multipart::server::Multipart;
5 |
6 | use hyper::header::ContentType;
7 | use hyper::server::*;
8 |
9 | use std::fs::File;
10 | use std::io;
11 |
12 | fn main() {
13 | let listening = Server::http("127.0.0.1:0").expect("failed to bind socket")
14 | .handle(read_multipart).expect("failed to handle request");
15 |
16 | println!("bound socket to: {}", listening.socket);
17 | }
18 |
19 | fn read_multipart(req: Request, mut resp: Response) {
20 | if let Ok(mut multipart) = Multipart::from_request(req) {
21 | if let Err(e) = multipart.foreach_entry(|_| {}) {
22 | println!("error handling field: {}", e);
23 | }
24 | }
25 |
26 | let mut file = File::open("src/bin/test_form.html")
27 | .expect("failed to open src/bind/test_form.html");
28 |
29 | resp.headers_mut().set(ContentType("text/html".parse().unwrap()));
30 |
31 | let mut resp = resp.start().expect("failed to open response");
32 | io::copy(&mut file, &mut resp).expect("failed to write response");
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Austin Bonander
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.
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 The `multipart` Crate Developers
2 |
3 | Permission is hereby granted, free of charge, to any
4 | person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the
6 | Software without restriction, including without
7 | limitation the rights to use, copy, modify, merge,
8 | publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software
10 | is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice
14 | shall be included in all copies or substantial portions
15 | of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 | DEALINGS IN THE SOFTWARE.
26 |
--------------------------------------------------------------------------------
/src/server/tiny_http.rs:
--------------------------------------------------------------------------------
1 | //! Integration with [`tiny_http`](https://github.com/frewsxcv/tiny-http) with the `tiny_http`
2 | //! feature (optional).
3 | //!
4 | //! Contains `impl `[`HttpRequest`](../trait.HttpRequest.html)` for tiny_http::Request` (not shown
5 | //! here; see [`HttpRequest`'s implementors](../trait.HttpRequest.html#implementors)).
6 |
7 | pub use tiny_http::Request as TinyHttpRequest;
8 |
9 | use super::HttpRequest;
10 |
11 | use std::io::Read;
12 |
13 | impl<'r> HttpRequest for &'r mut TinyHttpRequest {
14 | type Body = &'r mut dyn Read;
15 |
16 | fn multipart_boundary(&self) -> Option<&str> {
17 | const BOUNDARY: &str = "boundary=";
18 |
19 | let content_type = try_opt!(self
20 | .headers()
21 | .iter()
22 | .find(|header| header.field.equiv("Content-Type")))
23 | .value
24 | .as_str();
25 | let start = try_opt!(content_type.find(BOUNDARY)) + BOUNDARY.len();
26 | let end = content_type[start..]
27 | .find(';')
28 | .map_or(content_type.len(), |end| start + end);
29 |
30 | Some(&content_type[start..end])
31 | }
32 |
33 | fn body(self) -> Self::Body {
34 | self.as_reader()
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/examples/hyper_client.rs:
--------------------------------------------------------------------------------
1 | extern crate hyper;
2 | extern crate multipart;
3 |
4 | use hyper::client::Request;
5 | use hyper::method::Method;
6 | use hyper::net::Streaming;
7 |
8 | use multipart::client::Multipart;
9 |
10 | use std::io::Read;
11 |
12 | fn main() {
13 | let url = "http://localhost:80".parse()
14 | .expect("Failed to parse URL");
15 |
16 | let request = Request::new(Method::Post, url)
17 | .expect("Failed to create request");
18 |
19 | let mut multipart = Multipart::from_request(request)
20 | .expect("Failed to create Multipart");
21 |
22 | write_body(&mut multipart)
23 | .expect("Failed to write multipart body");
24 |
25 | let mut response = multipart.send().expect("Failed to send multipart request");
26 |
27 | if !response.status.is_success() {
28 | let mut res = String::new();
29 | response.read_to_string(&mut res).expect("failed to read response");
30 | println!("response reported unsuccessful: {:?}\n {}", response, res);
31 | }
32 |
33 | // Optional: read out response
34 | }
35 |
36 | fn write_body(multi: &mut Multipart>) -> hyper::Result<()> {
37 | let mut binary = "Hello world from binary!".as_bytes();
38 |
39 | multi.write_text("text", "Hello, world!")?;
40 | multi.write_file("file", "lorem_ipsum.txt")?;
41 | // &[u8] impl Read
42 | multi.write_stream("binary", &mut binary, None, None)
43 | .and(Ok(()))
44 | }
45 |
--------------------------------------------------------------------------------
/fuzz/fuzzers/server_basic.rs:
--------------------------------------------------------------------------------
1 | #![no_main]
2 | extern crate libfuzzer_sys;
3 | extern crate multipart;
4 |
5 | #[macro_use]
6 | extern crate log;
7 |
8 | use multipart::server::{Multipart, MultipartData};
9 | use multipart::mock::ServerRequest;
10 |
11 | mod logger;
12 |
13 | use std::io::BufRead;
14 |
15 | const BOUNDARY: &'static str = "--12--34--56";
16 |
17 | #[export_name="rust_fuzzer_test_input"]
18 | pub extern fn go(data: &[u8]) {
19 | logger::init();
20 |
21 | info!("Fuzzing started! Data len: {}", data.len());
22 |
23 | do_fuzz(data);
24 |
25 | info!("Finished fuzzing iteration");
26 | }
27 |
28 | fn do_fuzz(data: &[u8]) {
29 |
30 | if data.len() < BOUNDARY.len() { return; }
31 |
32 | let req = ServerRequest::new(data, BOUNDARY);
33 |
34 | info!("Request constructed!");
35 |
36 | let mut multipart = if let Ok(multi) = Multipart::from_request(req) {
37 | multi
38 | } else {
39 | panic!("This shouldn't have failed")
40 | };
41 |
42 | // A lot of requests will be malformed
43 | while let Ok(Some(entry)) = multipart.read_entry() {
44 | info!("read_entry() loop!");
45 | match entry.data {
46 | MultipartData::Text(_) => (),
47 | MultipartData::File(mut file) => loop {
48 | let consume = file.fill_buf().expect("This shouldn't fail").len();
49 |
50 | info!("Consume amt: {}", consume);
51 |
52 | if consume == 0 { break; }
53 | file.consume(consume);
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/examples/hyper_server.rs:
--------------------------------------------------------------------------------
1 | extern crate hyper;
2 | extern crate multipart;
3 |
4 | use std::io;
5 | use hyper::server::{Handler, Server, Request, Response};
6 | use hyper::status::StatusCode;
7 | use hyper::server::response::Response as HyperResponse;
8 | use multipart::server::hyper::{Switch, MultipartHandler, HyperRequest};
9 | use multipart::server::{Multipart, Entries, SaveResult};
10 | use multipart::mock::StdoutTee;
11 |
12 | struct NonMultipart;
13 | impl Handler for NonMultipart {
14 | fn handle(&self, _: Request, mut res: Response) {
15 | *res.status_mut() = StatusCode::ImATeapot;
16 | res.send(b"Please send a multipart req :(\n").unwrap();
17 | }
18 | }
19 |
20 | struct EchoMultipart;
21 | impl MultipartHandler for EchoMultipart {
22 | fn handle_multipart(&self, mut multipart: Multipart, res: HyperResponse) {
23 | match multipart.save().temp() {
24 | SaveResult::Full(entries) => process_entries(res, entries).unwrap(),
25 | SaveResult::Partial(entries, error) => {
26 | println!("Errors saving multipart:\n{:?}", error);
27 | process_entries(res, entries.into()).unwrap();
28 | }
29 | SaveResult::Error(error) => {
30 | println!("Errors saving multipart:\n{:?}", error);
31 | res.send(format!("An error occurred {}", error).as_bytes()).unwrap();
32 | }
33 | };
34 | }
35 | }
36 |
37 | fn process_entries(res: HyperResponse, entries: Entries) -> io::Result<()> {
38 | let mut res = res.start()?;
39 | let stdout = io::stdout();
40 | let out = StdoutTee::new(&mut res, &stdout);
41 | entries.write_debug(out)
42 | }
43 |
44 | fn main() {
45 | println!("Listening on 0.0.0.0:3333");
46 | Server::http("0.0.0.0:3333").unwrap().handle(
47 | Switch::new(
48 | NonMultipart,
49 | EchoMultipart
50 | )).unwrap();
51 | }
52 |
--------------------------------------------------------------------------------
/src/bin/read_file.rs:
--------------------------------------------------------------------------------
1 | #[macro_use] extern crate log;
2 | extern crate multipart;
3 | extern crate rand;
4 |
5 | use multipart::server::Multipart;
6 |
7 | use rand::{Rng, ThreadRng};
8 |
9 | use std::fs::File;
10 | use std::env;
11 | use std::io::{self, Read};
12 |
13 | const LOG_LEVEL: log::LevelFilter = log::LevelFilter::Debug;
14 |
15 | struct SimpleLogger;
16 |
17 | impl log::Log for SimpleLogger {
18 | fn enabled(&self, metadata: &log::Metadata) -> bool {
19 | LOG_LEVEL.to_level()
20 | .map_or(false, |level| metadata.level() <= level)
21 | }
22 |
23 | fn log(&self, record: &log::Record) {
24 | if self.enabled(record.metadata()) {
25 | println!("{} - {}", record.level(), record.args());
26 | }
27 | }
28 |
29 | fn flush(&self) {}
30 | }
31 |
32 | static LOGGER: SimpleLogger = SimpleLogger;
33 |
34 | fn main() {
35 | log::set_logger(&LOGGER).expect("Could not initialize logger");
36 |
37 | let mut args = env::args().skip(1);
38 |
39 | let boundary = args.next().expect("Boundary must be provided as the first argument");
40 |
41 | let file = args.next().expect("Filename must be provided as the second argument");
42 |
43 | let file = File::open(file).expect("Could not open file");
44 |
45 | let reader = RandomReader {
46 | inner: file,
47 | rng: rand::thread_rng()
48 | };
49 |
50 | let mut multipart = Multipart::with_body(reader, boundary);
51 |
52 | while let Some(field) = multipart.read_entry().unwrap() {
53 | println!("Read field: {:?}", field.headers.name);
54 | }
55 |
56 | println!("All entries read!");
57 | }
58 |
59 | struct RandomReader {
60 | inner: R,
61 | rng: ThreadRng,
62 | }
63 |
64 | impl Read for RandomReader {
65 | fn read(&mut self, buf: &mut [u8]) -> io::Result {
66 | if buf.len() == 0 {
67 | debug!("RandomReader::read() passed a zero-sized buffer.");
68 | return Ok(0);
69 | }
70 |
71 | let len = self.rng.gen_range(1, buf.len() + 1);
72 |
73 | self.inner.read(&mut buf[..len])
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/examples/nickel.rs:
--------------------------------------------------------------------------------
1 | extern crate multipart;
2 | extern crate nickel;
3 |
4 | use std::io::{self, Write};
5 | use nickel::{Action, HttpRouter, MiddlewareResult, Nickel, Request, Response};
6 | use nickel::status::StatusCode;
7 |
8 | use multipart::server::nickel::MultipartBody;
9 | use multipart::server::{Entries, SaveResult};
10 | use multipart::mock::StdoutTee;
11 |
12 | fn handle_multipart<'mw>(req: &mut Request, mut res: Response<'mw>) -> MiddlewareResult<'mw> {
13 | match (*req).multipart_body() {
14 | Some(mut multipart) => {
15 | match multipart.save().temp() {
16 | SaveResult::Full(entries) => process_entries(res, entries),
17 |
18 | SaveResult::Partial(entries, e) => {
19 | println!("Partial errors ... {:?}", e);
20 | return process_entries(res, entries.keep_partial());
21 | },
22 |
23 | SaveResult::Error(e) => {
24 | println!("There are errors in multipart POSTing ... {:?}", e);
25 | res.set(StatusCode::InternalServerError);
26 | return res.send(format!("Server could not handle multipart POST! {:?}", e));
27 | },
28 | }
29 | }
30 | None => {
31 | res.set(StatusCode::BadRequest);
32 | return res.send("Request seems not was a multipart request")
33 | }
34 | }
35 | }
36 |
37 | /// Processes saved entries from multipart request.
38 | /// Returns an OK response or an error.
39 | fn process_entries<'mw>(res: Response<'mw>, entries: Entries) -> MiddlewareResult<'mw> {
40 | let stdout = io::stdout();
41 | let mut res = res.start()?;
42 | if let Err(e) = entries.write_debug(StdoutTee::new(&mut res, &stdout)) {
43 | writeln!(res, "Error while reading entries: {}", e).expect("writeln");
44 | }
45 |
46 | Ok(Action::Halt(res))
47 | }
48 |
49 | fn main() {
50 | let mut srv = Nickel::new();
51 |
52 | srv.post("/multipart_upload/", handle_multipart);
53 |
54 | // Start this example via:
55 | //
56 | // `cargo run --example nickel --features nickel`
57 | //
58 | // And - if you are in the root of this repository - do an example
59 | // upload via:
60 | //
61 | // `curl -F file=@LICENSE 'http://localhost:6868/multipart_upload/'`
62 | srv.listen("127.0.0.1:6868").expect("Failed to bind server");
63 | }
64 |
--------------------------------------------------------------------------------
/examples/iron.rs:
--------------------------------------------------------------------------------
1 | extern crate multipart;
2 | extern crate iron;
3 |
4 | extern crate env_logger;
5 |
6 | use std::io::{self, Write};
7 | use multipart::mock::StdoutTee;
8 | use multipart::server::{Multipart, Entries, SaveResult};
9 | use iron::prelude::*;
10 | use iron::status;
11 |
12 | fn main() {
13 | env_logger::init();
14 |
15 | Iron::new(process_request).http("localhost:80").expect("Could not bind localhost:80");
16 | }
17 |
18 | /// Processes a request and returns response or an occured error.
19 | fn process_request(request: &mut Request) -> IronResult {
20 | // Getting a multipart reader wrapper
21 | match Multipart::from_request(request) {
22 | Ok(mut multipart) => {
23 | // Fetching all data and processing it.
24 | // save().temp() reads the request fully, parsing all fields and saving all files
25 | // in a new temporary directory under the OS temporary directory.
26 | match multipart.save().temp() {
27 | SaveResult::Full(entries) => process_entries(entries),
28 | SaveResult::Partial(entries, reason) => {
29 | process_entries(entries.keep_partial())?;
30 | Ok(Response::with((
31 | status::BadRequest,
32 | format!("error reading request: {}", reason.unwrap_err())
33 | )))
34 | }
35 | SaveResult::Error(error) => Ok(Response::with((
36 | status::BadRequest,
37 | format!("error reading request: {}", error)
38 | ))),
39 | }
40 | }
41 | Err(_) => {
42 | Ok(Response::with((status::BadRequest, "The request is not multipart")))
43 | }
44 | }
45 | }
46 |
47 | /// Processes saved entries from multipart request.
48 | /// Returns an OK response or an error.
49 | fn process_entries(entries: Entries) -> IronResult {
50 | let mut data = Vec::new();
51 |
52 | {
53 | let stdout = io::stdout();
54 | let tee = StdoutTee::new(&mut data, &stdout);
55 | entries.write_debug(tee).map_err(|e| {
56 | IronError::new(
57 | e,
58 | (status::InternalServerError, "Error printing request fields")
59 | )
60 | })?;
61 | }
62 |
63 | let _ = writeln!(data, "Entries processed");
64 |
65 | Ok(Response::with((status::Ok, data)))
66 | }
67 |
--------------------------------------------------------------------------------
/lorem_ipsum.txt:
--------------------------------------------------------------------------------
1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed dignissim, lectus a placerat vestibulum, mi massa dapibus ante, placerat efficitur dui est non lorem. Donec metus lacus, ultricies id suscipit sed, varius et augue. Vivamus erat lectus, interdum in quam non, euismod venenatis eros. Sed vitae magna orci. Duis finibus velit sem, eu luctus urna fringilla vitae. Suspendisse volutpat eros a tincidunt porttitor. Sed ut massa pretium, tempor neque nec, lobortis quam. Etiam vestibulum mauris eu sem consectetur, condimentum fermentum libero vulputate. Vestibulum porttitor leo et blandit condimentum. Pellentesque auctor odio eros, nec placerat lorem ultrices vitae. Suspendisse pretium tellus a ipsum sagittis consequat. Nullam pulvinar ligula ut fermentum laoreet. Maecenas rhoncus ut neque vitae tincidunt. Maecenas tincidunt at orci sed scelerisque. Sed porttitor tincidunt purus, ut efficitur leo lobortis vitae. Aenean et orci dolor.
2 |
3 | Vestibulum at laoreet felis. Cras et justo libero. Morbi pulvinar tincidunt odio, id finibus magna tincidunt non. Nulla facilisi. In at finibus lacus. Phasellus non volutpat dui. Vivamus porta fermentum dignissim. Nulla facilisi. Mauris laoreet semper ex lacinia interdum. Donec et dui non orci cursus scelerisque vulputate non neque. Fusce efficitur maximus turpis tempor interdum. Proin sit amet nunc pretium, varius dui sed, pretium nulla. Integer commodo orci ut felis bibendum feugiat.
4 |
5 | In interdum pulvinar tellus, quis porta eros consectetur in. Ut pharetra sem quam, id congue urna tempus eu. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed egestas mattis ex quis malesuada. Quisque justo enim, posuere non arcu id, dictum pulvinar ante. Curabitur at rhoncus turpis. Donec in odio ut dolor elementum ultricies. Integer massa lacus, ullamcorper non nisi sed, scelerisque commodo lacus. Aliquam erat volutpat. Etiam eleifend libero tincidunt lobortis dignissim. Aliquam in odio sed libero sollicitudin pharetra.
6 |
7 | In quis consectetur ex, nec tempus mi. Donec commodo urna augue, non hendrerit mi lobortis et. Duis a augue laoreet, pulvinar purus luctus, rhoncus est. Quisque sodales sollicitudin augue ac bibendum. Sed a metus risus. Nulla non nulla nisl. Aenean erat velit, tempor id pellentesque eu, volutpat vitae dolor. Praesent commodo, dui in luctus aliquet, est tortor vehicula nibh, sed sollicitudin dui elit eu purus. Integer lacinia rutrum convallis. Nullam varius fringilla dui, elementum finibus magna tincidunt id. Praesent et cursus purus, vitae blandit purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam vel cursus neque.
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "multipart"
3 |
4 | version = "0.18.0"
5 |
6 | authors = ["Austin Bonander "]
7 |
8 | description = "A backend-agnostic extension for HTTP libraries that provides support for POST multipart/form-data requests on both client and server."
9 |
10 | keywords = ["form-data", "hyper", "iron", "http", "upload"]
11 |
12 | repository = "http://github.com/abonander/multipart"
13 |
14 | documentation = "http://docs.rs/multipart/"
15 |
16 | license = "MIT OR Apache-2.0"
17 |
18 | readme = "README.md"
19 |
20 | [dependencies]
21 | lazy_static = { version = "1.2.0", optional = true }
22 | log = "0.4"
23 | mime = "0.3.14"
24 | mime_guess = "2.0.1"
25 | rand = "0.8"
26 | safemem = { version = "0.3", optional = true }
27 | tempfile = "3"
28 | clippy = { version = ">=0.0, <0.1", optional = true}
29 |
30 | #Server Dependencies
31 | buf_redux = { version = "0.8", optional = true, default-features = false }
32 | httparse = { version = "1.2", optional = true }
33 | twoway = { version = "0.1", optional = true }
34 | quick-error = { version = "1.2", optional = true }
35 |
36 | # Optional Integrations
37 | hyper = { version = ">=0.9, <0.11", optional = true, default-features = false }
38 | iron = { version = ">=0.4,<0.7", optional = true }
39 | tiny_http = { version = "0.6", optional = true }
40 | nickel = { version = ">=0.10.1", optional = true }
41 |
42 | # Only for Rocket example but dev-dependencies can't be optional
43 | rocket = { version = "0.4", optional = true }
44 |
45 | [dev-dependencies]
46 | env_logger = "0.5"
47 |
48 | [features]
49 | client = []
50 | default = ["client", "hyper", "iron", "mock", "nickel", "server", "tiny_http"]
51 | server = ["buf_redux", "httparse", "quick-error", "safemem", "twoway"]
52 | mock = []
53 | nightly = []
54 | bench = []
55 |
56 | [[example]]
57 | name = "hyper_client"
58 | required-features = ["client", "mock", "hyper"]
59 |
60 | [[example]]
61 | name = "hyper_reqbuilder"
62 | required-features = ["client", "mock", "hyper"]
63 |
64 | [[example]]
65 | name = "hyper_server"
66 | required-features = ["mock", "hyper", "server"]
67 |
68 | [[example]]
69 | name = "iron"
70 | required-features = ["mock", "iron", "server"]
71 |
72 | [[example]]
73 | name = "iron_intercept"
74 | required-features = ["mock", "iron", "server"]
75 |
76 | [[example]]
77 | name = "nickel"
78 | required-features = ["mock", "nickel", "server"]
79 |
80 | [[example]]
81 | name = "tiny_http"
82 | required-features = ["mock", "tiny_http", "server"]
83 |
84 | [[example]]
85 | name = "rocket"
86 | required-features = ["mock", "rocket", "server"]
87 |
88 | [[bin]]
89 | name = "form_test"
90 | required-features = ["mock", "hyper", "server"]
91 |
--------------------------------------------------------------------------------
/src/server/nickel.rs:
--------------------------------------------------------------------------------
1 | //! Support for `multipart/form-data` bodies in [Nickel](https://nickel.rs).
2 | pub extern crate nickel;
3 |
4 | use self::nickel::hyper;
5 | use self::hyper::header::ContentType;
6 |
7 | pub use self::nickel::Request as NickelRequest;
8 | pub use self::nickel::hyper::server::Request as HyperRequest;
9 |
10 | use server::{HttpRequest, Multipart};
11 |
12 | /// A wrapper for `&mut nickel::Request` which implements `multipart::server::HttpRequest`.
13 | ///
14 | /// Necessary because this crate cannot directly provide an impl of `HttpRequest` for
15 | /// `&mut NickelRequest`.
16 | pub struct Maybe<'r, 'mw: 'r, 'server: 'mw, D: 'mw>(pub &'r mut NickelRequest<'mw, 'server, D>);
17 |
18 | impl<'r, 'mw: 'r, 'server: 'mw, D: 'mw> HttpRequest for Maybe<'r, 'mw, 'server, D> {
19 | type Body = &'r mut HyperRequest<'mw, 'server>;
20 |
21 | fn multipart_boundary(&self) -> Option<&str> {
22 | // we can't use the impl from the `hyper` module because it might be the wrong version
23 | let cont_type = try_opt!(self.0.origin.headers.get::());
24 | cont_type.get_param("boundary").map(|v| v.as_str())
25 | }
26 |
27 | fn body(self) -> Self::Body {
28 | &mut self.0.origin
29 | }
30 | }
31 |
32 | /// Extension trait for getting the `multipart/form-data` body from `nickel::Request`.
33 | ///
34 | /// Implemented for `nickel::Request`.
35 | pub trait MultipartBody<'mw, 'server> {
36 | /// Get a multipart reader for the request body, if the request is of the right type.
37 | fn multipart_body(&mut self) -> Option>>;
38 | }
39 |
40 | impl<'mw, 'server, D: 'mw> MultipartBody<'mw, 'server> for NickelRequest<'mw, 'server, D> {
41 | fn multipart_body(&mut self) -> Option>> {
42 | Multipart::from_request(Maybe(self)).ok()
43 | }
44 | }
45 |
46 | impl<'r, 'mw: 'r, 'server: 'mw, D: 'mw> AsRef<&'r mut NickelRequest<'mw, 'server, D>> for Maybe<'r, 'mw, 'server, D> {
47 | fn as_ref(&self) -> &&'r mut NickelRequest<'mw, 'server, D> {
48 | &self.0
49 | }
50 | }
51 |
52 | impl<'r, 'mw: 'r, 'server: 'mw, D: 'mw> AsMut<&'r mut NickelRequest<'mw, 'server, D>> for Maybe<'r, 'mw, 'server, D> {
53 | fn as_mut(&mut self) -> &mut &'r mut NickelRequest<'mw, 'server, D> {
54 | &mut self.0
55 | }
56 | }
57 |
58 | impl<'r, 'mw: 'r, 'server: 'mw, D: 'mw> Into<&'r mut NickelRequest<'mw, 'server, D>> for Maybe<'r, 'mw, 'server, D> {
59 | fn into(self) -> &'r mut NickelRequest<'mw, 'server, D> {
60 | self.0
61 | }
62 | }
63 |
64 | impl<'r, 'mw: 'r, 'server: 'mw, D: 'mw> From<&'r mut NickelRequest<'mw, 'server, D>> for Maybe<'r, 'mw, 'server, D> {
65 | fn from(req: &'r mut NickelRequest<'mw, 'server, D>) -> Self {
66 | Maybe(req)
67 | }
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/src/client/hyper.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2016 `multipart` Crate Developers
2 | //
3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be
6 | // copied, modified, or distributed except according to those terms.
7 | //! Client-side integration with [Hyper](https://github.com/hyperium/hyper).
8 | //! Enabled with the `hyper` feature (on by default).
9 | //!
10 | //! Contains `impl HttpRequest for Request` and `impl HttpStream for Request`.
11 | //!
12 | //! Also see: [`lazy::Multipart::client_request()`](../lazy/struct.Multipart.html#method.client_request)
13 | //! and [`lazy::Multipart::client_request_mut()`](../lazy/struct.Multipart.html#method.client_request_mut)
14 | //! (adaptors for `hyper::client::RequestBuilder`).
15 | use hyper::client::request::Request;
16 | use hyper::client::response::Response;
17 | use hyper::header::{ContentType, ContentLength};
18 | use hyper::method::Method;
19 | use hyper::net::{Fresh, Streaming};
20 |
21 | use hyper::Error as HyperError;
22 |
23 | use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value};
24 |
25 | use super::{HttpRequest, HttpStream};
26 |
27 | /// #### Feature: `hyper`
28 | impl HttpRequest for Request {
29 | type Stream = Request;
30 | type Error = HyperError;
31 |
32 | /// # Panics
33 | /// If `self.method() != Method::Post`.
34 | fn apply_headers(&mut self, boundary: &str, content_len: Option) -> bool {
35 | if self.method() != Method::Post {
36 | error!(
37 | "Expected Hyper request method to be `Post`, was actually `{:?}`",
38 | self.method()
39 | );
40 |
41 | return false;
42 | }
43 |
44 | let headers = self.headers_mut();
45 |
46 | headers.set(ContentType(multipart_mime(boundary)));
47 |
48 | if let Some(size) = content_len {
49 | headers.set(ContentLength(size));
50 | }
51 |
52 | debug!("Hyper headers: {}", headers);
53 |
54 | true
55 | }
56 |
57 | fn open_stream(self) -> Result {
58 | self.start()
59 | }
60 | }
61 |
62 | /// #### Feature: `hyper`
63 | impl HttpStream for Request {
64 | type Request = Request;
65 | type Response = Response;
66 | type Error = HyperError;
67 |
68 | fn finish(self) -> Result {
69 | self.send()
70 | }
71 | }
72 |
73 | /// Create a `Content-Type: multipart/form-data;boundary={bound}`
74 | pub fn content_type(bound: &str) -> ContentType {
75 | ContentType(multipart_mime(bound))
76 | }
77 |
78 | fn multipart_mime(bound: &str) -> Mime {
79 | Mime(
80 | TopLevel::Multipart, SubLevel::Ext("form-data".into()),
81 | vec![(Attr::Ext("boundary".into()), Value::Ext(bound.into()))]
82 | )
83 | }
84 |
--------------------------------------------------------------------------------
/examples/tiny_http.rs:
--------------------------------------------------------------------------------
1 | extern crate tiny_http;
2 | extern crate multipart;
3 |
4 | use std::io::{self, Cursor, Write};
5 | use multipart::server::{Multipart, Entries, SaveResult};
6 | use multipart::mock::StdoutTee;
7 | use tiny_http::{Response, StatusCode, Request};
8 | fn main() {
9 | // Starting a server on `localhost:80`
10 | let server = tiny_http::Server::http("localhost:80").expect("Could not bind localhost:80");
11 | loop {
12 | // This blocks until the next request is received
13 | let mut request = server.recv().unwrap();
14 |
15 | // Processes a request and returns response or an occured error
16 | let result = process_request(&mut request);
17 | let resp = match result {
18 | Ok(resp) => resp,
19 | Err(e) => {
20 | println!("An error has occured during request proccessing: {:?}", e);
21 | build_response(500, "The received data was not correctly proccessed on the server")
22 | }
23 | };
24 |
25 | // Answers with a response to a client
26 | request.respond(resp).unwrap();
27 | }
28 | }
29 |
30 | type RespBody = Cursor>;
31 |
32 | /// Processes a request and returns response or an occured error.
33 | fn process_request(request: &mut Request) -> io::Result> {
34 | // Getting a multipart reader wrapper
35 | match Multipart::from_request(request) {
36 | Ok(mut multipart) => {
37 | // Fetching all data and processing it.
38 | // save().temp() reads the request fully, parsing all fields and saving all files
39 | // in a new temporary directory under the OS temporary directory.
40 | match multipart.save().temp() {
41 | SaveResult::Full(entries) => process_entries(entries),
42 | SaveResult::Partial(entries, reason) => {
43 | process_entries(entries.keep_partial())?;
44 | // We don't set limits
45 | Err(reason.unwrap_err())
46 | }
47 | SaveResult::Error(error) => Err(error),
48 | }
49 | }
50 | Err(_) => Ok(build_response(400, "The request is not multipart")),
51 | }
52 | }
53 |
54 | /// Processes saved entries from multipart request.
55 | /// Returns an OK response or an error.
56 | fn process_entries(entries: Entries) -> io::Result> {
57 | let mut data = Vec::new();
58 |
59 | {
60 | let stdout = io::stdout();
61 | let tee = StdoutTee::new(&mut data, &stdout);
62 | entries.write_debug(tee)?;
63 | }
64 |
65 | writeln!(data, "Entries processed")?;
66 |
67 | Ok(build_response(200, data))
68 | }
69 |
70 | fn build_response>>(status_code: u16, data: D) -> Response {
71 | let data = data.into();
72 | let data_len = data.len();
73 | Response::new(StatusCode(status_code),
74 | vec![],
75 | Cursor::new(data),
76 | Some(data_len),
77 | None)
78 | }
79 |
--------------------------------------------------------------------------------
/examples/rocket.rs:
--------------------------------------------------------------------------------
1 | // Example usage with Rocket (https://rocket.rs)
2 | //
3 | // Direct integration is not provided at this time as it appears the Rocket folks would prefer
4 | // to handle multipart requests behind the scenes.
5 | #![feature(proc_macro_hygiene, decl_macro)]
6 | #![feature(plugin, custom_attribute)]
7 |
8 | extern crate multipart;
9 | #[macro_use]
10 | extern crate rocket;
11 |
12 | use multipart::mock::StdoutTee;
13 | use multipart::server::Multipart;
14 | use multipart::server::save::Entries;
15 | use multipart::server::save::SaveResult::*;
16 |
17 | use rocket::Data;
18 | use rocket::http::{ContentType, Status};
19 | use rocket::response::Stream;
20 | use rocket::response::status::Custom;
21 |
22 | use std::io::{self, Cursor, Write};
23 |
24 | #[post("/upload", data = "")]
25 | // signature requires the request to have a `Content-Type`
26 | fn multipart_upload(cont_type: &ContentType, data: Data) -> Result>>, Custom> {
27 | // this and the next check can be implemented as a request guard but it seems like just
28 | // more boilerplate than necessary
29 | if !cont_type.is_form_data() {
30 | return Err(Custom(
31 | Status::BadRequest,
32 | "Content-Type not multipart/form-data".into()
33 | ));
34 | }
35 |
36 | let (_, boundary) = cont_type.params().find(|&(k, _)| k == "boundary").ok_or_else(
37 | || Custom(
38 | Status::BadRequest,
39 | "`Content-Type: multipart/form-data` boundary param not provided".into()
40 | )
41 | )?;
42 |
43 | match process_upload(boundary, data) {
44 | Ok(resp) => Ok(Stream::from(Cursor::new(resp))),
45 | Err(err) => Err(Custom(Status::InternalServerError, err.to_string()))
46 | }
47 | }
48 |
49 | fn process_upload(boundary: &str, data: Data) -> io::Result> {
50 | let mut out = Vec::new();
51 |
52 | // saves all fields, any field longer than 10kB goes to a temporary directory
53 | // Entries could implement FromData though that would give zero control over
54 | // how the files are saved; Multipart would be a good impl candidate though
55 | match Multipart::with_body(data.open(), boundary).save().temp() {
56 | Full(entries) => process_entries(entries, &mut out)?,
57 | Partial(partial, reason) => {
58 | writeln!(out, "Request partially processed: {:?}", reason)?;
59 | if let Some(field) = partial.partial {
60 | writeln!(out, "Stopped on field: {:?}", field.source.headers)?;
61 | }
62 |
63 | process_entries(partial.entries, &mut out)?
64 | },
65 | Error(e) => return Err(e),
66 | }
67 |
68 | Ok(out)
69 | }
70 |
71 | // having a streaming output would be nice; there's one for returning a `Read` impl
72 | // but not one that you can `write()` to
73 | fn process_entries(entries: Entries, mut out: &mut Vec) -> io::Result<()> {
74 | {
75 | let stdout = io::stdout();
76 | let tee = StdoutTee::new(&mut out, &stdout);
77 | entries.write_debug(tee)?;
78 | }
79 |
80 | writeln!(out, "Entries processed")
81 | }
82 |
83 | fn main() {
84 | rocket::ignite().mount("/", routes![multipart_upload]).launch();
85 | }
86 |
--------------------------------------------------------------------------------
/src/client/sized.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2016 `multipart` Crate Developers
2 | //
3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be
6 | // copied, modified, or distributed except according to those terms.
7 | //! Sized/buffered wrapper around `HttpRequest`.
8 |
9 | use client::{HttpRequest, HttpStream};
10 |
11 | use std::io;
12 | use std::io::prelude::*;
13 |
14 | /// A wrapper around a request object that measures the request body and sets the `Content-Length`
15 | /// header to its size in bytes.
16 | ///
17 | /// Sized requests are more human-readable and use less bandwidth
18 | /// (as chunking adds [visual noise and overhead][chunked-example]),
19 | /// but they must be able to load their entirety, including the contents of all files
20 | /// and streams, into memory so the request body can be measured.
21 | ///
22 | /// You should really only use sized requests if you intend to inspect the data manually on the
23 | /// server side, as it will produce a more human-readable request body. Also, of course, if the
24 | /// server doesn't support chunked requests or otherwise rejects them.
25 | ///
26 | /// [chunked-example]: http://en.wikipedia.org/wiki/Chunked_transfer_encoding#Example
27 | pub struct SizedRequest {
28 | inner: R,
29 | buffer: Vec,
30 | boundary: String,
31 | }
32 |
33 | impl SizedRequest {
34 | #[doc(hidden)]
35 | pub fn from_request(req: R) -> SizedRequest {
36 | SizedRequest {
37 | inner: req,
38 | buffer: Vec::new(),
39 | boundary: String::new(),
40 | }
41 | }
42 | }
43 |
44 | impl Write for SizedRequest {
45 | fn write(&mut self, data: &[u8]) -> io::Result {
46 | self.buffer.write(data)
47 | }
48 |
49 | fn flush(&mut self) -> io::Result<()> { Ok(()) }
50 | }
51 |
52 | impl HttpRequest for SizedRequest
53 | where ::Error: From {
54 | type Stream = Self;
55 | type Error = R::Error;
56 |
57 | /// `SizedRequest` ignores `_content_len` because it sets its own later.
58 | fn apply_headers(&mut self, boundary: &str, _content_len: Option) -> bool {
59 | self.boundary.clear();
60 | self.boundary.push_str(boundary);
61 | true
62 | }
63 |
64 | fn open_stream(mut self) -> Result {
65 | self.buffer.clear();
66 | Ok(self)
67 | }
68 | }
69 |
70 | impl HttpStream for SizedRequest
71 | where ::Error: From {
72 | type Request = Self;
73 | type Response = <::Stream as HttpStream>::Response;
74 | type Error = <::Stream as HttpStream>::Error;
75 |
76 | fn finish(mut self) -> Result {
77 | let content_len = self.buffer.len() as u64;
78 |
79 | if !self.inner.apply_headers(&self.boundary, Some(content_len)) {
80 | return Err(io::Error::new(
81 | io::ErrorKind::Other,
82 | "SizedRequest failed to apply headers to wrapped request."
83 | ).into());
84 | }
85 |
86 | let mut req = self.inner.open_stream()?;
87 | io::copy(&mut &self.buffer[..], &mut req)?;
88 | req.finish()
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | `multipart` Examples
2 | ===========================
3 |
4 | These example files show how to use `multipart` with the various crates it integrates with.
5 |
6 | These files carry the same licenses as [`multipart` itself](https://github.com/abonander/multipart#license), though this may be lightened to a copyright-free license in the near future.
7 |
8 | ## Client
9 |
10 | Examples for the client-side integrations of `multipart`'s API.
11 |
12 | [`hyper_client`](hyper_client.rs)
13 | ---------------------------------
14 | Author: [abonander]
15 |
16 | This example showcases usage of `multipart` with the `hyper::client::Request` API.
17 |
18 | ```
19 | $ cargo run --example hyper_client
20 | ```
21 |
22 | [`hyper_reqbuilder`](hyper_reqbuilder.rs)
23 | -----------------------------------------
24 | Author: [abonander]
25 |
26 | This example showcases usage of `multipart` with Hyper's new `Client` API,
27 | via the lazy-writing capabilities of `multipart::client::lazy`.
28 |
29 | ```
30 | $ cargo run --example hyper_reqbuilder
31 | ```
32 |
33 | ## Server
34 |
35 | [`hyper_server`](hyper_server.rs)
36 | ---------------------------------
37 | Author: [Puhrez]
38 |
39 | This example shows how to use `multipart` with a [`hyper::Server`] (http://hyper.rs/) to intercept multipart requests.
40 |
41 | ```
42 | $ cargo run --example hyper_server
43 | ```
44 |
45 | [`iron`](iron.rs)
46 | -----------------
47 | Author: [White-Oak]
48 |
49 | This example shows how to use `multipart` with the [Iron web application framework](https://github.com/iron/iron), via `multipart`'s support
50 | for the `iron::Request` type.
51 |
52 | To run:
53 |
54 | ```
55 | $ cargo run --features iron --example iron
56 | ```
57 |
58 | [`iron_intercept`](iron_intercept.rs)
59 | -------------------------------------
60 | Author: [abonander]
61 |
62 | This example shows how to use `multipart`'s specialized `Intercept` middleware with Iron, which reads out all fields and
63 | files to local storage so they can be accessed arbitrarily.
64 |
65 | ```
66 | $ cargo run --features iron --example iron_intercept
67 | ```
68 |
69 | [`tiny_http`](tiny_http.rs)
70 | ---------------------------
71 | Author: [White-Oak]
72 |
73 | This example shows how to use `multipart` with the [`tiny_http` crate](https://crates.io/crates/tiny_http), via `multipart`'s support for the `tiny_http::Request` type.
74 |
75 | ```
76 | $ cargo run --features tiny_http --example tiny_http
77 | ```
78 |
79 | [`hyper_server`](hyper_server.rs)
80 | ---------------------------------
81 | Author: [Puhrez]
82 |
83 | This example shows how to use `multipart` with a [`hyper::Server`] (http://hyper.rs/) to intercept multipart requests.
84 |
85 | ```
86 | $ cargo run --example hyper_server
87 | ```
88 |
89 | [`nickel`](nickel.rs)
90 | ---------------------
91 | Author: [iamsebastian]
92 |
93 | This example shows how to use `multipart` to handle multipart uploads in [nickel.rs](https://nickel.rs).
94 |
95 | ```
96 | $ cargo run --example nickel --features nickel
97 | ```
98 |
99 | [Rocket](rocket.rs)
100 | -------------------
101 | Author: [abonander]
102 |
103 | This example shows how `multipart`'s server API can be used with [Rocket](https://rocket.rs) without
104 | explicit support (the Rocket folks seem to want to handle `multipart/form-data` behind the scenes
105 | but haven't gotten around to implementing it yet; this would supercede any integration from `multipart`).
106 |
107 | ```
108 | $ cargo run --example rocket --features "rocket"
109 | ```
110 |
111 | [iamsebastian]: https://github.com/iamsebastian
112 | [Puhrez]: https://github.com/puhrez
113 | [White-Oak]: https://github.com/white-oak
114 | [abonander]: https://github.com/abonander
115 |
116 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2016 `multipart` Crate Developers
2 | //
3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be
6 | // copied, modified, or distributed except according to those terms.
7 | //! Client- and server-side abstractions for HTTP `multipart/form-data` requests.
8 | //!
9 | //! ### Features:
10 | //! This documentation is built with all features enabled.
11 | //!
12 | //! * `client`: The client-side abstractions for generating multipart requests.
13 | //!
14 | //! * `server`: The server-side abstractions for parsing multipart requests.
15 | //!
16 | //! * `mock`: Provides mock implementations of core `client` and `server` traits for debugging
17 | //! or non-standard use.
18 | //!
19 | //! * `hyper`: Integration with the [Hyper](https://crates.io/crates/hyper) HTTP library
20 | //! for client and/or server depending on which other feature flags are set.
21 | //!
22 | //! * `iron`: Integration with the [Iron](http://crates.io/crates/iron) web application
23 | //! framework. See the [`server::iron`](server/iron/index.html) module for more information.
24 | //!
25 | //! * `nickel` (returning in 0.14!): Integration with the [Nickel](https://crates.io/crates/nickel)
26 | //! web application framework. See the [`server::nickel`](server/nickel/index.html) module for more
27 | //! information.
28 | //!
29 | //! * `tiny_http`: Integration with the [`tiny_http`](https://crates.io/crates/tiny_http)
30 | //! crate. See the [`server::tiny_http`](server/tiny_http/index.html) module for more information.
31 | //!
32 | //! ### Note: Work in Progress
33 | //! I have left a number of Request-for-Comments (RFC) questions on various APIs and other places
34 | //! in the code as there are some cases where I'm not sure what the desirable behavior is.
35 | //!
36 | //! I have opened an issue as a place to collect responses and discussions for these questions
37 | //! [on Github](https://github.com/abonander/multipart/issues/96). Please quote the RFC-statement
38 | //! (and/or link to its source line) and provide your feedback there.
39 | #![cfg_attr(feature = "clippy", feature(plugin))]
40 | #![cfg_attr(feature = "clippy", plugin(clippy))]
41 | #![cfg_attr(feature = "clippy", deny(clippy))]
42 | #![cfg_attr(feature = "bench", feature(test))]
43 | #![deny(missing_docs)]
44 |
45 | #[macro_use]
46 | extern crate log;
47 |
48 | extern crate mime;
49 | extern crate mime_guess;
50 | extern crate rand;
51 | extern crate tempfile;
52 |
53 | #[cfg(feature = "quick-error")]
54 | #[macro_use]
55 | extern crate quick_error;
56 |
57 | #[cfg(feature = "server")]
58 | extern crate safemem;
59 |
60 | #[cfg(feature = "hyper")]
61 | extern crate hyper;
62 |
63 | #[cfg(feature = "iron")]
64 | extern crate iron;
65 |
66 | #[cfg(feature = "tiny_http")]
67 | extern crate tiny_http;
68 |
69 | #[cfg(test)]
70 | extern crate env_logger;
71 |
72 | #[cfg(any(feature = "mock", test))]
73 | pub mod mock;
74 |
75 | use rand::Rng;
76 |
77 | /// Chain a series of results together, with or without previous results.
78 | ///
79 | /// ```
80 | /// #[macro_use] extern crate multipart;
81 | ///
82 | /// fn try_add_one(val: u32) -> Result {
83 | /// if val < 5 {
84 | /// Ok(val + 1)
85 | /// } else {
86 | /// Err(val)
87 | /// }
88 | /// }
89 | ///
90 | /// fn main() {
91 | /// let res = chain_result! {
92 | /// try_add_one(1),
93 | /// prev -> try_add_one(prev),
94 | /// prev -> try_add_one(prev),
95 | /// prev -> try_add_one(prev)
96 | /// };
97 | ///
98 | /// println!("{:?}", res);
99 | /// }
100 | ///
101 | /// ```
102 | #[macro_export]
103 | macro_rules! chain_result {
104 | ($first_expr:expr, $($try_expr:expr),*) => (
105 | $first_expr $(.and_then(|_| $try_expr))*
106 | );
107 | ($first_expr:expr, $($($arg:ident),+ -> $try_expr:expr),*) => (
108 | $first_expr $(.and_then(|$($arg),+| $try_expr))*
109 | );
110 | }
111 |
112 | #[cfg(feature = "client")]
113 | pub mod client;
114 | #[cfg(feature = "server")]
115 | pub mod server;
116 |
117 | #[cfg(all(test, feature = "client", feature = "server"))]
118 | mod local_test;
119 |
120 | fn random_alphanumeric(len: usize) -> String {
121 | rand::thread_rng()
122 | .sample_iter(&rand::distributions::Alphanumeric)
123 | .take(len)
124 | .map(|c| c as char)
125 | .collect()
126 | }
127 |
128 | #[cfg(test)]
129 | fn init_log() {
130 | let _ = env_logger::try_init();
131 | }
132 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Multipart [](https://travis-ci.org/abonander/multipart) [](https://crates.io/crates/multipart)
2 |
3 | Client- and server-side abstractions for HTTP file uploads (POST requests with `Content-Type: multipart/form-data`).
4 |
5 | Supports several different (**sync**hronous API) HTTP crates.
6 | **Async**hronous (i.e. `futures`-based) API support will be provided by [multipart-async].
7 |
8 | ##### Minimum supported Rust version: 1.36.0
9 |
10 | ##### Maintenance Status: Passive
11 |
12 | As the web ecosystem in Rust moves towards asynchronous APIs, the need for this crate in synchronous
13 | API form becomes dubious. This crate in its current form is usable enough, so as of June 2020 it
14 | is now in passive maintenance mode; bug reports will be addressed as time permits and PRs will be
15 | accepted but otherwise no new development of the existing API is taking place.
16 |
17 | Look for a release of [multipart-async] soon which targets newer releases of Hyper.
18 |
19 | ### [Documentation](http://docs.rs/multipart/)
20 |
21 | ## Integrations
22 |
23 | Example files demonstrating how to use `multipart` with these crates are available under [`examples/`](examples).
24 |
25 | ### [Hyper ](https://crates.io/crates/hyper)
26 | via the `hyper` feature (enabled by default).
27 |
28 | **Note: Hyper 0.9, 0.10 (synchronous API) only**; support for asynchronous APIs will be provided by [multipart-async].
29 |
30 | Client integration includes support for regular `hyper::client::Request` objects via `multipart::client::Multipart`, as well
31 | as integration with the new `hyper::Client` API via `multipart::client::lazy::Multipart` (new in 0.5).
32 |
33 | Server integration for `hyper::server::Request` via `multipart::server::Multipart`.
34 |
35 | ### [Iron ](https://crates.io/crates/iron)
36 | via the `iron` feature.
37 |
38 | Provides regular server-side integration with `iron::Request` via `multipart::server::Multipart`,
39 | as well as a convenient `BeforeMiddleware` implementation in `multipart::server::iron::Intercept`.
40 |
41 | ### [Nickel ](https://crates.io/crates/nickel) returning to `multipart` in 0.14!
42 | via the `nickel` feature.
43 |
44 | Provides server-side integration with `&mut nickel::Request` via `multipart::server::Multipart`.
45 |
46 | ### [tiny_http ](https://crates.io/crates/tiny_http)
47 | via the `tiny_http` feature.
48 |
49 | Provides server-side integration with `tiny_http::Request` via `multipart::server::Multipart`.
50 |
51 | ### [Rocket ](https://crates.io/crates/rocket)
52 |
53 | Direct integration is not provided as the Rocket folks seem to want to handle `multipart/form-data`
54 | behind the scenes which would supercede any integration with `multipart`. However, an example is available
55 | showing how to use `multipart` on a Rocket server: [examples/rocket.rs](examples/rocket.rs)
56 |
57 | ## ⚡ Powered By ⚡
58 |
59 | ### [buf_redux ](https://crates.io/crates/buf_redux)
60 |
61 | Customizable drop-in `std::io::BufReader` replacement, created to be used in this crate.
62 | Needed because it can read more bytes into the buffer without the buffer being empty, necessary
63 | when a boundary falls across two reads. (It was easier to author a new crate than try to get this added
64 | to `std::io::BufReader`.)
65 |
66 | ### [httparse ](https://crates.io/crates/httparse)
67 |
68 | Fast, zero-copy HTTP header parsing, used to read field headers in `multipart/form-data` request bodies.
69 |
70 | ### [twoway ](https://crates.io/crates/twoway)
71 |
72 | Fast string and byte-string search. Used to find boundaries in the request body. Uses SIMD acceleration
73 | when possible.
74 |
75 | ## License
76 |
77 | Licensed under either of
78 |
79 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
80 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
81 |
82 | at your option.
83 |
84 | ## Contribution
85 |
86 | Unless you explicitly state otherwise, any contribution intentionally submitted
87 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
88 | additional terms or conditions.
89 |
90 | [multipart-async]: https://github.com/abonander/multipart-async
91 |
--------------------------------------------------------------------------------
/src/server/hyper.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2016 `multipart` Crate Developers
2 | //
3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be
6 | // copied, modified, or distributed except according to those terms.
7 | //! Server-side integration with [Hyper](https://github.com/hyperium/hyper).
8 | //! Enabled with the `hyper` feature (on by default).
9 | //!
10 | //! Also contains an implementation of [`HttpRequest`](../trait.HttpRequest.html)
11 | //! for `hyper::server::Request` and `&mut hyper::server::Request`.
12 | use hyper::net::Fresh;
13 | use hyper::header::ContentType;
14 | use hyper::method::Method;
15 | use hyper::server::{Handler, Request, Response};
16 |
17 | pub use hyper::server::Request as HyperRequest;
18 |
19 | use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value};
20 |
21 | use super::{Multipart, HttpRequest};
22 |
23 | /// A container that implements `hyper::server::Handler` which will switch
24 | /// the handler implementation depending on if the incoming request is multipart or not.
25 | ///
26 | /// Create an instance with `new()` and pass it to `hyper::server::Server::listen()` where
27 | /// you would normally pass a `Handler` instance.
28 | ///
29 | /// A convenient wrapper for `Multipart::from_request()`.
30 | pub struct Switch {
31 | normal: H,
32 | multipart: M,
33 | }
34 |
35 | impl Switch where H: Handler, M: MultipartHandler {
36 | /// Create a new `Switch` instance where
37 | /// `normal` handles normal Hyper requests and `multipart` handles Multipart requests
38 | pub fn new(normal: H, multipart: M) -> Switch {
39 | Switch { normal, multipart }
40 | }
41 | }
42 |
43 | impl Handler for Switch where H: Handler, M: MultipartHandler {
44 | fn handle<'a, 'k>(&'a self, req: Request<'a, 'k>, res: Response<'a, Fresh>) {
45 | match Multipart::from_request(req) {
46 | Ok(multi) => self.multipart.handle_multipart(multi, res),
47 | Err(req) => self.normal.handle(req, res),
48 | }
49 | }
50 | }
51 |
52 | /// A trait defining a type that can handle an incoming multipart request.
53 | ///
54 | /// Extends to closures of the type `Fn(Multipart, Response)`,
55 | /// and subsequently static functions.
56 | pub trait MultipartHandler: Send + Sync {
57 | /// Generate a response from this multipart request.
58 | fn handle_multipart<'a, 'k>(&self,
59 | multipart: Multipart>,
60 | response: Response<'a, Fresh>);
61 | }
62 |
63 | impl MultipartHandler for F
64 | where F: Fn(Multipart, Response), F: Send + Sync {
65 | fn handle_multipart<'a, 'k>(&self,
66 | multipart: Multipart>,
67 | response: Response<'a, Fresh>) {
68 | (*self)(multipart, response);
69 | }
70 | }
71 |
72 | impl<'a, 'b> HttpRequest for HyperRequest<'a, 'b> {
73 | type Body = Self;
74 |
75 | fn multipart_boundary(&self) -> Option<&str> {
76 | if self.method != Method::Post {
77 | return None;
78 | }
79 |
80 | self.headers.get::().and_then(|ct| {
81 | let ContentType(ref mime) = *ct;
82 | let params = match *mime {
83 | Mime(TopLevel::Multipart, SubLevel::FormData, ref params) => params,
84 | _ => return None,
85 | };
86 |
87 | params.iter().find(|&&(ref name, _)|
88 | match *name {
89 | Attr::Boundary => true,
90 | _ => false,
91 | }
92 | ).and_then(|&(_, ref val)|
93 | match *val {
94 | Value::Ext(ref val) => Some(&**val),
95 | _ => None,
96 | }
97 | )
98 | })
99 | }
100 |
101 | fn body(self) -> Self {
102 | self
103 | }
104 | }
105 |
106 | impl<'r, 'a, 'b> HttpRequest for &'r mut HyperRequest<'a, 'b> {
107 | type Body = Self;
108 |
109 | fn multipart_boundary(&self) -> Option<&str> {
110 | if self.method != Method::Post {
111 | return None;
112 | }
113 |
114 | self.headers.get::().and_then(|ct| {
115 | let ContentType(ref mime) = *ct;
116 | let params = match *mime {
117 | Mime(TopLevel::Multipart, SubLevel::FormData, ref params) => params,
118 | _ => return None,
119 | };
120 |
121 | params.iter().find(|&&(ref name, _)|
122 | match *name {
123 | Attr::Boundary => true,
124 | _ => false,
125 | }
126 | ).and_then(|&(_, ref val)|
127 | match *val {
128 | Value::Ext(ref val) => Some(&**val),
129 | _ => None,
130 | }
131 | )
132 | })
133 | }
134 |
135 | fn body(self) -> Self::Body {
136 | self
137 | }
138 | }
139 |
140 |
--------------------------------------------------------------------------------
/src/mock.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2016 `multipart` Crate Developers
2 | //
3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be
6 | // copied, modified, or distributed except according to those terms.
7 | //! Mocked types for client-side and server-side APIs.
8 | use std::io::{self, Read, Write};
9 | use std::fmt;
10 |
11 | use rand::{self, Rng};
12 | use rand::prelude::ThreadRng;
13 |
14 | /// A mock implementation of `client::HttpRequest` which can spawn an `HttpBuffer`.
15 | ///
16 | /// `client::HttpRequest` impl requires the `client` feature.
17 | #[derive(Default, Debug)]
18 | pub struct ClientRequest {
19 | boundary: Option,
20 | content_len: Option,
21 | }
22 |
23 | #[cfg(feature = "client")]
24 | impl ::client::HttpRequest for ClientRequest {
25 | type Stream = HttpBuffer;
26 | type Error = io::Error;
27 |
28 | fn apply_headers(&mut self, boundary: &str, content_len: Option) -> bool {
29 | self.boundary = Some(boundary.into());
30 | self.content_len = content_len;
31 | true
32 | }
33 |
34 | /// ## Panics
35 | /// If `apply_headers()` was not called.
36 | fn open_stream(self) -> Result {
37 | debug!("ClientRequest::open_stream called! {:?}", self);
38 | let boundary = self.boundary.expect("ClientRequest::set_headers() was not called!");
39 |
40 | Ok(HttpBuffer::new_empty(boundary, self.content_len))
41 | }
42 | }
43 |
44 |
45 | /// A writable buffer which stores the boundary and content-length, if provided.
46 | ///
47 | /// Implements `client::HttpStream` if the `client` feature is enabled.
48 | pub struct HttpBuffer {
49 | /// The buffer containing the raw bytes.
50 | pub buf: Vec,
51 | /// The multipart boundary.
52 | pub boundary: String,
53 | /// The value of the content-length header, if set.
54 | pub content_len: Option,
55 | rng: ThreadRng,
56 | }
57 |
58 | impl HttpBuffer {
59 | /// Create an empty buffer with the given boundary and optional content-length.
60 | pub fn new_empty(boundary: String, content_len: Option) -> HttpBuffer {
61 | Self::with_buf(Vec::new(), boundary, content_len)
62 | }
63 |
64 | /// Wrap the given buffer with the given boundary and optional content-length.
65 | pub fn with_buf(buf: Vec, boundary: String, content_len: Option) -> Self {
66 | HttpBuffer {
67 | buf,
68 | boundary,
69 | content_len,
70 | rng: rand::thread_rng()
71 | }
72 | }
73 |
74 | /// Get a `ServerRequest` wrapping the data in this buffer.
75 | pub fn for_server(&self) -> ServerRequest {
76 | ServerRequest {
77 | data: &self.buf,
78 | boundary: &self.boundary,
79 | content_len: self.content_len,
80 | rng: rand::thread_rng(),
81 | }
82 | }
83 | }
84 |
85 | impl Write for HttpBuffer {
86 | /// To simulate a network connection, this will copy a random number of bytes
87 | /// from `buf` to the buffer.
88 | fn write(&mut self, buf: &[u8]) -> io::Result {
89 | if buf.is_empty() {
90 | debug!("HttpBuffer::write() was passed a zero-sized buffer.");
91 | return Ok(0);
92 | }
93 |
94 | // Simulate the randomness of a network connection by not always reading everything
95 | let len = self.rng.gen_range(1..=buf.len());
96 |
97 | self.buf.write(&buf[..len])
98 | }
99 |
100 | fn flush(&mut self) -> io::Result<()> {
101 | self.buf.flush()
102 | }
103 | }
104 |
105 | #[cfg(feature = "client")]
106 | impl ::client::HttpStream for HttpBuffer {
107 | type Request = ClientRequest;
108 | type Response = HttpBuffer;
109 | type Error = io::Error;
110 |
111 | /// Returns `Ok(self)`.
112 | fn finish(self) -> Result { Ok(self) }
113 | }
114 |
115 | impl fmt::Debug for HttpBuffer {
116 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
117 | f.debug_struct("multipart::mock::HttpBuffer")
118 | .field("buf", &self.buf)
119 | .field("boundary", &self.boundary)
120 | .field("content_len", &self.content_len)
121 | .finish()
122 | }
123 | }
124 |
125 | /// A mock implementation of `server::HttpRequest` that can be read.
126 | ///
127 | /// Implements `server::HttpRequest` if the `server` feature is enabled.
128 | pub struct ServerRequest<'a> {
129 | /// Slice of the source `HttpBuffer::buf`
130 | pub data: &'a [u8],
131 | /// The multipart boundary.
132 | pub boundary: &'a str,
133 | /// The value of the content-length header, if set.
134 | pub content_len: Option,
135 | rng: ThreadRng,
136 | }
137 |
138 | impl<'a> ServerRequest<'a> {
139 | /// Create a new `ServerRequest` with the given data and boundary.
140 | ///
141 | /// Assumes `content_len: None`
142 | pub fn new(data: &'a [u8], boundary: &'a str) -> Self {
143 | ServerRequest {
144 | data,
145 | boundary,
146 | content_len: None,
147 | rng: rand::thread_rng(),
148 | }
149 | }
150 | }
151 |
152 | impl<'a> Read for ServerRequest<'a> {
153 | /// To simulate a network connection, this will copy a random number of bytes
154 | /// from the buffer to `out`.
155 | fn read(&mut self, out: &mut [u8]) -> io::Result {
156 | if out.is_empty() {
157 | debug!("ServerRequest::read() was passed a zero-sized buffer.");
158 | return Ok(0);
159 | }
160 |
161 | // Simulate the randomness of a network connection by not always reading everything
162 | let len = self.rng.gen_range(1..=out.len());
163 | self.data.read(&mut out[..len])
164 | }
165 | }
166 |
167 | #[cfg(feature = "server")]
168 | impl<'a> ::server::HttpRequest for ServerRequest<'a> {
169 | type Body = Self;
170 |
171 | fn multipart_boundary(&self) -> Option<&str> { Some(self.boundary) }
172 |
173 | fn body(self) -> Self::Body {
174 | self
175 | }
176 | }
177 |
178 | /// A `Write` adapter that duplicates all data written to the inner writer as well as stdout.
179 | pub struct StdoutTee<'s, W> {
180 | inner: W,
181 | stdout: io::StdoutLock<'s>,
182 | }
183 |
184 | impl<'s, W> StdoutTee<'s, W> {
185 | /// Constructor
186 | pub fn new(inner: W, stdout: &'s io::Stdout) -> Self {
187 | Self {
188 | inner, stdout: stdout.lock(),
189 | }
190 | }
191 | }
192 |
193 | impl<'s, W: Write> Write for StdoutTee<'s, W> {
194 | fn write(&mut self, buf: &[u8]) -> io::Result {
195 | self.inner.write_all(buf)?;
196 | self.stdout.write(buf)
197 | }
198 |
199 | fn flush(&mut self) -> io::Result<()> {
200 | self.inner.flush()?;
201 | self.stdout.flush()
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/fuzz/Cargo.lock:
--------------------------------------------------------------------------------
1 | [root]
2 | name = "multipart-fuzz"
3 | version = "0.0.1"
4 | dependencies = [
5 | "libfuzzer-sys 0.1.0 (git+https://github.com/rust-fuzz/libfuzzer-sys.git)",
6 | "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
7 | "multipart 0.11.0",
8 | ]
9 |
10 | [[package]]
11 | name = "buf_redux"
12 | version = "0.6.1"
13 | source = "registry+https://github.com/rust-lang/crates.io-index"
14 | dependencies = [
15 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
16 | "safemem 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
17 | ]
18 |
19 | [[package]]
20 | name = "gcc"
21 | version = "0.3.45"
22 | source = "registry+https://github.com/rust-lang/crates.io-index"
23 |
24 | [[package]]
25 | name = "httparse"
26 | version = "1.2.1"
27 | source = "registry+https://github.com/rust-lang/crates.io-index"
28 |
29 | [[package]]
30 | name = "libc"
31 | version = "0.2.21"
32 | source = "registry+https://github.com/rust-lang/crates.io-index"
33 |
34 | [[package]]
35 | name = "libfuzzer-sys"
36 | version = "0.1.0"
37 | source = "git+https://github.com/rust-fuzz/libfuzzer-sys.git#36a3928eef5c3c38eb0f251962395bb510c39d46"
38 | dependencies = [
39 | "gcc 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)",
40 | ]
41 |
42 | [[package]]
43 | name = "log"
44 | version = "0.3.7"
45 | source = "registry+https://github.com/rust-lang/crates.io-index"
46 |
47 | [[package]]
48 | name = "memchr"
49 | version = "0.1.11"
50 | source = "registry+https://github.com/rust-lang/crates.io-index"
51 | dependencies = [
52 | "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
53 | ]
54 |
55 | [[package]]
56 | name = "mime"
57 | version = "0.2.3"
58 | source = "registry+https://github.com/rust-lang/crates.io-index"
59 | dependencies = [
60 | "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
61 | ]
62 |
63 | [[package]]
64 | name = "mime_guess"
65 | version = "1.8.1"
66 | source = "registry+https://github.com/rust-lang/crates.io-index"
67 | dependencies = [
68 | "mime 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
69 | "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
70 | "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
71 | "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
72 | ]
73 |
74 | [[package]]
75 | name = "multipart"
76 | version = "0.11.0"
77 | dependencies = [
78 | "buf_redux 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
79 | "httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
80 | "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
81 | "mime 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
82 | "mime_guess 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
83 | "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
84 | "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
85 | "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
86 | "twoway 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
87 | ]
88 |
89 | [[package]]
90 | name = "phf"
91 | version = "0.7.21"
92 | source = "registry+https://github.com/rust-lang/crates.io-index"
93 | dependencies = [
94 | "phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
95 | ]
96 |
97 | [[package]]
98 | name = "phf_codegen"
99 | version = "0.7.21"
100 | source = "registry+https://github.com/rust-lang/crates.io-index"
101 | dependencies = [
102 | "phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
103 | "phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
104 | ]
105 |
106 | [[package]]
107 | name = "phf_generator"
108 | version = "0.7.21"
109 | source = "registry+https://github.com/rust-lang/crates.io-index"
110 | dependencies = [
111 | "phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
112 | "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
113 | ]
114 |
115 | [[package]]
116 | name = "phf_shared"
117 | version = "0.7.21"
118 | source = "registry+https://github.com/rust-lang/crates.io-index"
119 | dependencies = [
120 | "siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
121 | "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
122 | ]
123 |
124 | [[package]]
125 | name = "rand"
126 | version = "0.3.15"
127 | source = "registry+https://github.com/rust-lang/crates.io-index"
128 | dependencies = [
129 | "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
130 | ]
131 |
132 | [[package]]
133 | name = "rustc_version"
134 | version = "0.1.7"
135 | source = "registry+https://github.com/rust-lang/crates.io-index"
136 | dependencies = [
137 | "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
138 | ]
139 |
140 | [[package]]
141 | name = "safemem"
142 | version = "0.1.1"
143 | source = "registry+https://github.com/rust-lang/crates.io-index"
144 |
145 | [[package]]
146 | name = "safemem"
147 | version = "0.2.0"
148 | source = "registry+https://github.com/rust-lang/crates.io-index"
149 |
150 | [[package]]
151 | name = "semver"
152 | version = "0.1.20"
153 | source = "registry+https://github.com/rust-lang/crates.io-index"
154 |
155 | [[package]]
156 | name = "siphasher"
157 | version = "0.2.2"
158 | source = "registry+https://github.com/rust-lang/crates.io-index"
159 |
160 | [[package]]
161 | name = "tempdir"
162 | version = "0.3.5"
163 | source = "registry+https://github.com/rust-lang/crates.io-index"
164 | dependencies = [
165 | "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
166 | ]
167 |
168 | [[package]]
169 | name = "twoway"
170 | version = "0.1.3"
171 | source = "registry+https://github.com/rust-lang/crates.io-index"
172 | dependencies = [
173 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
174 | ]
175 |
176 | [[package]]
177 | name = "unicase"
178 | version = "1.4.0"
179 | source = "registry+https://github.com/rust-lang/crates.io-index"
180 | dependencies = [
181 | "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
182 | ]
183 |
184 | [metadata]
185 | "checksum buf_redux 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e1497634c131ba13483b6e8123f69e219253b018bb32949eefd55c6b5051585d"
186 | "checksum gcc 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)" = "40899336fb50db0c78710f53e87afc54d8c7266fb76262fecc78ca1a7f09deae"
187 | "checksum httparse 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e7a63e511f9edffbab707141fbb8707d1a3098615fb2adbd5769cdfcc9b17d"
188 | "checksum libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "88ee81885f9f04bff991e306fea7c1c60a5f0f9e409e99f6b40e3311a3363135"
189 | "checksum libfuzzer-sys 0.1.0 (git+https://github.com/rust-fuzz/libfuzzer-sys.git)" = ""
190 | "checksum log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5141eca02775a762cc6cd564d8d2c50f67c0ea3a372cbf1c51592b3e029e10ad"
191 | "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
192 | "checksum mime 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5514f038123342d01ee5f95129e4ef1e0470c93bc29edf058a46f9ee3ba6737e"
193 | "checksum mime_guess 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76da6df85047af8c0edfa53f48eb1073012ce1cc95c8fedc0a374f659a89dd65"
194 | "checksum phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "cb325642290f28ee14d8c6201159949a872f220c62af6e110a56ea914fbe42fc"
195 | "checksum phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d62594c0bb54c464f633175d502038177e90309daf2e0158be42ed5f023ce88f"
196 | "checksum phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6b07ffcc532ccc85e3afc45865469bf5d9e4ef5bfcf9622e3cfe80c2d275ec03"
197 | "checksum phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "07e24b0ca9643bdecd0632f2b3da6b1b89bbb0030e0b992afc1113b23a7bc2f2"
198 | "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d"
199 | "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"
200 | "checksum safemem 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "725b3bf47ae40b4abcd27b5f0a9540369426a29f7b905649b3e1468e13e22009"
201 | "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
202 | "checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac"
203 | "checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537"
204 | "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
205 | "checksum twoway 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e267e178055eb3b081224bbef62d4f508ae3c9f000b6ae6ccdb04a0d9c34b77f"
206 | "checksum unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a5906ca2b98c799f4b1ab4557b76367ebd6ae5ef14930ec841c74aed5f3764"
207 |
--------------------------------------------------------------------------------
/src/server/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2016 `multipart` Crate Developers
2 | //
3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be
6 | // copied, modified, or distributed except according to those terms.
7 | //! The server-side abstraction for multipart requests. Enabled with the `server` feature.
8 | //!
9 | //! Use this when you are implementing an HTTP server and want to
10 | //! to accept, parse, and serve HTTP `multipart/form-data` requests (file uploads).
11 | //!
12 | //! See the `Multipart` struct for more info.
13 |
14 | pub extern crate buf_redux;
15 | extern crate httparse;
16 | extern crate twoway;
17 |
18 | use std::borrow::Borrow;
19 | use std::io::prelude::*;
20 | use std::io;
21 |
22 | use self::boundary::BoundaryReader;
23 |
24 | use self::field::PrivReadEntry;
25 |
26 | pub use self::field::{FieldHeaders, MultipartField, MultipartData, ReadEntry, ReadEntryResult};
27 |
28 | use self::save::SaveBuilder;
29 |
30 | pub use self::save::{Entries, SaveResult, SavedField};
31 |
32 | macro_rules! try_opt (
33 | ($expr:expr) => (
34 | match $expr {
35 | Some(val) => val,
36 | None => return None,
37 | }
38 | );
39 | ($expr:expr, $before_ret:expr) => (
40 | match $expr {
41 | Some(val) => val,
42 | None => {
43 | $before_ret;
44 | return None;
45 | }
46 | }
47 | )
48 | );
49 |
50 | macro_rules! try_read_entry {
51 | ($self_:expr; $try:expr) => (
52 | match $try {
53 | Ok(res) => res,
54 | Err(err) => return ::server::ReadEntryResult::Error($self_, err),
55 | }
56 | )
57 | }
58 |
59 | mod boundary;
60 | mod field;
61 |
62 | #[cfg(feature = "hyper")]
63 | pub mod hyper;
64 |
65 | #[cfg(feature = "iron")]
66 | pub mod iron;
67 |
68 | #[cfg(feature = "tiny_http")]
69 | pub mod tiny_http;
70 |
71 | #[cfg(feature = "nickel")]
72 | pub mod nickel;
73 |
74 | pub mod save;
75 |
76 | /// The server-side implementation of `multipart/form-data` requests.
77 | ///
78 | /// Implements `Borrow` to allow access to the request body, if desired.
79 | pub struct Multipart {
80 | reader: BoundaryReader,
81 | }
82 |
83 | impl Multipart<()> {
84 | /// If the given `HttpRequest` is a multipart/form-data POST request,
85 | /// return the request body wrapped in the multipart reader. Otherwise,
86 | /// returns the original request.
87 | pub fn from_request(req: R) -> Result, R> {
88 | //FIXME: move `map` expr to `Some` arm when nonlexical borrow scopes land.
89 | let boundary = match req.multipart_boundary().map(String::from) {
90 | Some(boundary) => boundary,
91 | None => return Err(req),
92 | };
93 |
94 | Ok(Multipart::with_body(req.body(), boundary))
95 | }
96 | }
97 |
98 | impl Multipart {
99 | /// Construct a new `Multipart` with the given body reader and boundary.
100 | ///
101 | /// ## Note: `boundary`
102 | /// This will prepend the requisite `--` to the boundary string as documented in
103 | /// [IETF RFC 1341, Section 7.2.1: "Multipart: the common syntax"][rfc1341-7.2.1].
104 | /// Simply pass the value of the `boundary` key from the `Content-Type` header in the
105 | /// request (or use `Multipart::from_request()`, if supported).
106 | ///
107 | /// [rfc1341-7.2.1]: https://tools.ietf.org/html/rfc1341#page-30
108 | pub fn with_body>(body: R, boundary: Bnd) -> Self {
109 | let boundary = boundary.into();
110 |
111 | info!("Multipart::with_boundary(_, {:?})", boundary);
112 |
113 | Multipart {
114 | reader: BoundaryReader::from_reader(body, boundary),
115 | }
116 | }
117 |
118 | /// Read the next entry from this multipart request, returning a struct with the field's name and
119 | /// data. See `MultipartField` for more info.
120 | ///
121 | /// ## Warning: Risk of Data Loss
122 | /// If the previously returned entry had contents of type `MultipartField::File`,
123 | /// calling this again will discard any unread contents of that entry.
124 | pub fn read_entry(&mut self) -> io::Result