@@ -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:: { AsyncRead , AsyncReadExt } ;
25+ use tokio_util:: io:: StreamReader ;
2426use url:: Url ;
2527
2628use crate :: models:: {
@@ -35,7 +37,6 @@ 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 ;
3940use crate :: views:: {
4041 EncodableCrate , EncodableCrateDependency , GoodCrate , PublishMetadata , PublishWarnings ,
4142} ;
@@ -50,12 +51,20 @@ const MAX_DESCRIPTION_LENGTH: usize = 1000;
5051/// Handles the `PUT /crates/new` route.
5152/// Used by `cargo publish` to publish a new crate or to publish a new version of an
5253/// existing crate.
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) ?;
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) ;
5658
57- let metadata: PublishMetadata = serde_json:: from_slice ( & json_bytes)
58- . map_err ( |e| bad_request ( format_args ! ( "invalid upload request: {e}" ) ) ) ?;
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 ?;
5968
6069 Crate :: validate_crate_name ( "crate" , & metadata. name ) . map_err ( bad_request) ?;
6170
@@ -132,19 +141,13 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
132141 . check_rate_limit ( auth. user ( ) . id , rate_limit_action, & mut conn)
133142 . await ?;
134143
135- let content_length = tarball_bytes. len ( ) as u64 ;
136-
137144 let max_upload_size = existing_crate
138145 . as_ref ( )
139146 . and_then ( |c| c. max_upload_size ( ) )
140147 . unwrap_or ( app. config . max_upload_size ) ;
141148
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- }
149+ let tarball_bytes = read_tarball_bytes ( & mut reader, max_upload_size) . await ?;
150+ let content_length = tarball_bytes. len ( ) as u64 ;
148151
149152 let pkg_name = format ! ( "{}-{}" , & * metadata. name, & version_string) ;
150153 let max_unpack_size = std:: cmp:: max ( app. config . max_unpack_size , max_upload_size as u64 ) ;
@@ -569,43 +572,66 @@ async fn count_versions_published_today(
569572}
570573
571574#[ instrument( skip_all) ]
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
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+ } ) ?;
579586
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" ) ) ;
587+ if json_len > max_length {
588+ let message = "JSON metadata blob too large" ;
589+ return Err ( custom ( StatusCode :: PAYLOAD_TOO_LARGE , message ) ) ;
583590 }
584591
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- }
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+ } ) ?;
591601
592- let json_bytes = bytes. split_to ( json_len) ;
602+ serde_json:: from_slice ( & json_bytes)
603+ . map_err ( |e| bad_request ( format_args ! ( "invalid upload request: {e}" ) ) )
604+ }
593605
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- }
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+ } ) ?;
598618
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- ) ) ) ;
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) ) ;
604622 }
605623
606- let tarball_bytes = bytes. split_to ( tarball_len) ;
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+ } ) ?;
607633
608- Ok ( ( json_bytes , tarball_bytes) )
634+ Ok ( Bytes :: from ( tarball_bytes) )
609635}
610636
611637#[ instrument( skip_all) ]
0 commit comments