Compare commits
2 Commits
4907f2abeb
...
ffb6ffde80
Author | SHA1 | Date | |
---|---|---|---|
ffb6ffde80 | |||
088b2e3fe0 |
50
src/ASCII.agda
Normal file
50
src/ASCII.agda
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
open import Agda.Primitive renaming (Set to Type)
|
||||||
|
|
||||||
|
open import Relation.Binary.PropositionalEquality hiding ([_])
|
||||||
|
|
||||||
|
open import Data.Bool
|
||||||
|
open import Data.Nat
|
||||||
|
|
||||||
|
import Data.List as L
|
||||||
|
import Data.Vec as V
|
||||||
|
import Data.String as S
|
||||||
|
import Data.Char as C
|
||||||
|
open L using (List)
|
||||||
|
open V using (Vec)
|
||||||
|
open S using (String)
|
||||||
|
open C using (Char)
|
||||||
|
|
||||||
|
open import Base
|
||||||
|
open import Bits-and-Bytes
|
||||||
|
open import NonEmpty
|
||||||
|
|
||||||
|
module ASCII where
|
||||||
|
|
||||||
|
list-is-ascii : List Char → Bool
|
||||||
|
list-is-ascii L.[] = true
|
||||||
|
list-is-ascii (c L.∷ chs) = (C.toℕ c <ᵇ 128) ∧ list-is-ascii chs
|
||||||
|
|
||||||
|
string-is-ascii : String → Type
|
||||||
|
string-is-ascii x = T (list-is-ascii (S.toList x))
|
||||||
|
|
||||||
|
encode-vec : (s : String) → {string-is-ascii s} → Vec Byte (S.length s)
|
||||||
|
encode-vec x {prf} = go (S.toList x)
|
||||||
|
where
|
||||||
|
go : (l : List Char) → Vec Byte (L.length l)
|
||||||
|
go L.[] = V.[]
|
||||||
|
go (ch L.∷ chs) = mkClip (C.toℕ ch) V.∷ go chs
|
||||||
|
|
||||||
|
encode-list⁺ : (s : String) → {string-is-ascii s} → {T (0 <ᵇ S.length s)} → List⁺ Byte
|
||||||
|
encode-list⁺ s {ascii} {nonempty} = vec-to-list⁺ v
|
||||||
|
where
|
||||||
|
open import Data.Nat.Properties
|
||||||
|
open S
|
||||||
|
|
||||||
|
len≢0 : length s ≢ 0
|
||||||
|
len≢0 = m<n⇒n≢0 (<ᵇ⇒< 0 (length s) nonempty)
|
||||||
|
|
||||||
|
v : Vec Byte (suc (pred (length s)))
|
||||||
|
v = subst (λ x → Vec Byte x) (sym (suc[pred[n]]≡n {length s} len≢0)) (encode-vec s {ascii})
|
||||||
|
|
||||||
|
encode-list : (s : String) → {string-is-ascii s} → List Byte
|
||||||
|
encode-list s {ascii} = V.toList (encode-vec s {ascii})
|
94
src/HTTP.agda
Normal file
94
src/HTTP.agda
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
{-# OPTIONS --allow-unsolved-metas #-}
|
||||||
|
|
||||||
|
open import Agda.Primitive renaming (Set to Type)
|
||||||
|
|
||||||
|
import Data.String as S
|
||||||
|
import Data.List as L
|
||||||
|
open S using (String)
|
||||||
|
open L using (List)
|
||||||
|
|
||||||
|
open import Base
|
||||||
|
|
||||||
|
open import NonEmpty
|
||||||
|
open import Bits-and-Bytes
|
||||||
|
|
||||||
|
import UTF-8
|
||||||
|
import ASCII
|
||||||
|
|
||||||
|
open import HTTP.URL
|
||||||
|
|
||||||
|
module HTTP where
|
||||||
|
|
||||||
|
module HTTP-Method where
|
||||||
|
data Method : Type where
|
||||||
|
GET HEAD POST PUT DELETE CONNECT OPTIONS TRACE PATCH : Method
|
||||||
|
|
||||||
|
instance
|
||||||
|
ShowMethod : Show Method
|
||||||
|
ShowMethod .show x = go x
|
||||||
|
where
|
||||||
|
go : Method → String
|
||||||
|
go GET = "GET"
|
||||||
|
go HEAD = "HEAD"
|
||||||
|
go POST = "POST"
|
||||||
|
go PUT = "PUT"
|
||||||
|
go DELETE = "DELETE"
|
||||||
|
go CONNECT = "CONNECT"
|
||||||
|
go OPTIONS = "OPTIONS"
|
||||||
|
go TRACE = "TRACE"
|
||||||
|
go PATCH = "PATCH"
|
||||||
|
|
||||||
|
name-of-method : Method → List⁺ Byte
|
||||||
|
name-of-method GET = ASCII.encode-list⁺ "GET"
|
||||||
|
name-of-method HEAD = ASCII.encode-list⁺ "HEAD"
|
||||||
|
name-of-method POST = ASCII.encode-list⁺ "POST"
|
||||||
|
name-of-method PUT = ASCII.encode-list⁺ "PUT"
|
||||||
|
name-of-method DELETE = ASCII.encode-list⁺ "DELETE"
|
||||||
|
name-of-method CONNECT = ASCII.encode-list⁺ "CONNECT"
|
||||||
|
name-of-method OPTIONS = ASCII.encode-list⁺ "OPTIONS"
|
||||||
|
name-of-method TRACE = ASCII.encode-list⁺ "TRACE"
|
||||||
|
name-of-method PATCH = ASCII.encode-list⁺ "PATCH"
|
||||||
|
|
||||||
|
http-methods : List⁺ Method
|
||||||
|
http-methods = GET ∷⁺ HEAD ∷⁺ POST ∷⁺ PUT ∷⁺ DELETE ∷⁺ CONNECT ∷⁺ OPTIONS ∷⁺ TRACE ∷⁺ [ PATCH ]⁺
|
||||||
|
open HTTP-Method public
|
||||||
|
|
||||||
|
module HTTP-Header where
|
||||||
|
record Header : Type where
|
||||||
|
field
|
||||||
|
name : List⁺ Byte
|
||||||
|
value : List⁺ Byte
|
||||||
|
|
||||||
|
instance
|
||||||
|
ShowHeader : Show Header
|
||||||
|
ShowHeader .show hdr = show ⦃ ShowByteList ⦄ (list⁺-to-list (hdr .name)) S.++ ": " S.++ show ⦃ ShowByteList ⦄ (list⁺-to-list (hdr .value))
|
||||||
|
where
|
||||||
|
open Header
|
||||||
|
open HTTP-Header public
|
||||||
|
|
||||||
|
module HTTP-Request where
|
||||||
|
data RequestTargetPath : Type where
|
||||||
|
OriginForm : Path → RequestTargetPath
|
||||||
|
AbsoluteForm : Authority → Path → RequestTargetPath
|
||||||
|
-- We do not care about authority-form nor asterisk form as we don't do any CONNECT nor OPTIONS requests
|
||||||
|
|
||||||
|
record RequestTarget : Type where
|
||||||
|
field
|
||||||
|
path : RequestTargetPath
|
||||||
|
query : Maybe String
|
||||||
|
|
||||||
|
data Version : Type where
|
||||||
|
HTTP11 : Version -- we only support 1.1
|
||||||
|
|
||||||
|
instance
|
||||||
|
ShowVersion : Show Version
|
||||||
|
ShowVersion .show HTTP11 = "HTTP/1.1"
|
||||||
|
|
||||||
|
record Request : Type where
|
||||||
|
field
|
||||||
|
method : Method
|
||||||
|
target : URL
|
||||||
|
version : Version
|
||||||
|
headers : List Header
|
||||||
|
content : Maybe (List Byte)
|
||||||
|
open HTTP-Request public
|
|
@ -1,7 +1,3 @@
|
||||||
{-# OPTIONS --allow-unsolved-metas #-}
|
|
||||||
|
|
||||||
open import Agda.Primitive renaming (Set to Type)
|
|
||||||
|
|
||||||
open import Data.Nat
|
open import Data.Nat
|
||||||
open import Data.Nat.Properties using (<ᵇ⇒<)
|
open import Data.Nat.Properties using (<ᵇ⇒<)
|
||||||
open import Data.Bool hiding (_<_)
|
open import Data.Bool hiding (_<_)
|
||||||
|
@ -11,22 +7,25 @@ open import Data.Product
|
||||||
import Data.String as S
|
import Data.String as S
|
||||||
import Data.List as L
|
import Data.List as L
|
||||||
import Data.Vec as V
|
import Data.Vec as V
|
||||||
|
import Data.Char as C
|
||||||
open S using (String)
|
open S using (String)
|
||||||
open L using (List)
|
open L using (List)
|
||||||
open V using (Vec)
|
open V using (Vec)
|
||||||
|
open C using (Char)
|
||||||
|
|
||||||
open import Base
|
open import Base
|
||||||
|
|
||||||
open import Indexed
|
|
||||||
open import NonEmpty
|
open import NonEmpty
|
||||||
open import Bits-and-Bytes
|
open import Bits-and-Bytes
|
||||||
import UTF-8
|
open import Indexed
|
||||||
|
|
||||||
open import Parsing (Byte)
|
open import Parsing (Byte)
|
||||||
open import Parse-HTTP.Methods
|
|
||||||
open import Parse-HTTP.Helpers
|
|
||||||
|
|
||||||
module Parse-HTTP where
|
open import HTTP
|
||||||
|
open import ParseHelpers
|
||||||
|
|
||||||
|
import UTF-8
|
||||||
|
import ASCII
|
||||||
|
|
||||||
|
module HTTP.Parse where
|
||||||
|
|
||||||
-- helper functions
|
-- helper functions
|
||||||
private
|
private
|
||||||
|
@ -34,7 +33,7 @@ private
|
||||||
decode-utf8⁺ bs = UTF-8.decode-string (list⁺-to-list bs)
|
decode-utf8⁺ bs = UTF-8.decode-string (list⁺-to-list bs)
|
||||||
|
|
||||||
module Parse-URL where
|
module Parse-URL where
|
||||||
open import Parse-HTTP.URL
|
open import HTTP.URL
|
||||||
|
|
||||||
-- commonly used sub-parsers
|
-- commonly used sub-parsers
|
||||||
private
|
private
|
||||||
|
@ -78,7 +77,7 @@ module Parse-URL where
|
||||||
parse-absolute-url : [ Parser (Authority × Path) ]
|
parse-absolute-url : [ Parser (Authority × Path) ]
|
||||||
parse-absolute-url =
|
parse-absolute-url =
|
||||||
(parse-scheme >?=′ (_== "http"))
|
(parse-scheme >?=′ (_== "http"))
|
||||||
<&⃗>□ exact (string-to-ascii-vec "://")
|
<&⃗>□ exact (ASCII.encode-vec "://")
|
||||||
<&⃗>□ parse-authority
|
<&⃗>□ parse-authority
|
||||||
<&>□ parse-path
|
<&>□ parse-path
|
||||||
|
|
||||||
|
@ -91,17 +90,8 @@ module Parse-URL where
|
||||||
open Parse-URL public
|
open Parse-URL public
|
||||||
|
|
||||||
module Parse-Header where
|
module Parse-Header where
|
||||||
open import Parse-HTTP.URL using (Authority)
|
open import HTTP.URL using (Authority)
|
||||||
|
open HTTP.Header
|
||||||
record Header : Type where
|
|
||||||
field
|
|
||||||
name : List⁺ Byte
|
|
||||||
value : List⁺ Byte
|
|
||||||
open Header
|
|
||||||
|
|
||||||
instance
|
|
||||||
ShowHeader : Show Header
|
|
||||||
ShowHeader .show hdr = show ⦃ ShowByteList ⦄ (list⁺-to-list (hdr .name)) S.++ ": " S.++ show ⦃ ShowByteList ⦄ (list⁺-to-list (hdr .value))
|
|
||||||
|
|
||||||
get-field : List⁺ Byte → List Header → Maybe (List⁺ Byte)
|
get-field : List⁺ Byte → List Header → Maybe (List⁺ Byte)
|
||||||
get-field field-name L.[] = cake
|
get-field field-name L.[] = cake
|
||||||
|
@ -112,12 +102,12 @@ module Parse-Header where
|
||||||
|
|
||||||
content-length : List Header → Maybe ℕ
|
content-length : List Header → Maybe ℕ
|
||||||
content-length headers =
|
content-length headers =
|
||||||
get-field (string-to-ascii-list⁺ "Content-Length") headers
|
get-field (ASCII.encode-list⁺ "Content-Length") headers
|
||||||
>>= parse-list⁺ number
|
>>= parse-list⁺ number
|
||||||
|
|
||||||
host : List Header → Maybe Authority
|
host : List Header → Maybe Authority
|
||||||
host headers =
|
host headers =
|
||||||
get-field (string-to-ascii-list⁺ "Host") headers
|
get-field (ASCII.encode-list⁺ "Host") headers
|
||||||
>>= parse-list⁺ parse-authority
|
>>= parse-list⁺ parse-authority
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -149,18 +139,7 @@ module Parse-Header where
|
||||||
open Parse-Header public
|
open Parse-Header public
|
||||||
|
|
||||||
module Parse-Request where
|
module Parse-Request where
|
||||||
open import Parse-HTTP.URL
|
open import HTTP.URL
|
||||||
|
|
||||||
data RequestTargetPath : Type where
|
|
||||||
OriginForm : Path → RequestTargetPath
|
|
||||||
AbsoluteForm : Authority → Path → RequestTargetPath
|
|
||||||
-- We do not care about authority-form nor asterisk form as we don't do any CONNECT nor OPTIONS requests
|
|
||||||
|
|
||||||
record RequestTarget : Type where
|
|
||||||
field
|
|
||||||
path : RequestTargetPath
|
|
||||||
query : Maybe String
|
|
||||||
open RequestTarget public
|
|
||||||
|
|
||||||
parse-request-target : [ Parser RequestTarget ]
|
parse-request-target : [ Parser RequestTarget ]
|
||||||
parse-request-target =
|
parse-request-target =
|
||||||
|
@ -174,24 +153,16 @@ module Parse-Request where
|
||||||
parse-absolute-form =
|
parse-absolute-form =
|
||||||
parse-absolute-url >$=′ λ (auth , path) → AbsoluteForm auth path
|
parse-absolute-url >$=′ λ (auth , path) → AbsoluteForm auth path
|
||||||
|
|
||||||
|
parse-http-version : [ Parser Version ]
|
||||||
|
parse-http-version = (λ _ → HTTP11) <$>′ exact (ASCII.encode-vec "HTTP/1.1")
|
||||||
|
|
||||||
data HTTP-Version : Type where
|
parse-specific-http-method : Method → [ Parser Method ]
|
||||||
HTTP11 : HTTP-Version -- we only support 1.1
|
|
||||||
|
|
||||||
instance
|
|
||||||
ShowHTTP-Version : Show HTTP-Version
|
|
||||||
ShowHTTP-Version .show HTTP11 = "HTTP/1.1"
|
|
||||||
|
|
||||||
parse-http-version : [ Parser HTTP-Version ]
|
|
||||||
parse-http-version = (λ _ → HTTP11) <$>′ exact (string-to-ascii-vec "HTTP/1.1")
|
|
||||||
|
|
||||||
parse-specific-http-method : HTTP-Method → [ Parser HTTP-Method ]
|
|
||||||
parse-specific-http-method m = (λ _ → m) <$>′ exact (proj₂ (list⁺-to-vec (name-of-method m)))
|
parse-specific-http-method m = (λ _ → m) <$>′ exact (proj₂ (list⁺-to-vec (name-of-method m)))
|
||||||
|
|
||||||
parse-http-method : [ Parser HTTP-Method ]
|
parse-http-method : [ Parser Method ]
|
||||||
parse-http-method = foldr (λ m p → parse-specific-http-method m <|>′ p) fail http-methods
|
parse-http-method = foldr (λ m p → parse-specific-http-method m <|>′ p) fail http-methods
|
||||||
|
|
||||||
parse-request-line : [ Parser (HTTP-Method × RequestTarget × HTTP-Version) ]
|
parse-request-line : [ Parser (Method × RequestTarget × Version) ]
|
||||||
parse-request-line =
|
parse-request-line =
|
||||||
parse-http-method
|
parse-http-method
|
||||||
<&>□ (
|
<&>□ (
|
||||||
|
@ -206,13 +177,6 @@ module Parse-Request where
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
record Request : Type where
|
|
||||||
field
|
|
||||||
method : HTTP-Method
|
|
||||||
target : URL
|
|
||||||
version : HTTP-Version
|
|
||||||
headers : List Header
|
|
||||||
content : Maybe (List Byte)
|
|
||||||
|
|
||||||
parse-request : [ Parser Request ]
|
parse-request : [ Parser Request ]
|
||||||
parse-request =
|
parse-request =
|
||||||
|
@ -249,7 +213,7 @@ module Parse-Request where
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
find-target : ((HTTP-Method × RequestTarget × HTTP-Version) × List⁺ Header) → Maybe (HTTP-Method × URL × HTTP-Version × List⁺ Header)
|
find-target : ((Method × RequestTarget × Version) × List⁺ Header) → Maybe (Method × URL × Version × List⁺ Header)
|
||||||
find-target ((method , record { path = OriginForm path ; query = query } , ver) , headers) =
|
find-target ((method , record { path = OriginForm path ; query = query } , ver) , headers) =
|
||||||
(λ auth → method , (http:// auth / path ¿ query) , ver , headers) <$> Parse-Header.host (list⁺-to-list headers)
|
(λ auth → method , (http:// auth / path ¿ query) , ver , headers) <$> Parse-Header.host (list⁺-to-list headers)
|
||||||
find-target ((method , record { path = AbsoluteForm auth path ; query = query } , ver) , headers) =
|
find-target ((method , record { path = AbsoluteForm auth path ; query = query } , ver) , headers) =
|
|
@ -14,23 +14,19 @@ open import Base
|
||||||
open import Bits-and-Bytes
|
open import Bits-and-Bytes
|
||||||
open import Parsing
|
open import Parsing
|
||||||
import UTF-8
|
import UTF-8
|
||||||
|
import ASCII
|
||||||
open import NonEmpty
|
open import NonEmpty
|
||||||
|
|
||||||
open import Parse-HTTP
|
open import HTTP
|
||||||
open import Parse-HTTP.Helpers
|
open import ParseHelpers
|
||||||
open import Parse-HTTP.Methods
|
open import HTTP.URL
|
||||||
open import Parse-HTTP.URL
|
open import HTTP.Parse
|
||||||
|
|
||||||
module Parse-HTTP.Test where
|
module HTTP.Test where
|
||||||
|
|
||||||
enc : (s : String) → V.Vec Byte (L.length (UTF-8.encode-string s))
|
enc : (s : String) → V.Vec Byte (L.length (UTF-8.encode-string s))
|
||||||
enc x = V.fromList (UTF-8.encode-string x)
|
enc x = V.fromList (UTF-8.encode-string x)
|
||||||
|
|
||||||
module Test-Helpers where
|
|
||||||
test-percent-encoding : percent-encoded .parse (enc "%69abc")
|
|
||||||
≡ real ⟨ < 0x69 > , _ , enc "abc" ⟩
|
|
||||||
test-percent-encoding = refl
|
|
||||||
|
|
||||||
module Test-HTTP where
|
module Test-HTTP where
|
||||||
test-GET : (parse-http-method .parse (enc "GET /mjau.html"))
|
test-GET : (parse-http-method .parse (enc "GET /mjau.html"))
|
||||||
≡ real ⟨ GET , _ , enc " /mjau.html" ⟩
|
≡ real ⟨ GET , _ , enc " /mjau.html" ⟩
|
||||||
|
@ -97,7 +93,7 @@ module Test-HTTP where
|
||||||
test-url = refl
|
test-url = refl
|
||||||
|
|
||||||
test-header : parse-header .parse (enc "Content-Length: 6\r\n")
|
test-header : parse-header .parse (enc "Content-Length: 6\r\n")
|
||||||
≡ real ⟨ record { name = string-to-ascii-list⁺ "Content-Length" ; value = string-to-ascii-list⁺ "6" } , _ , enc "\r\n" ⟩
|
≡ real ⟨ record { name = ASCII.encode-list⁺ "Content-Length" ; value = ASCII.encode-list⁺ "6" } , _ , enc "\r\n" ⟩
|
||||||
test-header = refl
|
test-header = refl
|
||||||
|
|
||||||
test-request : parse-request .parse (enc "POST /site/index.html?mjau HTTP/1.1\r\nContent-Length: 6\r\nHost: agda.nu\r\n\r\nmjau:)\r\n")
|
test-request : parse-request .parse (enc "POST /site/index.html?mjau HTTP/1.1\r\nContent-Length: 6\r\nHost: agda.nu\r\n\r\nmjau:)\r\n")
|
||||||
|
@ -107,10 +103,10 @@ module Test-HTTP where
|
||||||
; target = http:// "agda.nu" ꞉ 80 / ("site" / "index.html" / $) ¿ real "mjau"
|
; target = http:// "agda.nu" ꞉ 80 / ("site" / "index.html" / $) ¿ real "mjau"
|
||||||
; version = HTTP11
|
; version = HTTP11
|
||||||
; headers =
|
; headers =
|
||||||
record { name = string-to-ascii-list⁺ "Content-Length" ; value = string-to-ascii-list⁺ "6" }
|
record { name = ASCII.encode-list⁺ "Content-Length" ; value = ASCII.encode-list⁺ "6" }
|
||||||
L.∷ record { name = string-to-ascii-list⁺ "Host" ; value = string-to-ascii-list⁺ "agda.nu" }
|
L.∷ record { name = ASCII.encode-list⁺ "Host" ; value = ASCII.encode-list⁺ "agda.nu" }
|
||||||
L.∷ L.[]
|
L.∷ L.[]
|
||||||
; content = real (string-to-ascii-list "mjau:)")
|
; content = real (ASCII.encode-list "mjau:)")
|
||||||
}
|
}
|
||||||
, _
|
, _
|
||||||
, enc "\r\n"
|
, enc "\r\n"
|
|
@ -6,7 +6,8 @@ open import Data.Bool
|
||||||
|
|
||||||
open import Base
|
open import Base
|
||||||
|
|
||||||
module Parse-HTTP.URL where
|
module HTTP.URL where
|
||||||
|
|
||||||
record Authority : Type where
|
record Authority : Type where
|
||||||
constructor _꞉_
|
constructor _꞉_
|
||||||
field
|
field
|
|
@ -3,11 +3,14 @@
|
||||||
module main where
|
module main where
|
||||||
|
|
||||||
open import Agda.Primitive renaming (Set to Type)
|
open import Agda.Primitive renaming (Set to Type)
|
||||||
|
|
||||||
import Data.String as S
|
import Data.String as S
|
||||||
import Data.Vec as V
|
import Data.Vec as V
|
||||||
import Data.List as L
|
import Data.List as L
|
||||||
open S using (String)
|
open S using (String)
|
||||||
open L using (List)
|
open L using (List)
|
||||||
|
open V using (Vec)
|
||||||
|
|
||||||
open import Data.Product using (_×_; proj₁; proj₂; _,_)
|
open import Data.Product using (_×_; proj₁; proj₂; _,_)
|
||||||
open import Data.Nat hiding (_<_; _>_)
|
open import Data.Nat hiding (_<_; _>_)
|
||||||
open import Data.Unit using (⊤; tt)
|
open import Data.Unit using (⊤; tt)
|
||||||
|
@ -20,14 +23,16 @@ open import Socket
|
||||||
open import Indexed
|
open import Indexed
|
||||||
open import Parsing (Byte)
|
open import Parsing (Byte)
|
||||||
open import NonEmpty
|
open import NonEmpty
|
||||||
open import Parse-HTTP
|
|
||||||
open import Parse-HTTP.URL hiding (_/_)
|
import HTTP
|
||||||
open import Parse-HTTP.Methods
|
import HTTP.Parse
|
||||||
open import Parse-HTTP.Helpers
|
import HTTP.URL as URL
|
||||||
open import HTML
|
open import HTML
|
||||||
import HTML.Syntax
|
import HTML.Syntax
|
||||||
import HTML.Render
|
import HTML.Render
|
||||||
|
|
||||||
import UTF-8
|
import UTF-8
|
||||||
|
import ASCII
|
||||||
|
|
||||||
data Content-Type : Type where
|
data Content-Type : Type where
|
||||||
text/plain text/html : Content-Type
|
text/plain text/html : Content-Type
|
||||||
|
@ -41,12 +46,12 @@ record Response : Type where
|
||||||
field
|
field
|
||||||
status-code : ℕ
|
status-code : ℕ
|
||||||
status : String
|
status : String
|
||||||
headers : List Header -- all headers besides Content-Type and Content-Length
|
headers : List HTTP.Header -- all headers besides Content-Type and Content-Length
|
||||||
content : Maybe (Content-Type × List Byte) -- mime type and data
|
content : Maybe (Content-Type × List Byte) -- mime type and data
|
||||||
open Response
|
open Response
|
||||||
|
|
||||||
crlfᵇ : List Byte
|
crlfᵇ : List Byte
|
||||||
crlfᵇ = string-to-ascii-list "\r\n"
|
crlfᵇ = ASCII.encode-list "\r\n"
|
||||||
|
|
||||||
encode-response : Response → List Byte
|
encode-response : Response → List Byte
|
||||||
encode-response res =
|
encode-response res =
|
||||||
|
@ -57,26 +62,26 @@ encode-response res =
|
||||||
status-line : String
|
status-line : String
|
||||||
status-line = "HTTP/1.1 " S.++ show (res .status-code) S.++ " " S.++ (res .status)
|
status-line = "HTTP/1.1 " S.++ show (res .status-code) S.++ " " S.++ (res .status)
|
||||||
|
|
||||||
extra-headers : List Header
|
extra-headers : List HTTP.Header
|
||||||
extra-headers = case res .content of λ where
|
extra-headers = case res .content of λ where
|
||||||
cake → L.[]
|
cake → L.[]
|
||||||
(real (content-type , content)) →
|
(real (content-type , content)) →
|
||||||
record
|
record
|
||||||
{ name = string-to-ascii-list⁺ "Content-Type"
|
{ name = ASCII.encode-list⁺ "Content-Type"
|
||||||
; value = UTF-8.encode-string (show content-type) ++⁺ string-to-ascii-list⁺ "; charset=utf-8"
|
; value = UTF-8.encode-string (show content-type) ++⁺ ASCII.encode-list⁺ "; charset=utf-8"
|
||||||
}
|
}
|
||||||
L.∷ record
|
L.∷ record
|
||||||
{ name = string-to-ascii-list⁺ "Content-Length"
|
{ name = ASCII.encode-list⁺ "Content-Length"
|
||||||
; value =
|
; value =
|
||||||
list-to-list⁺? (UTF-8.encode-string (show (L.length content)))
|
list-to-list⁺? (UTF-8.encode-string (show (L.length content)))
|
||||||
or-else (string-to-ascii-list⁺ "mjau")
|
or-else (ASCII.encode-list⁺ "mjau")
|
||||||
}
|
}
|
||||||
L.∷ L.[]
|
L.∷ L.[]
|
||||||
|
|
||||||
encode-header : Header → List Byte
|
encode-header : HTTP.Header → List Byte
|
||||||
encode-header hdr = list⁺-to-list (hdr .name ⁺++⁺ string-to-ascii-list⁺ ": " ⁺++⁺ hdr .value)
|
encode-header hdr = list⁺-to-list (hdr .name ⁺++⁺ ASCII.encode-list⁺ ": " ⁺++⁺ hdr .value)
|
||||||
where
|
where
|
||||||
open Header
|
open HTTP.Header
|
||||||
|
|
||||||
encoded-headers : List Byte
|
encoded-headers : List Byte
|
||||||
encoded-headers =
|
encoded-headers =
|
||||||
|
@ -101,7 +106,7 @@ encode-response res =
|
||||||
{ status-code = 400
|
{ status-code = 400
|
||||||
; status = "Bad Request"
|
; status = "Bad Request"
|
||||||
; headers = L.[]
|
; headers = L.[]
|
||||||
; content = real (text/plain , string-to-ascii-list "what is bro doing")
|
; content = real (text/plain , ASCII.encode-list "what is bro doing")
|
||||||
}
|
}
|
||||||
|
|
||||||
404-not-found : Response
|
404-not-found : Response
|
||||||
|
@ -110,7 +115,7 @@ encode-response res =
|
||||||
{ status-code = 404
|
{ status-code = 404
|
||||||
; status = "Not Found"
|
; status = "Not Found"
|
||||||
; headers = L.[]
|
; headers = L.[]
|
||||||
; content = real (text/plain , string-to-ascii-list "not found :(")
|
; content = real (text/plain , ASCII.encode-list "not found :(")
|
||||||
}
|
}
|
||||||
|
|
||||||
module Pages where
|
module Pages where
|
||||||
|
@ -129,7 +134,7 @@ module Pages where
|
||||||
< span style= "color: yellow;" > "hen" </ span > ,
|
< span style= "color: yellow;" > "hen" </ span > ,
|
||||||
< span style= "color: fuchsia;" > "ttp" </ span >
|
< span style= "color: fuchsia;" > "ttp" </ span >
|
||||||
</ h 1 > ,
|
</ h 1 > ,
|
||||||
< h 3 > "what is this?" </ h 1 > ,
|
< h 3 > "what is this?" </ h 3 > ,
|
||||||
< p >
|
< p >
|
||||||
"henttp is an HTTP server, HTML DSL and (soon to be) router, written in Agda"
|
"henttp is an HTTP server, HTML DSL and (soon to be) router, written in Agda"
|
||||||
</ p >
|
</ p >
|
||||||
|
@ -141,17 +146,31 @@ module Pages where
|
||||||
where
|
where
|
||||||
open HTML.Syntax
|
open HTML.Syntax
|
||||||
|
|
||||||
handle-request : Request → IO Response
|
handle-request : HTTP.Request → IO Response
|
||||||
handle-request req =
|
handle-request req =
|
||||||
case req .target .path of λ where
|
case req .target .path of λ where
|
||||||
|
($) →
|
||||||
|
pure (
|
||||||
|
record
|
||||||
|
{ status-code = 302
|
||||||
|
; status = "Found"
|
||||||
|
; headers =
|
||||||
|
record
|
||||||
|
{ name = ASCII.encode-list⁺ "Location"
|
||||||
|
; value = ASCII.encode-list⁺ ("/" S.++ show ("index.html" / $))
|
||||||
|
} L.∷ L.[]
|
||||||
|
; content = cake
|
||||||
|
}
|
||||||
|
)
|
||||||
("index.html" / $) →
|
("index.html" / $) →
|
||||||
pure (
|
pure (
|
||||||
200-ok text/html (HTML.Render.render-element (Pages.render-index (req .target .query)))
|
200-ok text/html (HTML.Render.render-element (Pages.render-index (req .target .query)))
|
||||||
)
|
)
|
||||||
_ → pure 404-not-found
|
_ → pure 404-not-found
|
||||||
where
|
where
|
||||||
open Request using (target)
|
open HTTP.Request using (target)
|
||||||
open import Parse-HTTP.URL
|
open URL
|
||||||
|
open import ParseHelpers using (number)
|
||||||
|
|
||||||
|
|
||||||
handle : Socket → IO (List Byte)
|
handle : Socket → IO (List Byte)
|
||||||
|
@ -159,7 +178,7 @@ handle sock = do
|
||||||
putStrLn "handle: new connection‼"
|
putStrLn "handle: new connection‼"
|
||||||
let got = get-bytes sock
|
let got = get-bytes sock
|
||||||
|
|
||||||
case parse-request .parse (V.fromList got) of λ where
|
case HTTP.Parse.parse-request .parse (V.fromList got) of λ where
|
||||||
(real ⟨ req , _ , _ ⟩) →
|
(real ⟨ req , _ , _ ⟩) →
|
||||||
(do
|
(do
|
||||||
putStrLn "handle: got request"
|
putStrLn "handle: got request"
|
||||||
|
@ -178,7 +197,7 @@ handle sock = do
|
||||||
encode-response <$> pure 400-bad-request
|
encode-response <$> pure 400-bad-request
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
open Request
|
open HTTP.Request
|
||||||
|
|
||||||
run-port : ℕ
|
run-port : ℕ
|
||||||
run-port = 1337
|
run-port = 1337
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
open import Agda.Primitive renaming (Set to Type)
|
|
||||||
open import Data.Product
|
|
||||||
open import Data.Nat
|
|
||||||
|
|
||||||
import Data.String as S
|
|
||||||
open S using (String)
|
|
||||||
|
|
||||||
open import Bits-and-Bytes
|
|
||||||
open import NonEmpty
|
|
||||||
open import Base
|
|
||||||
|
|
||||||
open import Parse-HTTP.Helpers
|
|
||||||
|
|
||||||
module Parse-HTTP.Methods where
|
|
||||||
|
|
||||||
data HTTP-Method : Type where
|
|
||||||
GET HEAD POST PUT DELETE CONNECT OPTIONS TRACE PATCH : HTTP-Method
|
|
||||||
|
|
||||||
instance
|
|
||||||
ShowMethod : Show HTTP-Method
|
|
||||||
ShowMethod .show x = go x
|
|
||||||
where
|
|
||||||
go : HTTP-Method → String
|
|
||||||
go GET = "GET"
|
|
||||||
go HEAD = "HEAD"
|
|
||||||
go POST = "POST"
|
|
||||||
go PUT = "PUT"
|
|
||||||
go DELETE = "DELETE"
|
|
||||||
go CONNECT = "CONNECT"
|
|
||||||
go OPTIONS = "OPTIONS"
|
|
||||||
go TRACE = "TRACE"
|
|
||||||
go PATCH = "PATCH"
|
|
||||||
|
|
||||||
name-of-method : HTTP-Method → List⁺ Byte
|
|
||||||
name-of-method GET = string-to-ascii-list⁺ "GET"
|
|
||||||
name-of-method HEAD = string-to-ascii-list⁺ "HEAD"
|
|
||||||
name-of-method POST = string-to-ascii-list⁺ "POST"
|
|
||||||
name-of-method PUT = string-to-ascii-list⁺ "PUT"
|
|
||||||
name-of-method DELETE = string-to-ascii-list⁺ "DELETE"
|
|
||||||
name-of-method CONNECT = string-to-ascii-list⁺ "CONNECT"
|
|
||||||
name-of-method OPTIONS = string-to-ascii-list⁺ "OPTIONS"
|
|
||||||
name-of-method TRACE = string-to-ascii-list⁺ "TRACE"
|
|
||||||
name-of-method PATCH = string-to-ascii-list⁺ "PATCH"
|
|
||||||
|
|
||||||
http-methods : List⁺ HTTP-Method
|
|
||||||
http-methods = GET ∷⁺ HEAD ∷⁺ POST ∷⁺ PUT ∷⁺ DELETE ∷⁺ CONNECT ∷⁺ OPTIONS ∷⁺ TRACE ∷⁺ [ PATCH ]⁺
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import UTF-8
|
||||||
|
|
||||||
open import Parsing (Byte)
|
open import Parsing (Byte)
|
||||||
|
|
||||||
module Parse-HTTP.Helpers where
|
module ParseHelpers where
|
||||||
|
|
||||||
-- Bytes are compared against codepoints, meaning any non-ascii Chars will be death
|
-- Bytes are compared against codepoints, meaning any non-ascii Chars will be death
|
||||||
_is_ : Byte → Char → Bool
|
_is_ : Byte → Char → Bool
|
||||||
|
@ -33,34 +33,6 @@ x is c = x .value == C.toℕ c
|
||||||
_between_and_ : Byte → Char → Char → Bool
|
_between_and_ : Byte → Char → Char → Bool
|
||||||
x between a and b = (C.toℕ a ≤ᵇ x .value) ∧ (x .value ≤ᵇ C.toℕ b)
|
x between a and b = (C.toℕ a ≤ᵇ x .value) ∧ (x .value ≤ᵇ C.toℕ b)
|
||||||
|
|
||||||
list-is-ascii : List Char → Bool
|
|
||||||
list-is-ascii L.[] = true
|
|
||||||
list-is-ascii (c L.∷ chs) = (C.toℕ c <ᵇ 128) ∧ list-is-ascii chs
|
|
||||||
|
|
||||||
string-is-ascii : String → Type
|
|
||||||
string-is-ascii x = T (list-is-ascii (S.toList x))
|
|
||||||
|
|
||||||
string-to-ascii-vec : (s : String) → {string-is-ascii s} → Vec Byte (S.length s)
|
|
||||||
string-to-ascii-vec x {prf} = go (S.toList x)
|
|
||||||
where
|
|
||||||
go : (l : List Char) → Vec Byte (L.length l)
|
|
||||||
go L.[] = V.[]
|
|
||||||
go (ch L.∷ chs) = mkClip (C.toℕ ch) V.∷ go chs
|
|
||||||
|
|
||||||
string-to-ascii-list⁺ : (s : String) → {string-is-ascii s} → {T (0 <ᵇ S.length s)} → List⁺ Byte
|
|
||||||
string-to-ascii-list⁺ s {ascii} {nonempty} = vec-to-list⁺ v
|
|
||||||
where
|
|
||||||
open import Data.Nat.Properties
|
|
||||||
open S
|
|
||||||
|
|
||||||
len≢0 : length s ≢ 0
|
|
||||||
len≢0 = m<n⇒n≢0 (<ᵇ⇒< 0 (length s) nonempty)
|
|
||||||
|
|
||||||
v : Vec Byte (suc (pred (length s)))
|
|
||||||
v = subst (λ x → Vec Byte x) (sym (suc[pred[n]]≡n {length s} len≢0)) (string-to-ascii-vec s {ascii})
|
|
||||||
|
|
||||||
string-to-ascii-list : (s : String) → {string-is-ascii s} → List Byte
|
|
||||||
string-to-ascii-list s {ascii} = V.toList (string-to-ascii-vec s {ascii})
|
|
||||||
|
|
||||||
any-of : String → [ Parser Byte ]
|
any-of : String → [ Parser Byte ]
|
||||||
any-of x = L.foldr (λ ch p → ((_is ch) <?>′ any₁) <|>′ p) fail (S.toList x)
|
any-of x = L.foldr (λ ch p → ((_is ch) <?>′ any₁) <|>′ p) fail (S.toList x)
|
Loading…
Reference in New Issue
Block a user