on master help sign in
removed original Focus files
commite23ebf6108af8ad211340f63af1ce3ac61dc260a authorhsloan <ishaqsloan@gmail.com> dateTue 22 Aug 2017, 15:37 (9 years ago) parentb491348
6 files changed−425
serve/README.md−1 serve/focus-serve.cabal−33 serve/src/Focus/HTTP/Accept.hs−212 serve/src/Focus/HTTP/Serve.hs−105 th/focus-http-th.cabal−28 th/src/Focus/HTTP/TH.hs−46
serve/README.mddeleted−1
@@ -1 +0,0 @@
1−# focus-http
serve/focus-serve.cabaldeleted−33
@@ -1,33 +0,0 @@
1−Name: focus-serve
2−Version: 0.1
3−Synopsis: Project Synopsis Here
4−Description: Project Description Here
5−License: AllRightsReserved
6−Author: Author
7−Maintainer: maintainer@example.com
8−Stability: Experimental
9−Category: Web
10−Build-type: Simple
11−Cabal-version: >=1.2
12−
13−library
14− hs-source-dirs: src
15−
16− build-depends:
17− base
18− , attoparsec
19− , bytestring
20− , containers
21− , directory
22− , filepath
23− , snap
24− , snap-core
25− , text
26− , transformers
27− , unix
28− exposed-modules:
29− Focus.HTTP.Accept
30− Focus.HTTP.Serve
31−
32− other-extensions: TemplateHaskell
33− ghc-options: -threaded -Wall -Werror -fno-warn-unused-do-bind -fwarn-tabs -funbox-strict-fields -O2 -fprof-auto-calls -rtsopts
serve/src/Focus/HTTP/Accept.hsdeleted−212
@@ -1,212 +0,0 @@
1−{-# LANGUAGE CPP, OverloadedStrings, BangPatterns, ScopedTypeVariables, GeneralizedNewtypeDeriving #-}
2−module Focus.HTTP.Accept where
3−
4−#ifdef __GLASGOW_HASKELL__
5−#if __GLASGOW_HASKELL__ < 710
6−import Prelude hiding (takeWhile, sequence)
7−import Control.Monad hiding (sequence)
8−import Data.Traversable (sequence)
9−#else
10−import Prelude hiding (takeWhile)
11−import Control.Monad
12−#endif
13−#else
14−import Prelude hiding (takeWhile)
15−import Control.Monad
16−#endif
17−
18−import Control.Applicative
19−import qualified Data.ByteString as BS
20−import Data.Attoparsec.ByteString as A
21−import Data.Attoparsec.ByteString.Char8 as AC8
22−import Data.ByteString (ByteString)
23−import Data.Maybe
24−import Data.Word (Word8)
25−import Data.Monoid
26−import Data.Fixed
27−import Data.Map (Map)
28−import qualified Data.Map as Map
29−import Data.Either
30−import Control.Arrow
31−import Data.List
32−import Data.String
33−import Data.Proxy
34−import Data.Ord
35−
36−newtype Encoding = Encoding { unEncoding :: ByteString } deriving (Show, Read, Eq, Ord, IsString)
37−type QValueResolution = E3 -- This resolution is given by the standard
38−newtype QValue = QValue { unQValue :: Fixed QValueResolution } deriving (Show, Read, Eq, Ord)
39−
40−-- | When no Accept-encoding header is present, prefer identity, then gzip or compress, then anything else available
41−--
42−-- See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
43−missingAcceptableEncodings :: AcceptableEncodings
44−missingAcceptableEncodings = AcceptableEncodings
45− { _acceptableEncodings_defaultQValue = QValue 0.001
46− , _acceptableEncodings_byEncoding = Map.fromList
47− [ (Encoding "identity", QValue 1)
48− , (Encoding "gzip", QValue 0.5)
49− , (Encoding "compress", QValue 0.5)
50− ]
51− }
52−
53−data AcceptableEncodings
54− = AcceptableEncodings { _acceptableEncodings_defaultQValue :: QValue
55− , _acceptableEncodings_byEncoding :: Map Encoding QValue
56− }
57− deriving (Show, Read, Eq, Ord)
58−
59−chooseEncoding :: [Encoding] -> AcceptableEncodings -> Maybe Encoding
60−chooseEncoding es ae = fmap snd $ listToMaybe $ sort $ catMaybes $ zipWith f es [(1::Int)..]
61− where f e n = case encodingQValue e ae of
62− QValue 0 -> Nothing
63− q -> Just ((Down q, n), e) -- Choose by quality first (in descending order), then by the server's preference order
64−
65−encodingQValue :: Encoding -> AcceptableEncodings -> QValue
66−encodingQValue e ae = Map.findWithDefault (_acceptableEncodings_defaultQValue ae) e $ _acceptableEncodings_byEncoding ae
67−
68−acceptEncodingBody :: Parser AcceptableEncodings
69−acceptEncodingBody = do
70− l <- hashRule (Just 1) Nothing $ do
71− c <- (Nothing <$ literal "*") <|> (Just <$> contentCoding)
72− mq <- optional $ do
73− literal ";"
74− literal "q"
75− literal "="
76− qvalue
77− let q = fromMaybe (QValue 1) mq
78− return (c, q)
79− let (stars, specificEncodings) = partitionEithers $ flip map l $ \(c, q) -> case c of
80− Nothing -> Left q
81− Just n -> Right (n, q)
82− starQValue <- case stars of
83− [] -> return Nothing
84− [q] -> return $ Just q
85− _ -> fail "acceptEncodingBody: multiple * values provided"
86− byEncodingProvided <- sequence $ Map.fromListWithKey (\k _ _ -> fail $ "acceptEncodingBody: encoding " <> show k <> " repeated multiple times") $ map (second return) specificEncodings
87− let defaultIdentityQValue = fromMaybe (QValue 1) starQValue -- identity has a default qvalue of 1 unless * is given a different qvalue explicitly
88− defaultQValue = fromMaybe (QValue 0) starQValue
89− byEncoding = Map.filter (/= defaultQValue) -- Canonicalize: qvalues equal to the default are redundant
90− . Map.alter (Just . fromMaybe defaultIdentityQValue) "identity" -- Add implicit "identity" encoding, unless it has been explicitly added
91− $ byEncodingProvided
92− return $ AcceptableEncodings defaultQValue byEncoding
93−
94−-- | See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.9
95−qvalue :: Parser QValue
96−qvalue = do
97− skipMany lws
98− q0 <|> q1
99− where q0 = do
100− char '0'
101− decimals <- option [] $ do
102− char '.'
103− starRule (Just 0) (Just numAllowedDigits) digit
104− return $ QValue $ MkFixed $ fromIntegral $ (read ('0' : decimals) :: Int) * 10 ^ (numAllowedDigits - length decimals)
105− q1 = do
106− char '1'
107− option () $ do
108− char '.'
109− starRule (Just 0) (Just numAllowedDigits) $ char '0'
110− return ()
111− return $ QValue 1
112− numAllowedDigits :: Int
113− numAllowedDigits = fromIntegral $ resolution (Proxy :: Proxy QValueResolution)
114−
115−contentCoding :: Parser Encoding
116−contentCoding = Encoding <$> token
117−
118−token :: Parser ByteString
119−token = skipMany lws >> A.takeWhile1 isTokenChar
120−
121−literal :: ByteString -> Parser ()
122−literal s = void $ skipMany lws >> string s
123−
124−isTokenChar :: Word8 -> Bool
125−isTokenChar c = isChar c && not (isCtl c || isSeparator c)
126−
127−isChar :: Word8 -> Bool
128−isChar c = c <= 127
129−
130−isCtl :: Word8 -> Bool
131−isCtl c = c <= 31 || c == 127
132−
133−isSeparator :: Word8 -> Bool
134−isSeparator c = BS.elem c $ spVal `BS.cons` htVal `BS.cons` "()<>@,;:\\\"/[]?={}"
135−
136−-- | A version of 'liftM2' that is strict in the result of its first
137−-- action.
138−liftM2' :: (Monad m) => (a -> b -> c) -> m a -> m b -> m c
139−liftM2' f a b = do
140− !x <- a
141− y <- b
142− return (f x y)
143−{-# INLINE liftM2' #-}
144−
145−-- | See http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.1
146−starRule :: Maybe Int -> Maybe Int -> Parser a -> Parser [a]
147−starRule minNum maxNum element = do
148− let numMandatory = fromMaybe 0 minNum
149− mandatoryVals <- replicateM numMandatory element
150− optionalVals <- case maxNum of
151− Nothing -> many' element
152− Just n -> do
153− let countUpTo 0 _ = return []
154− countUpTo m a = liftM2' (:) a (countUpTo (pred m) a) <|> return []
155− countUpTo (n - numMandatory) element
156− return $ mandatoryVals ++ optionalVals
157−
158−-- | See http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.1
159−-- The spec is a bit ambiguous on whether extra commas and whitespace are permitted before and after the elements; this implementation permits them
160−hashRule :: forall a. Maybe Int -> Maybe Int -> Parser a -> Parser [a]
161−hashRule minNum maxNum element = do
162− let numMandatory = fromMaybe 0 minNum
163− sep :: Parser ()
164− sep = do
165− skipMany1 $ do
166− skipMany lws
167− char ','
168− return ()
169− processMandatory :: Bool -> Int -> Parser [a]
170− processMandatory isInitial 0 = processOptional isInitial $ fmap (subtract numMandatory) maxNum
171− processMandatory isInitial n = do
172− if isInitial then void $ optional sep else sep
173− liftM2' (:) (skipMany lws >> element) $ processMandatory False $ pred n
174− processOptional :: Bool -> Maybe Int -> Parser [a]
175− processOptional _ (Just 0) = return []
176− processOptional isInitial n = (<|> return []) $ do
177− if isInitial then void $ optional sep else sep
178− liftM2' (:) (skipMany lws >> element) $ processOptional False $ fmap pred n
179− result <- processMandatory True numMandatory
180− _ <- optional sep
181− skipMany lws
182− return result
183−
184−-- | Linear whitespace
185−-- See http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2
186−lws :: Parser ()
187−lws = do
188− option () $ cr >> lf
189− starRule (Just 1) Nothing $ sp <|> ht
190− return ()
191−
192−-- | See http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2
193−cr :: Parser ()
194−cr = void $ word8 13
195−
196−-- | See http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2
197−lf :: Parser ()
198−lf = void $ word8 10
199−
200−-- | See http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2
201−sp :: Parser ()
202−sp = void $ word8 spVal
203−
204−spVal :: Word8
205−spVal = 32
206−
207−-- | See http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2
208−ht :: Parser ()
209−ht = void $ word8 htVal
210−
211−htVal :: Word8
212−htVal = 9
serve/src/Focus/HTTP/Serve.hsdeleted−105
@@ -1,105 +0,0 @@
1−{-# LANGUAGE CPP, OverloadedStrings, TemplateHaskell #-}
2−module Focus.HTTP.Serve where
3−
4−import Focus.HTTP.Accept
5−
6−import Snap
7−import Snap.Util.FileServe
8−
9−import Control.Applicative
10−import Control.Exception (try, throwIO, catchJust)
11−import Control.Monad
12−import Control.Monad.IO.Class
13−import Data.Attoparsec.ByteString (parseOnly, endOfInput)
14−import Data.ByteString (ByteString)
15−import qualified Data.ByteString as BS
16−import Data.List
17−import Data.Monoid
18−import qualified Data.Text as T
19−import Data.Text.Encoding
20−import System.Directory
21−import System.FilePath
22−import System.IO.Error
23−import System.Posix (getFileStatus, fileSize)
24−
25−cachePermanently :: MonadSnap m => m ()
26−cachePermanently = do
27− modifyResponse $ setHeader "Cache-Control" "public, max-age=315360000"
28− modifyResponse $ setHeader "Expires" "Tue, 01 Feb 2050 00:00:00 GMT" --TODO: This should be set to "approximately one year from the time the response is sent"
29−
30−doNotCache :: MonadSnap m => m ()
31−doNotCache = do
32− modifyResponse $ setHeader "Cache-Control" "no-cache, no-store, must-revalidate"
33− modifyResponse $ setHeader "Expires" "0"
34−
35−serveAssets :: MonadSnap m => FilePath -> FilePath -> m ()
36−serveAssets = serveAssets' True
37−
38−serveAssetsInPlace :: MonadSnap m => FilePath -> FilePath -> m ()
39−serveAssetsInPlace = serveAssets' False
40−
41−serveAssets' :: MonadSnap m => Bool -> FilePath -> FilePath -> m ()
42−serveAssets' doRedirect base fallback = do
43− pRaw <- getSafePath
44− let go p = do
45− assetType <- liftIO $ try $ BS.readFile $ base </> p </> "type"
46− case assetType of
47− Right "immutable" -> do
48− conditionalOnModification <- getsRequest $ getHeader "If-Modified-Since"
49− case conditionalOnModification of
50− Nothing -> do
51− encodedFiles <- liftM (filter (`notElem` [".", ".."])) $ liftIO $ getDirectoryContents $ base </> p </> "encodings"
52− availableEncodings <- liftM (map snd . sort) $ forM encodedFiles $ \f -> do
53− stat <- liftIO $ getFileStatus $ base </> p </> "encodings" </> f
54− return (fileSize stat, Encoding $ encodeUtf8 $ T.pack f)
55− acceptEncodingRaw <- getsRequest $ getHeader "Accept-Encoding"
56− ae <- case acceptEncodingRaw of
57− Nothing -> return missingAcceptableEncodings
58− Just aer -> case parseOnly (acceptEncodingBody <* endOfInput) aer of
59− Right ae -> return ae
60− Left err -> error err
61− Just (Encoding e) <- return $ chooseEncoding availableEncodings ae
62− modifyResponse $ setHeader "Content-Encoding" e . setHeader "Vary" "Accept-Encoding"
63− if doRedirect then cachePermanently else doNotCache --TODO: Use Etags when not redirecting
64− let finalFilename = base </> p </> "encodings" </> T.unpack (decodeUtf8 e)
65− stat <- liftIO $ getFileStatus finalFilename
66− modifyResponse $ setHeader "Last-Modified" "Thu, 1 Jan 1970 00:00:00 GMT"
67− modifyResponse $ setResponseCode 200 . setContentLength (fromIntegral $ fileSize stat) . setContentType (fileType defaultMimeTypes p)
68− sendFile finalFilename
69− Just _ -> cachePermanently >> modifyResponse (setResponseCode 304)
70− Right "redirect" -> do
71− mtarget <- liftIO $ getAssetTarget $ base </> p
72− case mtarget of
73− Just target -> if doRedirect
74− then do
75− doNotCache
76− redirect target
77− else go $ takeDirectory p </> T.unpack (decodeUtf8 target)
78− Nothing -> serveFile $ fallback </> p
79− Right unknown -> error $ T.unpack ("serveAssets': Unknown asset " <> decodeUtf8 unknown)
80− Left err | isDoesNotExistError err -> (doNotCache >> serveFileIfExists (fallback </> p)) <|> do
81− let (dirname, filename) = splitFileName p
82− unhashedFilename = drop 1 $ dropWhile (/= '-') filename
83− if null unhashedFilename then pass else do
84− doNotCache
85− serveFileIfExists $ fallback </> dirname </> unhashedFilename
86− | otherwise -> liftIO $ throwIO err
87− go $ if "/" `isSuffixOf` pRaw || pRaw == "" then pRaw <> "index.html" else pRaw
88−
89−serveFileIfExists :: MonadSnap m => FilePath -> m ()
90−serveFileIfExists f = do
91− exists <- liftIO $ doesFileExist f
92− if exists then serveFile f else pass
93−
94−serveFileIfExistsAs :: MonadSnap m => ByteString -> FilePath -> m ()
95−serveFileIfExistsAs mimeType f = do
96− exists <- liftIO $ doesFileExist f
97− if exists then serveFileAs mimeType f else pass
98−
99−getAssetTarget :: FilePath -> IO (Maybe ByteString)
100−getAssetTarget p = catchJust (\e -> if isDoesNotExistErrorType (ioeGetErrorType e) then Just () else Nothing) (liftM Just $ BS.readFile $ p </> "target") (\_ -> return Nothing)
101−
102−getAssetPath :: FilePath -> FilePath -> IO (Maybe FilePath)
103−getAssetPath base p = do
104− target <- getAssetTarget $ base </> p
105− return $ fmap ((takeDirectory p </>) . T.unpack . decodeUtf8) target
th/focus-http-th.cabaldeleted−28
@@ -1,28 +0,0 @@
1−Name: focus-http-th
2−Version: 0.1
3−Synopsis: Project Synopsis Here
4−Description: Project Description Here
5−License: AllRightsReserved
6−Author: Author
7−Maintainer: maintainer@example.com
8−Stability: Experimental
9−Category: Web
10−Build-type: Simple
11−Cabal-version: >=1.2
12−
13−library
14− hs-source-dirs: src
15−
16− build-depends:
17− base
18− , bytestring
19− , SHA
20− , filepath
21− , template-haskell
22− , text
23− , vector
24− exposed-modules:
25− Focus.HTTP.TH
26−
27− other-extensions: TemplateHaskell
28− ghc-options: -threaded -Wall -Werror -fno-warn-unused-do-bind -fwarn-tabs -funbox-strict-fields -O2 -fprof-auto-calls -rtsopts
th/src/Focus/HTTP/TH.hsdeleted−46
@@ -1,46 +0,0 @@
1−module Focus.HTTP.TH where
2−
3−import Language.Haskell.TH.Syntax
4−import System.FilePath
5−import qualified Data.Text as T
6−import qualified Data.ByteString as BS
7−import qualified Data.ByteString.Lazy as LBS
8−import qualified Data.ByteString.Lazy.Builder as LBS
9−import Data.Digest.Pure.SHA
10−import Data.Text.Encoding
11−import Data.Monoid
12−import Data.Bits
13−import qualified Data.Vector.Unboxed as UV
14−import Data.Char
15−import Data.Word
16−
17−-- | Warning: does not support negative numbers (will result in infinite loop)
18−toNixBase32 :: LBS.ByteString -> LBS.ByteString
19−toNixBase32 x = LBS.toLazyByteString $ mconcat $ map (LBS.word8 . (symbols UV.!) . fromIntegral) vals
20− where vals = byteStringToQuintets x
21− symbols = UV.fromList $ map (fromIntegral . ord) $ filter (`notElem` "eotu") $ ['0'..'9'] <> ['a'..'z']
22−
23−
24−-- See https://github.com/NixOS/nix/blob/6f1743b1a5116ca57a60b481ee4083c891b7a334/src/libutil/hash.cc#L109
25−byteStringToQuintets :: LBS.ByteString -> [Word8]
26−byteStringToQuintets hash = map f [len-1, len-2 .. 0]
27− where hashSize = fromIntegral $ LBS.length hash
28− len = (hashSize * 8 - 1) `div` 5 + 1
29− f n = let b = n * 5
30− (i, j) = b `divMod` 8
31− j' = fromIntegral j
32− c = ((hash `LBS.index` i) `shift` (-j')) .|. (if i >= hashSize - 1 then 0 else (hash `LBS.index` (i + 1)) `shift` (8 - j')) --TODO: This is probably pretty slow; replace with something that doesn't use LBS.index
33− in c .&. 0x1f
34−
35−assetPath' :: FilePath -> FilePath -> Q FilePath
36−assetPath' root relativePath = do
37− let path = root </> relativePath
38− qAddDependentFile path
39− contents <- fmap LBS.fromStrict $ runIO $ BS.readFile path
40− let hashPrefix = T.unpack $ decodeUtf8 $ LBS.toStrict $ toNixBase32 $ bytestringDigest $ sha256 contents
41− (dir, filename) = splitFileName relativePath
42− return $ dir </> (hashPrefix <> "-" <> filename)
43−
44−assetPath :: FilePath -> FilePath -> Q Exp
45−assetPath root relativePath =
46− LitE . StringL <$> assetPath' root relativePath