@@ -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:: Bytes ;
8
+ use axum:: body:: { Body , Bytes } ;
9
9
use axum:: Json ;
10
10
use cargo_manifest:: { Dependency , DepsSet , TargetDepsSet } ;
11
11
use chrono:: { DateTime , SecondsFormat , Utc } ;
@@ -18,10 +18,12 @@ use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl};
18
18
use futures_util:: TryFutureExt ;
19
19
use futures_util:: TryStreamExt ;
20
20
use hex:: ToHex ;
21
+ use http:: request:: Parts ;
21
22
use http:: StatusCode ;
22
- use hyper:: body:: Buf ;
23
23
use sha2:: { Digest , Sha256 } ;
24
24
use std:: collections:: HashMap ;
25
+ use tokio:: io:: { AsyncRead , AsyncReadExt } ;
26
+ use tokio_util:: io:: StreamReader ;
25
27
use url:: Url ;
26
28
27
29
use crate :: models:: {
@@ -36,7 +38,6 @@ use crate::rate_limiter::LimitedAction;
36
38
use crate :: schema:: * ;
37
39
use crate :: sql:: canon_crate_name;
38
40
use crate :: util:: errors:: { bad_request, custom, internal, AppResult , BoxedAppError } ;
39
- use crate :: util:: BytesRequest ;
40
41
use crate :: views:: {
41
42
EncodableCrate , EncodableCrateDependency , GoodCrate , PublishMetadata , PublishWarnings ,
42
43
} ;
@@ -51,12 +52,20 @@ const MAX_DESCRIPTION_LENGTH: usize = 1000;
51
52
/// Handles the `PUT /crates/new` route.
52
53
/// Used by `cargo publish` to publish a new crate or to publish a new version of an
53
54
/// existing crate.
54
- pub async fn publish ( app : AppState , req : BytesRequest ) -> AppResult < Json < GoodCrate > > {
55
- let ( req, bytes) = req. 0 . into_parts ( ) ;
56
- let ( json_bytes, tarball_bytes) = split_body ( bytes) ?;
55
+ pub async fn publish ( app : AppState , req : Parts , body : Body ) -> AppResult < Json < GoodCrate > > {
56
+ let stream = body. into_data_stream ( ) ;
57
+ let stream = stream. map_err ( |err| std:: io:: Error :: new ( std:: io:: ErrorKind :: Other , err) ) ;
58
+ let mut reader = StreamReader :: new ( stream) ;
57
59
58
- let metadata: PublishMetadata = serde_json:: from_slice ( & json_bytes)
59
- . map_err ( |e| bad_request ( format_args ! ( "invalid upload request: {e}" ) ) ) ?;
60
+ // The format of the req.body() of a publish request is as follows:
61
+ //
62
+ // metadata length
63
+ // metadata in JSON about the crate being published
64
+ // .crate tarball length
65
+ // .crate tarball file
66
+
67
+ const MAX_JSON_LENGTH : u32 = 1024 * 1024 ; // 1 MB
68
+ let metadata = read_json_metadata ( & mut reader, MAX_JSON_LENGTH ) . await ?;
60
69
61
70
Crate :: validate_crate_name ( "crate" , & metadata. name ) . map_err ( bad_request) ?;
62
71
@@ -133,19 +142,13 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
133
142
. check_rate_limit ( auth. user ( ) . id , rate_limit_action, & mut conn)
134
143
. await ?;
135
144
136
- let content_length = tarball_bytes. len ( ) as u64 ;
137
-
138
145
let max_upload_size = existing_crate
139
146
. as_ref ( )
140
147
. and_then ( |c| c. max_upload_size ( ) )
141
148
. unwrap_or ( app. config . max_upload_size ) ;
142
149
143
- if content_length > max_upload_size as u64 {
144
- return Err ( custom (
145
- StatusCode :: PAYLOAD_TOO_LARGE ,
146
- format ! ( "max upload size is: {max_upload_size}" ) ,
147
- ) ) ;
148
- }
150
+ let tarball_bytes = read_tarball_bytes ( & mut reader, max_upload_size) . await ?;
151
+ let content_length = tarball_bytes. len ( ) as u64 ;
149
152
150
153
let pkg_name = format ! ( "{}-{}" , & * metadata. name, & version_string) ;
151
154
let max_unpack_size = std:: cmp:: max ( app. config . max_unpack_size , max_upload_size as u64 ) ;
@@ -584,43 +587,66 @@ async fn count_versions_published_today(
584
587
}
585
588
586
589
#[ instrument( skip_all) ]
587
- fn split_body ( mut bytes : Bytes ) -> AppResult < ( Bytes , Bytes ) > {
588
- // The format of the req.body() of a publish request is as follows:
589
- //
590
- // metadata length
591
- // metadata in JSON about the crate being published
592
- // .crate tarball length
593
- // .crate tarball file
590
+ async fn read_json_metadata < R : AsyncRead + Unpin > (
591
+ reader : & mut R ,
592
+ max_length : u32 ,
593
+ ) -> Result < PublishMetadata , BoxedAppError > {
594
+ let json_len = reader. read_u32_le ( ) . await . map_err ( |e| {
595
+ if e. kind ( ) == std:: io:: ErrorKind :: UnexpectedEof {
596
+ bad_request ( "invalid metadata length" )
597
+ } else {
598
+ e. into ( )
599
+ }
600
+ } ) ?;
594
601
595
- if bytes . len ( ) < 4 {
596
- // Avoid panic in `get_u32_le()` if there is not enough remaining data
597
- return Err ( bad_request ( "invalid metadata length" ) ) ;
602
+ if json_len > max_length {
603
+ let message = "JSON metadata blob too large" ;
604
+ return Err ( custom ( StatusCode :: PAYLOAD_TOO_LARGE , message ) ) ;
598
605
}
599
606
600
- let json_len = bytes. get_u32_le ( ) as usize ;
601
- if json_len > bytes. len ( ) {
602
- return Err ( bad_request ( format ! (
603
- "invalid metadata length for remaining payload: {json_len}"
604
- ) ) ) ;
605
- }
607
+ let mut json_bytes = vec ! [ 0 ; json_len as usize ] ;
608
+ reader. read_exact ( & mut json_bytes) . await . map_err ( |e| {
609
+ if e. kind ( ) == std:: io:: ErrorKind :: UnexpectedEof {
610
+ let message = format ! ( "invalid metadata length for remaining payload: {json_len}" ) ;
611
+ bad_request ( message)
612
+ } else {
613
+ e. into ( )
614
+ }
615
+ } ) ?;
606
616
607
- let json_bytes = bytes. split_to ( json_len) ;
617
+ serde_json:: from_slice ( & json_bytes)
618
+ . map_err ( |e| bad_request ( format_args ! ( "invalid upload request: {e}" ) ) )
619
+ }
608
620
609
- if bytes. len ( ) < 4 {
610
- // Avoid panic in `get_u32_le()` if there is not enough remaining data
611
- return Err ( bad_request ( "invalid tarball length" ) ) ;
612
- }
621
+ #[ instrument( skip_all) ]
622
+ async fn read_tarball_bytes < R : AsyncRead + Unpin > (
623
+ reader : & mut R ,
624
+ max_length : u32 ,
625
+ ) -> Result < Bytes , BoxedAppError > {
626
+ let tarball_len = reader. read_u32_le ( ) . await . map_err ( |e| {
627
+ if e. kind ( ) == std:: io:: ErrorKind :: UnexpectedEof {
628
+ bad_request ( "invalid tarball length" )
629
+ } else {
630
+ e. into ( )
631
+ }
632
+ } ) ?;
613
633
614
- let tarball_len = bytes. get_u32_le ( ) as usize ;
615
- if tarball_len > bytes. len ( ) {
616
- return Err ( bad_request ( format ! (
617
- "invalid tarball length for remaining payload: {tarball_len}"
618
- ) ) ) ;
634
+ if tarball_len > max_length {
635
+ let message = format ! ( "max upload size is: {}" , max_length) ;
636
+ return Err ( custom ( StatusCode :: PAYLOAD_TOO_LARGE , message) ) ;
619
637
}
620
638
621
- let tarball_bytes = bytes. split_to ( tarball_len) ;
639
+ let mut tarball_bytes = vec ! [ 0 ; tarball_len as usize ] ;
640
+ reader. read_exact ( & mut tarball_bytes) . await . map_err ( |e| {
641
+ if e. kind ( ) == std:: io:: ErrorKind :: UnexpectedEof {
642
+ let message = format ! ( "invalid tarball length for remaining payload: {tarball_len}" ) ;
643
+ bad_request ( message)
644
+ } else {
645
+ e. into ( )
646
+ }
647
+ } ) ?;
622
648
623
- Ok ( ( json_bytes , tarball_bytes) )
649
+ Ok ( Bytes :: from ( tarball_bytes) )
624
650
}
625
651
626
652
#[ instrument( skip_all) ]
0 commit comments