]> gitweb @ CieloNegro.org - Lucu.git/blobdiff - Network/HTTP/Lucu/Preprocess.hs
Still working on Router arrow
[Lucu.git] / Network / HTTP / Lucu / Preprocess.hs
index 3a02ad8f194c4a0b6e41cd850e59725a0030752f..17a23b2435e50527674f3d51f75fb214d3dbad6b 100644 (file)
@@ -3,6 +3,7 @@
   , OverloadedStrings
   , RecordWildCards
   , UnicodeSyntax
+  , ViewPatterns
   #-}
 module Network.HTTP.Lucu.Preprocess
     ( AugmentedRequest(..)
@@ -11,19 +12,23 @@ module Network.HTTP.Lucu.Preprocess
     )
     where
 import Control.Applicative
+import Control.Applicative.Unicode
 import Control.Monad
 import Control.Monad.State.Strict
 import Data.Ascii (Ascii)
 import qualified Data.Ascii as A
 import qualified Data.ByteString.Char8 as C8
+import Data.CaseInsensitive (CI)
+import qualified Data.CaseInsensitive as CI
+import Data.Convertible.Base
+import Data.Convertible.Instances.Text ()
 import Data.Maybe
 import Data.Text (Text)
-import qualified Data.Text as T
 import qualified Data.Text.Encoding as T
 import Network.HTTP.Lucu.Headers
 import Network.HTTP.Lucu.HttpVersion
 import Network.HTTP.Lucu.Request
-import Network.HTTP.Lucu.Response
+import Network.HTTP.Lucu.Response.StatusCode
 import Network.Socket
 import Network.URI
 import Prelude.Unicode
@@ -33,7 +38,6 @@ data AugmentedRequest
         arRequest          ∷ !Request
       , arInitialStatus    ∷ !SomeStatusCode
       , arWillChunkBody    ∷ !Bool
-      , arWillDiscardBody  ∷ !Bool
       , arWillClose        ∷ !Bool
       , arExpectedContinue ∷ !Bool
       , arReqBodyLength    ∷ !(Maybe RequestBodyLength)
@@ -44,8 +48,8 @@ data RequestBodyLength
     | Chunked
     deriving (Eq, Show)
 
-preprocess ∷ Text → PortNumber → Request → AugmentedRequest
-preprocess localHost localPort req@(Request {..})
+preprocess ∷ CI Text → PortNumber → Bool → Request → AugmentedRequest
+preprocess localHost localPort isSSL req@(Request {..})
     = execState go initialAR
     where
       initialAR ∷ AugmentedRequest
@@ -53,7 +57,6 @@ preprocess localHost localPort req@(Request {..})
                     arRequest          = req
                   , arInitialStatus    = fromStatusCode OK
                   , arWillChunkBody    = False
-                  , arWillDiscardBody  = False
                   , arWillClose        = False
                   , arExpectedContinue = False
                   , arReqBodyLength    = Nothing
@@ -61,6 +64,7 @@ preprocess localHost localPort req@(Request {..})
       go ∷ State AugmentedRequest ()
       go = do examineHttpVersion
               examineMethod
+              examineScheme isSSL
               examineAuthority localHost localPort
               examineHeaders
               examineBodyLength
@@ -98,13 +102,27 @@ examineMethod
     = do req ← gets arRequest
          case reqMethod req of
            GET    → return ()
-           HEAD   → modify $ \ar → ar { arWillDiscardBody = True }
+           HEAD   → return ()
            POST   → return ()
            PUT    → return ()
            DELETE → return ()
            _      → setStatus NotImplemented
 
