@@ -5,7 +5,7 @@ use crate::auth::AuthCheck;
55use crate :: worker:: jobs:: {
66 self , CheckTyposquat , SendPublishNotificationsJob , UpdateDefaultVersion ,
77} ;
8- use axum:: body:: Bytes ;
8+ use axum:: body:: { Body , Bytes } ;
99use axum:: Json ;
1010use cargo_manifest:: { Dependency , DepsSet , TargetDepsSet } ;
1111use chrono:: { DateTime , SecondsFormat , Utc } ;
@@ -17,10 +17,12 @@ use diesel_async::scoped_futures::ScopedFutureExt;
1717use diesel_async:: { AsyncConnection , AsyncPgConnection , RunQueryDsl } ;
1818use futures_util:: TryStreamExt ;
1919use hex:: ToHex ;
20+ use http:: request:: Parts ;
2021use http:: StatusCode ;
21- use hyper:: body:: Buf ;
2222use sha2:: { Digest , Sha256 } ;
2323use std:: collections:: HashMap ;
24+ use tokio:: io:: AsyncReadExt ;
25+ use tokio_util:: io:: StreamReader ;
2426use url:: Url ;
2527
2628use crate :: models:: {
@@ -35,7 +37,7 @@ use crate::rate_limiter::LimitedAction;
3537use crate :: schema:: * ;
3638use crate :: sql:: canon_crate_name;
3739use crate :: util:: errors:: { bad_request, custom, internal, AppResult , BoxedAppError } ;
38- use crate :: util:: { BytesRequest , Maximums } ;
40+ use crate :: util:: Maximums ;
3941use crate :: views:: {
4042 EncodableCrate , EncodableCrateDependency , GoodCrate , PublishMetadata , PublishWarnings ,
4143} ;
@@ -54,13 +56,50 @@ const MAX_DESCRIPTION_LENGTH: usize = 1000;
5456/// Currently blocks the HTTP thread, perhaps some function calls can spawn new
5557/// threads and return completion or error through other methods a `cargo publish
5658/// --status` command, via crates.io's front end, or email.
57- pub async fn publish ( app : AppState , req : BytesRequest ) -> AppResult < Json < GoodCrate > > {
58- let ( req, bytes) = req. 0 . into_parts ( ) ;
59- let ( json_bytes, tarball_bytes) = split_body ( bytes) ?;
59+ pub async fn publish ( app : AppState , req : Parts , body : Body ) -> AppResult < Json < GoodCrate > > {
60+ let stream = body. into_data_stream ( ) ;
61+ let stream = stream. map_err ( |err| std:: io:: Error :: new ( std:: io:: ErrorKind :: Other , err) ) ;
62+ let mut reader = StreamReader :: new ( stream) ;
63+
64+ // The format of the req.body() of a publish request is as follows:
65+ //
66+ // metadata length
67+ // metadata in JSON about the crate being published
68+ // .crate tarball length
69+ // .crate tarball file
70+
71+ const MAX_JSON_LENGTH : u32 = 1024 * 1024 ; // 1 MB
72+
73+ let json_len = reader. read_u32_le ( ) . await . map_err ( |e| {
74+ if e. kind ( ) == std:: io:: ErrorKind :: UnexpectedEof {
75+ bad_request ( "invalid metadata length" )
76+ } else {
77+ e. into ( )
78+ }
79+ } ) ?;
80+
81+ if json_len > MAX_JSON_LENGTH {
82+ return Err ( custom (
83+ StatusCode :: PAYLOAD_TOO_LARGE ,
84+ "JSON metadata blob too large" ,
85+ ) ) ;
86+ }
87+
88+ let mut json_bytes = vec ! [ 0 ; json_len as usize ] ;
89+ reader. read_exact ( & mut json_bytes) . await . map_err ( |e| {
90+ if e. kind ( ) == std:: io:: ErrorKind :: UnexpectedEof {
91+ let message = format ! ( "invalid metadata length for remaining payload: {json_len}" ) ;
92+ bad_request ( message)
93+ } else {
94+ e. into ( )
95+ }
96+ } ) ?;
6097
6198 let metadata: PublishMetadata = serde_json:: from_slice ( & json_bytes)
6299 . map_err ( |e| bad_request ( format_args ! ( "invalid upload request: {e}" ) ) ) ?;
63100
101+ drop ( json_bytes) ;
102+
64103 Crate :: validate_crate_name ( "crate" , & metadata. name ) . map_err ( bad_request) ?;
65104
66105 let semver = match semver:: Version :: parse ( & metadata. vers ) {
@@ -136,7 +175,14 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
136175 . check_rate_limit ( auth. user ( ) . id , rate_limit_action, & mut conn)
137176 . await ?;
138177
139- let content_length = tarball_bytes. len ( ) as u64 ;
178+ let tarball_len = reader. read_u32_le ( ) . await . map_err ( |e| {
179+ if e. kind ( ) == std:: io:: ErrorKind :: UnexpectedEof {
180+ bad_request ( "invalid tarball length" )
181+ } else {
182+ e. into ( )
183+ }
184+ } ) ?;
185+ let content_length = tarball_len as u64 ;
140186
141187 let maximums = Maximums :: new (
142188 existing_crate. as_ref ( ) . and_then ( |c| c. max_upload_size ) ,
@@ -151,6 +197,18 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
151197 ) ) ;
152198 }
153199
200+ let mut tarball_bytes = vec ! [ 0 ; tarball_len as usize ] ;
201+ reader. read_exact ( & mut tarball_bytes) . await . map_err ( |e| {
202+ if e. kind ( ) == std:: io:: ErrorKind :: UnexpectedEof {
203+ let message = format ! ( "invalid tarball length for remaining payload: {tarball_len}" ) ;
204+ bad_request ( message)
205+ } else {
206+ e. into ( )
207+ }
208+ } ) ?;
209+
210+ let tarball_bytes = Bytes :: from ( tarball_bytes) ;
211+
154212 let pkg_name = format ! ( "{}-{}" , & * metadata. name, & version_string) ;
155213 let tarball_info =
156214 process_tarball ( & pkg_name, & * tarball_bytes, maximums. max_unpack_size ) . await ?;
@@ -573,46 +631,6 @@ async fn count_versions_published_today(
573631 . await
574632}
575633
576- #[ instrument( skip_all) ]
577- fn split_body ( mut bytes : Bytes ) -> AppResult < ( Bytes , Bytes ) > {
578- // The format of the req.body() of a publish request is as follows:
579- //
580- // metadata length
581- // metadata in JSON about the crate being published
582- // .crate tarball length
583- // .crate tarball file
584-
585- if bytes. len ( ) < 4 {
586- // Avoid panic in `get_u32_le()` if there is not enough remaining data
587- return Err ( bad_request ( "invalid metadata length" ) ) ;
588- }
589-
590- let json_len = bytes. get_u32_le ( ) as usize ;
591- if json_len > bytes. len ( ) {
592- return Err ( bad_request ( format ! (
593- "invalid metadata length for remaining payload: {json_len}"
594- ) ) ) ;
595- }
596-
597- let json_bytes = bytes. split_to ( json_len) ;
598-
599- if bytes. len ( ) < 4 {
600- // Avoid panic in `get_u32_le()` if there is not enough remaining data
601- return Err ( bad_request ( "invalid tarball length" ) ) ;
602- }
603-
604- let tarball_len = bytes. get_u32_le ( ) as usize ;
605- if tarball_len > bytes. len ( ) {
606- return Err ( bad_request ( format ! (
607- "invalid tarball length for remaining payload: {tarball_len}"
608- ) ) ) ;
609- }
610-
611- let tarball_bytes = bytes. split_to ( tarball_len) ;
612-
613- Ok ( ( json_bytes, tarball_bytes) )
614- }
615-
616634async fn is_reserved_name ( name : & str , conn : & mut AsyncPgConnection ) -> QueryResult < bool > {
617635 select ( exists ( reserved_crate_names:: table. filter (
618636 canon_crate_name ( reserved_crate_names:: name) . eq ( canon_crate_name ( name) ) ,
0 commit comments