[
  {
    "path": ".github/workflows/test.yml",
    "content": "name: CI\n\non:\n  pull_request:\n  push:\n    branches:\n      - master\n      - develop\n\nenv:\n  RUSTFLAGS: -Dwarnings\n\njobs:\n  build_and_test:\n    name: Build and test\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [ubuntu-latest]\n        rust: [stable, nightly]\n\n    steps:\n    - uses: actions/checkout@master\n\n    - name: Install ${{ matrix.rust }}\n      uses: dtolnay/rust-toolchain@master\n      with:\n        toolchain: ${{ matrix.rust }}\n\n    - name: check\n      run: cargo check --all --bins --examples --all-features\n\n    - name: tests\n      run: cargo test --all --all-features\n\n  check_fmt_and_docs:\n    name: Checking fmt, clippy, and docs\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@master\n\n    - name: clippy\n      run: cargo clippy --tests --examples --bins -- -D warnings\n\n    - name: fmt\n      run: cargo fmt --all -- --check\n\n    - name: Docs\n      run: cargo doc --no-deps\n"
  },
  {
    "path": ".gitignore",
    "content": "# Created by https://www.gitignore.io/api/rust,macos\n# Edit at https://www.gitignore.io/?templates=rust,macos\n\n### macOS ###\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n### Rust ###\n# Generated by Cargo\n# will have compiled files and executables\n/target/\n\n# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries\n# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html\nCargo.lock\n\n# These are backup files generated by rustfmt\n**/*.rs.bk\n\n### Others ###\n.halt.releez.yml\n/.idea\n/multer-rs.iml\n\n# End of https://www.gitignore.io/api/rust,macos\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"multer\"\nversion = \"3.1.0\"\ndescription = \"An async parser for `multipart/form-data` content-type in Rust.\"\nhomepage = \"https://github.com/rwf2/multer\"\nrepository = \"https://github.com/rwf2/multer\"\nkeywords = [\"multipart\", \"multipart-formdata\", \"multipart-uploads\", \"async\", \"formdata\"]\ncategories = [\"asynchronous\", \"web-programming\"]\nauthors = [\"Rousan Ali <hello@rousan.io>\"]\nreadme = \"README.md\"\nlicense = \"MIT\"\nedition = \"2018\"\n\n[package.metadata.docs.rs]\nall-features = true\n\n[package.metadata.playground]\nfeatures = [\"all\"]\n\n[features]\ndefault = []\nall = [\"json\"]\njson = [\"serde\", \"serde_json\"]\ntokio-io = [\"tokio\", \"tokio-util\"]\nlog = [\"dep:log\"]\n\n[dependencies]\nbytes = \"1.0\"\nfutures-util = { version = \"0.3\", default-features = false }\nmemchr = \"2.4\"\nhttp = \"1.0\"\nhttparse = \"1.3\"\nmime = \"0.3.10\"\nencoding_rs = \"0.8.20\"\nspin = { version = \"0.9\", default-features = false, features = [\"spin_mutex\"] }\n\nlog = { version = \"0.4.15\", optional = true }\nserde = { version = \"1.0\", optional = true }\nserde_json = { version = \"1.0\", optional = true }\ntokio = { version = \"1.0\", features = [],  optional = true }\ntokio-util = { version = \"0.7\", features = [\"io\"],  optional = true }\n\n[dev-dependencies]\nserde = { version = \"1.0\", features = [\"derive\"] }\ntokio = { version = \"1.0\", features = [\"full\"] }\nhyper = { version = \"1.0\", features = [\"server\", \"http1\"] }\nhttp-body-util = \"0.1\"\nhyper-util = { version = \"0.1.1\", features = [\"full\"] }\n\n[build-dependencies]\nversion_check = \"0.9\"\n\n[[example]]\nname = \"parse_async_read\"\npath = \"examples/parse_async_read.rs\"\nrequired-features = [\"tokio-io\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Rousan Ali\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![GitHub Actions Status](https://github.com/rousan/multer-rs/actions/workflows/test.yml/badge.svg)](https://github.com/rousan/multer-rs/actions)\n[![crates.io](https://img.shields.io/crates/v/multer.svg)](https://crates.io/crates/multer)\n[![Documentation](https://docs.rs/multer/badge.svg)](https://docs.rs/multer)\n[![MIT](https://img.shields.io/crates/l/multer.svg)](./LICENSE)\n\n# multer-rs\n\nAn async parser for `multipart/form-data` content-type in Rust.\n\nIt accepts a [`Stream`](https://docs.rs/futures/0.3/futures/stream/trait.Stream.html) of [`Bytes`](https://docs.rs/bytes/1/bytes/struct.Bytes.html) as\na source, so that It can be plugged into any async Rust environment e.g. any async server.\n\n[Docs](https://docs.rs/multer)\n\n## Install    \n\nAdd this to your `Cargo.toml`:\n\n```toml\n[dependencies]\nmulter = \"2.0\"\n```\n\n# Basic Example\n\n```rust\nuse bytes::Bytes;\nuse futures::stream::Stream;\n// Import multer types.\nuse multer::Multipart;\nuse std::convert::Infallible;\nuse futures::stream::once;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    // Generate a byte stream and the boundary from somewhere e.g. server request body.\n    let (stream, boundary) = get_byte_stream_from_somewhere().await;\n\n    // Create a `Multipart` instance from that byte stream and the boundary.\n    let mut multipart = Multipart::new(stream, boundary);\n\n    // Iterate over the fields, use `next_field()` to get the next field.\n    while let Some(mut field) = multipart.next_field().await? {\n        // Get field name.\n        let name = field.name();\n        // Get the field's filename if provided in \"Content-Disposition\" header.\n        let file_name = field.file_name();\n\n        println!(\"Name: {:?}, File Name: {:?}\", name, file_name);\n\n        // Process the field data chunks e.g. store them in a file.\n        while let Some(chunk) = field.chunk().await? {\n            // Do something with field chunk.\n            println!(\"Chunk: {:?}\", chunk);\n        }\n    }\n\n    Ok(())\n}\n\n// Generate a byte stream and the boundary from somewhere e.g. server request body.\nasync fn get_byte_stream_from_somewhere() -> (impl Stream<Item = Result<Bytes, Infallible>>, &'static str) {\n    let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\";\n    let stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) });\n    \n    (stream, \"X-BOUNDARY\")\n}\n``` \n\n## Prevent Denial of Service (DoS) Attacks\n\nThis crate also provides some APIs to prevent potential DoS attacks with fine grained control. It's recommended to add some constraints\non field (specially text field) size to prevent DoS attacks exhausting the server's memory.\n\nAn example:\n\n```rust\nuse multer::{Multipart, Constraints, SizeLimit};\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    // Create some constraints to be applied to the fields to prevent DoS attack.\n    let constraints = Constraints::new()\n         // We only accept `my_text_field` and `my_file_field` fields,\n         // For any unknown field, we will throw an error.\n         .allowed_fields(vec![\"my_text_field\", \"my_file_field\"])\n         .size_limit(\n             SizeLimit::new()\n                 // Set 15mb as size limit for the whole stream body.\n                 .whole_stream(15 * 1024 * 1024)\n                 // Set 10mb as size limit for all fields.\n                 .per_field(10 * 1024 * 1024)\n                 // Set 30kb as size limit for our text field only.\n                 .for_field(\"my_text_field\", 30 * 1024),\n         );\n\n    // Create a `Multipart` instance from a stream and the constraints.\n    let mut multipart = Multipart::with_constraints(some_stream, \"X-BOUNDARY\", constraints);\n\n    while let Some(field) = multipart.next_field().await.unwrap() {\n        let content = field.text().await.unwrap();\n        assert_eq!(content, \"abcd\");\n    } \n   \n    Ok(())\n}\n```\n\n## Usage with [hyper.rs](https://hyper.rs/) server\n\nAn [example](https://github.com/rousan/multer-rs/blob/master/examples/hyper_server_example.rs) showing usage with [hyper.rs](https://hyper.rs/).\n\nFor more examples, please visit [examples](https://github.com/rousan/multer-rs/tree/master/examples).\n\n## Contributing\n\nYour PRs and suggestions are always welcome.\n"
  },
  {
    "path": "build.rs",
    "content": "fn main() {\n    if let Some(true) = version_check::is_feature_flaggable() {\n        println!(\"cargo:rustc-cfg=nightly\");\n    }\n}\n"
  },
  {
    "path": "examples/README.md",
    "content": "# Examples of using multer-rs\n\nThese examples show of how to do common tasks using `multer-rs`.\n\nPlease visit: [Docs](https://docs.rs/multer) for the documentation.\n\nRun an example:\n\n```sh\n cargo run --example example_name\n```\n\n* [`simple_example`](simple_example.rs) - A basic example using `multer`.\n\n* [`hyper_server_example`](hyper_server_example.rs) - Shows how to use this crate with Rust HTTP server [hyper](https://hyper.rs/).\n\n* [`parse_async_read`](parse_async_read.rs) - Shows how to parse `multipart/form-data` from an [`AsyncRead`](https://docs.rs/tokio/1/tokio/io/trait.AsyncRead.html).\n\n* [`prevent_dos_attack`](prevent_dos_attack.rs) - Shows how to apply some rules to prevent potential DoS attacks while handling `multipart/form-data`.\n"
  },
  {
    "path": "examples/hyper_server_example.rs",
    "content": "use std::{convert::Infallible, net::SocketAddr};\n\nuse bytes::Bytes;\nuse futures_util::StreamExt;\nuse http_body_util::{BodyStream, Full};\nuse hyper::{body::Incoming, header::CONTENT_TYPE, Request, Response, StatusCode};\n// Import the multer types.\nuse multer::Multipart;\n\n// A handler for incoming requests.\nasync fn handle(req: Request<Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {\n    // Extract the `multipart/form-data` boundary from the headers.\n    let boundary = req\n        .headers()\n        .get(CONTENT_TYPE)\n        .and_then(|ct| ct.to_str().ok())\n        .and_then(|ct| multer::parse_boundary(ct).ok());\n\n    // Send `BAD_REQUEST` status if the content-type is not multipart/form-data.\n    if boundary.is_none() {\n        return Ok(Response::builder()\n            .status(StatusCode::BAD_REQUEST)\n            .body(Full::from(\"BAD REQUEST\"))\n            .unwrap());\n    }\n\n    // Process the multipart e.g. you can store them in files.\n    if let Err(err) = process_multipart(req.into_body(), boundary.unwrap()).await {\n        return Ok(Response::builder()\n            .status(StatusCode::INTERNAL_SERVER_ERROR)\n            .body(Full::from(format!(\"INTERNAL SERVER ERROR: {}\", err)))\n            .unwrap());\n    }\n\n    Ok(Response::new(Full::from(\"Success\")))\n}\n\n// Process the request body as multipart/form-data.\nasync fn process_multipart(body: Incoming, boundary: String) -> multer::Result<()> {\n    // Convert the body into a stream of data frames.\n    let body_stream = BodyStream::new(body)\n        .filter_map(|result| async move { result.map(|frame| frame.into_data().ok()).transpose() });\n\n    // Create a Multipart instance from the request body.\n    let mut multipart = Multipart::new(body_stream, boundary);\n\n    // Iterate over the fields, `next_field` method will return the next field if\n    // available.\n    while let Some(mut field) = multipart.next_field().await? {\n        // Get the field name.\n        let name = field.name();\n\n        // Get the field's filename if provided in \"Content-Disposition\" header.\n        let file_name = field.file_name();\n\n        // Get the \"Content-Type\" header as `mime::Mime` type.\n        let content_type = field.content_type();\n\n        println!(\n            \"Name: {:?}, FileName: {:?}, Content-Type: {:?}\",\n            name, file_name, content_type\n        );\n\n        // Process the field data chunks e.g. store them in a file.\n        let mut field_bytes_len = 0;\n        while let Some(field_chunk) = field.chunk().await? {\n            // Do something with field chunk.\n            field_bytes_len += field_chunk.len();\n        }\n\n        println!(\"Field Bytes Length: {:?}\", field_bytes_len);\n    }\n\n    Ok(())\n}\n\n#[tokio::main]\nasync fn main() {\n    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));\n    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();\n    println!(\"Server running at: {}\", addr);\n\n    let service = hyper::service::service_fn(handle);\n\n    loop {\n        let (socket, _remote_addr) = listener.accept().await.unwrap();\n        let socket = hyper_util::rt::TokioIo::new(socket);\n        tokio::spawn(async move {\n            if let Err(e) = hyper::server::conn::http1::Builder::new()\n                .serve_connection(socket, service)\n                .await\n            {\n                eprintln!(\"server error: {}\", e);\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "examples/parse_async_read.rs",
    "content": "use multer::Multipart;\nuse tokio::io::AsyncRead;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    // Generate an `AsyncRead` and the boundary from somewhere e.g. server request\n    // body.\n    let (reader, boundary) = get_async_reader_from_somewhere().await;\n\n    // Create a `Multipart` instance from that async reader and the boundary.\n    let mut multipart = Multipart::with_reader(reader, boundary);\n\n    // Iterate over the fields, use `next_field()` to get the next field.\n    while let Some(mut field) = multipart.next_field().await? {\n        // Get field name.\n        let name = field.name();\n        // Get the field's filename if provided in \"Content-Disposition\" header.\n        let file_name = field.file_name();\n\n        println!(\"Name: {:?}, File Name: {:?}\", name, file_name);\n\n        // Process the field data chunks e.g. store them in a file.\n        let mut field_bytes_len = 0;\n        while let Some(field_chunk) = field.chunk().await? {\n            // Do something with field chunk.\n            field_bytes_len += field_chunk.len();\n        }\n\n        println!(\"Field Bytes Length: {:?}\", field_bytes_len);\n    }\n\n    Ok(())\n}\n\n// Generate an `AsyncRead` and the boundary from somewhere e.g. server request\n// body.\nasync fn get_async_reader_from_somewhere() -> (impl AsyncRead, &'static str) {\n    let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_file_field\\\"; filename=\\\"a-text-file.txt\\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nHello world\\nHello\\r\\nWorld\\rAgain\\r\\n--X-BOUNDARY--\\r\\n\";\n\n    (data.as_bytes(), \"X-BOUNDARY\")\n}\n"
  },
  {
    "path": "examples/prevent_dos_attack.rs",
    "content": "use std::convert::Infallible;\n\nuse bytes::Bytes;\nuse futures_util::stream::Stream;\n// Import multer types.\nuse multer::{Constraints, Multipart, SizeLimit};\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    // Generate a byte stream and the boundary from somewhere e.g. server request\n    // body.\n    let (stream, boundary) = get_byte_stream_from_somewhere().await;\n\n    // Create some constraints to be applied to the fields to prevent DoS attacks.\n    let constraints = Constraints::new()\n        // We only accept `my_text_field` and `my_file_field` fields,\n        // For any unknown field, we will throw an error.\n        .allowed_fields(vec![\"my_text_field\", \"my_file_field\"])\n        .size_limit(\n            SizeLimit::new()\n                // Set 15mb as size limit for the whole stream body.\n                .whole_stream(15 * 1024 * 1024)\n                // Set 10mb as size limit for all fields.\n                .per_field(10 * 1024 * 1024)\n                // Set 30kb as size limit for our text field only.\n                .for_field(\"my_text_field\", 30 * 1024),\n        );\n\n    // Create a `Multipart` instance from that byte stream and the constraints.\n    let mut multipart = Multipart::with_constraints(stream, boundary, constraints);\n\n    // Iterate over the fields, use `next_field()` to get the next field.\n    while let Some(field) = multipart.next_field().await? {\n        // Get field name.\n        let name = field.name();\n        // Get the field's filename if provided in \"Content-Disposition\" header.\n        let file_name = field.file_name();\n\n        println!(\"Name: {:?}, File Name: {:?}\", name, file_name);\n\n        // Read field content as text.\n        let content = field.text().await?;\n        println!(\"Content: {:?}\", content);\n    }\n\n    Ok(())\n}\n\n// Generate a byte stream and the boundary from somewhere e.g. server request\n// body.\nasync fn get_byte_stream_from_somewhere() -> (impl Stream<Item = Result<Bytes, Infallible>>, &'static str) {\n    let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_file_field\\\"; filename=\\\"a-text-file.txt\\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nHello world\\nHello\\r\\nWorld\\rAgain\\r\\n--X-BOUNDARY--\\r\\n\";\n    let stream = futures_util::stream::iter(\n        data.chars()\n            .map(|ch| ch.to_string())\n            .map(|part| Ok(Bytes::copy_from_slice(part.as_bytes()))),\n    );\n\n    (stream, \"X-BOUNDARY\")\n}\n"
  },
  {
    "path": "examples/simple_example.rs",
    "content": "use std::convert::Infallible;\n\nuse bytes::Bytes;\nuse futures_util::stream::Stream;\n// Import multer types.\nuse multer::Multipart;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n    // Generate a byte stream and the boundary from somewhere e.g. server request\n    // body.\n    let (stream, boundary) = get_byte_stream_from_somewhere().await;\n\n    // Create a `Multipart` instance from that byte stream and the boundary.\n    let mut multipart = Multipart::new(stream, boundary);\n\n    // Iterate over the fields, use `next_field()` to get the next field.\n    while let Some(field) = multipart.next_field().await? {\n        // Get field name.\n        let name = field.name();\n        // Get the field's filename if provided in \"Content-Disposition\" header.\n        let file_name = field.file_name();\n\n        println!(\"Name: {:?}, File Name: {:?}\", name, file_name);\n\n        // Read field content as text.\n        let content = field.text().await?;\n        println!(\"Content: {:?}\", content);\n    }\n\n    Ok(())\n}\n\n// Generate a byte stream and the boundary from somewhere e.g. server request\n// body.\nasync fn get_byte_stream_from_somewhere() -> (impl Stream<Item = Result<Bytes, Infallible>>, &'static str) {\n    let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_file_field\\\"; filename=\\\"a-text-file.txt\\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nHello world\\nHello\\r\\nWorld\\rAgain\\r\\n--X-BOUNDARY--\\r\\n\";\n    let stream = futures_util::stream::iter(\n        data.chars()\n            .map(|ch| ch.to_string())\n            .map(|part| Ok(Bytes::copy_from_slice(part.as_bytes()))),\n    );\n\n    (stream, \"X-BOUNDARY\")\n}\n"
  },
  {
    "path": "fuzz/.gitignore",
    "content": "target\ncorpus/*/*\nartifacts\n!*.seed\ncoverage\n"
  },
  {
    "path": "fuzz/Cargo.toml",
    "content": "[package]\nname = \"multer-fuzz\"\nversion = \"0.0.0\"\nauthors = [\"Automatically generated\"]\npublish = false\nedition = \"2018\"\n\n[profile.release]\ndebug = true\ndebug-assertions = true\noverflow-checks = true\nlto = true\n\n[package.metadata]\ncargo-fuzz = true\n\n[dependencies]\nlibfuzzer-sys = \"0.3\"\nfutures-util = { version = \"0.3\", default-features = false }\ntokio = { version = \"1\", features = [\"rt\", \"time\"] }\n\n[dependencies.multer]\npath = \"..\"\n\n# Prevent this from interfering with workspaces\n[workspace]\nmembers = [\".\"]\n\n[[bin]]\nname = \"fuzz_multipart_bytes\"\npath = \"fuzz_targets/fuzz_multipart_bytes.rs\"\ntest = false\ndoc = false\n"
  },
  {
    "path": "fuzz/README.md",
    "content": "# Fuzzing\n\nInstall `cargo-fuzz`:\n\n```sh\ncargo install -f cargo-fuzz\n```\n\nRun any available target where `$target` is the name of the target and `$n` is\nthe number of CPUs to use for fuzzing:\n\n```sh\ncargo fuzz list # get list of targets\ncargo fuzz run $target -j $n\n```\n"
  },
  {
    "path": "fuzz/corpus/fuzz_multipart_bytes/multi.seed",
    "content": "--X-BOUNDARY\r\nContent-Disposition: form-data; name=\"sometext\"\r\n\r\nsome text that you wrote in your html form ...\r\n--X-BOUNDARY\r\nContent-Disposition: form-data; name=\"name_of_post_request\" filename=\"filename.xyz\"\r\n\r\ncontent of filename.xyz that you upload in your form with input[type=file]\r\n--X-BOUNDARY\r\nContent-Disposition: form-data; name=\"image\" filename=\"picture_of_sunset.jpg\"\r\n\r\ncontent of picture_of_sunset.jpg ...\r\n--X-BOUNDARY--\r\n"
  },
  {
    "path": "fuzz/corpus/fuzz_multipart_bytes/multi2.seed",
    "content": "--X-BOUNDARY\r\nContent-Disposition: form-data; name=\"text1\"\r\n\r\ntext default\r\n--X-BOUNDARY\r\nContent-Disposition: form-data; name=\"text2\"\r\n\r\naωb\r\n--X-BOUNDARY\r\nContent-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\r\nContent-Type: text/plain\r\n\r\nContent of a.txt.\r\n\r\n--X-BOUNDARY\r\nContent-Disposition: form-data; name=\"file2\"; filename=\"a.html\"\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE html><title>Content of a.html.</title>\r\n\r\n--X-BOUNDARY\r\nContent-Disposition: form-data; name=\"file3\"; filename=\"binary\"\r\nContent-Type: application/octet-stream\r\n\r\naωb\r\n--X-BOUNDARY--\r\n"
  },
  {
    "path": "fuzz/corpus/fuzz_multipart_bytes/simple.seed",
    "content": "--X-BOUNDARY\r\nContent-Disposition: form-data; name=my_text_field\r\n\r\n\r\n--X-BOUNDARY--\r\n"
  },
  {
    "path": "fuzz/corpus/fuzz_multipart_bytes/simple2.seed",
    "content": "--X-BOUNDARY\r\nContent-Disposition: form-data; name=\"field1\"\r\n\r\nvalue1\r\n--X-BOUNDARY\r\nContent-Disposition: form-data; name=\"field2\"; filename=\"example.txt\"\r\n\r\nvalue2\r\n--X-BOUNDARY--\r\n"
  },
  {
    "path": "fuzz/corpus/fuzz_multipart_bytes/simple3.seed",
    "content": "--X-BOUNDARY\r\nContent-Disposition: form-data; name=\"text\"\r\nContent-Type: text/plain\r\nBook\r\n--X-BOUNDARY\r\nContent-Disposition: form-data; name=\"file1\"; filename=\"a.json\"\r\nContent-Type: application/json\r\n{\r\n\"title\": \"Java 8 in Action\",\r\n\"author\": \"Mario Fusco\",\r\n\"year\": 2014\r\n}\r\n--X-BOUNDARY\r\nContent-Disposition: form-data; name=\"file2\"; filename=\"a.html\"\r\nContent-Type: text/html\r\n\r\n<title> Available for download! </title>\r\n--X-BOUNDARY--\r\n"
  },
  {
    "path": "fuzz/corpus/fuzz_multipart_bytes/single.seed",
    "content": "--X-BOUNDARY\r\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\r\n\r\nabcd\r\n--X-BOUNDARY--\r\n"
  },
  {
    "path": "fuzz/fuzz_targets/fuzz_multipart_bytes.rs",
    "content": "#![no_main]\n\nuse std::convert::Infallible;\nuse std::time::Duration;\n\nuse multer::Multipart;\nuse multer::bytes::Bytes;\nuse futures_util::stream::once;\nuse libfuzzer_sys::fuzz_target;\nuse tokio::{runtime, time::timeout};\n\nconst FIELD_TIMEOUT: Duration = Duration::from_millis(10);\n\nfuzz_target!(|data: &[u8]| {\n    let data = data.to_vec();\n    let stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) });\n\n    let mut multipart = Multipart::new(stream, \"X-BOUNDARY\");\n\n    let rt = runtime::Builder::new_current_thread()\n        .enable_time()\n        .build()\n        .expect(\"runtime\");\n\n    rt.block_on(async {\n        let mut breaks = 0;\n        while breaks < 3 {\n            let field = timeout(FIELD_TIMEOUT, multipart.next_field()).await;\n            match field {\n                Err(_) => panic!(\"timed out waiting for field\"),\n                Ok(Err(_)) | Ok(Ok(None)) => breaks += 1,\n                Ok(Ok(Some(_))) => continue,\n            }\n        }\n    })\n});\n"
  },
  {
    "path": "releez.yml",
    "content": "version: 1.0.0\nchecklist:\n  - name: Checkout master and sync with remote\n    type: auto\n    run:\n      - git checkout master\n      - git pull\n  - name: Check syntax\n    type: auto\n    run:\n      - cargo check --release --features=\"all\"\n  - name: Run tests\n    type: auto\n    run:\n      - cargo test --release --features=\"all\"\n  - name: Make sure code is formatted\n    type: auto\n    run:\n      - cargo fmt\n  - name: Bump version\n    type: manual\n    instructions:\n      - Please update version with ${VERSION} in Cargo.toml file.\n      - Please update version with ${VERSION} in README.md file if needed.\n  - name: Commit changes\n    type: auto\n    run:\n      - git add --all && git commit -m \"Bump version\"\n  - name: Create a release tag\n    type: auto\n    run:\n      - git tag \"v${VERSION}\" -a\n  - name: Push branches and tags to Github\n    type: auto\n    run:\n      - git push origin master\n      - git push --tags\n  - name: Edit tag on Github\n    type: manual\n    instructions:\n      - Tag is pushed to Github(https://github.com/rousan/multer-rs/releases). Edit it there and make it a release.\n  - name: Publish to crates.io\n    type: auto\n    confirm: Are you sure to publish it to crates.io?\n    run:\n      - cargo publish\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "max_width = 120\ntab_spaces = 4\nwrap_comments = true\ncondense_wildcard_suffixes = true\nformat_code_in_doc_comments = true\nnewline_style = \"Unix\"\nnormalize_comments = true\nreorder_impl_items = true\ngroup_imports = \"StdExternalCrate\"\nuse_field_init_shorthand = true\n"
  },
  {
    "path": "src/buffer.rs",
    "content": "use std::fmt;\nuse std::pin::Pin;\nuse std::task::{Context, Poll};\n\nuse bytes::{Buf, Bytes, BytesMut};\nuse futures_util::stream::Stream;\n\nuse crate::constants;\n\npub(crate) struct StreamBuffer<'r> {\n    pub(crate) eof: bool,\n    pub(crate) buf: BytesMut,\n    pub(crate) stream: Pin<Box<dyn Stream<Item = Result<Bytes, crate::Error>> + Send + 'r>>,\n    pub(crate) whole_stream_size_limit: u64,\n    pub(crate) stream_size_counter: u64,\n}\n\nimpl<'r> StreamBuffer<'r> {\n    pub fn new<S>(stream: S, whole_stream_size_limit: u64) -> Self\n    where\n        S: Stream<Item = Result<Bytes, crate::Error>> + Send + 'r,\n    {\n        StreamBuffer {\n            eof: false,\n            buf: BytesMut::new(),\n            stream: Box::pin(stream),\n            whole_stream_size_limit,\n            stream_size_counter: 0,\n        }\n    }\n\n    pub fn poll_stream(&mut self, cx: &mut Context<'_>) -> Result<(), crate::Error> {\n        if self.eof {\n            return Ok(());\n        }\n\n        loop {\n            match self.stream.as_mut().poll_next(cx) {\n                Poll::Ready(Some(Ok(data))) => {\n                    self.stream_size_counter += data.len() as u64;\n\n                    if self.stream_size_counter > self.whole_stream_size_limit {\n                        return Err(crate::Error::StreamSizeExceeded {\n                            limit: self.whole_stream_size_limit,\n                        });\n                    }\n\n                    self.buf.extend_from_slice(&data)\n                }\n                Poll::Ready(Some(Err(err))) => return Err(err),\n                Poll::Ready(None) => {\n                    self.eof = true;\n                    return Ok(());\n                }\n                Poll::Pending => return Ok(()),\n            }\n        }\n    }\n\n    pub fn read_exact(&mut self, size: usize) -> Option<Bytes> {\n        if size <= self.buf.len() {\n            Some(self.buf.split_to(size).freeze())\n        } else {\n            None\n        }\n    }\n\n    pub fn peek_exact(&mut self, size: usize) -> Option<&[u8]> {\n        self.buf.get(..size)\n    }\n\n    pub fn read_until(&mut self, pattern: &[u8]) -> Option<Bytes> {\n        memchr::memmem::find(&self.buf, pattern).map(|idx| self.buf.split_to(idx + pattern.len()).freeze())\n    }\n\n    pub fn read_to(&mut self, pattern: &[u8]) -> Option<Bytes> {\n        memchr::memmem::find(&self.buf, pattern).map(|idx| self.buf.split_to(idx).freeze())\n    }\n\n    pub fn advance_past_transport_padding(&mut self) -> bool {\n        match self.buf.iter().position(|b| *b != b' ' && *b != b'\\t') {\n            Some(pos) => {\n                self.buf.advance(pos);\n                true\n            }\n            None => {\n                self.buf.clear();\n                false\n            }\n        }\n    }\n\n    pub fn read_field_data(\n        &mut self,\n        boundary: &str,\n        field_name: Option<&str>,\n    ) -> crate::Result<Option<(bool, Bytes)>> {\n        trace!(\"finding next field: {:?}\", field_name);\n        if self.buf.is_empty() && self.eof {\n            trace!(\"empty buffer && EOF\");\n            return Err(crate::Error::IncompleteFieldData {\n                field_name: field_name.map(|s| s.to_owned()),\n            });\n        } else if self.buf.is_empty() {\n            return Ok(None);\n        }\n\n        let boundary_deriv = format!(\"{}{}{}\", constants::CRLF, constants::BOUNDARY_EXT, boundary);\n        let b_len = boundary_deriv.len();\n\n        match memchr::memmem::find(&self.buf, boundary_deriv.as_bytes()) {\n            Some(idx) => {\n                trace!(\"new field found at {}\", idx);\n                let bytes = self.buf.split_to(idx).freeze();\n\n                // discard \\r\\n.\n                self.buf.advance(constants::CRLF.len());\n\n                Ok(Some((true, bytes)))\n            }\n            None if self.eof => {\n                trace!(\"no new field found: EOF. terminating\");\n                Err(crate::Error::IncompleteFieldData {\n                    field_name: field_name.map(|s| s.to_owned()),\n                })\n            }\n            None => {\n                let buf_len = self.buf.len();\n                let rem_boundary_part_max_len = b_len - 1;\n                let rem_boundary_part_idx = if buf_len >= rem_boundary_part_max_len {\n                    buf_len - rem_boundary_part_max_len\n                } else {\n                    0\n                };\n\n                trace!(\"no new field found, not EOF, checking close\");\n                let bytes = &self.buf[rem_boundary_part_idx..];\n                match memchr::memmem::rfind(bytes, constants::CR.as_bytes()) {\n                    Some(rel_idx) => {\n                        let idx = rel_idx + rem_boundary_part_idx;\n\n                        match memchr::memmem::find(boundary_deriv.as_bytes(), &self.buf[idx..]) {\n                            Some(_) => {\n                                let bytes = self.buf.split_to(idx).freeze();\n\n                                match bytes.is_empty() {\n                                    true => Ok(None),\n                                    false => Ok(Some((false, bytes))),\n                                }\n                            }\n                            None => Ok(Some((false, self.read_full_buf()))),\n                        }\n                    }\n                    None => Ok(Some((false, self.read_full_buf()))),\n                }\n            }\n        }\n    }\n\n    pub fn read_full_buf(&mut self) -> Bytes {\n        self.buf.split_to(self.buf.len()).freeze()\n    }\n}\n\nimpl fmt::Debug for StreamBuffer<'_> {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"StreamBuffer\").finish()\n    }\n}\n"
  },
  {
    "path": "src/constants.rs",
    "content": "use std::borrow::Cow;\n\npub(crate) const DEFAULT_WHOLE_STREAM_SIZE_LIMIT: u64 = std::u64::MAX;\npub(crate) const DEFAULT_PER_FIELD_SIZE_LIMIT: u64 = std::u64::MAX;\n\npub(crate) const MAX_HEADERS: usize = 32;\npub(crate) const BOUNDARY_EXT: &str = \"--\";\npub(crate) const CR: &str = \"\\r\";\n#[allow(dead_code)]\npub(crate) const LF: &str = \"\\n\";\npub(crate) const CRLF: &str = \"\\r\\n\";\npub(crate) const CRLF_CRLF: &str = \"\\r\\n\\r\\n\";\n\n#[derive(PartialEq)]\npub(crate) enum ContentDispositionAttr {\n    Name,\n    FileName,\n}\n\nfn trim_ascii_ws_start(bytes: &[u8]) -> &[u8] {\n    bytes\n        .iter()\n        .position(|b| !b.is_ascii_whitespace())\n        .map_or_else(|| &bytes[bytes.len()..], |i| &bytes[i..])\n}\n\nfn trim_ascii_ws_then(bytes: &[u8], char: u8) -> Option<&[u8]> {\n    match trim_ascii_ws_start(bytes) {\n        [first, rest @ ..] if *first == char => Some(rest),\n        _ => None,\n    }\n}\n\nimpl ContentDispositionAttr {\n    /// Extract ContentDisposition Attribute from header.\n    ///\n    /// Some older clients may not quote the name or filename, so we allow them,\n    /// but require them to be percent encoded. Only allocates if percent\n    /// decoding, and there are characters that need to be decoded.\n    pub fn extract_from<'h>(&self, mut header: &'h [u8]) -> Option<Cow<'h, str>> {\n        // TODO: The prefix should be matched case-insensitively.\n        let prefix = match self {\n            ContentDispositionAttr::Name => &b\"name\"[..],\n            ContentDispositionAttr::FileName => &b\"filename\"[..],\n        };\n\n        while let Some(i) = memchr::memmem::find(header, prefix) {\n            // Check if we found a superstring of `prefix`; continue if so.\n            let suffix = &header[(i + prefix.len())..];\n            if i > 0 && !(header[i - 1].is_ascii_whitespace() || header[i - 1] == b';') {\n                header = suffix;\n                continue;\n            }\n\n            // Now find and trim the `=`. Handle quoted strings first.\n            let rest = trim_ascii_ws_then(suffix, b'=')?;\n            let (bytes, is_escaped) = if let Some(rest) = trim_ascii_ws_then(rest, b'\"') {\n                let (mut k, mut escaped) = (memchr::memchr(b'\"', rest)?, false);\n                while k > 0 && rest[k - 1] == b'\\\\' {\n                    escaped = true;\n                    k = k + 1 + memchr::memchr(b'\"', &rest[(k + 1)..])?;\n                }\n\n                (&rest[..k], escaped)\n            } else {\n                let rest = trim_ascii_ws_start(rest);\n                let j = memchr::memchr2(b';', b' ', rest).unwrap_or(rest.len());\n                (&rest[..j], false)\n            };\n\n            return match std::str::from_utf8(bytes).ok()? {\n                name if is_escaped => Some(name.replace(r#\"\\\"\"#, \"\\\"\").into()),\n                name => Some(name.into()),\n            };\n        }\n\n        None\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_content_disposition_name_only() {\n        let val = br#\"form-data; name=\"my_field\"\"#;\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        let filename = ContentDispositionAttr::FileName.extract_from(val);\n        assert_eq!(name.unwrap(), \"my_field\");\n        assert!(filename.is_none());\n\n        let val = br#\"form-data; name=my_field  \"#;\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        let filename = ContentDispositionAttr::FileName.extract_from(val);\n        assert_eq!(name.unwrap(), \"my_field\");\n        assert!(filename.is_none());\n\n        let val = br#\"form-data; name  =  my_field  \"#;\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        let filename = ContentDispositionAttr::FileName.extract_from(val);\n        assert_eq!(name.unwrap(), \"my_field\");\n        assert!(filename.is_none());\n\n        let val = br#\"form-data; name  =  \"#;\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        let filename = ContentDispositionAttr::FileName.extract_from(val);\n        assert_eq!(name.unwrap(), \"\");\n        assert!(filename.is_none());\n    }\n\n    #[test]\n    fn test_content_disposition_extraction() {\n        let val = br#\"form-data; name=\"my_field\"; filename=\"file abc.txt\"\"#;\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        let filename = ContentDispositionAttr::FileName.extract_from(val);\n        assert_eq!(name.unwrap(), \"my_field\");\n        assert_eq!(filename.unwrap(), \"file abc.txt\");\n\n        let val = \"form-data; name=\\\"你好\\\"; filename=\\\"file abc.txt\\\"\".as_bytes();\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        let filename = ContentDispositionAttr::FileName.extract_from(val);\n        assert_eq!(name.unwrap(), \"你好\");\n        assert_eq!(filename.unwrap(), \"file abc.txt\");\n\n        let val = \"form-data; name=\\\"কখগ\\\"; filename=\\\"你好.txt\\\"\".as_bytes();\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        let filename = ContentDispositionAttr::FileName.extract_from(val);\n        assert_eq!(name.unwrap(), \"কখগ\");\n        assert_eq!(filename.unwrap(), \"你好.txt\");\n    }\n\n    #[test]\n    fn test_content_disposition_file_name_only() {\n        // These are technically malformed, as RFC 7578 says the `name`\n        // parameter _must_ be included. But okay.\n        let val = br#\"form-data; filename=\"file-name.txt\"\"#;\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        let filename = ContentDispositionAttr::FileName.extract_from(val);\n        assert_eq!(filename.unwrap(), \"file-name.txt\");\n        assert!(name.is_none());\n\n        let val = \"form-data; filename=\\\"কখগ-你好.txt\\\"\".as_bytes();\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        let filename = ContentDispositionAttr::FileName.extract_from(val);\n        assert_eq!(filename.unwrap(), \"কখগ-你好.txt\");\n        assert!(name.is_none());\n    }\n\n    #[test]\n    fn test_content_distribution_misordered_fields() {\n        let val = br#\"form-data; filename=file-name.txt; name=file\"#;\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        let filename = ContentDispositionAttr::FileName.extract_from(val);\n        assert_eq!(filename.unwrap(), \"file-name.txt\");\n        assert_eq!(name.unwrap(), \"file\");\n\n        let val = br#\"form-data; filename=\"file-name.txt\"; name=\"file\"\"#;\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        let filename = ContentDispositionAttr::FileName.extract_from(val);\n        assert_eq!(filename.unwrap(), \"file-name.txt\");\n        assert_eq!(name.unwrap(), \"file\");\n\n        let val = \"form-data; filename=\\\"你好.txt\\\"; name=\\\"কখগ\\\"\".as_bytes();\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        let filename = ContentDispositionAttr::FileName.extract_from(val);\n        assert_eq!(name.unwrap(), \"কখগ\");\n        assert_eq!(filename.unwrap(), \"你好.txt\");\n    }\n\n    #[test]\n    fn test_content_disposition_name_unquoted() {\n        let val = br#\"form-data; name=my_field\"#;\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        let filename = ContentDispositionAttr::FileName.extract_from(val);\n        assert_eq!(name.unwrap(), \"my_field\");\n        assert!(filename.is_none());\n\n        let val = br#\"form-data; name=my_field; filename=file-name.txt\"#;\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        let filename = ContentDispositionAttr::FileName.extract_from(val);\n        assert_eq!(name.unwrap(), \"my_field\");\n        assert_eq!(filename.unwrap(), \"file-name.txt\");\n    }\n\n    #[test]\n    fn test_content_disposition_name_quoted() {\n        let val = br#\"form-data; name=\"my;f;ield\"\"#;\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        let filename = ContentDispositionAttr::FileName.extract_from(val);\n        assert_eq!(name.unwrap(), \"my;f;ield\");\n        assert!(filename.is_none());\n\n        let val = br#\"form-data; name=my_field; filename = \"file;name.txt\"\"#;\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        assert_eq!(name.unwrap(), \"my_field\");\n        let filename = ContentDispositionAttr::FileName.extract_from(val);\n        assert_eq!(filename.unwrap(), \"file;name.txt\");\n\n        let val = br#\"form-data; name=; filename=filename.txt\"#;\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        let filename = ContentDispositionAttr::FileName.extract_from(val);\n        assert_eq!(name.unwrap(), \"\");\n        assert_eq!(filename.unwrap(), \"filename.txt\");\n\n        let val = br#\"form-data; name=\";\"; filename=\";\"\"#;\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        let filename = ContentDispositionAttr::FileName.extract_from(val);\n        assert_eq!(name.unwrap(), \";\");\n        assert_eq!(filename.unwrap(), \";\");\n    }\n\n    #[test]\n    fn test_content_disposition_name_escaped_quote() {\n        let val = br#\"form-data; name=\"my\\\"field\\\"name\"\"#;\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        assert_eq!(name.unwrap(), r#\"my\"field\"name\"#);\n\n        let val = br#\"form-data; name=\"myfield\\\"name\"\"#;\n        let name = ContentDispositionAttr::Name.extract_from(val);\n        assert_eq!(name.unwrap(), r#\"myfield\"name\"#);\n    }\n}\n"
  },
  {
    "path": "src/constraints.rs",
    "content": "use crate::size_limit::SizeLimit;\n\n/// Represents some rules to be applied on the stream and field's content size\n/// to prevent DoS attacks.\n///\n/// It's recommended to add some rules on field (specially text field) size to\n/// avoid potential DoS attacks from attackers running the server out of memory.\n/// This type provides some API to apply constraints on very granular level to\n/// make `multipart/form-data` safe. By default, it does not apply any\n/// constraint.\n///\n/// # Examples\n///\n/// ```\n/// use multer::{Multipart, Constraints, SizeLimit};\n/// # use bytes::Bytes;\n/// # use std::convert::Infallible;\n/// # use futures_util::stream::once;\n///\n/// # async fn run() {\n/// # let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\";\n/// # let some_stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) });\n/// // Create some constraints to be applied to the fields to prevent DoS attack.\n/// let constraints = Constraints::new()\n///      // We only accept `my_text_field` and `my_file_field` fields,\n///      // For any unknown field, we will throw an error.\n///      .allowed_fields(vec![\"my_text_field\", \"my_file_field\"])\n///      .size_limit(\n///          SizeLimit::new()\n///              // Set 15mb as size limit for the whole stream body.\n///              .whole_stream(15 * 1024 * 1024)\n///              // Set 10mb as size limit for all fields.\n///              .per_field(10 * 1024 * 1024)\n///              // Set 30kb as size limit for our text field only.\n///              .for_field(\"my_text_field\", 30 * 1024),\n///      );\n///\n/// // Create a `Multipart` instance from a stream and the constraints.\n/// let mut multipart = Multipart::with_constraints(some_stream, \"X-BOUNDARY\", constraints);\n///\n/// while let Some(field) = multipart.next_field().await.unwrap() {\n///     let content = field.text().await.unwrap();\n///     assert_eq!(content, \"abcd\");\n/// }\n/// # }\n/// # tokio::runtime::Runtime::new().unwrap().block_on(run());\n/// ```\n#[derive(Debug, Default)]\npub struct Constraints {\n    pub(crate) size_limit: SizeLimit,\n    pub(crate) allowed_fields: Option<Vec<String>>,\n}\n\nimpl Constraints {\n    /// Creates a set of rules with default behaviour.\n    pub fn new() -> Constraints {\n        Constraints::default()\n    }\n\n    /// Applies rules on field's content length.\n    pub fn size_limit(self, size_limit: SizeLimit) -> Constraints {\n        Constraints {\n            size_limit,\n            allowed_fields: self.allowed_fields,\n        }\n    }\n\n    /// Specify which fields should be allowed, for any unknown field, the\n    /// [`next_field`](crate::Multipart::next_field) will throw an error.\n    pub fn allowed_fields<N: Into<String>>(self, allowed_fields: Vec<N>) -> Constraints {\n        let allowed_fields = allowed_fields.into_iter().map(|item| item.into()).collect();\n\n        Constraints {\n            size_limit: self.size_limit,\n            allowed_fields: Some(allowed_fields),\n        }\n    }\n\n    pub(crate) fn is_it_allowed(&self, field: Option<&str>) -> bool {\n        if let Some(ref allowed_fields) = self.allowed_fields {\n            field\n                .map(|field| allowed_fields.iter().any(|item| item == field))\n                .unwrap_or(false)\n        } else {\n            true\n        }\n    }\n}\n"
  },
  {
    "path": "src/content_disposition.rs",
    "content": "use http::header::{self, HeaderMap};\n\nuse crate::constants::ContentDispositionAttr;\n\n#[derive(Debug)]\npub(crate) struct ContentDisposition {\n    pub(crate) field_name: Option<String>,\n    pub(crate) file_name: Option<String>,\n}\n\nimpl ContentDisposition {\n    pub fn parse(headers: &HeaderMap) -> ContentDisposition {\n        let content_disposition = headers.get(header::CONTENT_DISPOSITION).map(|val| val.as_bytes());\n\n        let field_name = content_disposition\n            .and_then(|val| ContentDispositionAttr::Name.extract_from(val))\n            .map(|attr| attr.into_owned());\n\n        let file_name = content_disposition\n            .and_then(|val| ContentDispositionAttr::FileName.extract_from(val))\n            .map(|attr| attr.into_owned());\n\n        ContentDisposition { field_name, file_name }\n    }\n}\n"
  },
  {
    "path": "src/error.rs",
    "content": "use std::fmt::{self, Debug, Display, Formatter};\n\ntype BoxError = Box<dyn std::error::Error + Send + Sync>;\n\n/// A set of errors that can occur during parsing multipart stream and in other\n/// operations.\n#[non_exhaustive]\npub enum Error {\n    /// An unknown field is detected when multipart\n    /// [`constraints`](crate::Constraints::allowed_fields) are added.\n    UnknownField { field_name: Option<String> },\n\n    /// The field data is found incomplete.\n    IncompleteFieldData { field_name: Option<String> },\n\n    /// Couldn't read the field headers completely.\n    IncompleteHeaders,\n\n    /// Failed to read headers.\n    ReadHeaderFailed(httparse::Error),\n\n    /// Failed to decode the field's raw header name to\n    /// [`HeaderName`](http::header::HeaderName) type.\n    DecodeHeaderName { name: String, cause: BoxError },\n\n    /// Failed to decode the field's raw header value to\n    /// [`HeaderValue`](http::header::HeaderValue) type.\n    DecodeHeaderValue { value: Vec<u8>, cause: BoxError },\n\n    /// Multipart stream is incomplete.\n    IncompleteStream,\n\n    /// The incoming field size exceeded the maximum limit.\n    FieldSizeExceeded { limit: u64, field_name: Option<String> },\n\n    /// The incoming stream size exceeded the maximum limit.\n    StreamSizeExceeded { limit: u64 },\n\n    /// Stream read failed.\n    StreamReadFailed(BoxError),\n\n    /// Failed to lock the multipart shared state for any changes.\n    LockFailure,\n\n    /// The `Content-Type` header is not `multipart/form-data`.\n    NoMultipart,\n\n    /// Failed to convert the `Content-Type` to [`mime::Mime`] type.\n    DecodeContentType(mime::FromStrError),\n\n    /// No boundary found in `Content-Type` header.\n    NoBoundary,\n\n    /// Failed to decode the field data as `JSON` in\n    /// [`field.json()`](crate::Field::json) method.\n    #[cfg(feature = \"json\")]\n    #[cfg_attr(nightly, doc(cfg(feature = \"json\")))]\n    DecodeJson(serde_json::Error),\n}\n\nimpl Debug for Error {\n    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {\n        Display::fmt(self, f)\n    }\n}\n\nimpl Display for Error {\n    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {\n        match self {\n            Error::UnknownField { field_name } => {\n                let name = field_name.as_deref().unwrap_or(\"<unknown>\");\n                write!(f, \"unknown field received: {:?}\", name)\n            }\n            Error::IncompleteFieldData { field_name } => {\n                let name = field_name.as_deref().unwrap_or(\"<unknown>\");\n                write!(f, \"field {:?} received with incomplete data\", name)\n            }\n            Error::DecodeHeaderName { name, .. } => {\n                write!(f, \"failed to decode field's raw header name: {:?}\", name)\n            }\n            Error::DecodeHeaderValue { .. } => {\n                write!(f, \"failed to decode field's raw header value\")\n            }\n            Error::FieldSizeExceeded { limit, field_name } => {\n                let name = field_name.as_deref().unwrap_or(\"<unknown>\");\n                write!(f, \"field {:?} exceeded the size limit: {} bytes\", name, limit)\n            }\n            Error::StreamSizeExceeded { limit } => {\n                write!(f, \"stream size exceeded limit: {} bytes\", limit)\n            }\n            Error::ReadHeaderFailed(_) => write!(f, \"failed to read headers\"),\n            Error::StreamReadFailed(_) => write!(f, \"failed to read stream\"),\n            Error::DecodeContentType(_) => write!(f, \"failed to decode Content-Type\"),\n            Error::IncompleteHeaders => write!(f, \"failed to read field complete headers\"),\n            Error::IncompleteStream => write!(f, \"incomplete multipart stream\"),\n            Error::LockFailure => write!(f, \"failed to lock multipart state\"),\n            Error::NoMultipart => write!(f, \"Content-Type is not multipart/form-data\"),\n            Error::NoBoundary => write!(f, \"multipart boundary not found in Content-Type\"),\n            #[cfg(feature = \"json\")]\n            Error::DecodeJson(_) => write!(f, \"failed to decode field data as JSON\"),\n        }\n    }\n}\n\nimpl std::error::Error for Error {\n    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {\n        match self {\n            Error::ReadHeaderFailed(e) => Some(e),\n            Error::DecodeHeaderName { cause, .. } => Some(cause.as_ref()),\n            Error::DecodeHeaderValue { cause, .. } => Some(cause.as_ref()),\n            Error::StreamReadFailed(e) => Some(e.as_ref()),\n            Error::DecodeContentType(e) => Some(e),\n            #[cfg(feature = \"json\")]\n            Error::DecodeJson(e) => Some(e),\n            Error::UnknownField { .. }\n            | Error::IncompleteFieldData { .. }\n            | Error::IncompleteHeaders\n            | Error::IncompleteStream\n            | Error::FieldSizeExceeded { .. }\n            | Error::StreamSizeExceeded { .. }\n            | Error::LockFailure\n            | Error::NoMultipart\n            | Error::NoBoundary => None,\n        }\n    }\n}\n\nimpl PartialEq for Error {\n    fn eq(&self, other: &Self) -> bool {\n        self.to_string().eq(&other.to_string())\n    }\n}\n\nimpl Eq for Error {}\n"
  },
  {
    "path": "src/field.rs",
    "content": "use std::pin::Pin;\nuse std::sync::Arc;\nuse std::task::{Context, Poll};\n\nuse bytes::{Bytes, BytesMut};\nuse encoding_rs::{Encoding, UTF_8};\nuse futures_util::stream::{Stream, TryStreamExt};\nuse http::header::HeaderMap;\n#[cfg(feature = \"json\")]\nuse serde::de::DeserializeOwned;\nuse spin::mutex::spin::SpinMutex as Mutex;\n\nuse crate::content_disposition::ContentDisposition;\nuse crate::multipart::{MultipartState, StreamingStage};\nuse crate::{helpers, Error};\n\n/// A single field in a multipart stream.\n///\n/// Its content can be accessed via the [`Stream`] API or the methods defined in\n/// this type.\n///\n/// # Lifetime\n///\n/// The lifetime of the stream `'r` corresponds to the lifetime of the\n/// underlying `Stream`. If the underlying stream holds no references directly\n/// or transitively, then the lifetime can be `'static`.\n///\n/// # Examples\n///\n/// ```\n/// use std::convert::Infallible;\n///\n/// use bytes::Bytes;\n/// use futures_util::stream::once;\n/// use multer::Multipart;\n///\n/// # async fn run() {\n/// let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; \\\n///     name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\";\n///\n/// let stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) });\n/// let mut multipart = Multipart::new(stream, \"X-BOUNDARY\");\n///\n/// while let Some(field) = multipart.next_field().await.unwrap() {\n///     let content = field.text().await.unwrap();\n///     assert_eq!(content, \"abcd\");\n/// }\n/// # }\n/// # tokio::runtime::Runtime::new().unwrap().block_on(run());\n/// ```\n///\n/// [`Multipart`]: crate::Multipart\n#[derive(Debug)]\npub struct Field<'r> {\n    state: Arc<Mutex<MultipartState<'r>>>,\n    done: bool,\n    headers: HeaderMap,\n    content_disposition: ContentDisposition,\n    content_type: Option<mime::Mime>,\n    idx: usize,\n}\n\nimpl<'r> Field<'r> {\n    pub(crate) fn new(\n        state: Arc<Mutex<MultipartState<'r>>>,\n        headers: HeaderMap,\n        idx: usize,\n        content_disposition: ContentDisposition,\n    ) -> Self {\n        let content_type = helpers::parse_content_type(&headers);\n        Field {\n            state,\n            headers,\n            content_disposition,\n            content_type,\n            idx,\n            done: false,\n        }\n    }\n\n    /// The field name found in the [`Content-Disposition`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) header.\n    pub fn name(&self) -> Option<&str> {\n        self.content_disposition.field_name.as_deref()\n    }\n\n    /// The file name found in the [`Content-Disposition`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) header.\n    pub fn file_name(&self) -> Option<&str> {\n        self.content_disposition.file_name.as_deref()\n    }\n\n    /// Get the content type of the field.\n    pub fn content_type(&self) -> Option<&mime::Mime> {\n        self.content_type.as_ref()\n    }\n\n    /// Get a map of headers as [`HeaderMap`].\n    pub fn headers(&self) -> &HeaderMap {\n        &self.headers\n    }\n\n    /// Get the full data of the field as [`Bytes`].\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use std::convert::Infallible;\n    ///\n    /// use bytes::Bytes;\n    /// use futures_util::stream::once;\n    /// use multer::Multipart;\n    ///\n    /// # async fn run() {\n    /// let data =\n    ///     \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\";\n    /// let stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) });\n    /// let mut multipart = Multipart::new(stream, \"X-BOUNDARY\");\n    ///\n    /// while let Some(field) = multipart.next_field().await.unwrap() {\n    ///     let bytes = field.bytes().await.unwrap();\n    ///     assert_eq!(bytes.len(), 4);\n    /// }\n    /// # }\n    /// # tokio::runtime::Runtime::new().unwrap().block_on(run());\n    /// ```\n    pub async fn bytes(self) -> crate::Result<Bytes> {\n        let mut buf = BytesMut::new();\n\n        let mut this = self;\n        while let Some(bytes) = this.chunk().await? {\n            buf.extend_from_slice(&bytes);\n        }\n\n        Ok(buf.freeze())\n    }\n\n    /// Stream a chunk of the field data.\n    ///\n    /// When the field data has been exhausted, this will return [`None`].\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use std::convert::Infallible;\n    ///\n    /// use bytes::Bytes;\n    /// use futures_util::stream::once;\n    /// use multer::Multipart;\n    ///\n    /// # async fn run() {\n    /// let data =\n    ///     \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\";\n    /// let stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) });\n    /// let mut multipart = Multipart::new(stream, \"X-BOUNDARY\");\n    ///\n    /// while let Some(mut field) = multipart.next_field().await.unwrap() {\n    ///     while let Some(chunk) = field.chunk().await.unwrap() {\n    ///         println!(\"Chunk: {:?}\", chunk);\n    ///     }\n    /// }\n    /// # }\n    /// # tokio::runtime::Runtime::new().unwrap().block_on(run());\n    /// ```\n    pub async fn chunk(&mut self) -> crate::Result<Option<Bytes>> {\n        self.try_next().await\n    }\n\n    /// Try to deserialize the field data as JSON.\n    ///\n    /// # Optional\n    ///\n    /// This requires the optional `json` feature to be enabled.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use multer::Multipart;\n    /// use bytes::Bytes;\n    /// use std::convert::Infallible;\n    /// use futures_util::stream::once;\n    /// use serde::Deserialize;\n    ///\n    /// // This `derive` requires the `serde` dependency.\n    /// #[derive(Deserialize)]\n    /// struct User {\n    ///     name: String\n    /// }\n    ///\n    /// # async fn run() {\n    /// let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\n{ \\\"name\\\": \\\"Alice\\\" }\\r\\n--X-BOUNDARY--\\r\\n\";\n    /// let stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) });\n    /// let mut multipart = Multipart::new(stream, \"X-BOUNDARY\");\n    ///\n    /// while let Some(field) = multipart.next_field().await.unwrap() {\n    ///     let user = field.json::<User>().await.unwrap();\n    ///     println!(\"User Name: {}\", user.name);\n    /// }\n    /// # }\n    /// # tokio::runtime::Runtime::new().unwrap().block_on(run());\n    /// ```\n    ///\n    /// # Errors\n    ///\n    /// This method fails if the field data is not in JSON format\n    /// or it cannot be properly deserialized to target type `T`. For more\n    /// details please see [`serde_json::from_slice`].\n    #[cfg(feature = \"json\")]\n    #[cfg_attr(nightly, doc(cfg(feature = \"json\")))]\n    pub async fn json<T: DeserializeOwned>(self) -> crate::Result<T> {\n        serde_json::from_slice(&self.bytes().await?).map_err(Error::DecodeJson)\n    }\n\n    /// Get the full field data as text.\n    ///\n    /// This method decodes the field data with `BOM sniffing` and with\n    /// malformed sequences replaced with the `REPLACEMENT CHARACTER`.\n    /// Encoding is determined from the `charset` parameter of `Content-Type`\n    /// header, and defaults to `utf-8` if not presented.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use std::convert::Infallible;\n    ///\n    /// use bytes::Bytes;\n    /// use futures_util::stream::once;\n    /// use multer::Multipart;\n    ///\n    /// # async fn run() {\n    /// let data =\n    ///     \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\";\n    /// let stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) });\n    /// let mut multipart = Multipart::new(stream, \"X-BOUNDARY\");\n    ///\n    /// while let Some(field) = multipart.next_field().await.unwrap() {\n    ///     let content = field.text().await.unwrap();\n    ///     assert_eq!(content, \"abcd\");\n    /// }\n    /// # }\n    /// # tokio::runtime::Runtime::new().unwrap().block_on(run());\n    /// ```\n    pub async fn text(self) -> crate::Result<String> {\n        self.text_with_charset(\"utf-8\").await\n    }\n\n    /// Get the full field data as text given a specific encoding.\n    ///\n    /// This method decodes the field data with `BOM sniffing` and with\n    /// malformed sequences replaced with the `REPLACEMENT CHARACTER`.\n    /// You can provide a default encoding for decoding the raw message, while\n    /// the `charset` parameter of `Content-Type` header is still prioritized.\n    /// For more information about the possible encoding name, please go to\n    /// [encoding_rs] docs.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use std::convert::Infallible;\n    ///\n    /// use bytes::Bytes;\n    /// use futures_util::stream::once;\n    /// use multer::Multipart;\n    ///\n    /// # async fn run() {\n    /// let data =\n    ///     \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\";\n    /// let stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) });\n    /// let mut multipart = Multipart::new(stream, \"X-BOUNDARY\");\n    ///\n    /// while let Some(field) = multipart.next_field().await.unwrap() {\n    ///     let content = field.text_with_charset(\"utf-8\").await.unwrap();\n    ///     assert_eq!(content, \"abcd\");\n    /// }\n    /// # }\n    /// # tokio::runtime::Runtime::new().unwrap().block_on(run());\n    /// ```\n    pub async fn text_with_charset(self, default_encoding: &str) -> crate::Result<String> {\n        let encoding_name = self\n            .content_type()\n            .and_then(|mime| mime.get_param(mime::CHARSET))\n            .map(|charset| charset.as_str())\n            .unwrap_or(default_encoding);\n\n        let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8);\n        let bytes = self.bytes().await?;\n        Ok(encoding.decode(&bytes).0.into_owned())\n    }\n\n    /// Get the index of this field in order they appeared in the stream.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use std::convert::Infallible;\n    ///\n    /// use bytes::Bytes;\n    /// use futures_util::stream::once;\n    /// use multer::Multipart;\n    ///\n    /// # async fn run() {\n    /// let data =\n    ///     \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\";\n    /// let stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) });\n    /// let mut multipart = Multipart::new(stream, \"X-BOUNDARY\");\n    ///\n    /// while let Some(field) = multipart.next_field().await.unwrap() {\n    ///     let idx = field.index();\n    ///     println!(\"Field index: {}\", idx);\n    /// }\n    /// # }\n    /// # tokio::runtime::Runtime::new().unwrap().block_on(run());\n    /// ```\n    pub fn index(&self) -> usize {\n        self.idx\n    }\n}\n\nimpl Stream for Field<'_> {\n    type Item = Result<Bytes, Error>;\n\n    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {\n        if self.done {\n            return Poll::Ready(None);\n        }\n\n        debug_assert!(self.state.try_lock().is_some(), \"expected exlusive lock\");\n        let state = self.state.clone();\n        let mut lock = match state.try_lock() {\n            Some(lock) => lock,\n            None => return Poll::Ready(Some(Err(Error::LockFailure))),\n        };\n\n        let state = &mut *lock;\n        if let Err(err) = state.buffer.poll_stream(cx) {\n            return Poll::Ready(Some(Err(err)));\n        }\n\n        match state\n            .buffer\n            .read_field_data(&state.boundary, state.curr_field_name.as_deref())\n        {\n            Ok(Some((done, bytes))) => {\n                state.curr_field_size_counter += bytes.len() as u64;\n\n                if state.curr_field_size_counter > state.curr_field_size_limit {\n                    return Poll::Ready(Some(Err(Error::FieldSizeExceeded {\n                        limit: state.curr_field_size_limit,\n                        field_name: state.curr_field_name.clone(),\n                    })));\n                }\n\n                if done {\n                    state.stage = StreamingStage::ReadingBoundary;\n                    self.done = true;\n                }\n\n                Poll::Ready(Some(Ok(bytes)))\n            }\n            Ok(None) => Poll::Pending,\n            Err(err) => Poll::Ready(Some(Err(err))),\n        }\n    }\n}\n"
  },
  {
    "path": "src/helpers.rs",
    "content": "use std::convert::TryFrom;\n\nuse http::header::{self, HeaderMap, HeaderName, HeaderValue};\nuse httparse::Header;\n\npub(crate) fn convert_raw_headers_to_header_map(raw_headers: &[Header<'_>]) -> crate::Result<HeaderMap> {\n    let mut headers = HeaderMap::with_capacity(raw_headers.len());\n\n    for raw_header in raw_headers {\n        let name = HeaderName::try_from(raw_header.name).map_err(|err| crate::Error::DecodeHeaderName {\n            name: raw_header.name.to_owned(),\n            cause: err.into(),\n        })?;\n\n        let value = HeaderValue::try_from(raw_header.value).map_err(|err| crate::Error::DecodeHeaderValue {\n            value: raw_header.value.to_owned(),\n            cause: err.into(),\n        })?;\n\n        headers.insert(name, value);\n    }\n\n    Ok(headers)\n}\n\npub(crate) fn parse_content_type(headers: &HeaderMap) -> Option<mime::Mime> {\n    headers\n        .get(header::CONTENT_TYPE)\n        .and_then(|val| val.to_str().ok())\n        .and_then(|val| val.parse::<mime::Mime>().ok())\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "//! An async parser for `multipart/form-data` content-type in Rust.\n//!\n//! It accepts a [`Stream`](futures_util::stream::Stream) of\n//! [`Bytes`](bytes::Bytes), or with the `tokio-io` feature enabled, an\n//! `AsyncRead` reader as a source, so that it can be plugged into any async\n//! Rust environment e.g. any async server.\n//!\n//! To enable trace logging via the `log` crate, enable the `log` feature.\n//!\n//! # Examples\n//!\n//! ```no_run\n//! use std::convert::Infallible;\n//!\n//! use bytes::Bytes;\n//! // Import multer types.\n//! use futures_util::stream::once;\n//! use futures_util::stream::Stream;\n//! use multer::Multipart;\n//!\n//! #[tokio::main]\n//! async fn main() -> Result<(), Box<dyn std::error::Error>> {\n//!     // Generate a byte stream and the boundary from somewhere e.g. server request body.\n//!     let (stream, boundary) = get_byte_stream_from_somewhere().await;\n//!\n//!     // Create a `Multipart` instance from that byte stream and the boundary.\n//!     let mut multipart = Multipart::new(stream, boundary);\n//!\n//!     // Iterate over the fields, use `next_field()` to get the next field.\n//!     while let Some(mut field) = multipart.next_field().await? {\n//!         // Get field name.\n//!         let name = field.name();\n//!         // Get the field's filename if provided in \"Content-Disposition\" header.\n//!         let file_name = field.file_name();\n//!\n//!         println!(\"Name: {:?}, File Name: {:?}\", name, file_name);\n//!\n//!         // Process the field data chunks e.g. store them in a file.\n//!         while let Some(chunk) = field.chunk().await? {\n//!             // Do something with field chunk.\n//!             println!(\"Chunk: {:?}\", chunk);\n//!         }\n//!     }\n//!\n//!     Ok(())\n//! }\n//!\n//! // Generate a byte stream and the boundary from somewhere e.g. server request body.\n//! async fn get_byte_stream_from_somewhere(\n//! ) -> (impl Stream<Item = Result<Bytes, Infallible>>, &'static str) {\n//!     let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; \\\n//!         name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\";\n//!\n//!     let stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) });\n//!     (stream, \"X-BOUNDARY\")\n//! }\n//! ```\n//!\n//! ## Prevent Denial of Service (DoS) Attack\n//!\n//! This crate also provides some APIs to prevent potential DoS attacks with\n//! fine grained control. It's recommended to add some constraints\n//! on field (specially text field) size to avoid potential DoS attacks from\n//! attackers running the server out of memory.\n//!\n//! An example:\n//!\n//! ```\n//! use multer::{Constraints, Multipart, SizeLimit};\n//! # use bytes::Bytes;\n//! # use std::convert::Infallible;\n//! # use futures_util::stream::once;\n//!\n//! # async fn run() {\n//! # let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; \\\n//! #   name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\";\n//! # let some_stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) });\n//! // Create some constraints to be applied to the fields to prevent DoS attack.\n//! let constraints = Constraints::new()\n//!     // We only accept `my_text_field` and `my_file_field` fields,\n//!     // For any unknown field, we will throw an error.\n//!     .allowed_fields(vec![\"my_text_field\", \"my_file_field\"])\n//!     .size_limit(\n//!         SizeLimit::new()\n//!             // Set 15mb as size limit for the whole stream body.\n//!             .whole_stream(15 * 1024 * 1024)\n//!             // Set 10mb as size limit for all fields.\n//!             .per_field(10 * 1024 * 1024)\n//!             // Set 30kb as size limit for our text field only.\n//!             .for_field(\"my_text_field\", 30 * 1024),\n//!     );\n//!\n//! // Create a `Multipart` instance from a stream and the constraints.\n//! let mut multipart = Multipart::with_constraints(some_stream, \"X-BOUNDARY\", constraints);\n//!\n//! while let Some(field) = multipart.next_field().await.unwrap() {\n//!     let content = field.text().await.unwrap();\n//!     assert_eq!(content, \"abcd\");\n//! }\n//! # }\n//! # tokio::runtime::Runtime::new().unwrap().block_on(run());\n//! ```\n//!\n//! Please refer [`Constraints`] for more info.\n//!\n//! ## Usage with [hyper.rs](https://hyper.rs/) server\n//!\n//! An [example](https://github.com/rousan/multer-rs/blob/master/examples/hyper_server_example.rs) showing usage with [hyper.rs](https://hyper.rs/).\n//!\n//! For more examples, please visit [examples](https://github.com/rousan/multer-rs/tree/master/examples).\n\n#![forbid(unsafe_code)]\n#![warn(\n    missing_debug_implementations,\n    rust_2018_idioms,\n    trivial_casts,\n    unused_qualifications\n)]\n#![cfg_attr(nightly, feature(doc_cfg))]\n#![doc(test(attr(deny(rust_2018_idioms, warnings))))]\n#![doc(test(attr(allow(unused_extern_crates, unused_variables))))]\n\npub use bytes;\npub use constraints::Constraints;\npub use error::Error;\npub use field::Field;\npub use multipart::Multipart;\npub use size_limit::SizeLimit;\n\n#[cfg(feature = \"log\")]\nmacro_rules! trace {\n    ($($t:tt)*) => (::log::trace!($($t)*););\n}\n\n#[cfg(not(feature = \"log\"))]\nmacro_rules! trace {\n    ($($t:tt)*) => {};\n}\n\nmod buffer;\nmod constants;\nmod constraints;\nmod content_disposition;\nmod error;\nmod field;\nmod helpers;\nmod multipart;\nmod size_limit;\n\n/// A Result type often returned from methods that can have `multer` errors.\npub type Result<T, E = Error> = std::result::Result<T, E>;\n\n/// Parses the `Content-Type` header to extract the boundary value.\n///\n/// # Examples\n///\n/// ```\n/// # fn run(){\n/// let content_type = \"multipart/form-data; boundary=ABCDEFG\";\n///\n/// assert_eq!(\n///     multer::parse_boundary(content_type),\n///     Ok(\"ABCDEFG\".to_owned())\n/// );\n/// # }\n/// # run();\n/// ```\npub fn parse_boundary<T: AsRef<str>>(content_type: T) -> Result<String> {\n    let m = content_type\n        .as_ref()\n        .parse::<mime::Mime>()\n        .map_err(Error::DecodeContentType)?;\n\n    if !(m.type_() == mime::MULTIPART && m.subtype() == mime::FORM_DATA) {\n        return Err(Error::NoMultipart);\n    }\n\n    m.get_param(mime::BOUNDARY)\n        .map(|name| name.as_str().to_owned())\n        .ok_or(Error::NoBoundary)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_parse_boundary() {\n        let content_type = \"multipart/form-data; boundary=ABCDEFG\";\n        assert_eq!(parse_boundary(content_type), Ok(\"ABCDEFG\".to_owned()));\n\n        let content_type = \"multipart/form-data; boundary=------ABCDEFG\";\n        assert_eq!(parse_boundary(content_type), Ok(\"------ABCDEFG\".to_owned()));\n\n        let content_type = \"boundary=------ABCDEFG\";\n        assert!(parse_boundary(content_type).is_err());\n\n        let content_type = \"text/plain\";\n        assert!(parse_boundary(content_type).is_err());\n\n        let content_type = \"text/plain; boundary=------ABCDEFG\";\n        assert!(parse_boundary(content_type).is_err());\n    }\n}\n"
  },
  {
    "path": "src/multipart.rs",
    "content": "use std::sync::Arc;\nuse std::task::{Context, Poll};\n\nuse bytes::Bytes;\nuse futures_util::future;\nuse futures_util::stream::{Stream, TryStreamExt};\nuse spin::mutex::spin::SpinMutex as Mutex;\n#[cfg(feature = \"tokio-io\")]\nuse {tokio::io::AsyncRead, tokio_util::io::ReaderStream};\n\nuse crate::buffer::StreamBuffer;\nuse crate::constraints::Constraints;\nuse crate::content_disposition::ContentDisposition;\nuse crate::error::Error;\nuse crate::field::Field;\nuse crate::{constants, helpers, Result};\n\n/// Represents the implementation of `multipart/form-data` formatted data.\n///\n/// This will parse the source stream into [`Field`] instances via\n/// [`next_field()`](Self::next_field).\n///\n/// # Field Exclusivity\n///\n/// A `Field` represents a raw, self-decoding stream into multipart data. As\n/// such, only _one_ `Field` from a given `Multipart` instance may be live at\n/// once. That is, a `Field` emitted by `next_field()` must be dropped before\n/// calling `next_field()` again. Failure to do so will result in an error.\n///\n/// ```rust\n/// use std::convert::Infallible;\n///\n/// use bytes::Bytes;\n/// use futures_util::stream::once;\n/// use multer::Multipart;\n///\n/// # async fn run() {\n/// let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; \\\n///     name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\";\n///\n/// let stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) });\n/// let mut multipart = Multipart::new(stream, \"X-BOUNDARY\");\n///\n/// let field1 = multipart.next_field().await;\n/// let field2 = multipart.next_field().await;\n///\n/// assert!(field2.is_err());\n/// # }\n/// # tokio::runtime::Runtime::new().unwrap().block_on(run());\n/// ```\n///\n/// # Examples\n///\n/// ```\n/// use std::convert::Infallible;\n///\n/// use bytes::Bytes;\n/// use futures_util::stream::once;\n/// use multer::Multipart;\n///\n/// # async fn run() {\n/// let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; \\\n///     name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\";\n///\n/// let stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) });\n/// let mut multipart = Multipart::new(stream, \"X-BOUNDARY\");\n///\n/// while let Some(field) = multipart.next_field().await.unwrap() {\n///     println!(\"Field: {:?}\", field.text().await)\n/// }\n/// # }\n/// # tokio::runtime::Runtime::new().unwrap().block_on(run());\n/// ```\n#[derive(Debug)]\npub struct Multipart<'r> {\n    state: Arc<Mutex<MultipartState<'r>>>,\n}\n\n#[derive(Debug)]\npub(crate) struct MultipartState<'r> {\n    pub(crate) buffer: StreamBuffer<'r>,\n    pub(crate) boundary: String,\n    pub(crate) stage: StreamingStage,\n    pub(crate) next_field_idx: usize,\n    pub(crate) curr_field_name: Option<String>,\n    pub(crate) curr_field_size_limit: u64,\n    pub(crate) curr_field_size_counter: u64,\n    pub(crate) constraints: Constraints,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub(crate) enum StreamingStage {\n    FindingFirstBoundary,\n    ReadingBoundary,\n    DeterminingBoundaryType,\n    ReadingTransportPadding,\n    ReadingFieldHeaders,\n    ReadingFieldData,\n    Eof,\n}\n\nimpl<'r> Multipart<'r> {\n    /// Construct a new `Multipart` instance with the given [`Bytes`] stream and\n    /// the boundary.\n    pub fn new<S, O, E, B>(stream: S, boundary: B) -> Self\n    where\n        S: Stream<Item = Result<O, E>> + Send + 'r,\n        O: Into<Bytes> + 'static,\n        E: Into<Box<dyn std::error::Error + Send + Sync>> + 'r,\n        B: Into<String>,\n    {\n        Multipart::with_constraints(stream, boundary, Constraints::default())\n    }\n\n    /// Construct a new `Multipart` instance with the given [`Bytes`] stream and\n    /// the boundary.\n    pub fn with_constraints<S, O, E, B>(stream: S, boundary: B, constraints: Constraints) -> Self\n    where\n        S: Stream<Item = Result<O, E>> + Send + 'r,\n        O: Into<Bytes> + 'static,\n        E: Into<Box<dyn std::error::Error + Send + Sync>> + 'r,\n        B: Into<String>,\n    {\n        let stream = stream\n            .map_ok(|b| b.into())\n            .map_err(|err| Error::StreamReadFailed(err.into()));\n\n        Multipart {\n            state: Arc::new(Mutex::new(MultipartState {\n                buffer: StreamBuffer::new(stream, constraints.size_limit.whole_stream),\n                boundary: boundary.into(),\n                stage: StreamingStage::FindingFirstBoundary,\n                next_field_idx: 0,\n                curr_field_name: None,\n                curr_field_size_limit: constraints.size_limit.per_field,\n                curr_field_size_counter: 0,\n                constraints,\n            })),\n        }\n    }\n\n    /// Construct a new `Multipart` instance with the given [`AsyncRead`] reader\n    /// and the boundary.\n    ///\n    /// # Optional\n    ///\n    /// This requires the optional `tokio-io` feature to be enabled.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use multer::Multipart;\n    ///\n    /// # async fn run() {\n    /// let data =\n    ///     \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\";\n    /// let reader = data.as_bytes();\n    /// let mut multipart = Multipart::with_reader(reader, \"X-BOUNDARY\");\n    ///\n    /// while let Some(mut field) = multipart.next_field().await.unwrap() {\n    ///     while let Some(chunk) = field.chunk().await.unwrap() {\n    ///         println!(\"Chunk: {:?}\", chunk);\n    ///     }\n    /// }\n    /// # }\n    /// # tokio::runtime::Runtime::new().unwrap().block_on(run());\n    /// ```\n    #[cfg(feature = \"tokio-io\")]\n    #[cfg_attr(nightly, doc(cfg(feature = \"tokio-io\")))]\n    pub fn with_reader<R, B>(reader: R, boundary: B) -> Self\n    where\n        R: AsyncRead + Unpin + Send + 'r,\n        B: Into<String>,\n    {\n        let stream = ReaderStream::new(reader);\n        Multipart::new(stream, boundary)\n    }\n\n    /// Construct a new `Multipart` instance with the given [`AsyncRead`] reader\n    /// and the boundary.\n    ///\n    /// # Optional\n    ///\n    /// This requires the optional `tokio-io` feature to be enabled.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use multer::Multipart;\n    ///\n    /// # async fn run() {\n    /// let data =\n    ///     \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\";\n    /// let reader = data.as_bytes();\n    /// let mut multipart = Multipart::with_reader(reader, \"X-BOUNDARY\");\n    ///\n    /// while let Some(mut field) = multipart.next_field().await.unwrap() {\n    ///     while let Some(chunk) = field.chunk().await.unwrap() {\n    ///         println!(\"Chunk: {:?}\", chunk);\n    ///     }\n    /// }\n    /// # }\n    /// # tokio::runtime::Runtime::new().unwrap().block_on(run());\n    /// ```\n    #[cfg(feature = \"tokio-io\")]\n    #[cfg_attr(nightly, doc(cfg(feature = \"tokio-io\")))]\n    pub fn with_reader_with_constraints<R, B>(reader: R, boundary: B, constraints: Constraints) -> Self\n    where\n        R: AsyncRead + Unpin + Send + 'r,\n        B: Into<String>,\n    {\n        let stream = ReaderStream::new(reader);\n        Multipart::with_constraints(stream, boundary, constraints)\n    }\n\n    /// Yields the next [`Field`] if available.\n    ///\n    /// Any previous `Field` returned by this method must be dropped before\n    /// calling this method or [`Multipart::next_field_with_idx()`] again. See\n    /// [field-exclusivity](#field-exclusivity) for details.\n    pub async fn next_field(&mut self) -> Result<Option<Field<'r>>> {\n        future::poll_fn(|cx| self.poll_next_field(cx)).await\n    }\n\n    /// Yields the next [`Field`] if available.\n    ///\n    /// Any previous `Field` returned by this method must be dropped before\n    /// calling this method or [`Multipart::next_field_with_idx()`] again. See\n    /// [field-exclusivity](#field-exclusivity) for details.\n    ///\n    /// This method is available since version 2.1.0.\n    pub fn poll_next_field(&mut self, cx: &mut Context<'_>) -> Poll<Result<Option<Field<'r>>>> {\n        // This is consistent as we have an `&mut` and `Field` is not `Clone`.\n        // Here, we are guaranteeing that the returned `Field` will be the\n        // _only_ field with access to the multipart parsing state. This ensure\n        // that lock failure can never occur. This is effectively a dynamic\n        // version of passing an `&mut` of `self` to the `Field`.\n        if Arc::strong_count(&self.state) != 1 {\n            return Poll::Ready(Err(Error::LockFailure));\n        }\n\n        debug_assert_eq!(Arc::strong_count(&self.state), 1);\n        debug_assert!(self.state.try_lock().is_some(), \"expected exlusive lock\");\n        let mut lock = match self.state.try_lock() {\n            Some(lock) => lock,\n            None => return Poll::Ready(Err(Error::LockFailure)),\n        };\n\n        let state = &mut *lock;\n        if state.stage == StreamingStage::Eof {\n            return Poll::Ready(Ok(None));\n        }\n\n        state.buffer.poll_stream(cx)?;\n\n        if state.stage == StreamingStage::FindingFirstBoundary {\n            let boundary = &state.boundary;\n            let boundary_deriv = format!(\"{}{}\", constants::BOUNDARY_EXT, boundary);\n            match state.buffer.read_to(boundary_deriv.as_bytes()) {\n                Some(_) => state.stage = StreamingStage::ReadingBoundary,\n                None => {\n                    state.buffer.poll_stream(cx)?;\n                    if state.buffer.eof {\n                        return Poll::Ready(Err(Error::IncompleteStream));\n                    }\n                }\n            }\n        }\n\n        // The previous field did not finish reading its data.\n        if state.stage == StreamingStage::ReadingFieldData {\n            match state\n                .buffer\n                .read_field_data(state.boundary.as_str(), state.curr_field_name.as_deref())?\n            {\n                Some((done, bytes)) => {\n                    state.curr_field_size_counter += bytes.len() as u64;\n\n                    if state.curr_field_size_counter > state.curr_field_size_limit {\n                        return Poll::Ready(Err(Error::FieldSizeExceeded {\n                            limit: state.curr_field_size_limit,\n                            field_name: state.curr_field_name.clone(),\n                        }));\n                    }\n\n                    if done {\n                        state.stage = StreamingStage::ReadingBoundary;\n                    } else {\n                        return Poll::Pending;\n                    }\n                }\n                None => {\n                    return Poll::Pending;\n                }\n            }\n        }\n\n        if state.stage == StreamingStage::ReadingBoundary {\n            let boundary = &state.boundary;\n            let boundary_deriv_len = constants::BOUNDARY_EXT.len() + boundary.len();\n\n            let boundary_bytes = match state.buffer.read_exact(boundary_deriv_len) {\n                Some(bytes) => bytes,\n                None => {\n                    return if state.buffer.eof {\n                        Poll::Ready(Err(Error::IncompleteStream))\n                    } else {\n                        Poll::Pending\n                    };\n                }\n            };\n\n            if &boundary_bytes[..] == format!(\"{}{}\", constants::BOUNDARY_EXT, boundary).as_bytes() {\n                state.stage = StreamingStage::DeterminingBoundaryType;\n            } else {\n                return Poll::Ready(Err(Error::IncompleteStream));\n            }\n        }\n\n        if state.stage == StreamingStage::DeterminingBoundaryType {\n            let ext_len = constants::BOUNDARY_EXT.len();\n            let next_bytes = match state.buffer.peek_exact(ext_len) {\n                Some(bytes) => bytes,\n                None => {\n                    return if state.buffer.eof {\n                        Poll::Ready(Err(Error::IncompleteStream))\n                    } else {\n                        Poll::Pending\n                    };\n                }\n            };\n\n            if next_bytes == constants::BOUNDARY_EXT.as_bytes() {\n                state.stage = StreamingStage::Eof;\n                return Poll::Ready(Ok(None));\n            } else {\n                state.stage = StreamingStage::ReadingTransportPadding;\n            }\n        }\n\n        if state.stage == StreamingStage::ReadingTransportPadding {\n            if !state.buffer.advance_past_transport_padding() {\n                return if state.buffer.eof {\n                    Poll::Ready(Err(Error::IncompleteStream))\n                } else {\n                    Poll::Pending\n                };\n            }\n\n            let crlf_len = constants::CRLF.len();\n            let crlf_bytes = match state.buffer.read_exact(crlf_len) {\n                Some(bytes) => bytes,\n                None => {\n                    return if state.buffer.eof {\n                        Poll::Ready(Err(Error::IncompleteStream))\n                    } else {\n                        Poll::Pending\n                    };\n                }\n            };\n\n            if &crlf_bytes[..] == constants::CRLF.as_bytes() {\n                state.stage = StreamingStage::ReadingFieldHeaders;\n            } else {\n                return Poll::Ready(Err(Error::IncompleteStream));\n            }\n        }\n\n        if state.stage == StreamingStage::ReadingFieldHeaders {\n            let header_bytes = match state.buffer.read_until(constants::CRLF_CRLF.as_bytes()) {\n                Some(bytes) => bytes,\n                None => {\n                    return if state.buffer.eof {\n                        return Poll::Ready(Err(Error::IncompleteStream));\n                    } else {\n                        Poll::Pending\n                    };\n                }\n            };\n\n            let mut headers = [httparse::EMPTY_HEADER; constants::MAX_HEADERS];\n\n            let headers = match httparse::parse_headers(&header_bytes, &mut headers).map_err(Error::ReadHeaderFailed)? {\n                httparse::Status::Complete((_, raw_headers)) => {\n                    match helpers::convert_raw_headers_to_header_map(raw_headers) {\n                        Ok(headers) => headers,\n                        Err(err) => {\n                            return Poll::Ready(Err(err));\n                        }\n                    }\n                }\n                httparse::Status::Partial => {\n                    return Poll::Ready(Err(Error::IncompleteHeaders));\n                }\n            };\n\n            state.stage = StreamingStage::ReadingFieldData;\n\n            let field_idx = state.next_field_idx;\n            state.next_field_idx += 1;\n\n            let content_disposition = ContentDisposition::parse(&headers);\n            let field_size_limit = state\n                .constraints\n                .size_limit\n                .extract_size_limit_for(content_disposition.field_name.as_deref());\n\n            state.curr_field_name = content_disposition.field_name.clone();\n            state.curr_field_size_limit = field_size_limit;\n            state.curr_field_size_counter = 0;\n\n            let field_name = content_disposition.field_name.as_deref();\n            if !state.constraints.is_it_allowed(field_name) {\n                return Poll::Ready(Err(Error::UnknownField {\n                    field_name: field_name.map(str::to_owned),\n                }));\n            }\n\n            drop(lock); // The lock will be dropped anyway, but let's be explicit.\n            let field = Field::new(self.state.clone(), headers, field_idx, content_disposition);\n            return Poll::Ready(Ok(Some(field)));\n        }\n\n        Poll::Pending\n    }\n\n    /// Yields the next [`Field`] with their positioning index as a tuple\n    /// `(`[`usize`]`, `[`Field`]`)`.\n    ///\n    /// Any previous `Field` returned by this method must be dropped before\n    /// calling this method or [`Multipart::next_field()`] again. See\n    /// [field-exclusivity](#field-exclusivity) for details.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use std::convert::Infallible;\n    ///\n    /// use bytes::Bytes;\n    /// use futures_util::stream::once;\n    /// use multer::Multipart;\n    ///\n    /// # async fn run() {\n    /// let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; \\\n    ///     name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\";\n    ///\n    /// let stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) });\n    /// let mut multipart = Multipart::new(stream, \"X-BOUNDARY\");\n    ///\n    /// while let Some((idx, field)) = multipart.next_field_with_idx().await.unwrap() {\n    ///     println!(\"Index: {:?}, Content: {:?}\", idx, field.text().await)\n    /// }\n    /// # }\n    /// # tokio::runtime::Runtime::new().unwrap().block_on(run());\n    /// ```\n    pub async fn next_field_with_idx(&mut self) -> Result<Option<(usize, Field<'r>)>> {\n        self.next_field().await.map(|f| f.map(|field| (field.index(), field)))\n    }\n}\n"
  },
  {
    "path": "src/size_limit.rs",
    "content": "use std::collections::HashMap;\n\nuse crate::constants;\n\n/// Represents size limit of the stream to prevent DoS attacks.\n///\n/// Please refer [`Constraints`](crate::Constraints) for more info.\n#[derive(Debug)]\npub struct SizeLimit {\n    pub(crate) whole_stream: u64,\n    pub(crate) per_field: u64,\n    pub(crate) field_map: HashMap<String, u64>,\n}\n\nimpl SizeLimit {\n    /// Creates a default size limit which is [`u64::MAX`] for the whole stream\n    /// and for each field.\n    pub fn new() -> SizeLimit {\n        SizeLimit::default()\n    }\n\n    /// Sets size limit for the whole stream.\n    pub fn whole_stream(mut self, limit: u64) -> SizeLimit {\n        self.whole_stream = limit;\n        self\n    }\n\n    /// Sets size limit for each field.\n    pub fn per_field(mut self, limit: u64) -> SizeLimit {\n        self.per_field = limit;\n        self\n    }\n\n    /// Sets size limit for a specific field, it overrides the\n    /// [`per_field`](Self::per_field) value for this field.\n    ///\n    /// It is useful when you want to set a size limit on a textual field which\n    /// will be stored in memory to avoid potential DoS attacks from\n    /// attackers running the server out of memory.\n    pub fn for_field<N: Into<String>>(mut self, field_name: N, limit: u64) -> SizeLimit {\n        self.field_map.insert(field_name.into(), limit);\n        self\n    }\n\n    pub(crate) fn extract_size_limit_for(&self, field: Option<&str>) -> u64 {\n        field\n            .and_then(|field| self.field_map.get(&field.to_owned()))\n            .copied()\n            .unwrap_or(self.per_field)\n    }\n}\n\nimpl Default for SizeLimit {\n    fn default() -> Self {\n        SizeLimit {\n            whole_stream: constants::DEFAULT_WHOLE_STREAM_SIZE_LIMIT,\n            per_field: constants::DEFAULT_PER_FIELD_SIZE_LIMIT,\n            field_map: HashMap::default(),\n        }\n    }\n}\n"
  },
  {
    "path": "tests/integration.rs",
    "content": "use bytes::Bytes;\nuse futures_util::{stream, Stream};\nuse multer::{Constraints, Multipart, SizeLimit};\n\nfn str_stream(string: &'static str) -> impl Stream<Item = multer::Result<Bytes>> {\n    stream::iter(\n        string\n            .chars()\n            .map(|ch| ch.to_string())\n            .map(|part| Ok(Bytes::copy_from_slice(part.as_bytes()))),\n    )\n}\n\n#[tokio::test]\nasync fn test_multipart_basic() {\n    let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_file_field\\\"; filename=\\\"a-text-file.txt\\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nHello world\\nHello\\r\\nWorld\\rAgain\\r\\n--X-BOUNDARY--\\r\\n\";\n    let stream = str_stream(data);\n    let mut m = Multipart::new(stream, \"X-BOUNDARY\");\n\n    while let Some((idx, field)) = m.next_field_with_idx().await.unwrap() {\n        if idx == 0 {\n            assert_eq!(field.name(), Some(\"my_text_field\"));\n            assert_eq!(field.file_name(), None);\n            assert_eq!(field.content_type(), None);\n            assert_eq!(field.index(), 0);\n\n            assert_eq!(field.text().await, Ok(\"abcd\".to_owned()));\n        } else if idx == 1 {\n            assert_eq!(field.name(), Some(\"my_file_field\"));\n            assert_eq!(field.file_name(), Some(\"a-text-file.txt\"));\n            assert_eq!(field.content_type(), Some(&mime::TEXT_PLAIN));\n            assert_eq!(field.index(), 1);\n\n            assert_eq!(field.text().await, Ok(\"Hello world\\nHello\\r\\nWorld\\rAgain\".to_owned()));\n        }\n    }\n}\n\n#[tokio::test]\nasync fn test_multipart_empty() {\n    let data = \"--X-BOUNDARY--\\r\\n\";\n    let stream = str_stream(data);\n    let mut m = Multipart::new(stream, \"X-BOUNDARY\");\n\n    assert!(m.next_field().await.unwrap().is_none());\n    assert!(m.next_field().await.unwrap().is_none());\n}\n\n#[tokio::test]\nasync fn test_multipart_clean_field() {\n    let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_file_field\\\"; filename=\\\"a-text-file.txt\\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nHello world\\nHello\\r\\nWorld\\rAgain\\r\\n--X-BOUNDARY--\\r\\n\";\n    let stream = str_stream(data);\n\n    let mut m = Multipart::new(stream, \"X-BOUNDARY\");\n\n    assert!(m.next_field().await.unwrap().is_some());\n    assert!(m.next_field().await.unwrap().is_some());\n    assert!(m.next_field().await.unwrap().is_none());\n}\n\n#[tokio::test]\nasync fn test_multipart_transport_padding() {\n    let data = \"--X-BOUNDARY \\t \\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY     \\r\\nContent-Disposition: form-data; name=\\\"my_file_field\\\"; filename=\\\"a-text-file.txt\\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nHello world\\nHello\\r\\nWorld\\rAgain\\r\\n--X-BOUNDARY--\\t\\t\\t\\t\\t\\r\\n\";\n    let stream = str_stream(data);\n\n    let mut m = Multipart::new(stream, \"X-BOUNDARY\");\n\n    assert!(m.next_field().await.unwrap().is_some());\n    assert!(m.next_field().await.unwrap().is_some());\n    assert!(m.next_field().await.unwrap().is_none());\n\n    let bad_data = \"--X-BOUNDARY \\t \\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARYzz     \\r\\nContent-Disposition: form-data; name=\\\"my_file_field\\\"; filename=\\\"a-text-file.txt\\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nHello world\\nHello\\r\\nWorld\\rAgain\\r\\n--X-BOUNDARY--\\t\\t\\t\\t\\t\\r\\n\";\n    let bad_stream = str_stream(bad_data);\n    let mut m = Multipart::new(bad_stream, \"X-BOUNDARY\");\n    assert!(m.next_field().await.unwrap().is_some());\n    assert!(m.next_field().await.is_err());\n}\n\n#[tokio::test]\nasync fn test_multipart_header() {\n    let should_pass = [\n        \"ignored header\\r\\n--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\",\n        \"\\r\\nignored header\\r\\n--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\",\n        \"\\r\\n--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\",\n        \"\\r\\n\\r\\n--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY--\\r\\n\",\n    ];\n\n    for data in should_pass.iter() {\n        let stream = str_stream(data);\n        let mut m = Multipart::new(stream, \"X-BOUNDARY\");\n\n        assert_eq!(\n            m.next_field().await.unwrap().unwrap().text().await.unwrap(),\n            \"abcd\".to_owned()\n        );\n    }\n}\n\n#[tokio::test]\nasync fn test_multipart_constraint_allowed_fields_normal() {\n    let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_file_field\\\"; filename=\\\"a-text-file.txt\\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nHello world\\nHello\\r\\nWorld\\rAgain\\r\\n--X-BOUNDARY--\\r\\n\";\n    let stream = str_stream(data);\n\n    let constraints = Constraints::new().allowed_fields(vec![\"my_text_field\", \"my_file_field\"]);\n    let mut m = Multipart::with_constraints(stream, \"X-BOUNDARY\", constraints);\n\n    assert_eq!(\n        m.next_field().await.unwrap().unwrap().text().await.unwrap(),\n        \"abcd\".to_owned()\n    );\n    assert_eq!(\n        m.next_field().await.unwrap().unwrap().text().await.unwrap(),\n        \"Hello world\\nHello\\r\\nWorld\\rAgain\".to_owned()\n    );\n}\n\n#[tokio::test]\n#[should_panic]\nasync fn test_multipart_constraint_allowed_fields_unknown_field() {\n    let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_file_field\\\"; filename=\\\"a-text-file.txt\\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nHello world\\nHello\\r\\nWorld\\rAgain\\r\\n--X-BOUNDARY--\\r\\n\";\n    let stream = str_stream(data);\n\n    let constraints = Constraints::new().allowed_fields(vec![\"my_text_field\"]);\n    let mut m = Multipart::with_constraints(stream, \"X-BOUNDARY\", constraints);\n\n    assert!(m.next_field().await.unwrap().is_some());\n    assert!(m.next_field().await.unwrap().is_some());\n    assert!(m.next_field().await.unwrap().is_none());\n}\n\n#[tokio::test]\nasync fn test_multipart_constraint_size_limit_whole_stream() {\n    let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_file_field\\\"; filename=\\\"a-text-file.txt\\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nHello world\\nHello\\r\\nWorld\\rAgain\\r\\n--X-BOUNDARY--\\r\\n\";\n    let stream = str_stream(data);\n\n    let constraints = Constraints::new()\n        .allowed_fields(vec![\"my_text_field\", \"my_file_field\"])\n        .size_limit(SizeLimit::new().whole_stream(248));\n\n    let mut m = Multipart::with_constraints(stream, \"X-BOUNDARY\", constraints);\n\n    assert_eq!(\n        m.next_field().await.unwrap().unwrap().text().await.unwrap(),\n        \"abcd\".to_owned()\n    );\n    assert_eq!(\n        m.next_field().await.unwrap().unwrap().text().await.unwrap(),\n        \"Hello world\\nHello\\r\\nWorld\\rAgain\".to_owned()\n    );\n}\n\n#[tokio::test]\n#[should_panic]\nasync fn test_multipart_constraint_size_limit_whole_stream_size_exceeded() {\n    let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_file_field\\\"; filename=\\\"a-text-file.txt\\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nHello world\\nHello\\r\\nWorld\\rAgain\\r\\n--X-BOUNDARY--\\r\\n\";\n    let stream = str_stream(data);\n\n    let constraints = Constraints::new()\n        .allowed_fields(vec![\"my_text_field\", \"my_file_field\"])\n        .size_limit(SizeLimit::new().whole_stream(100));\n\n    let mut m = Multipart::with_constraints(stream, \"X-BOUNDARY\", constraints);\n\n    assert!(m.next_field().await.unwrap().is_some());\n    assert!(m.next_field().await.unwrap().is_some());\n    assert!(m.next_field().await.unwrap().is_none());\n}\n\n#[tokio::test]\nasync fn test_multipart_constraint_size_limit_per_field() {\n    let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_file_field\\\"; filename=\\\"a-text-file.txt\\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nHello world\\nHello\\r\\nWorld\\rAgain\\r\\n--X-BOUNDARY--\\r\\n\";\n    let stream = str_stream(data);\n\n    let constraints = Constraints::new()\n        .allowed_fields(vec![\"my_text_field\", \"my_file_field\"])\n        .size_limit(SizeLimit::new().whole_stream(248).per_field(100));\n\n    let mut m = Multipart::with_constraints(stream, \"X-BOUNDARY\", constraints);\n\n    assert_eq!(\n        m.next_field().await.unwrap().unwrap().text().await.unwrap(),\n        \"abcd\".to_owned()\n    );\n    assert_eq!(\n        m.next_field().await.unwrap().unwrap().text().await.unwrap(),\n        \"Hello world\\nHello\\r\\nWorld\\rAgain\".to_owned()\n    );\n}\n\n#[tokio::test]\n#[should_panic]\nasync fn test_multipart_constraint_size_limit_per_field_size_exceeded() {\n    let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_file_field\\\"; filename=\\\"a-text-file.txt\\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nHello world\\nHello\\r\\nWorld\\rAgain\\r\\n--X-BOUNDARY--\\r\\n\";\n    let stream = str_stream(data);\n\n    let constraints = Constraints::new()\n        .allowed_fields(vec![\"my_text_field\", \"my_file_field\"])\n        .size_limit(SizeLimit::new().whole_stream(248).per_field(10));\n\n    let mut m = Multipart::with_constraints(stream, \"X-BOUNDARY\", constraints);\n\n    assert!(m.next_field().await.unwrap().is_some());\n    assert!(m.next_field().await.unwrap().is_some());\n    assert!(m.next_field().await.unwrap().is_none());\n}\n\n#[tokio::test]\nasync fn test_multipart_constraint_size_limit_for_field() {\n    let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_file_field\\\"; filename=\\\"a-text-file.txt\\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nHello world\\nHello\\r\\nWorld\\rAgain\\r\\n--X-BOUNDARY--\\r\\n\";\n    let stream = str_stream(data);\n\n    let constraints = Constraints::new()\n        .allowed_fields(vec![\"my_text_field\", \"my_file_field\"])\n        .size_limit(\n            SizeLimit::new()\n                .whole_stream(248)\n                .per_field(100)\n                .for_field(\"my_text_field\", 4)\n                .for_field(\"my_file_field\", 30),\n        );\n\n    let mut m = Multipart::with_constraints(stream, \"X-BOUNDARY\", constraints);\n\n    assert_eq!(\n        m.next_field().await.unwrap().unwrap().text().await.unwrap(),\n        \"abcd\".to_owned()\n    );\n    assert_eq!(\n        m.next_field().await.unwrap().unwrap().text().await.unwrap(),\n        \"Hello world\\nHello\\r\\nWorld\\rAgain\".to_owned()\n    );\n}\n\n#[tokio::test]\n#[should_panic]\nasync fn test_multipart_constraint_size_limit_for_field_size_exceeded() {\n    let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_file_field\\\"; filename=\\\"a-text-file.txt\\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nHello world\\nHello\\r\\nWorld\\rAgain\\r\\n--X-BOUNDARY--\\r\\n\";\n    let stream = str_stream(data);\n\n    let constraints = Constraints::new()\n        .allowed_fields(vec![\"my_text_field\", \"my_file_field\"])\n        .size_limit(\n            SizeLimit::new()\n                .whole_stream(248)\n                .per_field(100)\n                .for_field(\"my_text_field\", 4)\n                .for_field(\"my_file_field\", 10),\n        );\n\n    let mut m = Multipart::with_constraints(stream, \"X-BOUNDARY\", constraints);\n\n    assert!(m.next_field().await.unwrap().is_some());\n    assert!(m.next_field().await.unwrap().is_some());\n    assert!(m.next_field().await.unwrap().is_none());\n}\n\n#[tokio::test]\nasync fn test_multiaccess_caught() {\n    let data = \"--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_text_field\\\"\\r\\n\\r\\nabcd\\r\\n--X-BOUNDARY\\r\\nContent-Disposition: form-data; name=\\\"my_file_field\\\"; filename=\\\"a-text-file.txt\\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\nHello world\\nHello\\r\\nWorld\\rAgain\\r\\n--X-BOUNDARY--\\r\\n\";\n    let stream = str_stream(data);\n    let mut m = Multipart::new(stream, \"X-BOUNDARY\");\n\n    let field1 = m.next_field().await;\n    let field2 = m.next_field().await;\n\n    assert!(matches!(field2.unwrap_err(), multer::Error::LockFailure));\n    assert!(field1.is_ok());\n}\n"
  },
  {
    "path": "tusk.yml",
    "content": "options:\n  version:\n    usage: The next release version\n    short: v\n    required: true\ntasks:\n  setup:\n    run:\n      - command: cargo install cargo-watch\n      - command: cargo install releez\n  check:dev:\n    run:\n      - command: cargo watch --watch ./src -x 'check --features=\"all\"'\n  doc:dev:\n    run:\n      - command: cargo doc --open --features=\"all\"\n      - command: cargo watch -x 'doc --features=\"all\"'\n  test:dev:\n    run:\n      - command: cargo watch -x 'test --features=\"all\"'\n  release:\n    run:\n      - command: releez \"${version}\"\n"
  }
]