-examineAuthority ∷ Text → PortNumber → State AugmentedRequest ()
+examineScheme ∷ Bool → State AugmentedRequest ()
+examineScheme isSSL
+    = do req ← gets arRequest
+         when (null ∘ uriScheme $ reqURI req) $
+             let uri' = (reqURI req) {
+                          uriScheme = if isSSL then
+                                          "https:"
+                                      else
+                                          "http:"
+                        }
+                 req' = req { reqURI = uri' }
+             in
+               setRequest req'
+
+examineAuthority ∷ CI Text → PortNumber → State AugmentedRequest ()
 examineAuthority localHost localPort
     = do req ← gets arRequest
          when (isNothing $ uriAuthority $ reqURI req) $
@@ -114,42 +132,55 @@ examineAuthority localHost localPort
                HttpVersion 1 0
                    → let host = localHost
                          port = case localPort of
-                                  80 → ""
-                                  n  → A.unsafeFromString $ ':':show n
+                                  n | Just n ≡ defaultPort (reqURI req)
+                                        → ""
+                                  n     → A.unsafeFromString $ ':':show n
                          req' = updateAuthority host port req
                      in
                        setRequest req'
-               -- HTTP/1.1 requests MUST have a Host header.
+               -- HTTP/1.1 requests MUST have a Host header, but if
+               -- the requested URI has an authority, the value of
+               -- Host header must be ignored. See:
+               -- http://tools.ietf.org/html/rfc2616#section-5.2
                HttpVersion 1 1
                    → case getHeader "Host" req of
                         Just str
-                            → let (host, port)
-                                       = parseHost str
-                                  req' = updateAuthority host port req
-                              in
-                                setRequest req'
+                            | isNothing ∘ uriAuthority ∘ reqURI $ req
+                                → let (host, port)
+                                           = parseHost str
+                                      req' = updateAuthority host port req
+                                  in
+                                    setRequest req'
+                            | otherwise
+                                → return ()
                         Nothing
                             → setStatus BadRequest
                -- Should never reach here...
                ver → fail ("internal error: unknown version: " ⧺ show ver)
 
-parseHost ∷ Ascii → (Text, Ascii)
+defaultPort ∷ Alternative f ⇒ URI → f PortNumber
+{-# INLINEABLE defaultPort #-}
+defaultPort (uriScheme → s)
+    | s ≡ "http:"  = pure 80
+    | s ≡ "https:" = pure 443
+    | otherwise    = (∅)
+
+parseHost ∷ Ascii → (CI Text, Ascii)
 parseHost hp
-    = let (h, p) = C8.break (≡ ':') $ A.toByteString hp
+    = let (h, p) = C8.break (≡ ':') $ cs hp
           -- FIXME: should decode punycode here.
-          hText  = T.decodeUtf8 h
+          hText  = CI.mk $ T.decodeUtf8 h
           pAscii = A.unsafeFromByteString p
       in
         (hText, pAscii)
 
-updateAuthority ∷ Text → Ascii → Request → Request
-updateAuthority host port req
-    = let uri  = reqURI req
-          uri' = uri {
+updateAuthority ∷ CI Text → Ascii → Request → Request
+updateAuthority host port req@(Request {..})
+    = let uri' = reqURI {
                    uriAuthority = Just URIAuth {
                                     uriUserInfo = ""
-                                  , uriRegName  = T.unpack host
-                                  , uriPort     = A.toString port
+                                  , uriRegName  = cs $ CI.original host
+                                  , uriPort     = cs port
                                   }
                  }
       in
@@ -177,7 +208,7 @@ examineHeaders
                | otherwise
                    → setStatus NotImplemented
 
-         case A.toByteString <$> getHeader "Content-Length" req of
+         case cs <$> getHeader "Content-Length" req of
            Nothing    → return ()
            Just value → case C8.readInt value of
                            Just (len, garbage)
@@ -195,7 +226,7 @@ examineBodyLength ∷ State AugmentedRequest ()
 examineBodyLength
     = do req ← gets arRequest
          len ← gets arReqBodyLength
-         if reqMustHaveBody req then
+         if reqHasBody req then
              -- POST and PUT requests must have an entity body.
              when (isNothing len)
                  $ setStatus LengthRequired