Skip to content

Commit fd22a8c

Browse files
authored
Merge pull request #262 from nowex35/main
add uri length check
2 parents 8d89b4b + 3af0020 commit fd22a8c

File tree

8 files changed

+114
-25
lines changed

8 files changed

+114
-25
lines changed

benchmark/bench.mojo

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ from lightbug_http.header import Headers, Header
55
from lightbug_http.io.bytes import ByteReader, ByteWriter
66
from lightbug_http.http import HTTPRequest, HTTPResponse, encode
77
from lightbug_http.uri import URI
8+
from lightbug_http.server import default_max_request_body_size, default_max_request_uri_length
89

910
alias headers = "GET /index.html HTTP/1.1\r\nHost: example.com\r\nUser-Agent: Mozilla/5.0\r\nContent-Type: text/html\r\nContent-Length: 1234\r\nConnection: close\r\nTrailer: end-of-message\r\n\r\n"
1011

@@ -73,7 +74,7 @@ fn lightbug_benchmark_request_parse(mut b: Bencher):
7374
@parameter
7475
fn request_parse():
7576
try:
76-
_ = HTTPRequest.from_bytes("127.0.0.1/path", 4096, Request.as_bytes())
77+
_ = HTTPRequest.from_bytes("127.0.0.1/path", default_max_request_body_size, default_max_request_uri_length, Request.as_bytes())
7778
except:
7879
pass
7980

lightbug_http/cookie/request_cookie_jar.mojo

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,13 @@ struct RequestCookieJar(Writable, Stringable):
7979

8080
fn __str__(self) -> String:
8181
return to_string(self)
82+
83+
fn __eq__(self, other: RequestCookieJar) -> Bool:
84+
if len(self._inner) != len(other._inner):
85+
return False
86+
87+
for value in self._inner.items():
88+
for other_value in other._inner.items():
89+
if value.key != other_value.key or value.value != other_value.value:
90+
return False
91+
return True

lightbug_http/header.mojo

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,13 @@ struct Headers(Writable, Stringable):
116116

117117
fn __str__(self) -> String:
118118
return String.write(self)
119+
120+
fn __eq__(self, other: Headers) -> Bool:
121+
if len(self._inner) != len(other._inner):
122+
return False
123+
124+
for value in self._inner.items():
125+
for other_value in other._inner.items():
126+
if value.key != other_value.key or value.value != other_value.value:
127+
return False
128+
return True

lightbug_http/http/common_response.mojo

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ fn NotFound(path: String) -> HTTPResponse:
5959
status_text="Not Found",
6060
)
6161

62+
fn URITooLong() -> HTTPResponse:
63+
return HTTPResponse(
64+
bytes("URI Too Long"),
65+
headers=Headers(Header(HeaderKey.CONTENT_TYPE, "text/plain")),
66+
status_code=414,
67+
status_text="URI Too Long"
68+
)
69+
6270

