3131import io .fusionauth .http .server .Instrumenter ;
3232
3333/**
34- * An InputStream that handles the HTTP body, including body bytes that were read while the preamble was processed. This class also handles
35- * chunked bodies by using a delegate InputStream that wraps the original source of the body bytes. The {@link ChunkedInputStream} is the
36- * delegate that this class leverages for chunking .
34+ * An InputStream intended to be read the HTTP request body.
35+ * <p>
36+ * This will handle fixed length requests, chunked requests as well as decompression if necessary .
3737 *
3838 * @author Brian Pontarelli
3939 */
@@ -140,17 +140,14 @@ public int read(byte[] b, int off, int len) throws IOException {
140140 initialize ();
141141 }
142142
143- // When we have a fixed length request, read beyond the remainingBytes if possible.
144- // - If we have read past the end of the current request, push those bytes back onto the InputStream.
145- // - When a maximum content length has been specified, read at most one byte past the maximum.
143+ // When a maximum content length has been specified, read at most one byte past the maximum.
146144 int maxReadLen = maximumContentLength == -1 ? len : Math .min (len , maximumContentLength - bytesRead + 1 );
147145 int read = delegate .read (b , off , maxReadLen );
148146 if (read > 0 ) {
149147 bytesRead += read ;
150148 }
151149
152- // Note that when the request is fixed length, we will have failed early during commit().
153- // - This will handle all requests that are not fixed length.
150+ // Throw an exception once we have read past the maximum configured content length,
154151 if (maximumContentLength != -1 && bytesRead > maximumContentLength ) {
155152 String detailedMessage = "The maximum request size has been exceeded. The maximum request size is [" + maximumContentLength + "] bytes." ;
156153 throw new ContentTooLargeException (maximumContentLength , detailedMessage );
@@ -167,9 +164,6 @@ private void initialize() throws IOException {
167164 // In practice, we will remove the Content-Length header when sent in addition to Transfer-Encoding. See HTTPWorker.validatePreamble.
168165 Long contentLength = request .getContentLength ();
169166 boolean hasBody = (contentLength != null && contentLength > 0 ) || request .isChunked ();
170-
171- // Note that isChunked() should take precedence over the fact that we have a Content-Length.
172- // - The client should not send both, but in the case they are both present we ignore Content-Length
173167 if (!hasBody ) {
174168 delegate = InputStream .nullInputStream ();
175169 } else if (request .isChunked ()) {
@@ -185,26 +179,7 @@ private void initialize() throws IOException {
185179 logger .trace ("Client indicated it was NOT sending an entity-body in the request" );
186180 }
187181
188- // If we have a maximumContentLength, and this is a fixed content length request, before we read any bytes, fail early.
189- // For good measure do this last so if anyone downstream wants to read from the InputStream they could in theory because
190- // we will have set up the InputStream.
191- // TODO : Compression : we may have to delete this code, or only enforce it when compression was not used.
192- // Content-Length represents the compressed size, not the uncompressed bytes.
193- if (contentLength != null && maximumContentLength != -1 && contentLength > maximumContentLength ) {
194- String detailedMessage = "The maximum request size has been exceeded. The reported Content-Length is [" + contentLength + "] and the maximum request size is [" + maximumContentLength + "] bytes." ;
195- throw new ContentTooLargeException (maximumContentLength , detailedMessage );
196- }
197-
198- // Current state
199- // Chunked HTTPInputStream (this) > Chunked > Pushback > Throughput > Socket
200- // Fixed length HTTPInputStream (this) > Fixed > Pushback > Throughput > Socket
201-
202- // When decompressing, the result will be something like this:
203- // Chunked HTTPInputStream (this) > Decompress > Chunked > Pushback > Throughput > Socket
204- // Fixed length HTTPInputStream (this) > Decompress> Fixed > Pushback > Throughput > Socket
205- //
206- // You may have one or more Decompress InputStreams in the above diagram.
207-
182+ // Now that we have the InputStream set up to read the body, handle decompression.
208183 if (hasBody ) {
209184 // The request may contain more than one value, apply in reverse order.
210185 // - These are both using the default 512 buffer size.
@@ -216,5 +191,14 @@ private void initialize() throws IOException {
216191 }
217192 }
218193 }
194+
195+ // If we have a fixed length request that is reporting a contentLength larger than the configured maximum, fail earl.
196+ // - Do this last so if anyone downstream wants to read from the InputStream it would work.
197+ // - Note that it is possible that the body is compressed which would mean the contentLength represents the compressed value.
198+ // But when we decompress the bytes the result will be larger than the reported contentLength, so we can safely throw this exception.
199+ if (contentLength != null && maximumContentLength != -1 && contentLength > maximumContentLength ) {
200+ String detailedMessage = "The maximum request size has been exceeded. The reported Content-Length is [" + contentLength + "] and the maximum request size is [" + maximumContentLength + "] bytes." ;
201+ throw new ContentTooLargeException (maximumContentLength , detailedMessage );
202+ }
219203 }
220204}
0 commit comments