Skip to content

Commit d77627f

Browse files
committed
Working
1 parent 61186e6 commit d77627f

File tree

6 files changed

+36
-19
lines changed

6 files changed

+36
-19
lines changed

src/main/java/io/fusionauth/http/server/internal/HTTPWorker.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@ public void run() {
145145
instrumenter.acceptedRequest();
146146
}
147147

148-
// Configure maximum content length
149148
int maximumContentLength = getMaximumContentLength(request);
150149
httpInputStream = new HTTPInputStream(configuration, request, inputStream, maximumContentLength);
151150
request.setInputStream(httpInputStream);
@@ -258,7 +257,7 @@ public void run() {
258257
closeSocketOnly(reason);
259258
} catch (ParseException e) {
260259
logger.debug("[{}] Closing socket with status [{}]. Bad request, failed to parse request. Reason [{}] Parser state [{}]", Thread.currentThread().threadId(), Status.BadRequest, e.getMessage(), e.getState());
261-
closeSocketOnError(response, Status.BadRequest);
260+
closeSocketOnError(response, Status.HTTPVersionNotSupported);
262261
} catch (SocketException e) {
263262
// When the HTTPServerThread shuts down, we will interrupt each client thread, so debug log it accordingly.
264263
// - This will cause the socket to throw a SocketException, so log it.

src/main/java/io/fusionauth/http/server/io/HTTPInputStream.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,9 @@ public int read(byte[] b, int off, int len) throws IOException {
151151

152152
// When we have a fixed length request, read beyond the remainingBytes if possible.
153153
// - If we have read past the end of the current request, push those bytes back onto the InputStream.
154-
int maxLen = maximumContentLength == -1 ? len : Math.min(len, maximumContentLength - bytesRead + 1);
155-
int read = delegate.read(b, off, maxLen);
154+
// - When a maximum content length has been specified, read at most one byte past the maximum.
155+
int maxReadLen = maximumContentLength == -1 ? len : Math.min(len, maximumContentLength - bytesRead + 1);
156+
int read = delegate.read(b, off, maxReadLen);
156157

157158
int reportBytesRead = read;
158159
if (fixedLength && read > 0) {
@@ -163,16 +164,14 @@ public int read(byte[] b, int off, int len) throws IOException {
163164
}
164165
}
165166

166-
if (read > 0) {
167-
if (fixedLength) {
168-
bytesRemaining -= reportBytesRead;
169-
}
167+
if (read > 0 && fixedLength) {
168+
bytesRemaining -= reportBytesRead;
170169
}
171170

172171
bytesRead += reportBytesRead;
173172

174-
// This won't cause us to fail as fast as we could, but it keeps the code a bit simpler.
175-
// - This means we will have read past the maximum by n where n is > 0 && < len. This seems like an acceptable over-read, in practice the buffers will be
173+
// Note that when the request is fixed length, we will have failed early during commit().
174+
// - This will handle all requests that are not fixed length.
176175
if (maximumContentLength != -1) {
177176
if (bytesRead > maximumContentLength) {
178177
String detailedMessage = "The maximum request size has been exceeded.The maximum request size is [" + maximumContentLength + "] bytes.";
@@ -186,8 +185,6 @@ public int read(byte[] b, int off, int len) throws IOException {
186185
private void commit() {
187186
committed = true;
188187

189-
// TODO : Handle : Content-Encoding
190-
191188
// Note that isChunked() should take precedence over the fact that we have a Content-Length.
192189
// - The client should not send both, but in the case they are both present we ignore Content-Length
193190
// In practice, we will remove the Content-Length header when sent in addition to Transfer-Encoding. See HTTPWorker.validatePreamble.

src/test/java/io/fusionauth/http/BaseSocketTest.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import io.fusionauth.http.server.HTTPHandler;
2323
import io.fusionauth.http.server.HTTPServer;
24+
import static org.testng.Assert.assertEquals;
2425

2526
/**
2627
* A base class to provide some helpers for socket based tests. A socket test doesn't use an HTTP client, but instead manually writes to the
@@ -54,6 +55,8 @@ private void assertResponse(String request, String chunkedExtension, String resp
5455
.start();
5556
Socket socket = makeClientSocket("http")) {
5657

58+
socket.setSoTimeout((int) Duration.ofSeconds(30).toMillis());
59+
5760
var bodyString = "These pretzels are making me thirsty. ";
5861
// Ensure this is larger than the default configured size for the request buffer.
5962
// - This body is added to each request to ensure we correctly drain the InputStream before we can write the HTTP response.
@@ -85,11 +88,22 @@ private void assertResponse(String request, String chunkedExtension, String resp
8588
}
8689

8790
request = request.replace("{body}", body);
88-
request = request.replace("{contentLength}", body.getBytes(StandardCharsets.UTF_8).length + "");
91+
var contentLength = body.getBytes(StandardCharsets.UTF_8).length;
92+
request = request.replace("{contentLength}", contentLength + "");
93+
94+
// Ensure the caller didn't add an extra line return to the request.
95+
int bodyStart = request.indexOf("\r\n\r\n") + 4;
96+
String payload = request.substring(bodyStart);
97+
assertEquals(contentLength, payload.getBytes(StandardCharsets.UTF_8).length, "Check the value you provided for 'withRequest' it looks like you may have a trailing line return or something.\n");
8998

9099
var os = socket.getOutputStream();
91100
os.write(request.getBytes(StandardCharsets.UTF_8));
92101

102+
// 1. Write bytes, the content-length is short by one byte.
103+
// 2. Next byte \n
104+
// 3. Success.
105+
// 4. Next keep-alive request reads preamble, and reads the \n and throws an exception
106+
93107
assertHTTPResponseEquals(socket, response);
94108
}
95109
}

src/test/java/io/fusionauth/http/BaseTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,9 +387,11 @@ protected void assertHTTPResponseEquals(Socket socket, String expectedResponse)
387387
var is = socket.getInputStream();
388388
var expectedResponseLength = expectedResponse.getBytes(StandardCharsets.UTF_8).length;
389389
try {
390-
byte[] buffer = new byte[expectedResponse.length() * 2];
390+
391+
byte[] buffer = new byte[expectedResponseLength * 2];
391392
int read = is.read(buffer);
392393
var actualResponse = new String(buffer, 0, read, StandardCharsets.UTF_8);
394+
393395
// Perform an initial equality check, this is fast. If it fails, it may because there are remaining bytes left to read. This is slower.
394396
if (!actualResponse.equals(expectedResponse)) {
395397
// Note this is going to block until the socket keep-alive times out.

src/test/java/io/fusionauth/http/HTTP11SocketTest.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ public void bad_request() throws Exception {
3030
withRequest("""
3131
cat /etc/password\r
3232
\r
33-
{body}
34-
"""
33+
{body}"""
3534
).expectResponse("""
3635
HTTP/1.1 400 \r
3736
connection: close\r
@@ -391,12 +390,18 @@ public void transfer_encoding_content_length() throws Exception {
391390
Content-Length: {contentLength}\r
392391
Transfer-Encoding: chunked\r
393392
\r
394-
{body}"""
393+
{body}
394+
"""
395395
).expectResponse("""
396396
HTTP/1.1 200 \r
397397
connection: keep-alive\r
398398
content-length: 0\r
399399
\r
400400
""");
401+
402+
// Order of operation:
403+
// 1. Write body w/ a trailing line return not accounted for in Content-Length.
404+
// 2. Read body, write 200 response. Keep alive.
405+
// 3. Read remaining byte, parse error. Write 400 response. Close connection.
401406
}
402407
}

src/test/java/io/fusionauth/http/io/HTTPInputStreamTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ public void read_chunked_withPushback() throws Exception {
3333
// Ensure that when we read a chunked encoded body that the InputStream returns the correct number of bytes read even when
3434
// we read past the end of the current request and use the PushbackInputStream.
3535

36-
int contentLength = 113;
3736
String content = "These pretzels are making me thirsty. These pretzels are making me thirsty. These pretzels are making me thirsty.";
37+
int contentLength = content.getBytes(StandardCharsets.UTF_8).length;
3838

3939
// Chunk the content
4040
byte[] bytes = """
@@ -60,8 +60,8 @@ public void read_fixedLength_withPushback() throws Exception {
6060
// Ensure that when we read a fixed length body that the InputStream returns the correct number of bytes read even when
6161
// we read past the end of the current request and use the PushbackInputStream.
6262

63-
int contentLength = 113;
6463
String content = "These pretzels are making me thirsty. These pretzels are making me thirsty. These pretzels are making me thirsty.";
64+
int contentLength = content.getBytes(StandardCharsets.UTF_8).length;
6565

6666
// Fixed length body with the start of the next request in the buffer
6767
byte[] bytes = (content + "GET / HTTP/1.1\r\n").getBytes(StandardCharsets.UTF_8);

0 commit comments

Comments
 (0)