Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@

### Added

- **Slow-client timeout (slow loris protection).** New connections must
complete HTTP headers within `App.header-timeout` seconds (default 15).
Connections that trickle bytes without completing the request line and
headers are closed, regardless of how frequently data arrives. The
per-request timer is tracked via `ConnState.read-start` and checked in
`sweep-idle`. Keep-alive connections reset the timer between requests.

- **Request line validation.** Before full parsing, the server validates
that the HTTP method is a recognized token (GET, HEAD, POST, PUT,
DELETE, PATCH, OPTIONS, TRACE, CONNECT), that the version is HTTP/1.0
or HTTP/1.1, and that the request line fits within `App.max-header-line`
bytes (default 8192). Malformed requests receive an immediate 400 Bad
Request response. `web-valid-method?` and `web-validate-request-line`
are available as public helpers.

- **HEAD method support** (RFC 7231). HEAD requests automatically match GET
routes and return the same headers (including `Content-Length`) but with an
empty body. For `sendfile` responses, the file size is computed for
Expand Down
95 changes: 94 additions & 1 deletion test/web.carp
Original file line number Diff line number Diff line change
Expand Up @@ -728,4 +728,97 @@
"Content-Length"
&[@"0"])]
(= (Array.unsafe-first &cl) "42"))
"finalize preserves explicit Content-Length"))
"finalize preserves explicit Content-Length")

; ---------------------------------------------------------------------------
; Request validation tests
; ---------------------------------------------------------------------------

; -- valid-method? --
(assert-true test (web-valid-method? "GET") "valid-method? accepts GET")

(assert-true test (web-valid-method? "POST") "valid-method? accepts POST")

(assert-true test
(web-valid-method? "OPTIONS")
"valid-method? accepts OPTIONS")

(assert-false test
(web-valid-method? "BREW")
"valid-method? rejects unknown method")

(assert-false test (web-valid-method? "") "valid-method? rejects empty string")

; -- validate-request-line: valid requests --
(assert-true test
(Maybe.nothing?
&(web-validate-request-line
&(String.to-bytes "GET / HTTP/1.1\r\nHost: x\r\n\r\n")))
"validate accepts valid GET request")

(assert-true test
(Maybe.nothing?
&(web-validate-request-line
&(String.to-bytes "POST /data HTTP/1.0\r\nHost: x\r\n\r\n")))
"validate accepts HTTP/1.0 POST")

(assert-true test
(Maybe.nothing?
&(web-validate-request-line
&(String.to-bytes "DELETE /item/42 HTTP/1.1\r\nHost: x\r\n\r\n")))
"validate accepts DELETE with path")

; -- validate-request-line: bad HTTP version --
(assert-true test
(Maybe.just?
&(web-validate-request-line
&(String.to-bytes "GET / HTTP/2.0\r\nHost: x\r\n\r\n")))
"validate rejects HTTP/2.0")

(assert-true test
(Maybe.just?
&(web-validate-request-line
&(String.to-bytes "GET / BLAH\r\nHost: x\r\n\r\n")))
"validate rejects non-HTTP version")

; -- validate-request-line: unknown method --
(assert-true test
(Maybe.just?
&(web-validate-request-line
&(String.to-bytes "BREW / HTTP/1.1\r\nHost: x\r\n\r\n")))
"validate rejects unknown method BREW")

; -- validate-request-line: missing version --
(assert-true test
(Maybe.just?
&(web-validate-request-line &(String.to-bytes "GET /\r\nHost: x\r\n\r\n")))
"validate rejects missing version")

; -- validate-request-line: no spaces --
(assert-true test
(Maybe.just?
&(web-validate-request-line &(String.to-bytes "GARBAGE\r\n\r\n")))
"validate rejects request line without spaces")

; -- validate-request-line: no CRLF --
(assert-true test
(Maybe.just?
&(web-validate-request-line &(String.to-bytes "GET / HTTP/1.1")))
"validate rejects missing CRLF")

; -- build-response returns 400 for malformed request --
(assert-equal test
400
(let [app (-> (App.create) (App.GET @"/" (fn [r p] (Response.text @"hi"))))
bh (the
(Array (Fn [&Request &(Map String String)] (Maybe Response)))
[])
ah (the
(Array (Fn [&Request &(Map String String) Response] Response))
[])
pair (web-build-response &app
&bh
&ah
&(String.to-bytes "INVALID\r\n\r\n"))]
@(Response.code (Pair.a &pair)))
"build-response returns 400 for garbage input"))
Loading