Skip to content

Commit 4f55cbe

Browse files
authored
Revert "Merge pull request #10069 from Turbo87/publish-stream" (#10102)
This reverts commit 76cc6c4, reversing changes made to cf4b176.
1 parent a27a914 commit 4f55cbe

File tree

2 files changed

+50
-90
lines changed

2 files changed

+50
-90
lines changed

src/controllers/krate/publish.rs

Lines changed: 44 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::auth::AuthCheck;
55
use crate::worker::jobs::{
66
self, CheckTyposquat, SendPublishNotificationsJob, UpdateDefaultVersion,
77
};
8-
use axum::body::{Body, Bytes};
8+
use axum::body::Bytes;
99
use axum::Json;
1010
use cargo_manifest::{Dependency, DepsSet, TargetDepsSet};
1111
use chrono::{DateTime, SecondsFormat, Utc};
@@ -17,12 +17,10 @@ use diesel_async::scoped_futures::ScopedFutureExt;
1717
use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl};
1818
use futures_util::TryStreamExt;
1919
use hex::ToHex;
20-
use http::request::Parts;
2120
use http::StatusCode;
21+
use hyper::body::Buf;
2222
use sha2::{Digest, Sha256};
2323
use std::collections::HashMap;
24-
use tokio::io::{AsyncRead, AsyncReadExt};
25-
use tokio_util::io::StreamReader;
2624
use url::Url;
2725

