@@ -69,9 +69,42 @@ public function handle(ConnectionInterface $conn)
6969 return ;
7070 }
7171
72+ $ contentLength = 0 ;
73+ if ($ request ->hasHeader ('Transfer-Encoding ' )) {
74+ $ contentLength = null ;
75+ } elseif ($ request ->hasHeader ('Content-Length ' )) {
76+ $ contentLength = (int )$ request ->getHeaderLine ('Content-Length ' );
77+ }
78+
79+ if ($ contentLength === 0 ) {
80+ // happy path: request body is known to be empty
81+ $ stream = new EmptyBodyStream ();
82+ $ request = $ request ->withBody ($ stream );
83+ } else {
84+ // otherwise body is present => delimit using Content-Length or ChunkedDecoder
85+ $ stream = new CloseProtectionStream ($ conn );
86+ if ($ contentLength !== null ) {
87+ $ stream = new LengthLimitedStream ($ stream , $ contentLength );
88+ } else {
89+ $ stream = new ChunkedDecoder ($ stream );
90+ }
91+
92+ $ request = $ request ->withBody (new HttpBodyStream ($ stream , $ contentLength ));
93+ }
94+
7295 $ bodyBuffer = isset ($ buffer [$ endOfHeader + 4 ]) ? \substr ($ buffer , $ endOfHeader + 4 ) : '' ;
7396 $ buffer = '' ;
74- $ that ->emit ('headers ' , array ($ request , $ bodyBuffer , $ conn ));
97+ $ that ->emit ('headers ' , array ($ request , $ conn ));
98+
99+ if ($ bodyBuffer !== '' ) {
100+ $ conn ->emit ('data ' , array ($ bodyBuffer ));
101+ }
102+
103+ // happy path: request body is known to be empty => immediately end stream
104+ if ($ contentLength === 0 ) {
105+ $ stream ->emit ('end ' );
106+ $ stream ->close ();
107+ }
75108 });
76109
77110 $ conn ->on ('close ' , function () use (&$ buffer , &$ fn ) {
@@ -135,7 +168,7 @@ public function parseRequest($headers, $remoteSocketUri, $localSocketUri)
135168
136169 // apply SERVER_ADDR and SERVER_PORT if server address is known
137170 // address should always be known, even for Unix domain sockets (UDS)
138- // but skip UDS as it doesn't have a concept of host/port.s
171+ // but skip UDS as it doesn't have a concept of host/port.
139172 if ($ localSocketUri !== null ) {
140173 $ localAddress = \parse_url ($ localSocketUri );
141174 if (isset ($ localAddress ['host ' ], $ localAddress ['port ' ])) {
@@ -199,6 +232,26 @@ public function parseRequest($headers, $remoteSocketUri, $localSocketUri)
199232 }
200233 }
201234
235+ // ensure message boundaries are valid according to Content-Length and Transfer-Encoding request headers
236+ if ($ request ->hasHeader ('Transfer-Encoding ' )) {
237+ if (\strtolower ($ request ->getHeaderLine ('Transfer-Encoding ' )) !== 'chunked ' ) {
238+ throw new \InvalidArgumentException ('Only chunked-encoding is allowed for Transfer-Encoding ' , 501 );
239+ }
240+
241+ // Transfer-Encoding: chunked and Content-Length header MUST NOT be used at the same time
242+ // as per https://tools.ietf.org/html/rfc7230#section-3.3.3
243+ if ($ request ->hasHeader ('Content-Length ' )) {
244+ throw new \InvalidArgumentException ('Using both `Transfer-Encoding: chunked` and `Content-Length` is not allowed ' , 400 );
245+ }
246+ } elseif ($ request ->hasHeader ('Content-Length ' )) {
247+ $ string = $ request ->getHeaderLine ('Content-Length ' );
248+
249+ if ((string )(int )$ string !== $ string ) {
250+ // Content-Length value is not an integer or not a single integer
251+ throw new \InvalidArgumentException ('The value of `Content-Length` is not valid ' , 400 );
252+ }
253+ }
254+
202255 // set URI components from socket address if not already filled via Host header
203256 if ($ request ->getUri ()->getHost () === '' ) {
204257 $ parts = \parse_url ($ localSocketUri );
0 commit comments