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;