mirror of
https://github.com/tokio-rs/axum.git
synced 2024-11-22 15:17:18 +01:00
Fix vulnerability in example-stream-to-file example (#1040)
* Fix vulnerability in example-stream-to-file example * Save files to separate directory
This commit is contained in:
parent
7d88bd3a66
commit
be71e7b286
1 changed files with 43 additions and 16 deletions
|
@ -18,6 +18,8 @@ use tokio::{fs::File, io::BufWriter};
|
|||
use tokio_util::io::StreamReader;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
const UPLOADS_DIRECTORY: &str = "uploads";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::registry()
|
||||
|
@ -27,6 +29,11 @@ async fn main() {
|
|||
.with(tracing_subscriber::fmt::layer())
|
||||
.init();
|
||||
|
||||
// save files to a separte directory to not override files in the current directory
|
||||
tokio::fs::create_dir(UPLOADS_DIRECTORY)
|
||||
.await
|
||||
.expect("failed to create `uploads` directory");
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(show_form).post(accept_form))
|
||||
.route("/file/:file_name", post(save_request_body));
|
||||
|
@ -46,9 +53,7 @@ async fn save_request_body(
|
|||
Path(file_name): Path<String>,
|
||||
body: BodyStream,
|
||||
) -> Result<(), (StatusCode, String)> {
|
||||
stream_to_file(&file_name, body)
|
||||
.await
|
||||
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))
|
||||
stream_to_file(&file_name, body).await
|
||||
}
|
||||
|
||||
// Handler that returns HTML for a multipart form.
|
||||
|
@ -88,30 +93,52 @@ async fn accept_form(mut multipart: Multipart) -> Result<Redirect, (StatusCode,
|
|||
continue;
|
||||
};
|
||||
|
||||
stream_to_file(&file_name, field)
|
||||
.await
|
||||
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
|
||||
stream_to_file(&file_name, field).await?;
|
||||
}
|
||||
|
||||
Ok(Redirect::to("/"))
|
||||
}
|
||||
|
||||
// Save a `Stream` to a file
|
||||
async fn stream_to_file<S, E>(path: &str, stream: S) -> Result<(), io::Error>
|
||||
async fn stream_to_file<S, E>(path: &str, stream: S) -> Result<(), (StatusCode, String)>
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, E>>,
|
||||
E: Into<BoxError>,
|
||||
{
|
||||
// Convert the stream into an `AsyncRead`.
|
||||
let body_with_io_error = stream.map_err(|err| io::Error::new(io::ErrorKind::Other, err));
|
||||
let body_reader = StreamReader::new(body_with_io_error);
|
||||
futures::pin_mut!(body_reader);
|
||||
if !path_is_valid(path) {
|
||||
return Err((StatusCode::BAD_REQUEST, "Invalid path".to_owned()));
|
||||
}
|
||||
|
||||
// Create the file. `File` implements `AsyncWrite`.
|
||||
let mut file = BufWriter::new(File::create(path).await?);
|
||||
async {
|
||||
// Convert the stream into an `AsyncRead`.
|
||||
let body_with_io_error = stream.map_err(|err| io::Error::new(io::ErrorKind::Other, err));
|
||||
let body_reader = StreamReader::new(body_with_io_error);
|
||||
futures::pin_mut!(body_reader);
|
||||
|
||||
// Copy the body into the file.
|
||||
tokio::io::copy(&mut body_reader, &mut file).await?;
|
||||
// Create the file. `File` implements `AsyncWrite`.
|
||||
let path = std::path::Path::new(UPLOADS_DIRECTORY).join(path);
|
||||
let mut file = BufWriter::new(File::create(path).await?);
|
||||
|
||||
Ok(())
|
||||
// Copy the body into the file.
|
||||
tokio::io::copy(&mut body_reader, &mut file).await?;
|
||||
|
||||
Ok::<_, io::Error>(())
|
||||
}
|
||||
.await
|
||||
.map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))
|
||||
}
|
||||
|
||||
// to prevent directory traversal attacks we ensure the path conists of exactly one normal
|
||||
// component
|
||||
fn path_is_valid(path: &str) -> bool {
|
||||
let path = std::path::Path::new(&*path);
|
||||
let mut components = path.components().peekable();
|
||||
|
||||
if let Some(first) = components.peek() {
|
||||
if !matches!(first, std::path::Component::Normal(_)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
components.count() == 1
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue