From 383d2cbe240600a86ab99fdefcea4e913d171ec6 Mon Sep 17 00:00:00 2001
From: Simon Hengel <sol@typeful.net>
Date: Thu, 24 Apr 2014 22:51:02 +0800
Subject: [PATCH] Depend on http-kit >= 0.2

---
 sproxy.cabal        |  2 +-
 src/Authenticate.hs | 17 ++++++++---------
 src/HTTP.hs         | 47 +++++++++--------------------------------------
 src/Proxy.hs        | 32 ++++++++++++++------------------
 4 files changed, 32 insertions(+), 66 deletions(-)

diff --git a/sproxy.cabal b/sproxy.cabal
index 08e1d61..91adf5d 100644
--- a/sproxy.cabal
+++ b/sproxy.cabal
@@ -49,7 +49,7 @@ executable sproxy
                        unix,
                        utf8-string,
                        x509,
-                       http-kit,
+                       http-kit >= 0.2,
                        yaml >= 0.8
   default-language:    Haskell2010
   ghc-options:         -Wall -threaded -O2
diff --git a/src/Authenticate.hs b/src/Authenticate.hs
index 7d4c218..15a69a9 100644
--- a/src/Authenticate.hs
+++ b/src/Authenticate.hs
@@ -30,8 +30,7 @@ import           System.Posix.Types (EpochTime)
 import           System.Posix.Time (epochTime)
 import           Data.Digest.Pure.SHA (hmacSha1, showDigest)
 
-import           Network.HTTP.Toolkit.Header
-import           Network.HTTP.Toolkit.Request
+import           Network.HTTP.Toolkit
 
 import           Type
 import           Cookies
@@ -90,19 +89,19 @@ instance FromJSON UserInfo where
 
 -- https://wiki.zalora.com/Main_Page -> https://wiki.zalora.com/
 -- Note that this always uses https:
-rootURI :: RequestHeader -> URI.URI
-rootURI (MessageHeader _ headers) =
+rootURI :: Request a -> URI.URI
+rootURI (Request _ _ headers _) =
   let host = cs $ fromMaybe (error "Host header not found") $ lookup "Host" headers
   in URI.URI "https:" (Just $ URI.URIAuth "" host "") "/" "" ""
 
-redirectForAuth :: AuthConfig -> RequestHeader -> SendData -> IO ()
-redirectForAuth c request@(MessageHeader (_, path_) _) send = do
+redirectForAuth :: AuthConfig -> Request a -> SendData -> IO ()
+redirectForAuth c request@(Request _ path_ _ _) send = do
   let redirectUri = rootURI request
       path = urlEncode True path_
       authURL = "https://accounts.google.com/o/oauth2/auth?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&state=" ++ cs path ++ "&redirect_uri=" ++ (cs $ show $ redirectUri) ++ "&response_type=code&client_id=" ++ authConfigClientID c ++ "&approval_prompt=force&access_type=offline"
-  sendResponse send found302 [("Location", UTF8.fromString $ authURL)] ""
+  sendResponse_ send found302 [("Location", UTF8.fromString $ authURL)] ""
 
-authenticate :: AuthConfig -> SendData -> RequestHeader -> ByteString -> ByteString -> IO ()
+authenticate :: AuthConfig -> SendData -> Request a -> ByteString -> ByteString -> IO ()
 authenticate config send request path code = do
   tokenRes <- post "https://accounts.google.com/o/oauth2/token" ["code=" ++ UTF8.toString code, "client_id=" ++ clientID, "client_secret=" ++ clientSecret, "redirect_uri=" ++ (cs $ show $ rootURI request), "grant_type=authorization_code"]
   case tokenRes of
@@ -121,7 +120,7 @@ authenticate config send request path code = do
                 Just userInfo -> do
                   clientToken <- authToken authTokenKey (userEmail userInfo) (userGivenName userInfo, userFamilyName userInfo)
                   let cookie = setCookie cookieDomain cookieName (show clientToken) authShelfLife
