11use std:: io;
22use std:: path:: { Path , PathBuf } ;
3+ use std:: pin:: Pin ;
34use std:: sync:: Arc ;
45use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
6+ use std:: task:: { Context , Poll } ;
57
68use zeroize:: Zeroizing ;
79
810static PHP_REQUEST_BODY_SPOOL_COUNTER : AtomicUsize = AtomicUsize :: new ( 0 ) ;
11+ const PHP_SPOOL_POSITIONAL_READ_BYTES : usize = 64 * 1024 ;
12+ type PhpSpoolReadTask = tokio:: task:: JoinHandle < io:: Result < ( Vec < u8 > , usize ) > > ;
913
1014pub struct PhpRequestBody {
1115 inner : Arc < PhpRequestBodyInner > ,
@@ -18,7 +22,15 @@ enum PhpRequestBodyInner {
1822}
1923
2024struct PhpRequestBodySpool {
21- file : std:: fs:: File ,
25+ file : Arc < std:: fs:: File > ,
26+ }
27+
28+ struct PhpSpoolReader {
29+ file : Arc < std:: fs:: File > ,
30+ offset : u64 ,
31+ pending : Option < PhpSpoolReadTask > ,
32+ ready : Vec < u8 > ,
33+ ready_start : usize ,
2234}
2335
2436impl PhpRequestBody {
@@ -37,7 +49,9 @@ impl PhpRequestBody {
3749 let file = file. into_std ( ) . await ;
3850 Ok ( Self {
3951 len,
40- inner : Arc :: new ( PhpRequestBodyInner :: Spool ( PhpRequestBodySpool { file } ) ) ,
52+ inner : Arc :: new ( PhpRequestBodyInner :: Spool ( PhpRequestBodySpool {
53+ file : Arc :: new ( file) ,
54+ } ) ) ,
4155 } )
4256 }
4357
@@ -56,16 +70,89 @@ impl PhpRequestBody {
5670 PhpRequestBodyInner :: Memory ( body) => {
5771 Ok ( Box :: new ( fastcgi_client:: io:: Cursor :: new ( body. clone ( ) ) ) )
5872 }
59- PhpRequestBodyInner :: Spool ( spool) => {
60- use std:: io:: { Seek as _, SeekFrom } ;
61-
62- let mut file = spool. file . try_clone ( ) ?;
63- file. seek ( SeekFrom :: Start ( 0 ) ) ?;
64- let file = tokio:: fs:: File :: from_std ( file) ;
65- Ok ( Box :: new (
66- fastcgi_client:: io:: TokioAsyncReadCompatExt :: compat ( file) ,
67- ) )
73+ PhpRequestBodyInner :: Spool ( spool) => Ok ( Box :: new ( PhpSpoolReader {
74+ file : Arc :: clone ( & spool. file ) ,
75+ offset : 0 ,
76+ pending : None ,
77+ ready : Vec :: new ( ) ,
78+ ready_start : 0 ,
79+ } ) ) ,
80+ }
81+ }
82+ }
83+
84+ impl fastcgi_client:: io:: AsyncRead for PhpSpoolReader {
85+ fn poll_read (
86+ self : Pin < & mut Self > ,
87+ context : & mut Context < ' _ > ,
88+ buffer : & mut [ u8 ] ,
89+ ) -> Poll < io:: Result < usize > > {
90+ use std:: future:: Future as _;
91+
92+ let this = self . get_mut ( ) ;
93+ if buffer. is_empty ( ) {
94+ return Poll :: Ready ( Ok ( 0 ) ) ;
95+ }
96+ loop {
97+ if this. ready_start < this. ready . len ( ) {
98+ let available = & this. ready [ this. ready_start ..] ;
99+ let copied = available. len ( ) . min ( buffer. len ( ) ) ;
100+ buffer[ ..copied] . copy_from_slice ( & available[ ..copied] ) ;
101+ this. ready_start += copied;
102+ if this. ready_start == this. ready . len ( ) {
103+ this. ready . clear ( ) ;
104+ this. ready_start = 0 ;
105+ }
106+ return Poll :: Ready ( Ok ( copied) ) ;
107+ }
108+ if let Some ( pending) = this. pending . as_mut ( ) {
109+ let result = match Pin :: new ( pending) . poll ( context) {
110+ Poll :: Pending => return Poll :: Pending ,
111+ Poll :: Ready ( result) => result,
112+ } ;
113+ this. pending = None ;
114+ let ( bytes, read) = result. map_err ( |error| {
115+ io:: Error :: other ( format ! ( "PHP spool read task failed: {error}" ) )
116+ } ) ??;
117+ if read > bytes. len ( ) {
118+ return Poll :: Ready ( Err ( io:: Error :: new (
119+ io:: ErrorKind :: InvalidData ,
120+ "PHP spool positional read returned an invalid length" ,
121+ ) ) ) ;
122+ }
123+ let read_len = read;
124+ let Ok ( read) = u64:: try_from ( read_len) else {
125+ return Poll :: Ready ( Err ( io:: Error :: new (
126+ io:: ErrorKind :: InvalidData ,
127+ "PHP spool positional read length is not representable" ,
128+ ) ) ) ;
129+ } ;
130+ let Some ( next_offset) = this. offset . checked_add ( read) else {
131+ return Poll :: Ready ( Err ( io:: Error :: new (
132+ io:: ErrorKind :: InvalidData ,
133+ "PHP spool read offset overflow" ,
134+ ) ) ) ;
135+ } ;
136+ this. offset = next_offset;
137+ this. ready = bytes;
138+ this. ready . truncate ( read_len) ;
139+ this. ready_start = 0 ;
140+ if read_len == 0 {
141+ return Poll :: Ready ( Ok ( 0 ) ) ;
142+ }
143+ continue ;
68144 }
145+
146+ let file = Arc :: clone ( & this. file ) ;
147+ let offset = this. offset ;
148+ let read_len = buffer. len ( ) . min ( PHP_SPOOL_POSITIONAL_READ_BYTES ) ;
149+ this. pending = Some ( tokio:: task:: spawn_blocking ( move || {
150+ use std:: os:: unix:: fs:: FileExt as _;
151+
152+ let mut bytes = vec ! [ 0_u8 ; read_len] ;
153+ let read = file. read_at ( & mut bytes, offset) ?;
154+ Ok ( ( bytes, read) )
155+ } ) ) ;
69156 }
70157 }
71158}
0 commit comments