2826
use crate::models::{
@@ -37,6 +35,7 @@ use crate::rate_limiter::LimitedAction;
3735
use crate::schema::*;
3836
use crate::sql::canon_crate_name;
3937
use crate::util::errors::{bad_request, custom, internal, AppResult, BoxedAppError};
38+
use crate::util::BytesRequest;
4039
use crate::views::{
4140
EncodableCrate, EncodableCrateDependency, GoodCrate, PublishMetadata, PublishWarnings,
4241
};
@@ -51,20 +50,12 @@ const MAX_DESCRIPTION_LENGTH: usize = 1000;
5150
/// Handles the `PUT /crates/new` route.
5251
/// Used by `cargo publish` to publish a new crate or to publish a new version of an
5352
/// existing crate.
54-
pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<GoodCrate>> {
55-
let stream = body.into_data_stream();
56-
let stream = stream.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err));
57-
let mut reader = StreamReader::new(stream);
53+
pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCrate>> {
54+
let (req, bytes) = req.0.into_parts();
55+
let (json_bytes, tarball_bytes) = split_body(bytes)?;
5856

59-
// The format of the req.body() of a publish request is as follows:
60-
//
61-
// metadata length
62-
// metadata in JSON about the crate being published
63-
// .crate tarball length
64-
// .crate tarball file
65-
66-
const MAX_JSON_LENGTH: u32 = 1024 * 1024; // 1 MB
67-
let metadata = read_json_metadata(&mut reader, MAX_JSON_LENGTH).await?;
57+
let metadata: PublishMetadata = serde_json::from_slice(&json_bytes)
58+
.map_err(|e| bad_request(format_args!("invalid upload request: {e}")))?;
6859

6960
Crate::validate_crate_name("crate", &metadata.name).map_err(bad_request)?;
7061

@@ -141,13 +132,19 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
141132
.check_rate_limit(auth.user().id, rate_limit_action, &mut conn)
142133
.await?;
143134

135+
let content_length = tarball_bytes.len() as u64;
136+
144137
let max_upload_size = existing_crate
145138
.as_ref()
146139
.and_then(|c| c.max_upload_size())
147140
.unwrap_or(app.config.max_upload_size);
148141

149-
let tarball_bytes = read_tarball_bytes(&mut reader, max_upload_size).await?;
150-
let content_length = tarball_bytes.len() as u64;
142+
if content_length > max_upload_size as u64 {
143+
return Err(custom(
144+
StatusCode::PAYLOAD_TOO_LARGE,
145+
format!("max upload size is: {max_upload_size}"),
146+
));
147+
}
151148

152149
let pkg_name = format!("{}-{}", &*metadata.name, &version_string);
153150
let max_unpack_size = std::cmp::max(app.config.max_unpack_size, max_upload_size as u64);
@@ -572,66 +569,43 @@ async fn count_versions_published_today(
572569
}
573570

574571
#[instrument(skip_all)]
575-
async fn read_json_metadata<R: AsyncRead + Unpin>(
576-
reader: &mut R,
577-
max_length: u32,
578-
) -> Result<PublishMetadata, BoxedAppError> {
579-
let json_len = reader.read_u32_le().await.map_err(|e| {
580-
if e.kind() == std::io::ErrorKind::UnexpectedEof {
581-
bad_request("invalid metadata length")
582-
} else {
583-
e.into()
584-
}
585-
})?;
572+
fn split_body(mut bytes: Bytes) -> AppResult<(Bytes, Bytes)> {
573+
// The format of the req.body() of a publish request is as follows:
574+
//
575+
// metadata length
576+
// metadata in JSON about the crate being published
577+
// .crate tarball length
578+
// .crate tarball file
586579

587-
if json_len > max_length {
588-
let message = "JSON metadata blob too large";
589-
return Err(custom(StatusCode::PAYLOAD_TOO_LARGE, message));
580+
if bytes.len() < 4 {
581+
// Avoid panic in `get_u32_le()` if there is not enough remaining data
582+
return Err(bad_request("invalid metadata length"));
590583
}
591584

592-
let mut json_bytes = vec![0; json_len as usize];
593-
reader.read_exact(&mut json_bytes).await.map_err(|e| {
594-
if e.kind() == std::io::ErrorKind::UnexpectedEof {
595-
let message = format!("invalid metadata length for remaining payload: {json_len}");
596-
bad_request(message)
597-
} else {
598-
e.into()
599-
}
600-
})?;
585+
let json_len = bytes.get_u32_le() as usize;
586+
if json_len > bytes.len() {
587+
return Err(bad_request(format!(
588+
"invalid metadata length for remaining payload: {json_len}"
589+
)));
590+
}
601591

602-
serde_json::from_slice(&json_bytes)
603-
.map_err(|e| bad_request(format_args!("invalid upload request: {e}")))
604-
}
592+
let json_bytes = bytes.split_to(json_len);
605593

606-
#[instrument(skip_all)]
607-
async fn read_tarball_bytes<R: AsyncRead + Unpin>(
608-
reader: &mut R,
609-
max_length: u32,
610-
) -> Result<Bytes, BoxedAppError> {
611-
let tarball_len = reader.read_u32_le().await.map_err(|e| {
612-
if e.kind() == std::io::ErrorKind::UnexpectedEof {
613-
bad_request("invalid tarball length")
614-
} else {
615-
e.into()
616-
}
617-
})?;
594+
if bytes.len() < 4 {
595+
// Avoid panic in `get_u32_le()` if there is not enough remaining data
596+
return Err(bad_request("invalid tarball length"));
597+
}
618598

619-
if tarball_len > max_length {
620-
let message = format!("max upload size is: {}", max_length);
621-
return Err(custom(StatusCode::PAYLOAD_TOO_LARGE, message));
599+
let tarball_len = bytes.get_u32_le() as usize;
600+
if tarball_len > bytes.len() {
601+
return Err(bad_request(format!(
602+
"invalid tarball length for remaining payload: {tarball_len}"
603+
)));
622604
}
623605

624-
let mut tarball_bytes = vec![0; tarball_len as usize];
625-
reader.read_exact(&mut tarball_bytes).await.map_err(|e| {
626-
if e.kind() == std::io::ErrorKind::UnexpectedEof {
627-
let message = format!("invalid tarball length for remaining payload: {tarball_len}");
628-
bad_request(message)
629-
} else {
630-
e.into()
631-
}
632-
})?;
606+
let tarball_bytes = bytes.split_to(tarball_len);
633607

634-
Ok(Bytes::from(tarball_bytes))
608+
Ok((json_bytes, tarball_bytes))
635609
}
636610

637611
#[instrument(skip_all)]

src/tests/krate/publish/tarball.rs

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crate::tests::builders::PublishBuilder;
22
use crate::tests::util::{RequestHelper, TestApp};
3-
use bytes::{BufMut, BytesMut};
43
use crates_io_tarball::TarballBuilder;
54
use googletest::prelude::*;
65
use http::StatusCode;
@@ -81,16 +80,9 @@ async fn json_bytes_truncated() {
8180
async fn tarball_len_truncated() {
8281
let (app, _, _, token) = TestApp::full().with_token().await;
8382

84-
let json = br#"{ "name": "foo", "vers": "1.0.0" }"#;
85-
86-
let mut bytes = BytesMut::new();
87-
bytes.put_u32_le(json.len() as u32);
88-
bytes.put_slice(json);
89-
bytes.put_u8(0);
90-
bytes.put_u8(0);
91-
92-
let response = token.publish_crate(bytes.freeze()).await;
93-
83+
let response = token
84+
.publish_crate(&[2, 0, 0, 0, b'{', b'}', 0, 0] as &[u8])
85+
.await;
9486
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
9587
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"invalid tarball length"}]}"#);
9688
assert_that!(app.stored_files().await, empty());
@@ -100,15 +92,9 @@ async fn tarball_len_truncated() {
10092
async fn tarball_bytes_truncated() {
10193
let (app, _, _, token) = TestApp::full().with_token().await;
10294

103-
let json = br#"{ "name": "foo", "vers": "1.0.0" }"#;
104-
105-
let mut bytes = BytesMut::new();
106-
bytes.put_u32_le(json.len() as u32);
107-
bytes.put_slice(json);
108-
bytes.put_u32_le(100);
109-
bytes.put_u8(0);
110-
111-
let response = token.publish_crate(bytes.freeze()).await;
95+
let response = token
96+
.publish_crate(&[2, 0, 0, 0, b'{', b'}', 100, 0, 0, 0, 0] as &[u8])
97+
.await;
11298
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
11399
assert_snapshot!(response.text(), @r#"{"errors":[{"detail":"invalid tarball length for remaining payload: 100"}]}"#);
114100
assert_that!(app.stored_files().await, empty());

0 commit comments

Comments
 (0)