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
27 changes: 20 additions & 7 deletions http.carp
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,27 @@ the type yourself, instead you can [parse](#parse) it.")
(the (Maybe String) (Maybe.Nothing))
hdrs)))

(private header-values)
(hidden header-values)
(defn header-values [hdrs name]
(let [lower-name &(String.ascii-to-lower name)]
(Map.kv-reduce
&(fn [acc k v]
(if (= &(String.ascii-to-lower k) lower-name)
(Array.concat &[acc @v])
acc))
(the (Array String) [])
hdrs)))

(private transfer-encoding-chunked?)
(hidden transfer-encoding-chunked?)
(defn transfer-encoding-chunked? [hdrs]
(match (header-lookup hdrs "transfer-encoding")
(Maybe.Just v)
(Array.any?
&(fn [v]
(Array.any?
&(fn [tok] (= &(String.trim tok) "chunked"))
&(String.split-by &(String.ascii-to-lower &v) &[\,]))
(Maybe.Nothing) false))
&(String.split-by &(String.ascii-to-lower v) &[\,])))
&(header-values hdrs "transfer-encoding")))

(doc Request "is a request data type. It holds the `verb` of the request, the
`version`, the `uri`, the `cookies`, the `headers`, and the `body`.")
Expand Down Expand Up @@ -1317,6 +1329,7 @@ server's `offers`, honoring the request's `Accept` header (RFC 7231 §5.3.2). A
request with no `Accept` header accepts anything. See
[`Accept.negotiate`](#negotiate).")
(defn negotiate [r offers]
(match (Request.header r "Accept")
(Maybe.Nothing) (Accept.negotiate "" offers)
(Maybe.Just a) (Accept.negotiate &a offers))))
(let [accepts (header-values (headers r) "Accept")]
(if (Array.empty? &accepts)
(Accept.negotiate "" offers)
(Accept.negotiate &(String.join ", " &accepts) offers)))))
37 changes: 37 additions & 0 deletions test/http.carp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@
(match (Request.negotiate &r offers)
(Maybe.Just m) m
(Maybe.Nothing) @"NONE")))

(defn req-parse-pick [s offers]
(match (Request.parse s)
(Result.Success r)
(match (Request.negotiate &r offers)
(Maybe.Just m) m
(Maybe.Nothing) @"NONE")
_ @"PARSE-FAIL"))
; ---- chunked transfer-encoding test helpers ----
(defn dechunk-ok [s]
(match (TransferEncoding.dechunk s) (Result.Success b) b _ @"FAIL"))
Expand Down Expand Up @@ -807,6 +815,20 @@
"text/html"
&(req-pick-noaccept &[@"text/html" @"application/json"])
"Request.negotiate with no Accept header accepts anything")
(assert-equal test
"application/json"
&(req-parse-pick &multi-header &[@"application/json"])
"Request.negotiate folds an Accept split across multiple lines")
(assert-equal test
"text/html"
&(req-parse-pick &multi-header &[@"text/html" @"application/json"])
"Request.negotiate still honors the first line of a multi-line Accept")
(assert-equal test
"text/html"
&(req-parse-pick
"GET / HTTP/1.1\r\nHost: localhost\r\nAccept: text/html\r\n\r\n"
&[@"text/html"])
"Request.negotiate honors a single-line Accept")
(assert-equal test
&(dechunk-ok "4\r\nWiki\r\n5\r\npedia\r\n0\r\n\r\n")
"Wikipedia"
Expand Down Expand Up @@ -887,6 +909,21 @@
(req-chunked? "POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n")
"Request.chunked? true for Transfer-Encoding: chunked")

(assert-true test
(req-chunked?
"POST / HTTP/1.1\r\nTransfer-Encoding: gzip\r\nTransfer-Encoding: chunked\r\n\r\n")
"Request.chunked? true when chunked is on a second Transfer-Encoding line")

(assert-true test
(req-chunked?
"POST / HTTP/1.1\r\nTransfer-Encoding: gzip\r\ntransfer-encoding: chunked\r\n\r\n")
"Request.chunked? folds Transfer-Encoding lines that differ only in case")

(assert-false test
(req-chunked?
"POST / HTTP/1.1\r\nTransfer-Encoding: gzip\r\nTransfer-Encoding: deflate\r\n\r\n")
"Request.chunked? false when no Transfer-Encoding line lists chunked")

; ---- Cache-Control ----
(assert-equal test
3600
Expand Down