Skip to content

Commit 8e3d6a5

Browse files
authored
Merge pull request #24 from nuclearcat/fix-multipart-ordering-requirement
Fix multipart ordering requirement
2 parents c51e1b5 + c0987ed commit 8e3d6a5

1 file changed

Lines changed: 171 additions & 2 deletions

File tree

src/main.rs

Lines changed: 171 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use headers::HeaderMap;
3333
use std::path;
3434
use std::sync::OnceLock;
3535
use std::{net::SocketAddr, path::PathBuf};
36-
use tokio::io::AsyncSeekExt;
36+
use tokio::io::{AsyncSeekExt, AsyncWriteExt};
3737

3838
use std::{collections::HashMap, sync::Arc};
3939
use std::pin::Pin;
@@ -558,6 +558,7 @@ async fn ax_post_file(
558558
let mut path: String = "".to_string();
559559
let mut file0_filename: String = "".to_string();
560560
let mut upload_result: Option<(StatusCode, Vec<u8>)> = None;
561+
let mut buffered_file: Option<tempfile::NamedTempFile> = None;
561562

562563
while let Some(field) = multipart.next_field().await.unwrap() {
563564
let name = field.name().unwrap().to_string();
@@ -580,7 +581,75 @@ async fn ax_post_file(
580581
Some(fname) => {
581582
file0_filename = fname.to_string();
582583

583-
// At this point we have path and filename, so we can verify permissions and start streaming
584+
if path.is_empty() {
585+
// BUFFERED PATH: file0 arrived before path, buffer to temp file
586+
debug_log!("file0 arrived before path, buffering to temp file");
587+
let tmp = match tempfile::NamedTempFile::new() {
588+
Ok(f) => f,
589+
Err(e) => {
590+
eprintln!("Failed to create temp file: {:?}", e);
591+
upload_result = Some((
592+
StatusCode::INTERNAL_SERVER_ERROR,
593+
"Failed to create temp file".to_string().into_bytes(),
594+
));
595+
break;
596+
}
597+
};
598+
let tmp_path = tmp.path().to_path_buf();
599+
let mut async_file = match tokio::fs::File::create(&tmp_path).await {
600+
Ok(f) => f,
601+
Err(e) => {
602+
eprintln!("Failed to open temp file for writing: {:?}", e);
603+
upload_result = Some((
604+
StatusCode::INTERNAL_SERVER_ERROR,
605+
"Failed to open temp file".to_string().into_bytes(),
606+
));
607+
break;
608+
}
609+
};
610+
let mut field_stream = FieldStream::new(field);
611+
let mut buf = [0u8; 64 * 1024];
612+
loop {
613+
use tokio::io::AsyncReadExt;
614+
let n = match field_stream.read(&mut buf).await {
615+
Ok(0) => break,
616+
Ok(n) => n,
617+
Err(e) => {
618+
eprintln!("Error reading file0 field: {:?}", e);
619+
upload_result = Some((
620+
StatusCode::BAD_REQUEST,
621+
"Error reading file data".to_string().into_bytes(),
622+
));
623+
break;
624+
}
625+
};
626+
if let Err(e) = async_file.write_all(&buf[..n]).await {
627+
eprintln!("Error writing temp file: {:?}", e);
628+
upload_result = Some((
629+
StatusCode::INTERNAL_SERVER_ERROR,
630+
"Error writing temp file".to_string().into_bytes(),
631+
));
632+
break;
633+
}
634+
}
635+
if upload_result.is_some() {
636+
break;
637+
}
638+
if let Err(e) = async_file.flush().await {
639+
eprintln!("Error flushing temp file: {:?}", e);
640+
upload_result = Some((
641+
StatusCode::INTERNAL_SERVER_ERROR,
642+
"Error flushing temp file".to_string().into_bytes(),
643+
));
644+
break;
645+
}
646+
drop(async_file);
647+
buffered_file = Some(tmp);
648+
// Continue loop to find path field
649+
continue;
650+
}
651+
652+
// FAST PATH: path already set, stream directly
584653
// if path ends on /, remove it
585654
if path.ends_with("/") {
586655
debug_log!("Removing trailing /, workaround");
@@ -695,6 +764,106 @@ async fn ax_post_file(
695764
}
696765
}
697766

767+
// Handle buffered file0 case (file0 arrived before path)
768+
if upload_result.is_none() {
769+
if let Some(tmp) = buffered_file {
770+
if path.is_empty() {
771+
return (
772+
StatusCode::BAD_REQUEST,
773+
b"Missing path field in upload".to_vec(),
774+
);
775+
}
776+
777+
if path.ends_with("/") {
778+
debug_log!("Removing trailing /, workaround");
779+
path.pop();
780+
}
781+
782+
let full_path = format!("{}/{}", path, file0_filename);
783+
784+
match verify_upload_permissions(&owner, &path) {
785+
Ok(_) => (),
786+
Err(e) => {
787+
return (StatusCode::FORBIDDEN, e.to_string().into_bytes());
788+
}
789+
}
790+
791+
let hdr_content_type = headers.get("Content-Type-Upstream");
792+
let semaphore = get_or_create_semaphore(&state.file_locks, &full_path).await;
793+
794+
let _permit = match semaphore.try_acquire() {
795+
Ok(permit) => permit,
796+
Err(_) => {
797+
return (
798+
StatusCode::CONFLICT,
799+
"Upload already in progress".to_string().into_bytes(),
800+
);
801+
}
802+
};
803+
804+
let content_type: String = match hdr_content_type {
805+
Some(content_type) => content_type.to_str().unwrap().to_string(),
806+
None => {
807+
let heuristic_ctype = heuristic_filetype(file0_filename.clone());
808+
debug_log!(
809+
"Content-Type not found, using heuristics: {}",
810+
heuristic_ctype
811+
);
812+
heuristic_ctype
813+
}
814+
};
815+
816+
debug_log!("Starting buffered upload for {}", full_path);
817+
let mut async_file = match tokio::fs::File::open(tmp.path()).await {
818+
Ok(f) => f,
819+
Err(e) => {
820+
eprintln!("Failed to reopen temp file: {:?}", e);
821+
return (
822+
StatusCode::INTERNAL_SERVER_ERROR,
823+
"Failed to reopen temp file".to_string().into_bytes(),
824+
);
825+
}
826+
};
827+
828+
let driver_name = get_driver_type();
829+
let driver = init_driver(&driver_name);
830+
831+
let (result, file_size) = driver
832+
.write_file_streaming(
833+
full_path.clone(),
834+
&mut async_file,
835+
content_type.to_string(),
836+
Some(owner.clone()),
837+
)
838+
.await;
839+
840+
// tmp (NamedTempFile) is dropped here, auto-deleting the temp file
841+
842+
if result.is_empty() {
843+
return (StatusCode::CONFLICT, Vec::new());
844+
}
845+
846+
let status = StatusCode::OK;
847+
let client_ip = client_ip_from_headers(&headers, remote_addr);
848+
let timestamp = std::time::SystemTime::now();
849+
let human_time = chrono::DateTime::<chrono::Utc>::from(timestamp);
850+
let request_target = original_uri.to_string();
851+
println!(
852+
"{} {} {} {} {} {} {} {} buggyclient",
853+
client_ip,
854+
status.as_u16(),
855+
file_size,
856+
human_time,
857+
Method::POST,
858+
request_target,
859+
full_path,
860+
owner
861+
);
862+
863+
return (status, Vec::new());
864+
}
865+
}
866+
698867
// Return the upload result if we processed the file
699868
if let Some(result) = upload_result {
700869
return result;

0 commit comments

Comments
 (0)