@@ -5,7 +5,7 @@ use crate::auth::AuthCheck;
5
5
use crate :: worker:: jobs:: {
6
6
self , CheckTyposquat , SendPublishNotificationsJob , UpdateDefaultVersion ,
7
7
} ;
8
- use axum:: body:: { Body , Bytes } ;
8
+ use axum:: body:: Bytes ;
9
9
use axum:: Json ;
10
10
use cargo_manifest:: { Dependency , DepsSet , TargetDepsSet } ;
11
11
use chrono:: { DateTime , SecondsFormat , Utc } ;
@@ -17,12 +17,10 @@ use diesel_async::scoped_futures::ScopedFutureExt;
17
17
use diesel_async:: { AsyncConnection , AsyncPgConnection , RunQueryDsl } ;
18
18
use futures_util:: TryStreamExt ;
19
19
use hex:: ToHex ;
20
- use http:: request:: Parts ;
21
20
use http:: StatusCode ;
21
+ use hyper:: body:: Buf ;
22
22
use sha2:: { Digest , Sha256 } ;
23
23
use std:: collections:: HashMap ;
24
- use tokio:: io:: { AsyncRead , AsyncReadExt } ;
25
- use tokio_util:: io:: StreamReader ;
26
24
use url:: Url ;
27
25
28
26
use crate :: models:: {
@@ -37,6 +35,7 @@ use crate::rate_limiter::LimitedAction;
37
35
use crate :: schema:: * ;
38
36
use crate :: sql:: canon_crate_name;
39
37
use crate :: util:: errors:: { bad_request, custom, internal, AppResult , BoxedAppError } ;
38
+ use crate :: util:: BytesRequest ;
40
39
use crate :: views:: {
41
40
EncodableCrate , EncodableCrateDependency , GoodCrate , PublishMetadata , PublishWarnings ,
42
41
} ;
@@ -51,20 +50,12 @@ const MAX_DESCRIPTION_LENGTH: usize = 1000;
51
50
/// Handles the `PUT /crates/new` route.
52
51
/// Used by `cargo publish` to publish a new crate or to publish a new version of an
53
52
/// 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) ?;
58
56
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}" ) ) ) ?;
68
59
69
60
Crate :: validate_crate_name ( "crate" , & metadata. name ) . map_err ( bad_request) ?;
70
61
@@ -141,13 +132,19 @@ pub async fn publish(app: AppState, req: Parts, body: Body) -> AppResult<Json<Go
141
132
. check_rate_limit ( auth. user ( ) . id , rate_limit_action, & mut conn)
142
133
. await ?;
143
134
135
+ let content_length = tarball_bytes. len ( ) as u64 ;
136
+
144
137
let max_upload_size = existing_crate
145
138
. as_ref ( )
146
139
. and_then ( |c| c. max_upload_size ( ) )
147
140
. unwrap_or ( app. config . max_upload_size ) ;
148
141
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
+ }
151
148
152
149
let pkg_name = format ! ( "{}-{}" , & * metadata. name, & version_string) ;
153
150
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(
572
569
}
573
570
574
571
#[ 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
586
579
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" ) ) ;
590
583
}
591
584
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
+ }
601
591
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) ;
605
593
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
+ }
618
598
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
+ ) ) ) ;
622
604
}
623
605
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) ;
633
607
634
- Ok ( Bytes :: from ( tarball_bytes) )
608
+ Ok ( ( json_bytes , tarball_bytes) )
635
609
}
636
610
637
611
#[ instrument( skip_all) ]
0 commit comments