{-# LANGUAGE FlexibleContexts, OverloadedStrings, RankNTypes, RecordWildCards #-}
module Clckwrks.Server where
import Clckwrks
import Clckwrks.Admin.Route (routeAdmin)
import Clckwrks.Monad (ClckwrksConfig(..), TLSSettings(..), calcBaseURI, calcTLSBaseURI, initialClckPluginsSt)
import Clckwrks.ProfileData.Types (Role(..))
import Clckwrks.ProfileData.URL (ProfileDataURL(..))
import Control.Arrow (second)
import Control.Concurrent (forkIO, killThread)
import Control.Concurrent.STM (atomically, newTVar)
import Control.Monad.State (get, evalStateT)
import qualified Data.ByteString.Char8 as B
import Data.Acid.Advanced (query')
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe (fromJust, fromMaybe, isNothing)
import Data.Monoid ((<>))
import Data.String (fromString)
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.UUID.Types as UUID
import Happstack.Server.FileServe.BuildingBlocks (guessContentTypeM, isSafePath, serveFile)
import Happstack.Server.SimpleHTTPS (TLSConf(..), nullTLSConf, simpleHTTPS)
import System.FilePath ((</>), makeRelative, splitDirectories)
import Web.Routes.Happstack (implSite)
import Web.Plugins.Core (Plugins, withPlugins, getPluginRouteFn, getPostHooks, serve)
import qualified Paths_clckwrks as Clckwrks
withClckwrks :: ClckwrksConfig -> (ClckState -> IO b) -> IO b
withClckwrks cc action = do
let top' = fmap (\top -> top </> "_state") (clckTopDir cc)
withAcid top' $ \acid ->
withPlugins cc (initialClckPluginsSt acid) $ \plugins -> do
u <- atomically $ newTVar 0
let clckState = ClckState { acidState = acid
, uniqueId = u
, adminMenus = []
, enableAnalytics = clckEnableAnalytics cc
, plugins = plugins
, requestInit = return ()
}
action clckState
simpleClckwrks :: ClckwrksConfig -> IO ()
simpleClckwrks cc =
withClckwrks cc $ \clckState ->
do let baseURI =
case calcTLSBaseURI cc of
(Just baseUri) -> baseUri
Nothing -> calcBaseURI cc
(clckState', cc') <- (clckInitHook cc) baseURI clckState cc
let p = plugins clckState'
hooks <- getPostHooks p
~(Just clckShowFn) <- getPluginRouteFn p "clck"
let showFn = \url params -> clckShowFn url []
clckState'' <- execClckT showFn clckState' $ sequence_ hooks
mHttpsTID <-
case clckTLS cc' of
Nothing -> return Nothing
(Just TLSSettings{..}) ->
do let tlsConf = nullTLSConf { tlsPort = clckTLSPort
, tlsCert = clckTLSCert
, tlsKey = clckTLSKey
, tlsCA = clckTLSCA
}
tid <- forkIO $ simpleHTTPS tlsConf (handlers cc' clckState'')
return (Just tid)
httpTID <- if isNothing mHttpsTID
then forkIO $ simpleHTTP (nullConf { port = clckPort cc' }) (handlers cc' clckState'')
else forkIO $ simpleHTTP (nullConf { port = clckPort cc' }) forceHTTPS
waitForTermination
killThread httpTID
maybe (return ()) killThread mHttpsTID
where
handlers :: ClckwrksConfig -> ClckState -> ServerPart Response
handlers cc clckState =
do forceCanonicalHost
decodeBody (defaultBodyPolicy "/tmp/" (10 * 10^6) (1 * 10^6) (1 * 10^6))
requestInit clckState
msum $
[ jsHandlers cc
, dir "static" $ (liftIO $ Clckwrks.getDataFileName "static") >>= serveDirectory DisableBrowsing []
, do nullDir
mRR <- query' (acidCore . acidState $ clckState) GetRootRedirect
seeOther (fromMaybe ("/page/view-page/1") mRR) (toResponse ())
, clckSite cc clckState
]
forceCanonicalHost :: ServerPart ()
forceCanonicalHost =
do rq <- askRq
case getHeader "host" rq of
Nothing -> return ()
(Just hostBS) ->
if (clckHostname cc == (B.unpack $ B.takeWhile (/= ':') hostBS))
then return ()
else escape $ seeOther ((if rqSecure rq then (fromJust $ calcTLSBaseURI cc) else (calcBaseURI cc)) <> (Text.pack $ rqUri rq) <> (Text.pack $ rqQuery rq)) (toResponse ())
forceHTTPS :: ServerPart Response
forceHTTPS =
msum [ do method GET
rq <- askRq
seeOther ((fromJust $ calcTLSBaseURI cc) <> (Text.pack $ rqUri rq) <> (Text.pack $ rqQuery rq)) (toResponse ())
, do forbidden (toResponse ("https:// required." :: Text))
]
jsHandlers :: (Happstack m) => ClckwrksConfig -> m Response
jsHandlers c =
msum [ dir "jquery" $ serveDirectory DisableBrowsing [] (clckJQueryPath c)
, dir "jquery-ui" $ serveDirectory DisableBrowsing [] (clckJQueryUIPath c)
, dir "jstree" $ serveDirectory DisableBrowsing [] (clckJSTreePath c)
, dir "json2" $ serveDirectory DisableBrowsing [] (clckJSON2Path c)
]
clckSite :: ClckwrksConfig -> ClckState -> ServerPart Response
clckSite cc clckState =
do ~(Just clckShowFn) <- getPluginRouteFn (plugins clckState) (Text.pack "clck")
evalClckT clckShowFn clckState (pluginsHandler (plugins clckState))
pluginsHandler :: (Functor m, ServerMonad m, FilterMonad Response m, MonadIO m) =>
Plugins theme (m Response) hook config ppm
-> m Response
pluginsHandler plugins =
do paths <- (map Text.pack . rqPaths) <$> askRq
case paths of
(p : ps) ->
do e <- liftIO $ serve plugins p ps
case e of
(Right c) -> c
(Left e) -> notFound $ toResponse e
_ -> notFound (toResponse ())