form_data/
lib.rs

1//! form-data implemented [rfc7578]
2//!
3//! # Example
4//!
5//! ```no_run
6//! #![deny(warnings)]
7//!
8//! use std::{env, net::SocketAddr};
9//!
10//! use anyhow::Result;
11//! use async_fs::File;
12//! use bytes::Bytes;
13//! use futures_util::{
14//!     io::{copy, AsyncWriteExt},
15//!     stream::TryStreamExt,
16//! };
17//! use http_body_util::Full;
18//! use hyper::{body::Incoming, header, server::conn::http1, service::service_fn, Request, Response};
19//! use hyper_util::rt::TokioIo;
20//! use tempfile::tempdir;
21//! use tokio::net::TcpListener;
22//!
23//! use form_data::{Error, FormData};
24//!
25//! #[path = "../tests/lib/mod.rs"]
26//! mod lib;
27//!
28//! use lib::IncomingBody;
29//!
30//! async fn hello(size: usize, req: Request<Incoming>) -> Result<Response<Full<Bytes>>, Error> {
31//!     let dir = tempdir()?;
32//!     let mut txt = String::new();
33//!
34//!     txt.push_str(&dir.path().to_string_lossy());
35//!     txt.push_str("\r\n");
36//!
37//!     let m = req
38//!         .headers()
39//!         .get(header::CONTENT_TYPE)
40//!         .and_then(|val| val.to_str().ok())
41//!         .and_then(|val| val.parse::<mime::Mime>().ok())
42//!         .ok_or(Error::InvalidHeader)?;
43//!
44//!     let mut form = FormData::new(
45//!         req.map(|body| IncomingBody::new(Some(body))).into_body(),
46//!         m.get_param(mime::BOUNDARY).unwrap().as_str(),
47//!     );
48//!
49//!     // 512KB for hyper lager buffer
50//!     form.set_max_buf_size(size)?;
51//!
52//!     while let Some(mut field) = form.try_next().await? {
53//!         let name = field.name.to_owned();
54//!         let mut bytes: u64 = 0;
55//!
56//!         assert_eq!(bytes as usize, field.length);
57//!
58//!         if let Some(filename) = &field.filename {
59//!             let filepath = dir.path().join(filename);
60//!
61//!             match filepath.extension().and_then(|s| s.to_str()) {
62//!                 Some("txt") => {
63//!                     // buffer <= 8KB
64//!                     let mut writer = File::create(&filepath).await?;
65//!                     bytes = copy(&mut field, &mut writer).await?;
66//!                     writer.close().await?;
67//!                 }
68//!                 Some("iso") => {
69//!                     field.ignore().await?;
70//!                 }
71//!                 _ => {
72//!                     // 8KB <= buffer <= 512KB
73//!                     // let mut writer = File::create(&filepath).await?;
74//!                     // bytes = field.copy_to(&mut writer).await?;
75//!
76//!                     let mut writer = std::fs::File::create(&filepath)?;
77//!                     bytes = field.copy_to_file(&mut writer).await?;
78//!                 }
79//!             }
80//!
81//!             tracing::info!("file {} {}", name, bytes);
82//!             txt.push_str(&format!("file {name} {bytes}\r\n"));
83//!         } else {
84//!             let buffer = field.bytes().await?;
85//!             bytes = buffer.len() as u64;
86//!             tracing::info!("text {} {}", name, bytes);
87//!             txt.push_str(&format!("text {name} {bytes}\r\n"));
88//!         }
89//!
90//!         tracing::info!("{:?}", field);
91//!
92//!         assert_eq!(
93//!             bytes,
94//!             match name.as_str() {
95//!                 "empty" => 0,
96//!                 "tiny1" => 7,
97//!                 "tiny0" => 122,
98//!                 "small1" => 315,
99//!                 "small0" => 1_778,
100//!                 "medium" => 13_196,
101//!                 "large" => 2_413_677,
102//!                 "book" => 400_797_393,
103//!                 "crate" => 9,
104//!                 _ => bytes,
105//!             }
106//!         );
107//!     }
108//!
109//!     dir.close()?;
110//!
111//!     Ok(Response::new(Full::from(Into::<String>::into(txt))))
112//! }
113//!
114//! #[tokio::main]
115//! pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
116//!     tracing_subscriber::fmt()
117//!         // From env var: `RUST_LOG`
118//!         .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
119//!         .try_init()
120//!         .map_err(|e| anyhow::anyhow!(e))?;
121//!
122//!     let mut arg = env::args()
123//!         .find(|a| a.starts_with("--size="))
124//!         .unwrap_or_else(|| "--size=8".to_string());
125//!
126//!     // 512
127//!     // 8 * 2
128//!     // 8
129//!     let size = arg.split_off(7).parse::<usize>().unwrap_or(8) * 1024;
130//!     let addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
131//!
132//!     println!("Listening on http://{addr}");
133//!     println!("FormData max buffer size is {}KB", size / 1024);
134//!
135//!     let listener = TcpListener::bind(addr).await?;
136//!
137//!     loop {
138//!         let (stream, _) = listener.accept().await?;
139//!
140//!         tokio::task::spawn(async move {
141//!             if let Err(err) = http1::Builder::new()
142//!                 .max_buf_size(size)
143//!                 .serve_connection(
144//!                     TokioIo::new(stream),
145//!                     service_fn(|req: Request<Incoming>| hello(size, req)),
146//!                 )
147//!                 .await
148//!             {
149//!                 println!("Error serving connection: {:?}", err);
150//!             }
151//!         });
152//!     }
153//! }
154//! ```
155//!
156//! [rfc7578]: <https://tools.ietf.org/html/rfc7578>
157
158#![forbid(unsafe_code)]
159#![deny(nonstandard_style)]
160#![warn(missing_docs, unreachable_pub)]
161#![allow(clippy::missing_errors_doc)]
162
163mod error;
164pub use error::Error;
165
166mod field;
167pub use field::Field;
168
169mod form;
170pub use form::FormData;
171
172mod limits;
173pub use limits::Limits;
174
175mod state;
176pub use state::*;
177
178mod utils;
179
180pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
181
182#[cfg(all(feature = "async", not(feature = "sync")))]
183mod r#async;
184#[cfg(all(feature = "sync", not(feature = "async")))]
185mod sync;