multipart 0.17.0

A backend-agnostic extension for HTTP libraries that provides support for POST multipart/form-data requests on both client and server.
Documentation
// Copyright 2016 `multipart` Crate Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//! The server-side abstraction for multipart requests. Enabled with the `server` feature.
//!
//! Use this when you are implementing an HTTP server and want to
//! to accept, parse, and serve HTTP `multipart/form-data` requests (file uploads).
//!
//! See the `Multipart` struct for more info.

pub extern crate buf_redux;
extern crate httparse;
extern crate twoway;

use std::borrow::Borrow;
use std::io::prelude::*;
use std::io;

use self::boundary::BoundaryReader;

use self::field::PrivReadEntry;

pub use self::field::{FieldHeaders, MultipartField, MultipartData, ReadEntry, ReadEntryResult};

use self::save::SaveBuilder;

pub use self::save::{Entries, SaveResult, SavedField};

macro_rules! try_opt (
    ($expr:expr) => (
        match $expr {
            Some(val) => val,
            None => return None,
        }
    );
    ($expr:expr, $before_ret:expr) => (
        match $expr {
            Some(val) => val,
            None => {
                $before_ret;
                return None;
            }
        }
    )
);

macro_rules! try_read_entry {
    ($self_:expr; $try:expr) => (
        match $try {
            Ok(res) => res,
            Err(err) => return ::server::ReadEntryResult::Error($self_, err),
        }
    )
}

mod boundary;
mod field;

#[cfg(feature = "hyper")]
pub mod hyper;

#[cfg(feature = "iron")]
pub mod iron;

#[cfg(feature = "tiny_http")]
pub mod tiny_http;

#[cfg(feature = "nickel")]
pub mod nickel;

pub mod save;

/// The server-side implementation of `multipart/form-data` requests.
///
/// Implements `Borrow<R>` to allow access to the request body, if desired.
pub struct Multipart<R> {
    reader: BoundaryReader<R>,
}

impl Multipart<()> {
    /// If the given `HttpRequest` is a multipart/form-data POST request,
    /// return the request body wrapped in the multipart reader. Otherwise,
    /// returns the original request.
    pub fn from_request<R: HttpRequest>(req: R) -> Result<Multipart<R::Body>, R> {
        //FIXME: move `map` expr to `Some` arm when nonlexical borrow scopes land.
        let boundary = match req.multipart_boundary().map(String::from) {
            Some(boundary) => boundary,
            None => return Err(req),
        };

        Ok(Multipart::with_body(req.body(), boundary))        
    }   
}

impl<R: Read> Multipart<R> {
    /// Construct a new `Multipart` with the given body reader and boundary.
    ///
    /// ## Note: `boundary`
    /// This will prepend the requisite `--` to the boundary string as documented in
    /// [IETF RFC 1341, Section 7.2.1: "Multipart: the common syntax"][rfc1341-7.2.1].
    /// Simply pass the value of the `boundary` key from the `Content-Type` header in the
    /// request (or use `Multipart::from_request()`, if supported).
    ///
    /// [rfc1341-7.2.1]: https://tools.ietf.org/html/rfc1341#page-30
    pub fn with_body<Bnd: Into<String>>(body: R, boundary: Bnd) -> Self {
        let boundary = boundary.into();

        info!("Multipart::with_boundary(_, {:?})", boundary);

        Multipart { 
            reader: BoundaryReader::from_reader(body, boundary),
        }
    }

    /// Read the next entry from this multipart request, returning a struct with the field's name and
    /// data. See `MultipartField` for more info.
    ///
    /// ## Warning: Risk of Data Loss
    /// If the previously returned entry had contents of type `MultipartField::File`,
    /// calling this again will discard any unread contents of that entry.
    pub fn read_entry(&mut self) -> io::Result<Option<MultipartField<&mut Self>>> {
        self.read_entry_mut().into_result()
    }

    /// Read the next entry from this multipart request, returning a struct with the field's name and
    /// data. See `MultipartField` for more info.
    pub fn into_entry(self) -> ReadEntryResult<Self> {
        self.read_entry()
    }

    /// Call `f` for each entry in the multipart request.
    /// 
    /// This is a substitute for Rust not supporting streaming iterators (where the return value
    /// from `next()` borrows the iterator for a bound lifetime).
    ///
    /// Returns `Ok(())` when all fields have been read, or the first error.
    pub fn foreach_entry<F>(&mut self, mut foreach: F) -> io::Result<()> where F: FnMut(MultipartField<&mut Self>) {
        loop {
            match self.read_entry() {
                Ok(Some(field)) => foreach(field),
                Ok(None) => return Ok(()),
                Err(err) => return Err(err),
            }
        }
    }