-                  sendResponse send found302 [("Location", cs $ (show $ (rootURI request) {URI.uriPath = ""}) ++ cs (urlDecode False path)), ("Set-Cookie", UTF8.fromString cookie)] ""
+                  sendResponse_ send found302 [("Location", cs $ (show $ (rootURI request) {URI.uriPath = ""}) ++ cs (urlDecode False path)), ("Set-Cookie", UTF8.fromString cookie)] ""
   where
     cookieDomain = authConfigCookieDomain config
     cookieName = authConfigCookieName config
diff --git a/src/HTTP.hs b/src/HTTP.hs
index 07038a0..dbcae71 100644
--- a/src/HTTP.hs
+++ b/src/HTTP.hs
@@ -1,19 +1,14 @@
 {-# LANGUAGE OverloadedStrings #-}
 module HTTP (
-  sendRequest
-, sendResponse
-, sendResponse_
+  sendResponse_
 , internalServerError
 ) where
 
-import           Data.Foldable (forM_)
 import           Data.ByteString (ByteString)
-import qualified Data.ByteString as B
-import qualified Data.ByteString.Char8 as B8
-import qualified Data.ByteString.UTF8 as UTF8
-import qualified Data.CaseInsensitive as CI
+import qualified Data.ByteString.Char8 as B
 import           Network.HTTP.Types
-import           Network.HTTP.Toolkit.Body
+import           Network.HTTP.Toolkit
+import qualified Network.HTTP.Toolkit.Body as Body
 
 import           Type
 import qualified Log
@@ -21,34 +16,10 @@ import qualified Log
 internalServerError :: SendData -> String -> IO ()
 internalServerError send err = do
   Log.debug $ show err
-  sendResponse send internalServerError500 [] "Internal Server Error"
+  sendResponse_ send internalServerError500 [] "Internal Server Error"
 
-sendRequest :: SendData -> Method -> ByteString -> [Header] -> BodyReader -> IO ()
-sendRequest send method path headers body = do
-  sendHeader send startLine headers
-  sendBody send body
+sendResponse_ :: SendData -> Status -> [Header] -> ByteString -> IO ()
+sendResponse_ send status headers_ body = do
+  Body.fromByteString body >>= sendResponse send . Response status headers
   where
-    startLine = B8.unwords [method, path, "HTTP/1.1"]
-
-sendResponse :: SendData -> Status -> [Header] -> ByteString -> IO ()
-sendResponse send status headers_ body = do
-  sendHeader send (statusLine status) headers
-  send body
-  where
-    headers = ("Content-Length", UTF8.fromString $ show $ B.length body) : headers_
-
-sendResponse_ :: SendData -> Status -> [Header] -> BodyReader -> IO ()
-sendResponse_ send status headers body = do
-  sendHeader send (statusLine status) headers
-  sendBody send body
-
-statusLine :: Status -> ByteString
-statusLine status = B.concat ["HTTP/1.1 ", UTF8.fromString $ show (statusCode status), " ", statusMessage status]
-
-sendHeader :: SendData -> ByteString -> [Header] -> IO ()
-sendHeader send startLine headers = do
-  send startLine
-  send "\r\n"
-  forM_ headers $ \(k, v) -> do
-    send $ B.concat [CI.original k, ": ", v, "\r\n"]
-  send "\r\n"
+    headers = ("Content-Length", B.pack . show . B.length $ body) : headers_
diff --git a/src/Proxy.hs b/src/Proxy.hs
index aa320af..88b95d9 100644
--- a/src/Proxy.hs
+++ b/src/Proxy.hs
@@ -32,11 +32,7 @@ import qualified Network.URI as URI
 import Options.Applicative hiding (action)
 import System.IO
 
-import Network.HTTP.Toolkit.Body
-import Network.HTTP.Toolkit.Header
-import Network.HTTP.Toolkit.Connection
-import Network.HTTP.Toolkit.Request
-import Network.HTTP.Toolkit.Response
+import Network.HTTP.Toolkit
 
 import Type
 import Util
@@ -142,10 +138,10 @@ runProxy port config authConfig authorize = (listen port (serve config authConfi
 redirectToHttps :: SockAddr -> Socket -> IO ()
 redirectToHttps _ sock = do
   conn <- makeConnection (Socket.recv sock 4096)
-  (request, _) <- readRequest conn
-  sendResponse (Socket.sendAll sock) seeOther303 [("Location", cs $ show $ requestURI request)] ""
+  request <- readRequest conn
+  sendResponse_ (Socket.sendAll sock) seeOther303 [("Location", cs $ show $ requestURI request)] ""
   where
-    requestURI (MessageHeader (_, path) headers) =
+    requestURI (Request _ path headers _) =
       let host = fromMaybe (error "Host header not found") $ lookup "Host" headers
       in fromJust $ URI.parseURI $ "https://" ++ cs host ++ cs path
 
@@ -171,8 +167,8 @@ serve config authConfig withAuthorizeAction addr sock = do
     serve_ send conn authorize = go
       where
         go :: IO ()
-        go = forever $ readRequest conn >>= \(request, body) -> case request of
-          MessageHeader (_, url) headers -> do
+        go = forever $ readRequest conn >>= \request -> case request of
+          Request _ url headers _ -> do
             -- TODO: Don't loop for more input on Connection: close header.
             -- Check if this is an authorization response.
             case URI.parseURIReference $ BU.toString url of
@@ -192,17 +188,17 @@ serve config authConfig withAuthorizeAction addr sock = do
                         case auth of
                           Nothing -> redirectForAuth authConfig request send
                           Just token -> do
-                            forwardRequest config send authorize cookies addr request body token
+                            forwardRequest config send authorize cookies addr request token
 
 -- Check our access control list for this user's request and forward it to the backend if allowed.
-forwardRequest :: Config -> SendData -> AuthorizeAction -> [(Name, Cookies.Value)] -> SockAddr -> RequestHeader -> BodyReader -> AuthToken -> IO ()
-forwardRequest config send authorize cookies addr (MessageHeader (method, path) headers) body token = do
+forwardRequest :: Config -> SendData -> AuthorizeAction -> [(Name, Cookies.Value)] -> SockAddr -> Request BodyReader -> AuthToken -> IO ()
+forwardRequest config send authorize cookies addr request@(Request method path headers _) token = do
     groups <- authorize (authEmail token) (maybe (error "No Host") cs $ lookup "Host" headers) path method
     ip <- formatSockAddr addr
     case groups of
         [] -> do
             -- TODO: Send back a page that allows the user to request authorization.
-            sendResponse send forbidden403 [] "Access Denied"
+            sendResponse_ send forbidden403 [] "Access Denied"
         _ -> do
             -- TODO: Reuse connections to the backend server.
             let downStreamHeaders =
@@ -216,10 +212,10 @@ forwardRequest config send authorize cookies addr (MessageHeader (method, path)
                     setCookies $
                     fromList headers
             bracket (connectTo host port) hClose $ \h -> do
-              sendRequest (B.hPutStr h) method path downStreamHeaders body
-              conn <- makeConnection (B.hGetSome h 4096)
-              (MessageHeader status responseHeaders, responseBody) <- readResponse method conn
-              sendResponse_ send status (removeConnectionHeader responseHeaders) responseBody
+              sendRequest (B.hPutStr h) request{requestHeaders = downStreamHeaders}
+              conn <- connectionFromHandle h
+              response <- readResponse method conn
+              sendResponse send response{responseHeaders = removeConnectionHeader (responseHeaders response)}
   where
     host = configBackendAddress config
     port = PortNumber (configBackendPort config)
-- 
1.9.1