From 4e41b11200285142757434e9d67e17ed20fae455 Mon Sep 17 00:00:00 2001 From: PHO <pho@cielonegro.org> Date: Thu, 10 Nov 2011 10:13:44 +0900 Subject: [PATCH] ImplantFile started working again. Ditz-issue: 123424c3b4a0d83452e26403cd79676f319d4295 --- ImplantFile.hs | 480 +++-------------------- Lucu.cabal | 11 +- Network/HTTP/Lucu/ETag.hs | 17 +- Network/HTTP/Lucu/Implant/Input.hs | 96 +++++ Network/HTTP/Lucu/Implant/PrettyPrint.hs | 213 ++++++++++ Network/HTTP/Lucu/Implant/Rewrite.hs | 129 ++++++ Network/HTTP/Lucu/Interaction.hs | 2 + Network/HTTP/Lucu/StaticFile.hs | 1 + Network/HTTP/Lucu/Utils.hs | 94 ++++- 9 files changed, 604 insertions(+), 439 deletions(-) create mode 100644 Network/HTTP/Lucu/Implant/Input.hs create mode 100644 Network/HTTP/Lucu/Implant/PrettyPrint.hs create mode 100644 Network/HTTP/Lucu/Implant/Rewrite.hs diff --git a/ImplantFile.hs b/ImplantFile.hs index c253c2a..b6545a8 100644 --- a/ImplantFile.hs +++ b/ImplantFile.hs @@ -1,34 +1,22 @@ {-# LANGUAGE - OverloadedStrings - , UnicodeSyntax + UnicodeSyntax #-} -module Main where -import Codec.Compression.GZip +module Main (main) where import Control.Applicative import Control.Monad import qualified Data.Ascii as A -import qualified Data.ByteString.Base64 as B64 -import qualified Data.ByteString.Char8 as Strict -import qualified Data.ByteString.Lazy as Lazy import Data.Char -import Data.Digest.Pure.SHA -import Data.Int import Data.Maybe -import Data.Time -import Data.Time.Clock.POSIX -import Language.Haskell.Exts.Build -import Language.Haskell.Exts.Extension -import Language.Haskell.Exts.Pretty -import Language.Haskell.Exts.Syntax +import Language.Haskell.TH.PprLib +import Language.Haskell.TH.Syntax import Network.HTTP.Lucu.ETag +import Network.HTTP.Lucu.Implant.Input +import Network.HTTP.Lucu.Implant.PrettyPrint import Network.HTTP.Lucu.MIMEType -import Network.HTTP.Lucu.MIMEType.DefaultExtensionMap -import Network.HTTP.Lucu.MIMEType.Guess import Prelude.Unicode import System.Console.GetOpt import System.Environment import System.Exit -import System.Posix.Files import System.IO data CmdOpt @@ -73,7 +61,7 @@ printUsage = do mapM_ putStrLn msg where msg = [ "" , "Description:" - , concat [ " lucu-implant-file is an utility that generates " + , concat [ " lucu-implant-file is an utility that generates " , "Haskell code containing an arbitrary file to " , "compile it directly into programs and serve it " , "statically with the Lucu HTTP server." @@ -104,291 +92,15 @@ main = do (opts, sources, errors) â getOpt Permute options <$> getArgs generateHaskellSource opts (head sources) -generateHaskellSource â· [CmdOpt] â FilePath â IO () -generateHaskellSource opts srcFile - = do modName â getModuleName opts - symName â getSymbolName opts modName - mType â getMIMEType opts srcFile - lastMod â getLastModified srcFile - input â openInput srcFile - output â openOutput opts - tag â getETag opts input - - let compParams = defaultCompressParams { compressLevel = bestCompression } - gzippedData = compressWith compParams input - originalLen = Lazy.length input - gzippedLen = Lazy.length gzippedData - useGZip = originalLen > gzippedLen - rawB64 = B64.encode <$> Lazy.toChunks input - gzippedB64 = B64.encode <$> Lazy.toChunks gzippedData - - header â mkHeader srcFile originalLen gzippedLen useGZip mType tag lastMod - - let hsModule = mkModule modName symName imports decls - imports = mkImports useGZip - decls = concat [ resourceDecl symName useGZip - , entityTagDecl tag - , lastModifiedDecl lastMod - , contentTypeDecl mType - , if useGZip then - dataDecl (name "gzippedData") gzippedB64 - else - dataDecl (name "rawData") rawB64 - ] - - hPutStrLn output header - hPutStrLn output (prettyPrint hsModule) - hClose output - -mkModule â· ModuleName â Name â [ImportDecl] â [Decl] â Module -mkModule modName symName imports decls - = let modPragma = [ LanguagePragma (â¥) [ name (show OverloadedStrings) ] ] - exports = [ EVar (UnQual symName) ] - in - Module (â¥) modName modPragma Nothing (Just exports) imports decls - -mkImports â· Bool â [ImportDecl] -mkImports useGZip - = [ ImportDecl (â¥) (ModuleName "Data.ByteString.Base64") - True False Nothing (Just (ModuleName "B64")) Nothing - , ImportDecl (â¥) (ModuleName "Data.ByteString.Lazy") - True False Nothing (Just (ModuleName "Lazy")) Nothing - , ImportDecl (â¥) (ModuleName "Data.Time") - False False Nothing Nothing Nothing - , ImportDecl (â¥) (ModuleName "Network.HTTP.Lucu") - False False Nothing Nothing Nothing - ] - ⧺ - [ ImportDecl (â¥) (ModuleName "Codec.Compression.GZip") - False False Nothing Nothing Nothing - | useGZip ] - -resourceDecl â· Name â Bool â [Decl] -resourceDecl symName useGZip - = [ TypeSig (â¥) [symName] (TyCon (UnQual (name "ResourceDef"))) - , nameBind (â¥) symName valExp - ] - where - valExp â· Exp - valExp = RecUpdate (function "emptyResource") - [ FieldUpdate (UnQual (name "resGet" )) resGet - , FieldUpdate (UnQual (name "resHead")) resHead - ] - - resGet â· Exp - resGet | useGZip = resGetGZipped - | otherwise = resGetRaw - -resHead â· Exp -resHead - = function "Just" `app` - paren (doE [ foundEntityStmt - , setContentTypeStmt - ]) - -resGetGZipped â· Exp -resGetGZipped - = function "Just" `app` - paren (doE [ foundEntityStmt - , setContentTypeStmt - , bindGZipStmt - , conditionalOutputStmt - ]) - where - condVarName â· Name - condVarName = name "gzipAllowed" - - dataVarName â· Name - dataVarName = name "gzippedData" - - bindGZipStmt â· Stmt - bindGZipStmt - = genStmt (â¥) - (pvar condVarName) - (function "isEncodingAcceptable" `app` strE "gzip") - - conditionalOutputStmt â· Stmt - conditionalOutputStmt - = qualStmt $ - If (var condVarName) - (doE [ setContentEncodingGZipStmt - , putChunksStmt (var dataVarName) - ]) - (putChunksExp - (paren - (function "decompress" `app` var dataVarName))) - -resGetRaw â· Exp -resGetRaw - = function "Just" `app` - paren (doE [ foundEntityStmt - , setContentTypeStmt - , putChunksStmt (function "rawData") - ]) - -setContentEncodingGZipStmt â· Stmt -setContentEncodingGZipStmt - = qualStmt - ( function "setContentEncoding" - `app` - listE [ strE "gzip" ] - ) - -foundEntityStmt â· Stmt -foundEntityStmt - = qualStmt $ - metaFunction "foundEntity" - [ var (name "entityTag") - , var (name "lastModified") - ] - -setContentTypeStmt â· Stmt -setContentTypeStmt - = qualStmt - ( function "setContentType" - `app` - function "contentType" - ) - -putChunksExp â· Exp â Exp -putChunksExp = app (function "putChunks") - -putChunksStmt â· Exp â Stmt -putChunksStmt = qualStmt â putChunksExp - -entityTagDecl â· ETag â [Decl] -entityTagDecl tag - = [ TypeSig (â¥) [varName] (TyCon (UnQual (name "ETag"))) - , nameBind (â¥) varName valExp - ] - where - varName â· Name - varName = name "entityTag" - - valExp â· Exp - valExp = function "parseETag" `app` strE (eTagToString tag) - -lastModifiedDecl â· UTCTime â [Decl] -lastModifiedDecl lastMod - = [ TypeSig (â¥) [varName] (TyCon (UnQual (name "UTCTime"))) - , nameBind (â¥) varName valExp - , InlineSig (â¥) False AlwaysActive (UnQual varName) - ] - where - varName â· Name - varName = name "lastModified" - - valExp â· Exp - valExp = function "read" `app` strE (show lastMod) - -contentTypeDecl â· MIMEType â [Decl] -contentTypeDecl mime - = [ TypeSig (â¥) [varName] (TyCon (UnQual (name "MIMEType"))) - , nameBind (â¥) varName valExp - , InlineSig (â¥) False AlwaysActive (UnQual varName) - ] - where - varName â· Name - varName = name "contentType" - - valExp â· Exp - valExp = function "parseMIMEType" `app` strE (mimeToString mime) - - mimeToString â· MIMEType â String - mimeToString = A.toString â A.fromAsciiBuilder â printMIMEType - -dataDecl â· Name â [Strict.ByteString] â [Decl] -dataDecl varName chunks - = [ TypeSig (â¥) [varName] (TyCon (Qual (ModuleName "Lazy") (name "ByteString"))) - , nameBind (â¥) varName valExp - , InlineSig (â¥) False AlwaysActive (UnQual varName) - ] - where - valExp â· Exp - valExp = qvar (ModuleName "Lazy") (name "fromChunks") - `app` - listE (chunkToExp <$> chunks) - - chunkToExp â· Strict.ByteString â Exp - chunkToExp chunk - = qvar (ModuleName "B64") (name "decodeLenient") - `app` - strE (Strict.unpack chunk) - -mkHeader â· FilePath â Int64 â Int64 â Bool â MIMEType â ETag â UTCTime â IO String -mkHeader srcFile originalLen gzippedLen useGZip mType tag lastMod - = do localLastMod â utcToLocalZonedTime lastMod - return $ concat - [ "{- DO NOT EDIT THIS FILE.\n" - , " This file is automatically generated by the lucu-implant-file program.\n" - , "\n" - , " Source: ", if srcFile â¡ "-" then - "(stdin)" - else - srcFile - , "\n" - , " Original Length: ", show originalLen, " bytes\n" - , if useGZip then - " Compressed Length: " ⧺ show gzippedLen ⧺ " bytes\n" ⧺ - " Compression: gzip\n" - else - " Compression: disabled\n" - , " MIME Type: ", mimeTypeToString mType, "\n" - , " ETag: ", eTagToString tag, "\n" - , " Last Modified: ", show localLastMod, "\n" - , " -}" - ] - -eTagToString â· ETag â String -eTagToString = A.toString â A.fromAsciiBuilder â printETag - -mimeTypeToString â· MIMEType â String -mimeTypeToString = A.toString â A.fromAsciiBuilder â printMIMEType - -getModuleName â· [CmdOpt] â IO ModuleName -getModuleName opts - = case modNameOpts of - [] â fail "a module name must be given." - OptModName modName:[] â return $ ModuleName modName - _ â fail "too many --module options." - where - modNameOpts â· [CmdOpt] - modNameOpts = filter (\ x â case x of - OptModName _ â True - _ â False) opts - -getSymbolName â· [CmdOpt] â ModuleName â IO Name -getSymbolName opts (ModuleName modName) - = case symNameOpts of - [] â return defaultSymName - OptSymName symName:[] â return $ name symName - _ â fail "too many --symbol options." - where - symNameOpts â· [CmdOpt] - symNameOpts = filter (\ x â case x of - OptSymName _ â True - _ â False) opts - - defaultSymName â· Name - defaultSymName - = name $ headToLower $ getLastComp modName - - headToLower â· String â String - headToLower [] = error "module name must not be empty" - headToLower (x:xs) = toLower x : xs - - getLastComp â· String â String - getLastComp = reverse â fst â break (â¡ '.') â reverse - -getMIMEType â· [CmdOpt] â FilePath â IO MIMEType -getMIMEType opts srcFile +getMIMEType â· [CmdOpt] â Maybe MIMEType +getMIMEType opts = case mimeTypeOpts of - [] â return defaultType + [] â Nothing OptMIMEType ty:[] â case A.fromChars ty of - Just a â return $ parseMIMEType a - Nothing â fail "MIME type must not contain any non-ASCII letters." - _ â fail "too many --mime-type options." + Just a â Just $ parseMIMEType a + Nothing â error "MIME types must not contain any non-ASCII letters." + _ â error "too many --mime-type options." where mimeTypeOpts â· [CmdOpt] mimeTypeOpts @@ -396,50 +108,31 @@ getMIMEType opts srcFile OptMIMEType _ â True _ â False) opts - octetStream â· MIMEType - octetStream = parseMIMEType "application/octet-stream" - - defaultType â· MIMEType - defaultType = fromMaybe octetStream - $ guessTypeByFileName defaultExtensionMap srcFile - -getLastModified â· FilePath â IO UTCTime -getLastModified "-" = getCurrentTime -getLastModified fpath = (posixSecondsToUTCTime â fromRational â toRational â modificationTime) - <$> - getFileStatus fpath - -getETag â· [CmdOpt] â Lazy.ByteString â IO ETag -getETag opts input +getETag â· [CmdOpt] â Maybe ETag +getETag opts = case eTagOpts of - [] â return mkETagFromInput - OptETag str:[] â return $ strToETag str - _ â fail "too many --etag options." + [] â Nothing + OptETag str:[] â Just $ strToETag str + _ â error "too many --etag options." where eTagOpts â· [CmdOpt] eTagOpts = filter (\ x â case x of OptETag _ â True _ â False) opts - mkETagFromInput â· ETag - mkETagFromInput - = strToETag $ "SHA-1:" ⧺ showDigest (sha1 input) - strToETag â· String â ETag strToETag str = case A.fromChars str of Just a â strongETag a Nothing â error "ETag must not contain any non-ASCII letters." -openInput â· FilePath â IO Lazy.ByteString -openInput "-" = Lazy.getContents -openInput fpath = Lazy.readFile fpath - openOutput â· [CmdOpt] â IO Handle openOutput opts = case outputOpts of [] â return stdout - OptOutput fpath:[] â openFile fpath WriteMode + OptOutput fpath:[] â do h â openFile fpath WriteMode + hSetEncoding h utf8 + return h _ â fail "two many --output options." where outputOpts â· [CmdOpt] @@ -447,96 +140,51 @@ openOutput opts OptOutput _ â True _ â False) opts -{- - ä½ããããã¡ã¤ã«ã®ä¾ (å£ç¸®ãããªãå ´å): - ------------------------------------------------------------------------------ - {- DO NOT EDIT THIS FILE. - This file is automatically generated by the lucu-implant-file program. - - Source: baz.png - Original Length: 302 bytes - Compressed Length: 453 bytes -- ãã㯠Compression: disabled ã®æã«ã¯ç¡ã - Compression: disabled - MIME Type: image/png - ETag: "d41d8cd98f00b204e9800998ecf8427e" - Last Modified: 2007-11-05 13:53:42.231882 JST - -} - {-# LANGUAGE OverloadedStrings #-} - module Foo.Bar.Baz (baz) where - import qualified Data.ByteString.Base64 as B64 - import qualified Data.ByteString.Lazy as Lazy - import Data.Time - import Network.HTTP.Lucu - - baz â· ResourceDef - baz = ResourceDef { - resUsesNativeThread = False - , resIsGreedy = False - , resGet - = Just $ do foundEntity entityTag lastModified - setContentType contentType - putChunk rawData - , resHead - = Just $ do foundEntity entityTag lastModified - setContentType contentType - , resPost = Nothing - , resPut = Nothing - , resDelete = Nothing - } - - entityTag â· ETag - entityTag = strongETag "d41d8cd98f00b204e9800998ecf8427e" - - lastModified â· UTCTime - {-# NOINLINE lastModified #-} - lastModified = read "2007-11-05 04:47:56.008366 UTC" - - contentType â· MIMEType - {-# NOINLINE contentType #-} - contentType = parseMIMEType "image/png" +getModuleName â· [CmdOpt] â ModName +getModuleName opts + = case modNameOpts of + [] â error "a module name must be given." + OptModName name:[] â mkModName name + _ â error "too many --module options." + where + modNameOpts â· [CmdOpt] + modNameOpts = filter (\ x â case x of + OptModName _ â True + _ â False) opts - rawData â· Lazy.ByteString - {-# NOINLINE rawData #-} - rawData = Lazy.fromChunks - [ B64.decodeLenient "IyEvdXNyL2Jpbi9lbnYgcnVuZ2hjCgppbXBvcnQgRG..." - , B64.decodeLenient "Otb/+DniOlRgAAAAYAAAAGAAAAB/6QOmToAEIGAAAA..." - ] - ------------------------------------------------------------------------------ +getSymbolName â· [CmdOpt] â Maybe Name +getSymbolName opts + = case symNameOpts of + [] â Nothing + OptSymName name:[] â Just $ mkName name + _ â fail "too many --symbol options." + where + symNameOpts â· [CmdOpt] + symNameOpts = filter (\ x â case x of + OptSymName _ â True + _ â False) opts - å£ç¸®ãããå ´åã¯æ¬¡ã®ããã«è®ã¯ã: - ------------------------------------------------------------------------------ - -- import ã«è¿½å - import Codec.Compression.Zlib +defaultSymName â· ModName â Name +defaultSymName = headToLower â getLastComp + where + headToLower â· String â Name + headToLower [] = error "module name must not be empty" + headToLower (x:xs) = mkName (toLower x:xs) - -- ResourceDef ã¯æ¬¡ã®ããã«è®å - baz â· ResourceDef - baz = ResourceDef { - resUsesNativeThread = False - , resIsGreedy = False - , resGet - = Just $ do foundEntity entityTag lastModified - setContentType contentType + getLastComp â· ModName â String + getLastComp = reverse â fst â break (â¡ '.') â reverse â modString - gzipAllowed â isEncodingAcceptable "gzip" - if gzipAllowed then - do setContentEncoding ["gzip"] - putChunks gzippedData - else - putChunks (decompress gzippedData) - , resHead - = Just $ do foundEntity entityTag lastModified - setContentType contentType - , resPost = Nothing - , resPut = Nothing - , resDelete = Nothing - } +generateHaskellSource â· [CmdOpt] â FilePath â IO () +generateHaskellSource opts srcFile + = do i â openInput srcFile (getMIMEType opts) (getETag opts) + o â openOutput opts + doc â pprInput i modName symName + hPutStrLn o $ show $ to_HPJ_Doc doc + hClose o + where + modName â· ModName + modName = getModuleName opts - -- rawData ã®ä»£ã¯ãã« gzippedData - gzippedData â· Lazy.ByteString - {-# NOINLINE gzippedData #-} - gzippedData = Lazy.fromChunks - [ B64.decodeLenient "IyEvdXNyL2Jpbi9lbnYgcnVuZ2hjCgppbXBvcnQ..." - , B64.decodeLenient "Otb/+DniOlRgAAAAYAAAAGAAAAB/6QOmToAEIGA..." - ] - ------------------------------------------------------------------------------ - -} + symName â· Name + symName = fromMaybe (defaultSymName modName) + $ getSymbolName opts diff --git a/Lucu.cabal b/Lucu.cabal index deed597..7ceb6c2 100644 --- a/Lucu.cabal +++ b/Lucu.cabal @@ -117,10 +117,15 @@ Executable lucu-implant-file Main-Is: ImplantFile.hs + Other-Modules: + Network.HTTP.Lucu.Implant.Input + Network.HTTP.Lucu.Implant.PrettyPrint + Network.HTTP.Lucu.Implant.Rewrite + Build-Depends: - SHA == 1.5.*, - haskell-src-exts == 1.11.*, - zlib == 0.5.* + SHA == 1.5.*, + syb == 0.3.*, + zlib == 0.5.* ghc-options: -Wall diff --git a/Network/HTTP/Lucu/ETag.hs b/Network/HTTP/Lucu/ETag.hs index d871000..b8191a3 100644 --- a/Network/HTTP/Lucu/ETag.hs +++ b/Network/HTTP/Lucu/ETag.hs @@ -1,5 +1,8 @@ {-# LANGUAGE - OverloadedStrings + DeriveDataTypeable + , OverloadedStrings + , RecordWildCards + , TemplateHaskell , UnicodeSyntax #-} -- |Entity tags @@ -19,7 +22,9 @@ import Control.Monad import Data.Ascii (Ascii, AsciiBuilder) import qualified Data.Ascii as A import Data.Attoparsec.Char8 +import Data.Data import Data.Monoid.Unicode +import Language.Haskell.TH.Syntax import Network.HTTP.Lucu.Parser import Network.HTTP.Lucu.Parser.Http hiding (token) import Network.HTTP.Lucu.Utils @@ -34,7 +39,15 @@ data ETag = ETag { -- |An opaque string. Only characters from 0x20 (sp) to 0x7e (~) -- are allowed. , etagToken â· !Ascii - } deriving (Eq, Show) + } deriving (Eq, Show, Data, Typeable) + +instance Lift ETag where + lift (ETag {..}) + = [| ETag { + etagIsWeak = $(lift etagIsWeak) + , etagToken = $(liftAscii etagToken) + } + |] -- |Convert an 'ETag' to an 'AsciiBuilder'. printETag â· ETag â AsciiBuilder diff --git a/Network/HTTP/Lucu/Implant/Input.hs b/Network/HTTP/Lucu/Implant/Input.hs new file mode 100644 index 0000000..4b462e3 --- /dev/null +++ b/Network/HTTP/Lucu/Implant/Input.hs @@ -0,0 +1,96 @@ +{-# LANGUAGE + QuasiQuotes + , RecordWildCards + , UnicodeSyntax + #-} +module Network.HTTP.Lucu.Implant.Input + ( Input(..) + + , originalLen + , gzippedLen + , useGZip + + , openInput + ) + where +import Codec.Compression.GZip +import Control.Applicative +import qualified Data.Ascii as A +import qualified Data.ByteString.Lazy as L +import Data.Digest.Pure.SHA +import Data.Maybe +import Data.Time +import Data.Time.Clock.POSIX +import Network.HTTP.Lucu.ETag +import Network.HTTP.Lucu.MIMEType hiding (mimeType) +import Network.HTTP.Lucu.MIMEType.DefaultExtensionMap +import Network.HTTP.Lucu.MIMEType.Guess +import Network.HTTP.Lucu.MIMEType.TH +import Prelude.Unicode +import System.Posix.Files + +data Input + = Input { + iPath â· !FilePath + , iLastMod â· !UTCTime + , iType â· !MIMEType + , iETag â· !ETag + , iRawData â· !L.ByteString + , iGZipped â· !L.ByteString + } + +originalLen â· Input â Integer +originalLen (Input {..}) + = fromIntegral $ L.length iRawData + +gzippedLen â· Input â Integer +gzippedLen (Input {..}) + = fromIntegral $ L.length iGZipped + +useGZip â· Input â Bool +useGZip i + = originalLen i ⥠gzippedLen i + +openInput â· FilePath â Maybe MIMEType â Maybe ETag â IO Input +openInput fpath ctype etag + = do lastMod â lastModified fpath + input â openInputFile fpath + return Input { + iPath = fpath + , iLastMod = lastMod + , iType = fromMaybe octetStream + $ ctype <|> guessType fpath + , iETag = fromMaybe (mkETagFromInput input) etag + , iRawData = input + , iGZipped = compressWith compParams input + } + where + octetStream â· MIMEType + octetStream = [mimeType| application/octet-stream |] + + compParams â· CompressParams + compParams = defaultCompressParams { + compressLevel = bestCompression + } + +lastModified â· FilePath â IO UTCTime +lastModified "-" = getCurrentTime +lastModified fpath = ( posixSecondsToUTCTime + â fromRational + â toRational + â modificationTime + ) + <$> + getFileStatus fpath + +openInputFile â· FilePath â IO L.ByteString +openInputFile "-" = L.getContents +openInputFile fpath = L.readFile fpath + +guessType â· FilePath â Maybe MIMEType +guessType = guessTypeByFileName defaultExtensionMap + +mkETagFromInput â· L.ByteString â ETag +mkETagFromInput input + = strongETag $ A.unsafeFromString + $ "SHA-1:" ⧺ showDigest (sha1 input) diff --git a/Network/HTTP/Lucu/Implant/PrettyPrint.hs b/Network/HTTP/Lucu/Implant/PrettyPrint.hs new file mode 100644 index 0000000..027003d --- /dev/null +++ b/Network/HTTP/Lucu/Implant/PrettyPrint.hs @@ -0,0 +1,213 @@ +{-# LANGUAGE + DoAndIfThenElse + , OverloadedStrings + , RecordWildCards + , TemplateHaskell + , UnicodeSyntax + #-} +module Network.HTTP.Lucu.Implant.PrettyPrint + ( pprInput + ) + where +import Codec.Compression.GZip +import Control.Monad +import Data.Ascii (CIAscii) +import qualified Data.Ascii as A +import qualified Data.ByteString.Lazy as L +import Data.List +import qualified Data.Map as M +import Data.Time +import Language.Haskell.TH.Lib +import Language.Haskell.TH.Ppr +import Language.Haskell.TH.PprLib +import Language.Haskell.TH.Syntax +import Network.HTTP.Lucu.ETag +import Network.HTTP.Lucu.Implant.Input +import Network.HTTP.Lucu.Implant.Rewrite +import Network.HTTP.Lucu.MIMEType +import Network.HTTP.Lucu.Resource +import Network.HTTP.Lucu.Utils +import Prelude.Unicode + +header â· Input â Doc +header i@(Input {..}) + = vcat [ text "{- DO NOT EDIT THIS FILE." + , nest 3 $ + vcat [ text "This file is automatically generated by lucu-implant-file." + , text "" + , text " Source:" <+> if iPath â¡ "-" then + text "(stdin)" + else + text iPath + , hsep [ text " Original Length:" + , integer (originalLen i) + , text "bytes" + ] + , if useGZip i then + vcat [ hsep [ text "Compressed Length:" + , integer (gzippedLen i) + , text "bytes" + ] + , text " Compression: gzip" + ] + else + text " Compression: disabled" + , text " MIME Type:" <+> mimeTypeToDoc iType + , text " ETag:" <+> eTagToDoc iETag + , text " Last Modified:" <+> text (show iLastMod) + ] + , text " -}" + , text "{-# LANGUAGE MagicHash #-}" + ] + where + eTagToDoc â· ETag â Doc + eTagToDoc = text â A.toString â A.fromAsciiBuilder â printETag + + mimeTypeToDoc â· MIMEType â Doc + mimeTypeToDoc = text â A.toString â A.fromAsciiBuilder â printMIMEType + +moduleDecl â· ModName â Name â Doc +moduleDecl modName symName + = hsep [ text "module" + , text (modString modName) + , lparen + , ppr symName + , rparen + , text "where" + ] + +importDecls â· ModMap â Doc +importDecls = vcat â map f â M.toAscList + where + f â· (ModName, Maybe ModName) â Doc + f (m, Nothing) = hsep [ text "import" + , text (modString m) + ] + f (m, Just m') = hsep [ text "import" + , text "qualified" + , text (modString m) + , text "as" + , text (modString m') + ] + +entityTag â· Name +entityTag = mkName "entityTag" + +lastModified â· Name +lastModified = mkName "lastModified" + +contentType â· Name +contentType = mkName "contentType" + +rawData â· Name +rawData = mkName "rawData" + +gzippedData â· Name +gzippedData = mkName "gzippedData" + +gzipEncoding â· Name +gzipEncoding = mkName "gzipEncoding" + +resourceDecl â· Input â Name â Q [Dec] +resourceDecl i symName + = sequence [ sigD symName [t| ResourceDef |] + , valD (varP symName) (normalB (resourceE i)) decls + ] + where + decls â· [Q Dec] + decls | useGZip i + = [ sigD gzipEncoding [t| CIAscii |] + , valD (varP gzipEncoding) (normalB (liftCIAscii "gzip")) [] + ] + | otherwise + = [] + +resourceE â· Input â Q Exp +resourceE i = [| emptyResource { + resGet = $(resGetE i) + , resHead = $(resHeadE i) + } + |] + +resGetE â· Input â Q Exp +resGetE i + | useGZip i + = [| Just $ + do foundEntity $(varE entityTag) $(varE lastModified) + setContentType $(varE contentType) + + gzipAllowed â isEncodingAcceptable $(varE gzipEncoding) + if gzipAllowed then + do setContentEncoding [$(varE gzipEncoding)] + putChunks $(varE gzippedData) + else + putChunks (decompress $(varE gzippedData)) + |] + | otherwise + = [| Just $ + do foundEntity $(varE entityTag) $(varE lastModified) + setContentType $(varE contentType) + putChunks $(varE rawData) + |] + +resHeadE â· Input â Q Exp +resHeadE i + | useGZip i + = [| Just $ + do foundEntity $(varE entityTag) $(varE lastModified) + setContentType $(varE contentType) + + gzipAllowed â isEncodingAcceptable $(varE gzipEncoding) + when gzipAllowed (setContentEncoding [$(varE gzipEncoding)]) + |] + | otherwise + = [| Just $ + do foundEntity $(varE entityTag) + $(varE lastModified) + setContentType $(varE contentType) + |] + +eTagDecl â· Input â Q [Dec] +eTagDecl (Input {..}) + = sequence [ sigD entityTag [t| ETag |] + , valD (varP entityTag) (normalB (lift iETag)) [] + ] + +lastModDecl â· Input â Q [Dec] +lastModDecl (Input {..}) + = sequence [ sigD lastModified [t| UTCTime |] + , valD (varP lastModified) (normalB (liftUTCTime iLastMod)) [] + ] + +contTypeDecl â· Input â Q [Dec] +contTypeDecl (Input {..}) + = sequence [ sigD contentType [t| MIMEType |] + , valD (varP contentType) (normalB (lift iType)) [] + ] + +binDecl â· Input â Q [Dec] +binDecl i@(Input {..}) + | useGZip i + = sequence [ sigD gzippedData [t| L.ByteString |] + , valD (varP gzippedData) (normalB (liftLazyByteString iGZipped)) [] + ] + | otherwise + = sequence [ sigD rawData [t| L.ByteString |] + , valD (varP rawData) (normalB (liftLazyByteString iRawData)) [] + ] + +pprInput â· Quasi m â Input â ModName â Name â m Doc +pprInput i modName symName + = do decls â runQ $ sequence [ resourceDecl i symName + , eTagDecl i + , lastModDecl i + , contTypeDecl i + , binDecl i + ] + let (decls', mods) = rewriteNames decls + return $ vcat [ header i + , moduleDecl modName symName + , importDecls mods + , text "" + , vcat $ intersperse (text "") $ map ppr decls' + ] diff --git a/Network/HTTP/Lucu/Implant/Rewrite.hs b/Network/HTTP/Lucu/Implant/Rewrite.hs new file mode 100644 index 0000000..37fbfbb --- /dev/null +++ b/Network/HTTP/Lucu/Implant/Rewrite.hs @@ -0,0 +1,129 @@ +{-# LANGUAGE + UnicodeSyntax + #-} +module Network.HTTP.Lucu.Implant.Rewrite + ( ModMap + , rewriteNames + ) + where +import Control.Applicative +import Control.Monad.State +import Data.Data +import Data.Generics.Aliases +import Data.Generics.Schemes +import Data.Map (Map) +import qualified Data.Map as M +import Data.Monoid.Unicode +import Language.Haskell.TH.Syntax +import Prelude.Unicode + +-- FIXME: Document at least these data types. +type ModMap = Map ModName (Maybe ModName) +data RewriteTo = Qual (Maybe ModName) ModName | UnQual (Maybe ModName) + +rewriteNames â· Data d â d â (d, ModMap) +rewriteNames + = flip runState (â ) â gmapM (everywhereM (mkM rewriteName)) + +rewriteName â· (Functor m, Monad m) + â Name + â StateT ModMap m Name +rewriteName (Name o fl) = Name o <$> rewriteNameFlavour fl + +rewriteNameFlavour â· (Functor m, Monad m) + â NameFlavour + â StateT ModMap m NameFlavour +rewriteNameFlavour fl + = case getModName fl of + Nothing â return fl + Just m â do let r = M.lookup m modules + insertIntoModMap m r + return $ setModName r fl + +insertIntoModMap â· Monad m + â ModName + â Maybe RewriteTo + â StateT ModMap m () +insertIntoModMap _ (Just (Qual (Just m) m')) = modify $ M.insert m (Just m') +insertIntoModMap m (Just (Qual Nothing m')) = modify $ M.insert m (Just m') +insertIntoModMap _ (Just (UnQual (Just m) )) = modify $ M.insert m Nothing +insertIntoModMap _ (Just (UnQual Nothing )) = return () +insertIntoModMap m Nothing = modify $ M.insert m Nothing + +getModName â· NameFlavour â Maybe ModName +getModName (NameQ m) = Just m +getModName (NameG _ _ m) = Just m +getModName _ = Nothing + +setModName â· Maybe RewriteTo â NameFlavour â NameFlavour +setModName (Just (Qual _ m)) (NameQ _ ) = NameQ m +setModName (Just (Qual _ m)) (NameG _ _ _) = NameQ m +setModName (Just (UnQual _)) (NameQ _ ) = NameS +setModName (Just (UnQual _)) (NameG _ _ _) = NameS +setModName Nothing (NameQ _ ) = NameS +setModName Nothing (NameG _ _ _) = NameS +setModName _ _ = error "setModName: internal error" + +modules â· Map ModName RewriteTo +modules + = M.fromList + [ ( mkModName "Codec.Compression.GZip" + , Qual Nothing $ mkModName "G" + ) + , ( mkModName "Data.Ascii" + , Qual Nothing $ mkModName "A" + ) + , ( mkModName "Data.ByteString.Char8" + , Qual Nothing $ mkModName "B" + ) + , ( mkModName "Data.ByteString.Lazy.Internal" + , Qual Nothing $ mkModName "L" + ) + , ( mkModName "Data.ByteString.Unsafe" + , Qual Nothing $ mkModName "B" + ) + , ( mkModName "Data.Map" + , Qual Nothing $ mkModName "M" + ) + , ( mkModName "Data.Maybe" + , UnQual Nothing + ) + , ( mkModName "Data.Text" + , Qual Nothing $ mkModName "T" + ) + , ( mkModName "Data.Time.Calendar.Days" + , UnQual $ Just $ mkModName "Data.Time" + ) + , ( mkModName "Data.Time.Clock.Scale" + , UnQual $ Just $ mkModName "Data.Time" + ) + , ( mkModName "Data.Time.Clock.UTC" + , UnQual $ Just $ mkModName "Data.Time" + ) + , ( mkModName "GHC.Base" + , UnQual Nothing + ) + , ( mkModName "GHC.Bool" + , UnQual Nothing + ) + , ( mkModName "GHC.IO" + -- for 'unsafePerformIO', but rather problematic... + , UnQual $ Just $ mkModName "System.IO.Unsafe" + ) + , ( mkModName "GHC.Real" + -- for '%', but rather problematic... + , UnQual $ Just $ mkModName "Data.Ratio" + ) + , ( mkModName "Network.HTTP.Lucu.ETag" + , UnQual $ Just $ mkModName "Network.HTTP.Lucu" + ) + , ( mkModName "Network.HTTP.Lucu.MIMEType" + , UnQual $ Just $ mkModName "Network.HTTP.Lucu" + ) + , ( mkModName "Network.HTTP.Lucu.Resource" + , UnQual $ Just $ mkModName "Network.HTTP.Lucu" + ) + , ( mkModName "Network.HTTP.Lucu.Resource.Internal" + , UnQual $ Just $ mkModName "Network.HTTP.Lucu" + ) + ] diff --git a/Network/HTTP/Lucu/Interaction.hs b/Network/HTTP/Lucu/Interaction.hs index df5e230..abc1cf5 100644 --- a/Network/HTTP/Lucu/Interaction.hs +++ b/Network/HTTP/Lucu/Interaction.hs @@ -147,6 +147,7 @@ data NormalInteraction , niWillDiscardBody â· !(TVar Bool) , niWillClose â· !(TVar Bool) , niResponseHasCType â· !(TVar Bool) + -- FIXME: use TBChan Builder (in stm-chans package) , niBodyToSend â· !(TMVar Builder) , niState â· !(TVar InteractionState) @@ -216,6 +217,7 @@ type InteractionQueue = TVar (Seq SomeInteraction) mkInteractionQueue â· IO InteractionQueue mkInteractionQueue = newTVarIO (â ) +-- FIXME: Response.hs should provide setStatus â· sc â Response â Response setResponseStatus â· StatusCode sc â NormalInteraction â sc â STM () setResponseStatus (NI {..}) sc = do res â readTVar niResponse diff --git a/Network/HTTP/Lucu/StaticFile.hs b/Network/HTTP/Lucu/StaticFile.hs index 90cdcb0..ffda4cf 100644 --- a/Network/HTTP/Lucu/StaticFile.hs +++ b/Network/HTTP/Lucu/StaticFile.hs @@ -66,6 +66,7 @@ handleStaticFile sendContent path $ abort $ mkAbortion Forbidden [] Nothing + -- FIXME: Forget about ETags of a static file. tag â liftIO $ generateETagFromFile path let lastMod = posixSecondsToUTCTime $ fromRational diff --git a/Network/HTTP/Lucu/Utils.hs b/Network/HTTP/Lucu/Utils.hs index d6e571d..55acf0a 100644 --- a/Network/HTTP/Lucu/Utils.hs +++ b/Network/HTTP/Lucu/Utils.hs @@ -1,5 +1,6 @@ {-# LANGUAGE OverloadedStrings + , RecordWildCards , TemplateHaskell , UnicodeSyntax #-} @@ -11,28 +12,37 @@ module Network.HTTP.Lucu.Utils , parseWWWFormURLEncoded , splitPathInfo , trim + , liftByteString + , liftLazyByteString + , liftAscii , liftCIAscii , liftText , liftMap + , liftUTCTime ) where import Control.Monad import Data.Ascii (Ascii, CIAscii, AsciiBuilder) import qualified Data.Ascii as A import Data.ByteString (ByteString) -import qualified Data.ByteString.Char8 as BS +import qualified Data.ByteString.Char8 as Strict +import qualified Data.ByteString.Unsafe as Strict +import qualified Data.ByteString.Lazy.Internal as Lazy import Data.Char import Data.List hiding (last) import Data.Map (Map) import qualified Data.Map as M import Data.Monoid.Unicode +import Data.Ratio import Data.Text (Text) import qualified Data.Text as T +import Data.Time import Language.Haskell.TH.Lib import Language.Haskell.TH.Syntax import Network.URI import Prelude hiding (last) import Prelude.Unicode +import System.IO.Unsafe -- |>>> splitBy (== ':') "ab:c:def" -- ["ab", "c", "def"] @@ -53,15 +63,17 @@ quoteStr str = A.toAsciiBuilder "\"" â go (A.toByteString str) (â ) â A.toAsciiBuilder "\"" where - go â· BS.ByteString â AsciiBuilder â AsciiBuilder + go â· Strict.ByteString â AsciiBuilder â AsciiBuilder go bs ab - = case BS.break (â¡ '"') bs of + = case Strict.break (â¡ '"') bs of (x, y) - | BS.null y â ab â b2ab x - | otherwise â go (BS.tail y) (ab â b2ab x - â A.toAsciiBuilder "\\\"") + | Strict.null y + â ab â b2ab x + | otherwise + â go (Strict.tail y) + (ab â b2ab x â A.toAsciiBuilder "\\\"") - b2ab â· BS.ByteString â AsciiBuilder + b2ab â· Strict.ByteString â AsciiBuilder b2ab = A.toAsciiBuilder â A.unsafeFromByteString -- |>>> parseWWWFormURLEncoded "aaa=bbb&ccc=ddd" @@ -80,7 +92,7 @@ parseWWWFormURLEncoded src ) where unescape â· String â ByteString - unescape = BS.pack â unEscapeString â map plusToSpace + unescape = Strict.pack â unEscapeString â map plusToSpace plusToSpace â· Char â Char plusToSpace '+' = ' ' @@ -93,7 +105,7 @@ splitPathInfo uri = let reqPathStr = uriPath uri reqPath = [unEscapeString x | x â splitBy (â¡ '/') reqPathStr, (¬) (null x)] in - map BS.pack reqPath + map Strict.pack reqPath -- |>>> trim " ab c d " -- "ab c d" @@ -102,24 +114,70 @@ trim = reverse â f â reverse â f where f = dropWhile isSpace +-- |Convert a 'ByteString' to an 'Exp' representing it as a literal. +liftByteString â· ByteString â Q Exp +liftByteString bs + = [| Strict.pack $(litE $ stringL $ Strict.unpack bs) |] + +-- |Convert a 'Lazy.ByteString' to an 'Exp' representing it as a +-- literal. +liftLazyByteString â· Lazy.ByteString â Q Exp +liftLazyByteString = Lazy.foldrChunks f [| Lazy.Empty |] + where + f â· ByteString â Q Exp â Q Exp + f bs e = [| Lazy.Chunk $(liftByteString bs) $e |] + +-- |Convert an 'Ascii' to an 'Exp' representing it as a literal. +liftAscii â· Ascii â Q Exp +liftAscii a = [| A.unsafeFromByteString + $ unsafePerformIO + $ Strict.unsafePackAddressLen $len $ptr + |] + where + bs â· Strict.ByteString + bs = A.toByteString a + + len, ptr â· Q Exp + len = lift $ Strict.length bs + ptr = litE $ stringPrimL $ Strict.unpack bs + -- |Convert a 'CIAscii' to an 'Exp' representing it as a literal. liftCIAscii â· CIAscii â Q Exp -liftCIAscii a = [| A.toCIAscii (A.unsafeFromString $(strLit a)) |] - where - strLit â· CIAscii â Q Exp - strLit = liftString â A.toString â A.fromCIAscii +liftCIAscii a = [| A.toCIAscii $(liftAscii $ A.fromCIAscii a) |] -- |Convert a 'Text' to an 'Exp' representing it as a literal. liftText â· Text â Q Exp -liftText t = [| T.pack $(strLit t) |] - where - strLit â· Text â Q Exp - strLit = liftString â T.unpack +liftText t = [| T.pack $(litE $ stringL $ T.unpack t) |] -- |Convert an arbitrary 'Map' to an 'Exp' representing it as a -- literal, using a given key lifter and a value lifter. liftMap â· Eq k â (k â Q Exp) â (v â Q Exp) â Map k v â Q Exp -liftMap liftK liftV m = [| M.fromAscList $(liftPairs $ M.toAscList m) |] +liftMap liftK liftV m + | M.null m = [| M.empty |] + | otherwise = [| M.fromDistinctAscList $(liftPairs (M.toAscList m)) |] where liftPairs = listE â map liftPair liftPair (k, v) = tupE [liftK k, liftV v] + +-- |Convert an 'UTCTime' to an 'Exp' representing it as a literal. +liftUTCTime â· UTCTime â Q Exp +liftUTCTime (UTCTime {..}) + = [| UTCTime { + utctDay = $(liftDay utctDay) + , utctDayTime = $(liftDiffTime utctDayTime) + } + |] + +liftDay â· Day â Q Exp +liftDay (ModifiedJulianDay {..}) + = [| ModifiedJulianDay { + toModifiedJulianDay = $(lift toModifiedJulianDay) + } + |] + +liftDiffTime â· DiffTime â Q Exp +liftDiffTime dt = [| fromRational ($n % $d) â· DiffTime |] + where + n, d â· Q Exp + n = lift $ numerator $ toRational dt + d = lift $ denominator $ toRational dt -- 2.40.0