{-# LANGUAGE OverloadedStrings #-}
module Network.URI.Cache(shouldCacheHTTP) where -- , storeCacheHTTP, writeCacheHTTP)
import Network.HTTP.Client
import Network.HTTP.Types.Status
import Network.HTTP.Types.Header
-- For escaping filepaths, since I already have this dependency
import Network.URI (escapeURIString, isUnescapedInURIComponent)
import Data.ByteString as Strict
import Data.ByteString.Char8 as C
import Data.ByteString.Lazy as Lazy
import System.IO as IO
import System.FilePath
import Data.Maybe (mapMaybe, listToMaybe, isJust, isNothing)
import Data.Char (isSpace)
import Data.List as L
import Control.Monad (forM)
strip = C.dropWhile isSpace -- FIXME Upgrade bytestring dependency for a real strip function.
httpCacheDirective :: Response b -> Strict.ByteString -> Maybe Strict.ByteString
httpCacheDirective response key | Just header <- lookup hCacheControl $ responseHeaders response =
let directives = Prelude.map strip $ C.split ',' header
in if key `Prelude.elem` directives
then Just ""
else listToMaybe $ mapMaybe (C.stripPrefix $ C.snoc key '=') directives
| otherwise = Nothing
shouldCacheHTTP :: Response b -> Bool
-- IETF RFC7234 Section 3
shouldCacheHTTP response = -- Assume GET
statusCode (responseStatus response) `Prelude.elem` [200, 201, 404] && -- Supported response code
isNothing (httpCacheDirective response "no-store") && -- Honor no-store
True && -- This is a private cache, don't check for Cache-Control: private
(isJust (lookup hExpires $ responseHeaders response) || -- Support Expires: header
isJust (httpCacheDirective response "max-age") ||
isJust (httpCacheDirective response "public")) -- Override directive
------
--- Key-value storage
------
read :: FilePath -> String -> IO ([(String, String)], Lazy.ByteString)
write :: FilePath -> String -> ([(String, String)], Lazy.ByteString) -> IO ()
openKey :: FilePath -> String -> (Handle -> IO r) -> IO r
openKey dir key =
IO.withFile (dir </> escapeURIString isUnescapedInURIComponent key) ReadMode
read dir key = openKey dir key parseHeaders
where
parseHeaders h = do
line <- IO.hGetLine h
case L.break isSpace $ strip' line of
("", "") -> do
body <- Lazy.hGetContents h
return ([], body)
(key, value) -> do
(headers, body) <- parseHeaders h
return ((key, strip' value):headers, body)
strip' = L.dropWhile isSpace . L.dropWhileEnd isSpace
write dir key (headers, body) = openKey dir key $ \h -> do
forM headers $ \(key, value) -> do
IO.hPutStrLn h (key++' ':value)
IO.hPutStrLn h ""
Lazy.hPut h body