6371
fn InternalError() -> HTTPResponse:
6472
return HTTPResponse(

lightbug_http/http/request.mojo

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ struct HTTPRequest(Writable, Stringable, Encodable):
4444
var timeout: Duration
4545

4646
@staticmethod
47-
fn from_bytes(addr: String, max_body_size: Int, b: Span[Byte]) raises -> HTTPRequest:
47+
fn from_bytes(addr: String, max_body_size: Int, max_uri_length: Int, b: Span[Byte]) raises -> HTTPRequest:
4848
var reader = ByteReader(b)
4949
var headers = Headers()
5050
var method: String
@@ -56,6 +56,9 @@ struct HTTPRequest(Writable, Stringable, Encodable):
5656
except e:
5757
raise Error("HTTPRequest.from_bytes: Failed to parse request headers: " + String(e))
5858

59+
if len(uri.as_bytes()) > max_uri_length:
60+
raise Error("HTTPRequest.from_bytes: Request URI too long")
61+
5962
var cookies = RequestCookieJar()
6063
try:
6164
cookies.parse_cookies(headers)
@@ -189,3 +192,21 @@ struct HTTPRequest(Writable, Stringable, Encodable):
189192

190193
fn __str__(self) -> String:
191194
return String.write(self)
195+
196+
fn __eq__(self, other: HTTPRequest) -> Bool:
197+
return (
198+
self.method == other.method
199+
and self.protocol == other.protocol
200+
and self.uri == other.uri
201+
and self.headers == other.headers
202+
and self.cookies == other.cookies
203+
and self.body_raw.__str__() == other.body_raw.__str__()
204+
)
205+
206+
fn __isnot__(self, other: HTTPRequest) -> Bool:
207+
return not self.__eq__(other)
208+
209+
fn __isnot__(self, other: None) -> Bool:
210+
if self.get_body() != "" or self.uri.request_uri != "":
211+
return True
212+
return False

lightbug_http/server.mojo

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ from lightbug_http._logger import logger
55
from lightbug_http.connection import NoTLSListener, default_buffer_size, TCPConnection, ListenConfig
66
from lightbug_http.socket import Socket
77
from lightbug_http.http import HTTPRequest, encode
8-
from lightbug_http.http.common_response import InternalError, BadRequest
8+
from lightbug_http.http.common_response import InternalError, BadRequest, URITooLong
99
from lightbug_http.uri import URI
1010
from lightbug_http.header import Headers
1111
from lightbug_http.service import HTTPService
@@ -14,6 +14,7 @@ from lightbug_http.error import ErrorHandler
1414

1515
alias DefaultConcurrency: Int = 256 * 1024
1616
alias default_max_request_body_size = 4 * 1024 * 1024 # 4MB
17+
alias default_max_request_uri_length = 8192
1718

1819

1920
struct Server(Movable):
@@ -27,6 +28,7 @@ struct Server(Movable):
2728
var max_requests_per_connection: UInt
2829

2930
var _max_request_body_size: UInt
31+
var _max_request_uri_length: UInt
3032
var tcp_keep_alive: Bool
3133

3234
fn __init__(
@@ -37,13 +39,15 @@ struct Server(Movable):
3739
max_concurrent_connections: UInt = 1000,
3840
max_requests_per_connection: UInt = 0,
3941
max_request_body_size: UInt = default_max_request_body_size,
42+
max_request_uri_length: UInt = default_max_request_uri_length,
4043
tcp_keep_alive: Bool = False,
4144
) raises:
4245
self.error_handler = error_handler
4346
self.name = name
4447
self._address = address
4548
self.max_requests_per_connection = max_requests_per_connection
46-
self._max_request_body_size = default_max_request_body_size
49+
self._max_request_body_size = max_request_body_size
50+
self._max_request_uri_length = max_request_uri_length
4751
self.tcp_keep_alive = tcp_keep_alive
4852
if max_concurrent_connections == 0:
4953
self.max_concurrent_connections = DefaultConcurrency
@@ -57,6 +61,7 @@ struct Server(Movable):
5761
self.max_concurrent_connections = other.max_concurrent_connections
5862
self.max_requests_per_connection = other.max_requests_per_connection
5963
self._max_request_body_size = other._max_request_body_size
64+
self._max_request_uri_length = other._max_request_uri_length
6065
self.tcp_keep_alive = other.tcp_keep_alive
6166

6267
fn address(self) -> ref [self._address] String:
@@ -71,6 +76,12 @@ struct Server(Movable):
7176
fn set_max_request_body_size(mut self, size: UInt) -> None:
7277
self._max_request_body_size = size
7378

79+
fn max_request_uri_length(self) -> UInt:
80+
return self._max_request_uri_length
81+
82+
fn set_max_request_uri_length(mut self, length: UInt) -> None:
83+
self._max_request_uri_length = length
84+
7485
fn get_concurrency(self) -> UInt:
7586
"""Retrieve the concurrency level which is either
7687
the configured `max_concurrent_connections` or the `DefaultConcurrency`.
@@ -128,6 +139,10 @@ struct Server(Movable):
128139
var max_request_body_size = self.max_request_body_size()
129140
if max_request_body_size <= 0:
130141
max_request_body_size = default_max_request_body_size
142+
143+
var max_request_uri_length = self.max_request_uri_length()
144+
if max_request_uri_length <= 0:
145+
max_request_uri_length = default_max_request_uri_length
131146

132147
var req_number = 0
133148
while True:
@@ -163,7 +178,7 @@ struct Server(Movable):
163178

164179
var request: HTTPRequest
165180
try:
166-
request = HTTPRequest.from_bytes(self.address(), max_request_body_size, request_buffer)
181+
request = HTTPRequest.from_bytes(self.address(), max_request_body_size, max_request_uri_length, request_buffer)
167182
var response: HTTPResponse
168183
var close_connection = (not self.tcp_keep_alive) or request.connection_close()
169184
try:
@@ -200,7 +215,10 @@ struct Server(Movable):
200215
except e:
201216
logger.error("Failed to parse HTTPRequest:", String(e))
202217
try:
203-
_ = conn.write(encode(BadRequest()))
218+
if String(e) == "HTTPRequest.from_bytes: Request URI too long":
219+
_ = conn.write(encode(URITooLong()))
220+
else:
221+
_ = conn.write(encode(BadRequest()))
204222
except e:
205223
logger.error("Failed to write BadRequest response to the connection:", String(e))
206224
conn.teardown()

lightbug_http/uri.mojo

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,17 @@ struct URI(Writable, Stringable, Representable):
240240
fn __repr__(self) -> String:
241241
return String.write(self)
242242

243+
fn __eq__(self, other: URI) -> Bool:
244+
return (
245+
self.scheme == other.scheme
246+
and self.host == other.host
247+
and self.path == other.path
248+
and self.query_string == other.query_string
249+
and self._original_path == other._original_path
250+
and self.full_uri == other.full_uri
251+
and self.request_uri == other.request_uri
252+
)
253+
243254
fn write_to[T: Writer](self, mut writer: T):
244255
writer.write(
245256
"URI(",

tests/lightbug_http/http/test_request.mojo

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,42 @@ from memory import Span
33
from collections.string import StringSlice
44
from lightbug_http.http import HTTPRequest, StatusCode
55
from lightbug_http.strings import to_string
6-
6+
from lightbug_http.server import default_max_request_body_size, default_max_request_uri_length
77

88
def test_request_from_bytes():
99
alias data = "GET /redirect HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nUser-Agent: python-requests/2.32.3\r\nAccept-Encoding: gzip, deflate, br, zstd\r\nAccept: */*\r\nconnection: keep-alive\r\n\r\n"
10-
var request = HTTPRequest.from_bytes("127.0.0.1", 4096, data.as_bytes())
11-
testing.assert_equal(request.protocol, "HTTP/1.1")
12-
testing.assert_equal(request.method, "GET")
13-
testing.assert_equal(request.uri.request_uri, "/redirect")
14-
testing.assert_equal(request.headers["Host"], "127.0.0.1:8080")
15-
testing.assert_equal(request.headers["User-Agent"], "python-requests/2.32.3")
16-
17-
testing.assert_false(request.connection_close())
18-
request.set_connection_close()
19-
testing.assert_true(request.connection_close())
10+
var request: HTTPRequest
11+
try:
12+
request = HTTPRequest.from_bytes("127.0.0.1", default_max_request_body_size, default_max_request_uri_length, data.as_bytes())
13+
if request is not None:
14+
testing.assert_equal(request.protocol, "HTTP/1.1")
15+
testing.assert_equal(request.method, "GET")
16+
testing.assert_equal(request.uri.request_uri, "/redirect")
17+
testing.assert_equal(request.headers["Host"], "127.0.0.1:8080")
18+
testing.assert_equal(request.headers["User-Agent"], "python-requests/2.32.3")
19+
20+
testing.assert_false(request.connection_close())
21+
request.set_connection_close()
22+
testing.assert_true(request.connection_close())
23+
except e:
24+
testing.assert_true(False, "Failed to parse HTTP request: " + String(e))
25+
2026

2127

2228
def test_read_body():
2329
alias data = "GET /redirect HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nUser-Agent: python-requests/2.32.3\r\nAccept-Encoding: gzip, deflate, br, zstd\r\nAccept: */\r\nContent-Length: 17\r\nconnection: keep-alive\r\n\r\nThis is the body!"
24-
var request = HTTPRequest.from_bytes("127.0.0.1", 4096, data.as_bytes())
25-
testing.assert_equal(request.protocol, "HTTP/1.1")
26-
testing.assert_equal(request.method, "GET")
27-
testing.assert_equal(request.uri.request_uri, "/redirect")
28-
testing.assert_equal(request.headers["Host"], "127.0.0.1:8080")
29-
testing.assert_equal(request.headers["User-Agent"], "python-requests/2.32.3")
30-
31-
testing.assert_equal(String(request.get_body()), String("This is the body!"))
30+
var request: HTTPRequest
31+
try:
32+
request = HTTPRequest.from_bytes("127.0.0.1", default_max_request_body_size, default_max_request_uri_length, data.as_bytes())
33+
if request is not None:
34+
testing.assert_equal(request.protocol, "HTTP/1.1")
35+
testing.assert_equal(request.method, "GET")
36+
testing.assert_equal(request.uri.request_uri, "/redirect")
37+
testing.assert_equal(request.headers["Host"], "127.0.0.1:8080")
38+
testing.assert_equal(request.headers["User-Agent"], "python-requests/2.32.3")
39+
testing.assert_equal(String(request.get_body()), String("This is the body!"))
40+
except e:
41+
testing.assert_true(False, "Failed to parse HTTP request: " + String(e))
3242

3343

3444
def test_encode():

0 commit comments

Comments
 (0)