    /// Get a builder type for saving the files in this request to the filesystem.
    ///
    /// See [`SaveBuilder`](save/struct.SaveBuilder.html) for more information.
    pub fn save(&mut self) -> SaveBuilder<&mut Self> {
        SaveBuilder::new(self)
    }
}

impl<R> Borrow<R> for Multipart<R> {
    fn borrow(&self) -> &R {
        self.reader.borrow()
    }
}

impl<R: Read> PrivReadEntry for Multipart<R> {
    type Source = BoundaryReader<R>;

    fn source_mut(&mut self) -> &mut BoundaryReader<R> {
        &mut self.reader
    }

    fn set_min_buf_size(&mut self, min_buf_size: usize) {
        self.reader.set_min_buf_size(min_buf_size)
    }

    /// Consume the next boundary.
    /// Returns `true` if a field should follow this boundary, `false` otherwise.
    fn consume_boundary(&mut self) -> io::Result<bool> {
        debug!("Consume boundary!");
        self.reader.consume_boundary()
    }
}

/// A server-side HTTP request that may or may not be multipart.
///
/// May be implemented by mutable references if providing the request or body by-value is
/// undesirable.
pub trait HttpRequest {
    /// The body of this request.
    type Body: Read;
    /// Get the boundary string of this request if it is a POST request
    /// with the `Content-Type` header set to `multipart/form-data`.
    ///
    /// The boundary string should be supplied as an extra value of the `Content-Type` header, e.g.
    /// `Content-Type: multipart/form-data; boundary={boundary}`.
    fn multipart_boundary(&self) -> Option<&str>;

    /// Return the request body for reading.
    fn body(self) -> Self::Body;
}

#[test]
fn issue_104() {
    ::init_log();

    use std::io::Cursor;

    let body = "\
    POST /test.html HTTP/1.1\r\n\
    Host: example.org\r\n\
    Content-Type: multipart/form-data;boundary=\"boundary\"\r\n\r\n\
    Content-Disposition: form-data; name=\"field1\"\r\n\r\n\
    value1\r\n\
    Content-Disposition: form-data; name=\"field2\"; filename=\"example.txt\"\r\n\r\n\
    value2 ";

    let request = Cursor::new(body);

    let mut multipart = Multipart::with_body(request, "boundary");
    multipart.foreach_entry(|_field| {/* Do nothing */}).unwrap_err();
}

#[test]
fn issue_114() {
    ::init_log();

    fn consume_all<R: BufRead>(mut rdr: R) {
        loop {
            let consume = rdr.fill_buf().unwrap().len();
            if consume == 0 { return; }
            rdr.consume(consume);
        }
    }

    use std::io::Cursor;

    let body = "\
    --------------------------c616e5fded96a3c7\r\n\
    Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
    v1,\r\n\
    --------------------------c616e5fded96a3c7\r\n\
    Content-Disposition: form-data; name=\"key2\"\r\n\r\n\
    v2,\r\n\
    --------------------------c616e5fded96a3c7\r\n\
    Content-Disposition: form-data; name=\"key3\"\r\n\r\n\
    v3\r\n\
    --------------------------c616e5fded96a3c7--\r\n";

    let request = Cursor::new(body);
    let mut multipart = Multipart::with_body(request, "------------------------c616e5fded96a3c7");

    // one error if you do nothing
    multipart.foreach_entry(|_entry| { /* do nothing */}).unwrap();

    // a different error if you skip the first field
    multipart.foreach_entry(|entry| if *entry.headers.name != *"key1" { consume_all(entry.data); })
        .unwrap();


    multipart.foreach_entry(|_entry| () /* match entry.headers.name.as_str() {
        "file" => {
            let mut vec = Vec::new();
            entry.data.read_to_end(&mut vec).expect("can't read");
            // message.file = String::from_utf8(vec).ok();
            println!("key file got");
        }

        "key1" => {
            let mut vec = Vec::new();
            entry.data.read_to_end(&mut vec).expect("can't read");
            // message.key1 = String::from_utf8(vec).ok();
            println!("key1 got");
        }

        "key2" => {
            let mut vec = Vec::new();
            entry.data.read_to_end(&mut vec).expect("can't read");
            // message.key2 = String::from_utf8(vec).ok();
            println!("key2 got");
        }

        _ => {
            // as multipart has a bug https://github.com/abonander/multipart/issues/114
            // we manually do read_to_end here
            //let mut _vec = Vec::new();
            //entry.data.read_to_end(&mut _vec).expect("can't read");
            println!("key neglected");
        }
    }*/)
    .expect("Unable to iterate multipart?")
}