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
21 changes: 17 additions & 4 deletions http.carp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(load "git@github.com:carpentry-org/base64.carp@0.2.0")
(load "git@github.com:carpentry-org/uri@0.1.0")
(load "git@github.com:carpentry-org/time@0.5.0")
(load "git@github.com:carpentry-org/strbuf@0.1.0")
(load "git@github.com:carpentry-org/uri@0.5.1")
(load "git@github.com:carpentry-org/time@0.5.2")
(load "git@github.com:carpentry-org/strbuf@0.2.1")

(defmodule String
(doc find-crlf
Expand Down Expand Up @@ -310,12 +310,25 @@ attribute, whatever order the two appear in.")
(doc ignore-body? "checks whether the body of the request should be ignored.")
(defn ignore-body? [r] (= (verb r) "HEAD"))

(private target)
(hidden target)
; RFC 9112 §3.2.1: an origin-form target with an empty path is sent as `/`.
(defn target [u]
(let [s (URI.str u)]
(if (and (Maybe.nothing? (URI.path u))
(Maybe.nothing? (URI.scheme u))
(Maybe.nothing? (URI.host u))
(Maybe.nothing? (URI.user u))
(Maybe.nothing? (URI.port u)))
(String.append "/" &s)
s)))

(doc str "stringifies the request as it looks on the wire.")
(defn str [r]
(let-do [sb (StringBuf.create)]
(StringBuf.append-str &sb (verb r))
(StringBuf.append-char &sb \space)
(StringBuf.append-str &sb &(URI.str (uri r)))
(StringBuf.append-str &sb &(target (uri r)))
(StringBuf.append-char &sb \space)
(StringBuf.append-str &sb (version r))
(let [ks &(Map.keys (headers r))]
Expand Down
58 changes: 56 additions & 2 deletions test/http.carp
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,29 @@
"GET"
"request roundtrip preserves verb")

(assert-equal test
"GET / HTTP/1.1\r\n\r\n"
&(Request.str &(Request.get (URI.zero) [] {} @""))
"request-target of an empty path is /")

(assert-equal test
"GET /?a=1 HTTP/1.1\r\n\r\n"
&(Request.str
&(Request.get (URI.set-query (URI.zero) (Maybe.Just @"a=1")) [] {} @""))
"request-target of a query-only URI keeps its leading /")

(assert-equal test
"GET http://example.com HTTP/1.1\r\n\r\n"
&(Request.str
&(Request.get
(URI.set-scheme
(URI.set-host (URI.zero) (Maybe.Just @"example.com"))
(Maybe.Just @"http"))
[]
{}
@""))
"absolute-form request-target is left alone")

(assert-true test
(match (Cookie.parse "name=value")
(Result.Success c)
Expand Down Expand Up @@ -816,11 +839,11 @@
"encode & as %26")

(assert-equal test
"k=a%2bb"
"k=a%2Bb"
&(let-do [m (the (Map String String) {})]
(Map.put! &m "k" "a+b")
(Form.encode &m))
"encode literal + as %2b")
"encode literal + as %2B")

(assert-equal test
"key+name=val"
Expand All @@ -838,6 +861,37 @@
_ false))
"encode/parse roundtrip single pair")

(assert-equal test
"k=%E2%82%AC"
&(let-do [m (the (Map String String) {})]
(Map.put! &m "k" "€")
(Form.encode &m))
"encode a non-ASCII value one byte at a time")

(assert-equal test
"€"
&(let-do [m (the (Map String String) {})]
(Map.put! &m "k" "€")
(match (the (Result (Map String String) String)
(Form.parse &(Form.encode &m)))
(Result.Success parsed) (Map.get &parsed "k")
_ @""))
"encode/parse roundtrip a non-ASCII value")

(assert-equal test
"b%4"
&(match (the (Result (Map String String) String) (Form.parse "k=b%4"))
(Result.Success m) (Map.get &m "k")
_ @"")
"parse a value ending in a truncated percent escape")

(assert-equal test
"b%"
&(match (the (Result (Map String String) String) (Form.parse "k=b%"))
(Result.Success m) (Map.get &m "k")
_ @"")
"parse a value ending in a bare percent")

(assert-equal test
2
(match (the
Expand Down