packages feed

happstack-server 0.3.3 → 0.4.1

raw patch · 13 files changed

+478/−568 lines, 13 filesdep +filepathdep +sendfiledep ~happstack-datadep ~happstack-utildep ~networknew-uploaderPVP ok

version bump matches the API change (PVP)

Dependencies added: filepath, sendfile

Dependency ranges changed: happstack-data, happstack-util, network

API changes (from Hackage documentation)

+ Happstack.Server.Cookie: secure :: Cookie -> Bool
+ Happstack.Server.HTTP.FileServe: asContentType :: (Monad m) => String -> (FilePath -> m String)
+ Happstack.Server.HTTP.FileServe: defaultIxFiles :: [String]
+ Happstack.Server.HTTP.FileServe: doIndex' :: (ServerMonad m, FilterMonad Response m, MonadIO m) => (String -> FilePath -> m Response) -> (FilePath -> m String) -> [String] -> String -> m Response
+ Happstack.Server.HTTP.FileServe: doIndexLazy :: (ServerMonad m, FilterMonad Response m, MonadIO m) => [String] -> MimeMap -> String -> m Response
+ Happstack.Server.HTTP.FileServe: filePathLazy :: (ServerMonad m, MonadIO m) => String -> FilePath -> m Response
+ Happstack.Server.HTTP.FileServe: filePathSendFile :: (ServerMonad m, MonadIO m) => String -> FilePath -> m Response
+ Happstack.Server.HTTP.FileServe: filePathStrict :: (ServerMonad m, MonadIO m) => String -> FilePath -> m Response
+ Happstack.Server.HTTP.FileServe: fileServe' :: (WebMonad Response m, ServerMonad m, FilterMonad Response m, MonadIO m) => (String -> FilePath -> m Response) -> (FilePath -> m String) -> [FilePath] -> FilePath -> m Response
+ Happstack.Server.HTTP.FileServe: fileServeLazy :: (WebMonad Response m, ServerMonad m, FilterMonad Response m, MonadIO m) => [FilePath] -> FilePath -> m Response
+ Happstack.Server.HTTP.FileServe: guessContentType :: MimeMap -> FilePath -> Maybe String
+ Happstack.Server.HTTP.FileServe: guessContentTypeM :: (Monad m) => MimeMap -> (FilePath -> m String)
+ Happstack.Server.HTTP.FileServe: lazyByteStringResponse :: String -> ByteString -> Maybe (CalendarTime, Request) -> Integer -> Integer -> Response
+ Happstack.Server.HTTP.FileServe: sendFileResponse :: String -> FilePath -> Maybe (CalendarTime, Request) -> Integer -> Integer -> Response
+ Happstack.Server.HTTP.FileServe: serveFile :: (ServerMonad m, FilterMonad Response m, MonadIO m) => (FilePath -> m String) -> FilePath -> m Response
+ Happstack.Server.HTTP.FileServe: serveFileUsing :: (ServerMonad m, FilterMonad Response m, MonadIO m) => (String -> FilePath -> m Response) -> (FilePath -> m String) -> FilePath -> m Response
+ Happstack.Server.HTTP.FileServe: strictByteStringResponse :: String -> ByteString -> Maybe (CalendarTime, Request) -> Integer -> Integer -> Response
+ Happstack.Server.HTTP.LowLevel: listen' :: Socket -> Conf -> (Request -> IO Response) -> IO ()
+ Happstack.Server.HTTP.Types: SendFile :: Int -> Headers -> RsFlags -> Maybe (Response -> IO Response) -> FilePath -> Integer -> Integer -> Response
+ Happstack.Server.HTTP.Types: sfCount :: Response -> Integer
+ Happstack.Server.HTTP.Types: sfFilePath :: Response -> FilePath
+ Happstack.Server.HTTP.Types: sfOffset :: Response -> Integer
+ Happstack.Server.SimpleHTTP: bindPort :: Conf -> IO Socket
+ Happstack.Server.SimpleHTTP: host :: (ServerMonad m, MonadPlus m) => String -> m a -> m a
+ Happstack.Server.SimpleHTTP: ifModifiedSince :: CalendarTime -> Request -> Response -> Response
+ Happstack.Server.SimpleHTTP: instance [overlap ok] (Error e, ServerMonad m) => ServerMonad (ErrorT e m)
+ Happstack.Server.SimpleHTTP: simpleHTTP'' :: (ToMessage b, Monad m, Functor m) => ServerPartT m b -> Request -> m Response
+ Happstack.Server.SimpleHTTP: simpleHTTPWithSocket :: (ToMessage a) => Socket -> Conf -> ServerPartT IO a -> IO ()
+ Happstack.Server.SimpleHTTP: simpleHTTPWithSocket' :: (ToMessage b, Monad m, Functor m) => (UnWebT m a -> UnWebT IO b) -> Socket -> Conf -> ServerPartT m a -> IO ()
+ Happstack.Server.SimpleHTTP: trailingSlash :: (ServerMonad m, MonadPlus m) => m ()
+ Happstack.Server.SimpleHTTP: withHost :: (ServerMonad m, MonadPlus m) => (String -> m a) -> m a
- Happstack.Server.Cookie: Cookie :: String -> String -> String -> String -> String -> Cookie
+ Happstack.Server.Cookie: Cookie :: String -> String -> String -> String -> String -> Bool -> Cookie
- Happstack.Server.HTTP.FileServe: fileServeStrict :: (ServerMonad m, FilterMonad Response m, MonadIO m) => [FilePath] -> FilePath -> m Response
+ Happstack.Server.HTTP.FileServe: fileServeStrict :: (WebMonad Response m, ServerMonad m, FilterMonad Response m, MonadIO m) => [FilePath] -> FilePath -> m Response
- Happstack.Server.SimpleHTTP: rproxyServe :: (MonadIO m, WebMonad Response m) => String -> [(String, String)] -> ServerPartT m Response
+ Happstack.Server.SimpleHTTP: rproxyServe :: (MonadIO m) => String -> [(String, String)] -> ServerPartT m Response

Files

happstack-server.cabal view
@@ -1,5 +1,5 @@ Name:                happstack-server-Version:             0.3.3+Version:             0.4.1 Synopsis:            Web related tools and services. Description:         Web framework License:             BSD3@@ -14,7 +14,7 @@ source-repository head     type:     darcs     subdir:   happstack-server-    location: http://patch-tag.com/publicrepos/happstack+    location: http://patch-tag.com/r/mae/happstack/pullrepo  Flag base4     Description: Choose the even newer, even smaller, split-up base package.@@ -43,7 +43,6 @@     Exposed-modules:                           Happstack.Server.Tests   Other-modules:       -                       Happstack.Server.S3                        Happstack.Server.HTTPClient.HTTP                        Happstack.Server.HTTPClient.Stream                        Happstack.Server.HTTPClient.TCP@@ -63,10 +62,11 @@                        containers,                        directory,                        extensible-exceptions,+                       filepath,                        HaXml >= 1.13 && < 1.14,                        hslogger >= 1.0.2,-                       happstack-data >= 0.3.2 && < 0.4,-                       happstack-util >= 0.3.2 && < 0.4,+                       happstack-data >= 0.4.1 && < 0.5,+                       happstack-util >= 0.4.1 && < 0.5,                        html,                        MaybeT,                        mtl,@@ -75,6 +75,7 @@                        old-time,                        parsec < 3,                        process,+                       sendfile >= 0.6.1 && < 0.7,                        template-haskell,                        time,                        utf8-string >= 0.3.4 && < 0.4,
src/Happstack/Server/Cookie.hs view
@@ -28,11 +28,12 @@     , cookieDomain  :: String     , cookieName    :: String     , cookieValue   :: String+    , secure        :: Bool     } deriving(Show,Eq,Read,Typeable,Data)  -- | Creates a cookie with a default version of 1 and path of "/" mkCookie :: String -> String -> Cookie-mkCookie key val = Cookie "1" "/" "" key val+mkCookie key val = Cookie "1" "/" "" key val False  -- | Set a Cookie in the Result. -- The values are escaped as per RFC 2109, but some browsers may@@ -47,7 +48,7 @@         s f   = '\"' : concatMap e (f cookie) ++ "\""         e c | fctl c || c == '"' = ['\\',c]             | otherwise          = [c]-    in concat $ intersperse ";" ((cookieName cookie++"="++s cookieValue):[ (k++v) | (k,v) <- l, "" /= v ])+    in concat $ intersperse ";" ((cookieName cookie++"="++s cookieValue):[ (k++v) | (k,v) <- l, "" /= v ] ++ if secure cookie then ["Secure"] else [])  fctl :: Char -> Bool fctl ch = ch == chr 127 || ch <= chr 31@@ -74,7 +75,7 @@             val<-value             path<-option "" $ try (cookieSep >> cookie_path)             domain<-option "" $ try (cookieSep >> cookie_domain)-            return $ Cookie ver path domain (low name) val+            return $ Cookie ver path domain (low name) val False           cookie_version = cookie_special "$Version"           cookie_path = cookie_special "$Path"           cookie_domain = cookie_special "$Domain"
src/Happstack/Server/HTTP/Client.hs view
@@ -4,6 +4,7 @@ import Happstack.Server.HTTP.Handler import Happstack.Server.HTTP.Types import Data.Maybe+import Control.Monad import qualified Data.ByteString.Lazy.Char8 as L   import System.IO@@ -43,10 +44,10 @@   csv v x = x++", " ++ v  unrproxify :: String -> [(String, String)] -> Request -> Request-unrproxify defaultHost list rq = unproxify rq {rqPaths = host: rqPaths rq}-  where-  host::String-  host = maybe defaultHost (f .B.unpack) $-         getHeader "host" rq-  f = maybe defaultHost id . flip lookup list+unrproxify defaultHost list rq = +  let host::String+      host = fromMaybe defaultHost $ flip lookup list =<< B.unpack `liftM` getHeader "host" rq +      newrq = rq {rqPaths = host: rqPaths rq}+  in  unproxify newrq+ 
src/Happstack/Server/HTTP/FileServe.hs view
@@ -1,34 +1,54 @@ {-# LANGUAGE FlexibleContexts, Rank2Types #-}+-- |File Serving functions module Happstack.Server.HTTP.FileServe     (+     -- * Content-Type \/ Mime-Type      MimeMap,+     mimeTypes,+     asContentType,+     guessContentType,+     guessContentTypeM,+     -- * Low-Level+     sendFileResponse,     +     lazyByteStringResponse,+     strictByteStringResponse,+     filePathSendFile,+     filePathLazy,+     filePathStrict,+     -- * High-Level+     -- ** Serving a single file+     serveFile,+     serveFileUsing,+     -- ** Serving files from a directory+     fileServe',+     fileServe,+     fileServeLazy,+     fileServeStrict,+     -- * Other      blockDotFiles,+     defaultIxFiles,      doIndex,+     doIndex',+     doIndexLazy,      doIndexStrict,      errorwrapper,-     fileServe,-     fileServeStrict,-     isDot,-     mimeTypes+     isDot     ) where -import Control.Exception.Extensible--import Control.Monad.Reader-import Control.Monad.Trans-import Data.List-import Data.Maybe-import Data.Int-import Happstack.Server.SimpleHTTP hiding (path)-import System.Directory-import System.IO-import System.Locale(defaultTimeLocale)-import System.Log.Logger-import System.Time -- (formatCalendarTime, toUTCTime,TOD(..))-import qualified Data.ByteString.Char8 as P+import Control.Exception.Extensible (IOException, SomeException, Exception(fromException), bracket, handleJust)+import Control.Monad (MonadPlus)+import Control.Monad.Trans (MonadIO(liftIO)) import qualified Data.ByteString.Lazy.Char8 as L+import qualified Data.ByteString.Char8 as S+import Data.Maybe (fromMaybe)+import           Data.Map (Map) import qualified Data.Map as Map-import qualified Happstack.Server.SimpleHTTP as SH+import Happstack.Server.SimpleHTTP (FilterMonad, ServerMonad(askRq), Request(..), Response(..), WebMonad, toResponse, resultBS, setHeader, forbidden, nullRsFlags, result, require, rsfContentLength, seeOther, ifModifiedSince )+import System.Directory (doesDirectoryExist, doesFileExist, getModificationTime)+import System.IO (IOMode(ReadMode), hFileSize, hClose, openBinaryFile)+import System.FilePath ((</>), addTrailingPathSeparator, joinPath, takeExtension)+import System.Log.Logger (Priority(DEBUG), logM)+import System.Time (CalendarTime, toUTCTime)  ioErrors :: SomeException -> Maybe IOException ioErrors = fromException@@ -45,269 +65,287 @@                      then fmap Just $ readFile loglocation                      else return Nothing +-- * Mime-Type / Content-Type -type MimeMap = Map.Map String String+type MimeMap = Map String String -type GetFileFunc = (MonadIO m) =>-    Map.Map String String-    -> String-    -> m (Either String ((ClockTime, Integer), (String, L.ByteString)))+-- | Ready collection of common mime types.+-- Except for the first two entries, the mappings come from an Ubuntu 8.04 /etc/mime.types file.+mimeTypes :: MimeMap+mimeTypes = Map.fromList [("gz","application/x-gzip"),("cabal","application/x-cabal"),("%","application/x-trash"),("323","text/h323"),("3gp","video/3gpp"),("7z","application/x-7z-compressed"),("abw","application/x-abiword"),("ai","application/postscript"),("aif","audio/x-aiff"),("aifc","audio/x-aiff"),("aiff","audio/x-aiff"),("alc","chemical/x-alchemy"),("art","image/x-jg"),("asc","text/plain"),("asf","video/x-ms-asf"),("asn","chemical/x-ncbi-asn1"),("aso","chemical/x-ncbi-asn1-binary"),("asx","video/x-ms-asf"),("atom","application/atom"),("atomcat","application/atomcat+xml"),("atomsrv","application/atomserv+xml"),("au","audio/basic"),("avi","video/x-msvideo"),("b","chemical/x-molconn-Z"),("bak","application/x-trash"),("bat","application/x-msdos-program"),("bcpio","application/x-bcpio"),("bib","text/x-bibtex"),("bin","application/octet-stream"),("bmp","image/x-ms-bmp"),("boo","text/x-boo"),("book","application/x-maker"),("bsd","chemical/x-crossfire"),("c","text/x-csrc"),("c++","text/x-c++src"),("c3d","chemical/x-chem3d"),("cab","application/x-cab"),("cac","chemical/x-cache"),("cache","chemical/x-cache"),("cap","application/cap"),("cascii","chemical/x-cactvs-binary"),("cat","application/vnd.ms-pki.seccat"),("cbin","chemical/x-cactvs-binary"),("cbr","application/x-cbr"),("cbz","application/x-cbz"),("cc","text/x-c++src"),("cdf","application/x-cdf"),("cdr","image/x-coreldraw"),("cdt","image/x-coreldrawtemplate"),("cdx","chemical/x-cdx"),("cdy","application/vnd.cinderella"),("cef","chemical/x-cxf"),("cer","chemical/x-cerius"),("chm","chemical/x-chemdraw"),("chrt","application/x-kchart"),("cif","chemical/x-cif"),("class","application/java-vm"),("cls","text/x-tex"),("cmdf","chemical/x-cmdf"),("cml","chemical/x-cml"),("cod","application/vnd.rim.cod"),("com","application/x-msdos-program"),("cpa","chemical/x-compass"),("cpio","application/x-cpio"),("cpp","text/x-c++src"),("cpt","application/mac-compactpro"),("crl","application/x-pkcs7-crl"),("crt","application/x-x509-ca-cert"),("csf","chemical/x-cache-csf"),("csh","application/x-csh"),("csm","chemical/x-csml"),("csml","chemical/x-csml"),("css","text/css"),("csv","text/csv"),("ctab","chemical/x-cactvs-binary"),("ctx","chemical/x-ctx"),("cu","application/cu-seeme"),("cub","chemical/x-gaussian-cube"),("cxf","chemical/x-cxf"),("cxx","text/x-c++src"),("d","text/x-dsrc"),("dat","chemical/x-mopac-input"),("dcr","application/x-director"),("deb","application/x-debian-package"),("dif","video/dv"),("diff","text/x-diff"),("dir","application/x-director"),("djv","image/vnd.djvu"),("djvu","image/vnd.djvu"),("dl","video/dl"),("dll","application/x-msdos-program"),("dmg","application/x-apple-diskimage"),("dms","application/x-dms"),("doc","application/msword"),("dot","application/msword"),("dv","video/dv"),("dvi","application/x-dvi"),("dx","chemical/x-jcamp-dx"),("dxr","application/x-director"),("emb","chemical/x-embl-dl-nucleotide"),("embl","chemical/x-embl-dl-nucleotide"),("eml","message/rfc822"),("ent","chemical/x-ncbi-asn1-ascii"),("eps","application/postscript"),("etx","text/x-setext"),("exe","application/x-msdos-program"),("ez","application/andrew-inset"),("fb","application/x-maker"),("fbdoc","application/x-maker"),("fch","chemical/x-gaussian-checkpoint"),("fchk","chemical/x-gaussian-checkpoint"),("fig","application/x-xfig"),("flac","application/x-flac"),("fli","video/fli"),("fm","application/x-maker"),("frame","application/x-maker"),("frm","application/x-maker"),("gal","chemical/x-gaussian-log"),("gam","chemical/x-gamess-input"),("gamin","chemical/x-gamess-input"),("gau","chemical/x-gaussian-input"),("gcd","text/x-pcs-gcd"),("gcf","application/x-graphing-calculator"),("gcg","chemical/x-gcg8-sequence"),("gen","chemical/x-genbank"),("gf","application/x-tex-gf"),("gif","image/gif"),("gjc","chemical/x-gaussian-input"),("gjf","chemical/x-gaussian-input"),("gl","video/gl"),("gnumeric","application/x-gnumeric"),("gpt","chemical/x-mopac-graph"),("gsf","application/x-font"),("gsm","audio/x-gsm"),("gtar","application/x-gtar"),("h","text/x-chdr"),("h++","text/x-c++hdr"),("hdf","application/x-hdf"),("hh","text/x-c++hdr"),("hin","chemical/x-hin"),("hpp","text/x-c++hdr"),("hqx","application/mac-binhex40"),("hs","text/x-haskell"),("hta","application/hta"),("htc","text/x-component"),("htm","text/html"),("html","text/html"),("hxx","text/x-c++hdr"),("ica","application/x-ica"),("ice","x-conference/x-cooltalk"),("ico","image/x-icon"),("ics","text/calendar"),("icz","text/calendar"),("ief","image/ief"),("iges","model/iges"),("igs","model/iges"),("iii","application/x-iphone"),("inp","chemical/x-gamess-input"),("ins","application/x-internet-signup"),("iso","application/x-iso9660-image"),("isp","application/x-internet-signup"),("ist","chemical/x-isostar"),("istr","chemical/x-isostar"),("jad","text/vnd.sun.j2me.app-descriptor"),("jar","application/java-archive"),("java","text/x-java"),("jdx","chemical/x-jcamp-dx"),("jmz","application/x-jmol"),("jng","image/x-jng"),("jnlp","application/x-java-jnlp-file"),("jpe","image/jpeg"),("jpeg","image/jpeg"),("jpg","image/jpeg"),("js","application/x-javascript"),("kar","audio/midi"),("key","application/pgp-keys"),("kil","application/x-killustrator"),("kin","chemical/x-kinemage"),("kml","application/vnd.google-earth.kml+xml"),("kmz","application/vnd.google-earth.kmz"),("kpr","application/x-kpresenter"),("kpt","application/x-kpresenter"),("ksp","application/x-kspread"),("kwd","application/x-kword"),("kwt","application/x-kword"),("latex","application/x-latex"),("lha","application/x-lha"),("lhs","text/x-literate-haskell"),("lsf","video/x-la-asf"),("lsx","video/x-la-asf"),("ltx","text/x-tex"),("lyx","application/x-lyx"),("lzh","application/x-lzh"),("lzx","application/x-lzx"),("m3u","audio/mpegurl"),("m4a","audio/mpeg"),("maker","application/x-maker"),("man","application/x-troff-man"),("mcif","chemical/x-mmcif"),("mcm","chemical/x-macmolecule"),("mdb","application/msaccess"),("me","application/x-troff-me"),("mesh","model/mesh"),("mid","audio/midi"),("midi","audio/midi"),("mif","application/x-mif"),("mm","application/x-freemind"),("mmd","chemical/x-macromodel-input"),("mmf","application/vnd.smaf"),("mml","text/mathml"),("mmod","chemical/x-macromodel-input"),("mng","video/x-mng"),("moc","text/x-moc"),("mol","chemical/x-mdl-molfile"),("mol2","chemical/x-mol2"),("moo","chemical/x-mopac-out"),("mop","chemical/x-mopac-input"),("mopcrt","chemical/x-mopac-input"),("mov","video/quicktime"),("movie","video/x-sgi-movie"),("mp2","audio/mpeg"),("mp3","audio/mpeg"),("mp4","video/mp4"),("mpc","chemical/x-mopac-input"),("mpe","video/mpeg"),("mpeg","video/mpeg"),("mpega","audio/mpeg"),("mpg","video/mpeg"),("mpga","audio/mpeg"),("ms","application/x-troff-ms"),("msh","model/mesh"),("msi","application/x-msi"),("mvb","chemical/x-mopac-vib"),("mxu","video/vnd.mpegurl"),("nb","application/mathematica"),("nc","application/x-netcdf"),("nwc","application/x-nwc"),("o","application/x-object"),("oda","application/oda"),("odb","application/vnd.oasis.opendocument.database"),("odc","application/vnd.oasis.opendocument.chart"),("odf","application/vnd.oasis.opendocument.formula"),("odg","application/vnd.oasis.opendocument.graphics"),("odi","application/vnd.oasis.opendocument.image"),("odm","application/vnd.oasis.opendocument.text-master"),("odp","application/vnd.oasis.opendocument.presentation"),("ods","application/vnd.oasis.opendocument.spreadsheet"),("odt","application/vnd.oasis.opendocument.text"),("oga","audio/ogg"),("ogg","application/ogg"),("ogv","video/ogg"),("ogx","application/ogg"),("old","application/x-trash"),("otg","application/vnd.oasis.opendocument.graphics-template"),("oth","application/vnd.oasis.opendocument.text-web"),("otp","application/vnd.oasis.opendocument.presentation-template"),("ots","application/vnd.oasis.opendocument.spreadsheet-template"),("ott","application/vnd.oasis.opendocument.text-template"),("oza","application/x-oz-application"),("p","text/x-pascal"),("p7r","application/x-pkcs7-certreqresp"),("pac","application/x-ns-proxy-autoconfig"),("pas","text/x-pascal"),("pat","image/x-coreldrawpattern"),("patch","text/x-diff"),("pbm","image/x-portable-bitmap"),("pcap","application/cap"),("pcf","application/x-font"),("pcf.Z","application/x-font"),("pcx","image/pcx"),("pdb","chemical/x-pdb"),("pdf","application/pdf"),("pfa","application/x-font"),("pfb","application/x-font"),("pgm","image/x-portable-graymap"),("pgn","application/x-chess-pgn"),("pgp","application/pgp-signature"),("php","application/x-httpd-php"),("php3","application/x-httpd-php3"),("php3p","application/x-httpd-php3-preprocessed"),("php4","application/x-httpd-php4"),("phps","application/x-httpd-php-source"),("pht","application/x-httpd-php"),("phtml","application/x-httpd-php"),("pk","application/x-tex-pk"),("pl","text/x-perl"),("pls","audio/x-scpls"),("pm","text/x-perl"),("png","image/png"),("pnm","image/x-portable-anymap"),("pot","text/plain"),("ppm","image/x-portable-pixmap"),("pps","application/vnd.ms-powerpoint"),("ppt","application/vnd.ms-powerpoint"),("prf","application/pics-rules"),("prt","chemical/x-ncbi-asn1-ascii"),("ps","application/postscript"),("psd","image/x-photoshop"),("py","text/x-python"),("pyc","application/x-python-code"),("pyo","application/x-python-code"),("qt","video/quicktime"),("qtl","application/x-quicktimeplayer"),("ra","audio/x-pn-realaudio"),("ram","audio/x-pn-realaudio"),("rar","application/rar"),("ras","image/x-cmu-raster"),("rd","chemical/x-mdl-rdfile"),("rdf","application/rdf+xml"),("rgb","image/x-rgb"),("rhtml","application/x-httpd-eruby"),("rm","audio/x-pn-realaudio"),("roff","application/x-troff"),("ros","chemical/x-rosdal"),("rpm","application/x-redhat-package-manager"),("rss","application/rss+xml"),("rtf","application/rtf"),("rtx","text/richtext"),("rxn","chemical/x-mdl-rxnfile"),("sct","text/scriptlet"),("sd","chemical/x-mdl-sdfile"),("sd2","audio/x-sd2"),("sda","application/vnd.stardivision.draw"),("sdc","application/vnd.stardivision.calc"),("sdd","application/vnd.stardivision.impress"),("sdf","application/vnd.stardivision.math"),("sds","application/vnd.stardivision.chart"),("sdw","application/vnd.stardivision.writer"),("ser","application/java-serialized-object"),("sgf","application/x-go-sgf"),("sgl","application/vnd.stardivision.writer-global"),("sh","application/x-sh"),("shar","application/x-shar"),("shtml","text/html"),("sid","audio/prs.sid"),("sik","application/x-trash"),("silo","model/mesh"),("sis","application/vnd.symbian.install"),("sisx","x-epoc/x-sisx-app"),("sit","application/x-stuffit"),("sitx","application/x-stuffit"),("skd","application/x-koan"),("skm","application/x-koan"),("skp","application/x-koan"),("skt","application/x-koan"),("smi","application/smil"),("smil","application/smil"),("snd","audio/basic"),("spc","chemical/x-galactic-spc"),("spl","application/futuresplash"),("spx","audio/ogg"),("src","application/x-wais-source"),("stc","application/vnd.sun.xml.calc.template"),("std","application/vnd.sun.xml.draw.template"),("sti","application/vnd.sun.xml.impress.template"),("stl","application/vnd.ms-pki.stl"),("stw","application/vnd.sun.xml.writer.template"),("sty","text/x-tex"),("sv4cpio","application/x-sv4cpio"),("sv4crc","application/x-sv4crc"),("svg","image/svg+xml"),("svgz","image/svg+xml"),("sw","chemical/x-swissprot"),("swf","application/x-shockwave-flash"),("swfl","application/x-shockwave-flash"),("sxc","application/vnd.sun.xml.calc"),("sxd","application/vnd.sun.xml.draw"),("sxg","application/vnd.sun.xml.writer.global"),("sxi","application/vnd.sun.xml.impress"),("sxm","application/vnd.sun.xml.math"),("sxw","application/vnd.sun.xml.writer"),("t","application/x-troff"),("tar","application/x-tar"),("taz","application/x-gtar"),("tcl","application/x-tcl"),("tex","text/x-tex"),("texi","application/x-texinfo"),("texinfo","application/x-texinfo"),("text","text/plain"),("tgf","chemical/x-mdl-tgf"),("tgz","application/x-gtar"),("tif","image/tiff"),("tiff","image/tiff"),("tk","text/x-tcl"),("tm","text/texmacs"),("torrent","application/x-bittorrent"),("tr","application/x-troff"),("ts","text/texmacs"),("tsp","application/dsptype"),("tsv","text/tab-separated-values"),("txt","text/plain"),("udeb","application/x-debian-package"),("uls","text/iuls"),("ustar","application/x-ustar"),("val","chemical/x-ncbi-asn1-binary"),("vcd","application/x-cdlink"),("vcf","text/x-vcard"),("vcs","text/x-vcalendar"),("vmd","chemical/x-vmd"),("vms","chemical/x-vamas-iso14976"),("vrm","x-world/x-vrml"),("vrml","model/vrml"),("vsd","application/vnd.visio"),("wad","application/x-doom"),("wav","audio/x-wav"),("wax","audio/x-ms-wax"),("wbmp","image/vnd.wap.wbmp"),("wbxml","application/vnd.wap.wbxml"),("wk","application/x-123"),("wm","video/x-ms-wm"),("wma","audio/x-ms-wma"),("wmd","application/x-ms-wmd"),("wml","text/vnd.wap.wml"),("wmlc","application/vnd.wap.wmlc"),("wmls","text/vnd.wap.wmlscript"),("wmlsc","application/vnd.wap.wmlscriptc"),("wmv","video/x-ms-wmv"),("wmx","video/x-ms-wmx"),("wmz","application/x-ms-wmz"),("wp5","application/wordperfect5.1"),("wpd","application/wordperfect"),("wrl","model/vrml"),("wsc","text/scriptlet"),("wvx","video/x-ms-wvx"),("wz","application/x-wingz"),("xbm","image/x-xbitmap"),("xcf","application/x-xcf"),("xht","application/xhtml+xml"),("xhtml","application/xhtml+xml"),("xlb","application/vnd.ms-excel"),("xls","application/vnd.ms-excel"),("xlt","application/vnd.ms-excel"),("xml","application/xml"),("xpi","application/x-xpinstall"),("xpm","image/x-xpixmap"),("xsl","application/xml"),("xtel","chemical/x-xtel"),("xul","application/vnd.mozilla.xul+xml"),("xwd","image/x-xwindowdump"),("xyz","chemical/x-xyz"),("zip","application/zip"),("zmt","chemical/x-mopac-input"),("~","application/x-trash")]  +guessContentType :: MimeMap -> FilePath -> Maybe String+guessContentType mimeMap filepath =+    case getExt filepath of+      "" -> Nothing+      ext -> Map.lookup ext mimeMap -doIndex :: (ServerMonad m, FilterMonad Response m, MonadIO m) =>-           [String] -> MimeMap -> String -> m Response-doIndex = doIndex' getFile+guessContentTypeM :: (Monad m) => MimeMap -> (FilePath -> m String)+guessContentTypeM mimeMap filePath = return $ fromMaybe "text/plain" $ guessContentType mimeMap filePath --- | A variant of 'doIndex' that relies on 'getFileStrict'-doIndexStrict :: (ServerMonad m, FilterMonad Response m, MonadIO m) =>-                 [String] -> MimeMap -> String -> m Response-doIndexStrict = doIndex' getFileStrict+asContentType :: (Monad m) => String -> (FilePath -> m String)+asContentType = const . return +defaultIxFiles :: [String]+defaultIxFiles= ["index.html","index.xml","index.gif"] -doIndex' :: (ServerMonad m, FilterMonad Response m, MonadIO m) =>-            GetFileFunc-         -> [String]-         -> MimeMap-         -> String-         -> m Response-doIndex' _getFileFunc []           _mime _fp = forbidden $ toResponse "Directory index forbidden"-doIndex'  getFileFunc (index:rest)  mime  fp =-    do-    let path = fp++'/':index-    --print path-    fe <- liftIO $ doesFileExist path-    if fe then retFile path else doIndex rest mime fp-    where retFile = returnFile getFileFunc mime+fileNotFound :: (Monad m, FilterMonad Response m) => FilePath -> m Response+fileNotFound fp = return $ result 404 $ "File not found " ++ fp +-- | Similar to 'takeExtension' but does not include the extension separator char+getExt :: FilePath -> String+getExt fp = drop 1 $ takeExtension fp +-- | Prevents files of the form '.foo' or 'bar/.foo' from being served+blockDotFiles :: (Request -> IO Response) -> Request -> IO Response+blockDotFiles fn rq+    | isDot (joinPath (rqPaths rq)) = return $ result 403 "Dot files not allowed."+    | otherwise = fn rq -defaultIxFiles :: [String]-defaultIxFiles= ["index.html","index.xml","index.gif"]+-- | Returns True if the given String either starts with a . or is of the form+-- "foo/.bar", e.g. the typical *nix convention for hidden files.+isDot :: String -> Bool+isDot = isD . reverse+    where+    isD ('.':'/':_) = True+    isD ['.']       = True+    --isD ('/':_)     = False+    isD (_:cs)      = isD cs+    isD []          = False +-- * Low-level functions for generating a Response --- | Serve a file (lazy version). For efficiency reasons when serving large--- files, will escape the computation early if a file is successfully served,--- to prevent filters from being applied; if a filter were applied, we would--- need to compute the content-length (thereby forcing the spine of the--- ByteString into memory) rather than reading it from the filesystem.+-- | Use sendFile to send the contents of a Handle+sendFileResponse :: String  -- ^ content-type string+                 -> FilePath  -- ^ file path for content to send+                 -> Maybe (CalendarTime, Request) -- ^ mod-time for the handle (MUST NOT be later than server's time of message origination), incoming request (used to check for if-modified-since header)+                 -> Integer -- ^ offset into Handle+                 -> Integer -- ^ number of bytes to send+                 -> Response+sendFileResponse ct filePath mModTime _offset count =+    let res = ((setHeader "Content-Length" (show count)) .+               (setHeader "Content-Type" ct) $ +               (SendFile 200 Map.empty nullRsFlags{rsfContentLength=False} Nothing filePath 0 count)+              )+    in case mModTime of+         Nothing -> res+         (Just (modTime, request)) -> ifModifiedSince modTime request res++-- | Send the contents of a Lazy ByteString+lazyByteStringResponse :: String   -- ^ content-type string (e.g. @\"text/plain; charset=utf-8\"@)+                       -> L.ByteString   -- ^ lazy bytestring content to send+                       -> Maybe (CalendarTime, Request) -- ^ mod-time for the bytestring, incoming request (used to check for if-modified-since header)+                       -> Integer -- ^ offset into the bytestring+                       -> Integer -- ^ number of bytes to send (offset + count must be less than or equal to the length of the bytestring)+                       -> Response+lazyByteStringResponse ct body mModTime offset count =+    let res = ((setHeader "Content-Type" ct) $+               resultBS 200 (L.take (fromInteger count) $ (L.drop (fromInteger offset))  body)+              )+    in case mModTime of+         Nothing -> res+         (Just (modTime, request)) -> ifModifiedSince modTime request res++-- | Send the contents of a Lazy ByteString+strictByteStringResponse :: String   -- ^ content-type string (e.g. @\"text/plain; charset=utf-8\"@)+                         -> S.ByteString   -- ^ lazy bytestring content to send+                         -> Maybe (CalendarTime, Request) -- ^ mod-time for the bytestring, incoming request (used to check for if-modified-since header)+                         -> Integer -- ^ offset into the bytestring+                         -> Integer -- ^ number of bytes to send (offset + count must be less than or equal to the length of the bytestring)+                         -> Response+strictByteStringResponse ct body mModTime offset count =+    let res = ((setHeader "Content-Type" ct) $+               resultBS 200 (L.fromChunks [S.take (fromInteger count) $ S.drop (fromInteger offset) body])+              )+    in case mModTime of+         Nothing -> res+         (Just (modTime, request)) -> ifModifiedSince modTime request res++-- | Send the specified file with the specified mime-type using sendFile() ----- Note that using lazy fileServe can result in some filehandles staying open--- until the garbage collector gets around to closing them.-fileServe :: (WebMonad Response m, ServerMonad m, FilterMonad Response m, MonadIO m) =>-             [FilePath]         -- ^ index files if the path is a directory-          -> FilePath           -- ^ file/directory to serve-          -> m Response-fileServe ixFiles localpath = do-    resp <- fileServe' localpath-                       (doIndex (ixFiles++defaultIxFiles))-                       mimeTypes-                       getFile+-- NOTE: assumes file exists and is readable by the server. See 'serveFileUsing'.+--+-- WARNING: No security checks are performed.+filePathSendFile :: (ServerMonad m, MonadIO m)+                 => String   -- ^ content-type string+                 -> FilePath -- ^ path to file on disk+                 -> m Response+filePathSendFile contentType fp =+    do handle  <- liftIO $ openBinaryFile fp ReadMode -- garbage collection should close this+       modtime <- liftIO $ getModificationTime fp+       count   <- liftIO $ hFileSize handle+       rq      <- askRq+       return $ sendFileResponse contentType fp (Just (toUTCTime modtime, rq)) 0 count -    escape' $ resp { rsFlags = RsFlags {rsfContentLength=False} }+-- | Send the specified file with the specified mime-type using Lazy ByteStrings+--+-- NOTE: assumes file exists and is readable by the server. See 'serveFileUsing'.+--+-- WARNING: No security checks are performed.+filePathLazy :: (ServerMonad m, MonadIO m)+                 => String   -- ^ content-type string+                 -> FilePath -- ^ path to file on disk+                 -> m Response+filePathLazy contentType fp =+    do handle  <- liftIO $ openBinaryFile fp ReadMode -- garbage collection should close this+       contents <- liftIO $ L.hGetContents handle+       modtime  <- liftIO $ getModificationTime fp+       count    <- liftIO $ hFileSize handle+       rq       <- askRq+       return $ lazyByteStringResponse contentType contents (Just (toUTCTime modtime, rq)) 0 count +-- | Send the specified file with the specified mime-type using Lazy ByteStrings+--+-- NOTE: assumes file exists and is readable by the server. See 'serveFileUsing'.+--+-- WARNING: No security checks are performed.+filePathStrict :: (ServerMonad m, MonadIO m)+                 => String   -- ^ content-type string+                 -> FilePath -- ^ path to file on disk+                 -> m Response+filePathStrict contentType fp =+    do contents <- liftIO $ S.readFile fp+       modtime  <- liftIO $ getModificationTime fp+       count    <- liftIO $ bracket (openBinaryFile fp ReadMode) hClose hFileSize+       rq       <- askRq+       return $ strictByteStringResponse contentType contents (Just (toUTCTime modtime, rq)) 0 count +-- * High-level functions for serving files --- | Serve a file (strict version). Reads the entire file strictly into--- memory, and ensures that the handle is properly closed. Unlike lazy--- fileServe, this function doesn't shortcut the computation early, and it--- allows for filtering (ex: gzip compression) to be applied-fileServeStrict :: (ServerMonad m, FilterMonad Response m, MonadIO m) =>-                   [FilePath]   -- ^ index files if the path is a directory-                -> FilePath     -- ^ file/directory to serve-                -> m Response-fileServeStrict ixFiles localpath = do-    resp <- fileServe' localpath-                       (doIndex (ixFiles++defaultIxFiles))-                       mimeTypes-                       getFileStrict -    -- clear "Content-Length" because it could be modified by filters-    -- downstream-    let headers = rsHeaders resp-    return $ resp {rsHeaders = Map.delete (P.pack "content-length") headers}+-- ** Serve a single file +-- | Serve a single, specified file.+-- +-- example 1:+-- +--  Serve using sendfile() and the specified content-type+--+-- > serveFileUsing filePathSendFile (asContentType "image/jpeg") "/srv/data/image.jpg"+--+--+-- example 2:+-- +--  Serve using a lazy ByteString and the guess the content-type from the extension+-- +-- > serveFileUsing filePathLazy (guessContentTypeM mimeTypes) "/srv/data/image.jpg"+-- +-- WARNING: No security checks are performed.+serveFileUsing :: (ServerMonad m, FilterMonad Response m, MonadIO m) +               => (String -> FilePath -> m Response) -- ^ typically 'filePathSendFile', 'filePathLazy', or 'filePathStrict'+               -> (FilePath -> m String)  -- ^ function for determining content-type of file. Typically 'asContentType' or 'guessContentTypeM'+               -> FilePath -- ^ path to the file to serve+               -> m Response+serveFileUsing serveFn mimeFn fp = +    do fe <- liftIO $ doesFileExist fp+       if fe+          then do mt <- mimeFn fp+                  serveFn mt fp+          else fileNotFound fp +-- | Alias for 'serveFileUsing' 'filePathSendFile'+serveFile :: (ServerMonad m, FilterMonad Response m, MonadIO m) => (FilePath -> m String) -> FilePath -> m Response+serveFile = serveFileUsing filePathSendFile --- | Serve files with a mime type map under a directory.---   Uses the function to transform URIs to FilePaths.-fileServe' :: (ServerMonad m, FilterMonad Response m, MonadIO m) =>-              String-              -> (Map.Map String String -> String -> m Response)-              -> Map.Map String String-              -> GetFileFunc-              -> m Response-fileServe' localpath fdir mime getFileFunc = do+-- ** Serve files from a directory++-- | Serve files from a directory and it's subdirectories (parameterizable version)+-- +-- Parameterize this function to create functions like, 'fileServe', 'fileServeLazy', and 'fileServeStrict'+--+-- You supply:+--+--  1. a low-level function which takes a content-type and 'FilePath' and generates a Response+--  2. a function which determines the content-type from the 'FilePath'+--  3. a list of all the default index files+--+-- NOTE: unlike fileServe, there are no index files by default. See 'defaultIxFiles'.+fileServe' :: ( WebMonad Response m+              , ServerMonad m+              , FilterMonad Response m+              , MonadIO m+              ) +           => (String -> FilePath -> m Response) -- ^ function which takes a content-type and filepath and generates a response (typically 'filePathSendFile', 'filePathLazy', or 'filePathStrict')+           -> (FilePath -> m String) -- ^ function which returns the mime-type for FilePath+           -> [FilePath]         -- ^ index files if the path is a directory+           -> FilePath           -- ^ file/directory to serve+           -> m Response+fileServe' serveFn mimeFn ixFiles localpath = do     rq <- askRq-    let fp2 = takeWhile (/=',') fp-        fp = filepath-        safepath = filter (\x->not (null x) && head x /= '.') (rqPaths rq)-        filepath = intercalate "/"  (localpath:safepath)-        fp' = if null safepath then "" else last safepath-    if "TESTH" `isPrefixOf` fp'-        then renderResponse mime $ fakeFile $ (read $ drop 5 $ fp' :: Integer)-        else do+    let safepath = filter (\x->not (null x) && x /= ".." && x /= ".") (rqPaths rq)+        fp = joinPath  (localpath:safepath)     fe <- liftIO $ doesFileExist fp-    fe2 <- liftIO $ doesFileExist fp2     de <- liftIO $ doesDirectoryExist fp-    -- error $ "show ilepath: " ++show (fp,de)     let status | de   = "DIR"                | fe   = "file"-               | fe2  = "group"                | True = "NOT FOUND"     liftIO $ logM "Happstack.Server.HTTP.FileServe" DEBUG ("fileServe: "++show fp++" \t"++status)-    if de then fdir mime fp else do-    getFileFunc mime fp >>= flip either (renderResponse mime)-        (const $ returnGroup localpath mime safepath)---returnFile :: (ServerMonad m, FilterMonad Response m, MonadIO m) =>-              GetFileFunc -> Map.Map String String -> String -> m Response-returnFile getFileFunc mime fp =-    getFileFunc mime fp >>=  either fileNotFound (renderResponse mime)----- if fp has , separated then return concatenation with content-type of last--- and last modified of latest-tr :: (Eq a) => a -> a -> [a] -> [a]-tr a b = map (\x->if x==a then b else x)-ltrim :: String -> String-ltrim = dropWhile (flip elem " \t\r")--returnGroup :: (ServerMonad m, FilterMonad Response m, MonadIO m) =>-               String -> Map.Map String String -> [String] -> m Response-returnGroup localPath mime fp = do-  let fps0 = map ((:[]). ltrim) $ lines $ tr ',' '\n' $ last fp-      fps = map (intercalate "/" . ((localPath:init fp) ++)) fps0--  -- if (head $ head fps0)=="TEST" then   renderResponse mime rq fakeFile else do--  mbFiles <-  mapM (getFile mime) $ fps-  let notFounds = [x | Left x <- mbFiles]-      files = [x | Right x <- mbFiles]-  if not $ null notFounds-    then fileNotFound $ drop (length localPath) $ head notFounds else do-  let totSize = sum $ map (snd . fst) files-      maxTime = maximum $ map (fst . fst) files :: ClockTime--  renderResponse mime ((maxTime,totSize),(fst $ snd $ head files,-                                             L.concat $ map (snd . snd) files))----fileNotFound :: (Monad m, FilterMonad Response m) => String -> m Response-fileNotFound fp = return $ result 404 $ "File not found " ++ fp-----fakeLen = 71* 1024-fakeFile :: (Integral a) =>-            a -> ((ClockTime, Int64), (String, L.ByteString))-fakeFile fakeLen = ((TOD 0 0,L.length body),("text/javascript",body))-    where-      body = L.pack $ (("//"++(show len)++" ") ++ ) $ (replicate len '0') ++ "\n"-      len = fromIntegral fakeLen---- | @getFile mimeMap path@ will lazily read the file as a ByteString--- with a content type provided by matching the file extension with the--- @mimeMap@.  getFile will return an error string or ((timeFetched,size), (contentType,fileContents))-getFile :: (MonadIO m) =>-           Map.Map String String-           -> String-           -> m (Either String ((ClockTime, Integer), (String, L.ByteString)))-getFile mime fp = do-  let ct = Map.findWithDefault "text/plain" (getExt fp) mime-  fe <- liftIO $ doesFileExist fp-  if not fe then return $ Left fp else do--  time <- liftIO $ getModificationTime fp-  h    <- liftIO $ openBinaryFile fp ReadMode-  size <- liftIO $ hFileSize h-  lbs  <- liftIO $ L.hGetContents h-  return $ Right ((time,size),(ct,lbs))---- | As 'getFile' but strictly fetches the file, instead of lazily.-getFileStrict :: (MonadIO m) =>-                 Map.Map String String-              -> String-              -> m (Either String ((ClockTime, Integer), (String, L.ByteString)))-getFileStrict mime fp = do-  let ct = Map.findWithDefault "text/plain" (getExt fp) mime-  fe     <- liftIO $ doesFileExist fp--  if not fe then return $ Left fp else do--  time     <- liftIO $ getModificationTime fp-  s        <- liftIO $ P.readFile fp-  let lbs  = L.fromChunks [s]-  let size = toInteger . P.length $ s-  return $ Right ((time,size),(ct,lbs))---renderResponse :: (Monad m,-                   ServerMonad m,-                   FilterMonad Response m,-                   Show t1) =>-                  t-                  -> ((ClockTime, t1), (String, L.ByteString))-                  -> m Response-renderResponse _ ((modtime,size),(ct,body)) = do-  rq <- askRq-  let notmodified = getHeader "if-modified-since" rq == Just (P.pack $ repr)-      repr = formatCalendarTime defaultTimeLocale-             "%a, %d %b %Y %X GMT" (toUTCTime modtime)-  -- "Mon, 07 Jan 2008 19:51:02 GMT"-  -- when (isJust $ getHeader "if-modified-since"  rq) $ error $ show $ getHeader "if-modified-since" rq-  if notmodified then-      return $ result 304 ""-    else do-      return $ ((setHeader "Last-modified" repr) .-                (setHeader "Content-Length" (show size)) .-                (setHeader "Content-Type" ct)) $-               resultBS 200 body+    if de+        then if last (rqUri rq) == '/'+             then doIndex' serveFn mimeFn (ixFiles++defaultIxFiles) fp+             else do let path' = addTrailingPathSeparator (rqUri rq)+                     seeOther path' (toResponse path')+        else if fe +                then serveFileUsing serveFn mimeFn fp+                else fileNotFound fp +-- | Serve files from a directory and it's subdirectories (sendFile version). Should perform much better than its predecessors.+fileServe :: (WebMonad Response m, ServerMonad m, FilterMonad Response m, MonadIO m) =>+             [FilePath]         -- ^ index files if the path is a directory+          -> FilePath           -- ^ file/directory to serve+          -> m Response+fileServe ixFiles localPath = fileServe' filePathSendFile (guessContentTypeM mimeTypes) (ixFiles ++ defaultIxFiles) localPath +-- | Serve files from a directory and it's subdirectories (lazy ByteString version).+-- +-- May leak file handles.+fileServeLazy :: (WebMonad Response m, ServerMonad m, FilterMonad Response m, MonadIO m) =>+             [FilePath]         -- ^ index files if the path is a directory+          -> FilePath           -- ^ file/directory to serve+          -> m Response+fileServeLazy ixFiles localPath = fileServe' filePathLazy (guessContentTypeM mimeTypes) (ixFiles ++ defaultIxFiles) localPath +-- | Serve files from a directory and it's subdirectories (strict ByteString version). +fileServeStrict :: (WebMonad Response m, ServerMonad m, FilterMonad Response m, MonadIO m) =>+             [FilePath]         -- ^ index files if the path is a directory+          -> FilePath           -- ^ file/directory to serve+          -> m Response+fileServeStrict ixFiles localPath = fileServe' filePathStrict (guessContentTypeM mimeTypes) (ixFiles ++ defaultIxFiles) localPath -getExt :: String -> String-getExt = reverse . takeWhile (/='.') . reverse+-- * Index --- | Ready collection of common mime types.-mimeTypes :: MimeMap-mimeTypes = Map.fromList-	    [("xml","application/xml")-	    ,("xsl","application/xml")-	    ,("js","text/javascript")-	    ,("html","text/html")-	    ,("htm","text/html")-	    ,("css","text/css")-	    ,("gif","image/gif")-	    ,("jpg","image/jpeg")-	    ,("png","image/png")-	    ,("txt","text/plain")-	    ,("doc","application/msword")-	    ,("exe","application/octet-stream")-	    ,("pdf","application/pdf")-	    ,("zip","application/zip")-	    ,("gz","application/x-gzip")-	    ,("ps","application/postscript")-	    ,("rtf","application/rtf")-	    ,("wav","application/x-wav")-	    ,("hs","text/plain")]+doIndex :: (ServerMonad m, FilterMonad Response m, MonadIO m)+        => [String]+        -> MimeMap+        -> String+        -> m Response+doIndex ixFiles mimeMap localPath = doIndex' filePathSendFile (guessContentTypeM mimeMap) ixFiles localPath +doIndexLazy :: (ServerMonad m, FilterMonad Response m, MonadIO m)+        => [String]+        -> MimeMap+        -> String+        -> m Response+doIndexLazy ixFiles mimeMap localPath = doIndex' filePathLazy (guessContentTypeM mimeMap) ixFiles localPath --- | Prevents files of the form '.foo' or 'bar/.foo' from being served-blockDotFiles :: (Request -> IO Response) -> Request -> IO Response-blockDotFiles fn rq-    | isDot (intercalate "/" (rqPaths rq)) = return $ result 403 "Dot files not allowed."-    | otherwise = fn rq+doIndexStrict :: (ServerMonad m, FilterMonad Response m, MonadIO m)+        => [String]+        -> MimeMap+        -> String+        -> m Response+doIndexStrict ixFiles mimeMap localPath = doIndex' filePathStrict (guessContentTypeM mimeMap) ixFiles localPath --- | Returns True if the given String either starts with a . or is of the form--- "foo/.bar", e.g. the typical *nix convention for hidden files.-isDot :: String -> Bool-isDot = isD . reverse-    where-    isD ('.':'/':_) = True-    isD ['.']       = True-    --isD ('/':_)     = False-    isD (_:cs)      = isD cs-    isD []          = False+doIndex' :: (ServerMonad m, FilterMonad Response m, MonadIO m)+        => (String -> FilePath -> m Response)+        -> (FilePath -> m String)+        -> [String]+        -> String+        -> m Response+doIndex' _serveFn _mime  []          _fp = forbidden $ toResponse "Directory index forbidden"+doIndex'  serveFn mimeFn (index:rest) fp =+    do let path = fp </> index+       fe <- liftIO $ doesFileExist path+       if fe +          then serveFileUsing serveFn mimeFn path +          else doIndex' serveFn mimeFn rest fp
src/Happstack/Server/HTTP/Handler.hs view
@@ -33,6 +33,8 @@ import Happstack.Util.TimeOut import Happstack.Util.LogFormat (formatRequestCombined) import Data.Time.Clock (getCurrentTime)+import Network.Socket (Socket, fdSocket)+import Network.Socket.SendFile (unsafeSendFile') import System.Log.Logger (Priority(..), logM)  request :: Conf -> Handle -> Host -> (Request -> IO Response) -> IO ()@@ -84,18 +86,7 @@                          user = "-"                          requestLn = unwords [show $ rqMethod req, rqUri req, show $ rqVersion req]                          responseCode = rsCode res-                         sendContentLength = rsfContentLength (rsFlags res)--                         -- don't force the bytestring if "sendContentLength"-                         -- is false, at least not if a content-length header-                         -- has been set-                         size = if not sendContentLength then-                                    maybe (toInteger $ L.length $ rsBody res)-                                          ((read . P.unpack) :: P.ByteString -> Integer)-                                          (getHeaderBS contentLengthC req)-                                  else-                                    toInteger $ L.length $ rsBody res-+                         size = maybe (-1) (read . B.unpack) (getHeader "Content-Length" res) -- -1 indicates unknown size                          referer = B.unpack $ fromMaybe (B.pack "") $ getHeader "Referer" req                          userAgent = B.unpack $ fromMaybe (B.pack "") $ getHeader "User-Agent" req                      logM "Happstack.Server.AccessLog.Combined" INFO $ formatRequestCombined host' user time requestLn responseCode size referer userAgent@@ -192,29 +183,45 @@     foldr (uncurry setHeaderBS) (mkHeaders [])     [ (serverC, happsC), (contentTypeC, textHtmlC) ] +-- FIXME: we should not be controlling the response headers in mysterious ways in this low level code+-- headers should be set by application code and the core http engine should be very lean. putAugmentedResult :: Handle -> Request -> Response -> IO ()-putAugmentedResult h req res = do-  let ph (HeaderPair k vs) = map (\v -> P.concat [k, fsepC, v, crlfC]) vs-  raw <- getApproximateTime-  let cl = L.length $ rsBody res-  let put = P.hPut h-  -- TODO: Hoist static headers to the toplevel.-  let stdHeaders = staticHeaders `M.union`-                   M.fromList ( [ (dateCLower,       HeaderPair dateC [raw])-                                , (connectionCLower, HeaderPair connectionC [if continueHTTP req res then keepAliveC else closeC])-                                ] ++ if rsfContentLength (rsFlags res)-                                     then [(contentlengthC, HeaderPair contentLengthC [P.pack (show cl)])]-                                     else [] )-      allHeaders = rsHeaders res `M.union` stdHeaders  -- 'union' prefers 'headers res' when duplicate keys are encountered.+putAugmentedResult outp req res = do+    case res of+        -- standard bytestring response+        Response {} -> do+            sendTop (fromIntegral (L.length (rsBody res)))+            when (rqMethod req /= HEAD) (L.hPut outp $ rsBody res)+        -- zero-copy sendfile response+        -- the handle *should* be closed by the garbage collector+        SendFile {} -> do+            let infp = sfFilePath res+                off = sfOffset res+                count = sfCount res+            sendTop count+            unsafeSendFile' outp infp off count+    hFlush outp+    where ph (HeaderPair k vs) = map (\v -> P.concat [k, fsepC, v, crlfC]) vs+          sendTop cl = do+              allHeaders <- augmentHeaders req res cl+              mapM_ (P.hPut outp) $ concat+                [ (pversion $ rqVersion req)          -- Print HTTP version+                , [responseMessage $ rsCode res]      -- Print responseCode+                , concatMap ph (M.elems allHeaders)   -- Print all headers+                , [crlfC]+                ] -  mapM_ put $ concat-    [ (pversion $ rqVersion req)          -- Print HTTP version-    , [responseMessage $ rsCode res]      -- Print responseCode-    , concatMap ph (M.elems allHeaders)   -- Print all headers-    , [crlfC]-    ]-  when (rqMethod req /= HEAD) $ L.hPut h $ rsBody res-  hFlush h+augmentHeaders :: Request -> Response -> Integer -> IO Headers+augmentHeaders req res cl = do+    -- TODO: Hoist static headers to the toplevel.+    raw <- getApproximateTime+    let stdHeaders = staticHeaders `M.union`+          M.fromList ( [ (dateCLower,       HeaderPair dateC [raw])+                       , (connectionCLower, HeaderPair connectionC [if continueHTTP req res then keepAliveC else closeC])+                       ] ++ if rsfContentLength (rsFlags res)+                              then [(contentlengthC, HeaderPair contentLengthC [P.pack (show cl)])]+                              else [] )+    return (rsHeaders res `M.union` stdHeaders) -- 'union' prefers 'headers res' when duplicate keys are encountered.  -- | Serializes the request to the given handle putRequest :: Handle -> Request -> IO ()
src/Happstack/Server/HTTP/Listen.hs view
@@ -1,12 +1,12 @@ {-# LANGUAGE CPP, ScopedTypeVariables, PatternSignatures #-}-module Happstack.Server.HTTP.Listen(listen) where+module Happstack.Server.HTTP.Listen(listen, listen') where  import Happstack.Server.HTTP.Types import Happstack.Server.HTTP.Handler import Happstack.Server.HTTP.Socket (acceptLite) import Control.Exception.Extensible as E import Control.Concurrent-import Network(PortID(..), listenOn, sClose)+import Network(PortID(..), listenOn, sClose, Socket) import System.IO {- #ifndef mingw32_HOST_OS@@ -19,8 +19,16 @@ log':: Priority -> String -> IO () log' = logM "Happstack.Server.HTTP.Listen" +-- | Bind and listen port listen :: Conf -> (Request -> IO Response) -> IO () listen conf hand = do+    let port' = port conf+    socket <- listenOn (PortNumber $ toEnum port')+    listen' socket conf hand++-- | Use a previously bind port and listen+listen' :: Socket -> Conf -> (Request -> IO Response) -> IO ()+listen' s conf hand = do {- #ifndef mingw32_HOST_OS -}@@ -30,7 +38,6 @@ -}   let port' = port conf   log' NOTICE ("Listening on port " ++ show port')-  s <- listenOn $ PortNumber $ toEnum port'   let work (h,hn,p) = do -- hSetBuffering h NoBuffering                          let eh (x::SomeException) = log' ERROR ("HTTP request failed with: "++show x)                          request conf h (hn,fromIntegral p) hand `E.catch` eh
src/Happstack/Server/HTTP/SocketTH.hs view
@@ -8,9 +8,8 @@  -- find out at compile time if the SockAddr6 / HostAddress6 constructors are available supportsIPv6 :: Bool-supportsIPv6 = $(let c = "Network.Socket.SockAddrInet6"; d = ''SockAddr in+supportsIPv6 = $(let c = ["Network.Socket.SockAddrInet6", "Network.Socket.Internal.SockAddrInet6"] ; d = ''SockAddr in                  do TyConI (DataD _ _ _ cs _) <- reify d-                    if isJust (find (\(NormalC n _) -> show n == c) cs)+                    if isJust (find (\(NormalC n _) -> show n `elem` c) cs)                        then [| True |]                        else [| False |] )-                       
src/Happstack/Server/HTTP/Types.hs view
@@ -29,6 +29,7 @@ import Happstack.Server.Cookie import Data.List import Text.Show.Functions ()+import System.IO (Handle)  -- | HTTP version data Version = Version Int Int@@ -90,12 +91,21 @@  type Host = (String,Int) -data Response  = Response  { rsCode    :: Int,-                             rsHeaders :: Headers,-                             rsFlags   :: RsFlags,-                             rsBody    :: L.ByteString,-                             rsValidator:: Maybe (Response -> IO Response)-                           } deriving (Show,Typeable) +data Response  = Response  { rsCode      :: Int,+                             rsHeaders   :: Headers,+                             rsFlags     :: RsFlags,+                             rsBody      :: L.ByteString,+                             rsValidator :: Maybe (Response -> IO Response)+                           }+               | SendFile  { rsCode      :: Int,+                             rsHeaders   :: Headers,+                             rsFlags     :: RsFlags,+                             rsValidator :: Maybe (Response -> IO Response),+                             sfFilePath  :: FilePath,  -- file handle to send from+                             sfOffset    :: Integer, -- offset to start at+                             sfCount     :: Integer  -- number of bytes to send+                           }+               deriving (Show,Typeable)   data Request = Request { rqMethod  :: Method,                          rqPaths   :: [String],@@ -108,7 +118,6 @@                          rqBody    :: RqBody,                          rqPeer    :: Host                        } deriving(Show,Read,Typeable)-  -- | Converts a Request into a String representing the corresponding URL rqURL :: Request -> String
src/Happstack/Server/MessageWrap.hs view
@@ -18,7 +18,7 @@                                xs    -> xs)  bodyInput :: Request -> [(String, Input)]-bodyInput req | rqMethod req /= POST = []+bodyInput req | (rqMethod req /= POST) && (rqMethod req /= PUT) = [] bodyInput req =     let ctype = getHeader "content-type" req >>= parseContentType . P.unpack         getBS (Body bs) = bs
src/Happstack/Server/Parts.hs view
@@ -137,7 +137,7 @@                  encoding1 :: GenParser Char st ([Char], Maybe Double)         encoding1 = do-            encoding <- many1 alphaNum <|> string "*"+            encoding <- many1 (alphaNum <|> char '-') <|> string "*"             ws             quality<-optionMaybe qual             return (encoding, fmap read quality)
− src/Happstack/Server/S3.hs
@@ -1,240 +0,0 @@-{-# LANGUAGE PatternGuards #-}-module Happstack.Server.S3-    ( newS3        -- :: AccessKey -> SecretKey -> URI -> IO S3-    , closeS3      -- :: S3 -> IO ()-    , createBucket -- :: S3 -> BucketId -> IO ()-    , createObject -- :: S3 -> BucketId -> ObjectId -> String -> IO ()-    , getObject    -- :: S3 -> BucketId -> ObjectId -> IO String-    , deleteBucket -- :: S3 -> BucketId -> IO ()-    , deleteObject -- :: S3 -> BucketId -> ObjectId -> IO ()-    , listObjects  -- :: S3 -> BucketId -> IO [String]-    , sendRequest  -- :: S3 -> Request -> IO String-    , sendRequest_ -- :: S3 -> Request -> IO ()---    , sendRequests -- :: S3 -> [Request] -> IO ()-    , BucketId, ObjectId, AccessKey, SecretKey-    , amazonURI-    ) where--import Happstack.Crypto.HMAC             ( hmacSHA1 )-import Happstack.Server.HTTPClient.HTTP-import qualified Happstack.Server.HTTPClient.Stream as Stream--import Network.URI hiding (path)-import Control.Concurrent               ( newMVar, modifyMVar, swapMVar-                                        , modifyMVar_, MVar )-import Data.Maybe                       ( fromJust, fromMaybe )-import Data.List                        ( intersperse )-import System.Time                      ( getClockTime, toCalendarTime-                                        , formatCalendarTime )-import System.Locale                    ( defaultTimeLocale, rfc822DateFormat )--import Text.XML.HaXml                   ( xmlParse, Document(..), Content(..) )-import Text.XML.HaXml.Xtract.Parse      ( xtract )--type BucketId = String-type ObjectId = String-type AccessKey = String-type SecretKey = String--data S3-    = S3-    { s3AccessKey        :: AccessKey-    , s3SecretKey        :: SecretKey-    , s3URI              :: URI---    , s3KeepAliveTimeout :: Int-    , s3Conn             :: MVar (Maybe Connection)-    }--{- |-  Sign a request using the access key and secret key from the S3 data-  type.--}-signRequest :: S3 -> Request -> IO Request-signRequest s3-    = let akey = s3AccessKey s3-          skey = s3SecretKey s3-      in signRequest' akey skey--{- |-  Fill in necessary information (such as a date header) and then sign-  then request.--}-signRequest' :: AccessKey -> SecretKey -> Request -> IO Request-signRequest' akey skey request-    = do now <- getClockTime-         cal <- toCalendarTime now-         let isoDate = formatCalendarTime defaultTimeLocale rfc822DateFormat cal-             auth = fromJust (uriAuthority (rqURI request))---             authErr = error "S3.hs: internal error: failed to parse authority"-         let dat = concat $ intersperse "\n"-                   [show (rqMethod request)-                   ,lookupHeader HdrContentMD5-                   ,lookupHeader HdrContentType-                   ,isoDate-                   ,uriPath (rqURI request)]-             authorization = Header HdrAuthorization $ "AWS " ++ akey ++ ":" ++ signature-             signature = hmacSHA1 skey dat-             lookupHeader hn = fromMaybe "" (findHeader hn request)-             dateHdr = Header HdrDate isoDate-             lengthHdr = Header HdrContentLength (show $ length (rqBody request))-             connHdr = Header HdrConnection "Keep-Alive"-             hostHdr = Header HdrHost (uriRegName auth)-         return $ request-                    { rqHeaders = hostHdr:connHdr:lengthHdr:dateHdr:-                                  authorization:rqHeaders request-                    , rqURI = (rqURI request) { uriScheme = ""-                                              , uriAuthority = Nothing}}--{- |-  Return a connection to an S3 server. Will initiate a new-  connection if no previous was found.--}-getConnection :: S3 -> IO Connection-getConnection s3-    = modifyMVar (s3Conn s3) $ \mbConn ->-      case mbConn of-        Just conn -> return (mbConn,conn)-        Nothing -> do print (uriRegName auth, uriPort auth)-                      c <- openTCPPort (uriRegName auth) (if null $ uriPort auth then 80 else read$ uriPort auth)-                      return (Just c,c)-    where auth = fromJust (uriAuthority (s3URI s3))--createRequest :: S3 -> RequestMethod -> String -> String -> Request-createRequest _s3 method path body-    = Request uri method [] body-    where uri = localhost { uriPath = '/':escapeURIString isAllowedInURI path }--{- |-  Send a single request to an S3 server returning the body-  of the result.--}-sendRequest :: S3 -> Request -> IO String-sendRequest s3 request-    = loop =<< signRequest s3 request-    where loop request'-              = do c <- getConnection s3-                   ret <- sendHTTP c request'-                   case ret of-                     Left ErrorClosed-                         -> do putStrLn "Connection closed."-                               swapMVar (s3Conn s3) Nothing-                               loop request'-                     Left err  -> error ("Failed to connect: " ++ show err) -- FIXME-                     Right res-                         | (2,_,_) <- rspCode res -> return (rspBody res)-                         | otherwise -> error ("Server error: " ++ rspReason res)--{- |-  Same as 'sendRequest' except that it ignored the result.--}-sendRequest_ :: S3 -> Request -> IO ()-sendRequest_ s3 request-    = do sendRequest s3 request-         return ()--{--  Sign and send requests pipelined over a keep-alive connection.--}-{--  S3 imposes a quite severe limitation on pipelined requests.-  Sending too many requests or exceeding a size limit will-  result in a disconnect. The precise borders of these limits-  are hard-wired and unknown to the general public. At the time-  of this writing, sending three requests at a time seems optimal.--  Quote from Amazons web-forum:-  (http://developer.amazonwebservices.com/connect/thread.jspa?messageID=39883)-  "OK, we have located the cause of the behavior you are seeing.-   Your pipelined requests are being aborted because one of the-   network devices handling the connection has certain limitations-   on the amount of pipelined data it is willing to accept per-   connection, and that limit is being exceeded. Unfortunately, this-   limit is phrased in very low-level terms so it isn't possible to-   say in a platform or network independent way whether any given-   size, sequence or number of requests will exceed the limit and-   cause a disconnection or not. We have engaged with the device-   vendor and found out that this limit is hard-wired and that this-   behavior is not likely to change any time soon.--   In light of these facts, here is some guidance on pipelining HTTP-   requests to Amazon S3.-    1) Be optimistic and pipeline a modest number of GET or HEAD-       requests, say two to four.-    2) Handle asynchronous disconnects by re-connecting and re-sending-       unacknowledged requests left in your pipeline. (As Colin points-       out, correct HTTP clients must do this anyway)-    3) If possible, try to minimize the number of TCP segments your-       pipelined requests generate. In particular, leave the TCP socket-       no delay option off and send as many requests per socket write call-       as is practical."--}------------------------------------------------------------------- Initiate-----------------------------------------------------------------newS3 :: AccessKey -> SecretKey -> URI -> IO S3-newS3 akey skey uri-    = do conn <- newMVar Nothing-         return $ S3 { s3AccessKey = akey-                     , s3SecretKey = skey-                     , s3URI       = uri---                     , s3KeepAliveTimeout :: Int-                     , s3Conn      = conn-                     }--closeS3 :: S3 -> IO ()-closeS3 s3-    = modifyMVar_ (s3Conn s3) $ \mbConn ->-      case mbConn of-        Nothing -> return Nothing-        Just conn -> do Stream.close conn-                        return Nothing-------------------------------------------------------------------- Requests------------------------------------------------------------------createBucket :: S3 -> BucketId -> Request-createBucket s3 bucket-    = createRequest s3 PUT bucket ""--createObject :: S3 -> BucketId -> ObjectId -> String -> Request-createObject s3 bucket object-    = createRequest s3 PUT (bucket ++ "/" ++ object)--getObject :: S3 -> BucketId -> ObjectId -> Request-getObject s3 bucket object-    = createRequest s3 GET (bucket ++ "/" ++ object) ""--deleteBucket :: S3 -> BucketId -> Request-deleteBucket s3 bucket-    = createRequest s3 DELETE bucket ""--deleteObject :: S3 -> BucketId -> ObjectId -> Request-deleteObject s3 bucket object-    = createRequest s3 DELETE (bucket ++ "/" ++ object) ""------------------------------------------------------------------- Actions------------------------------------------------------------------listObjects :: S3 -> BucketId -> IO [String]-listObjects s3 bucket-    = do lst <- sendRequest s3 (createRequest s3 GET bucket "")-         return $ ppContent . auxFilter . getContent . xmlParse bucket $ lst-    where auxFilter = xtract "*/Key/-"-          getContent (Document _ _ e _) = CElem e--          ppContent xs  = [ s | CString _ s <- xs ]----amazonURI :: URI-amazonURI = fromJust $ parseURI "http://s3.amazonaws.com/"-localhost :: URI-localhost = fromJust $ parseURI "http://localhost/"-
src/Happstack/Server/SimpleHTTP.hs view
@@ -28,7 +28,7 @@ -- like CGI\/FastCGI can used if so desired. -- -- So the general nature of 'simpleHTTP' is no different than what you'd expect--- from a web application container.  First you figure out when function is+-- from a web application container.  First you figure out which function is -- going to process your request, process the request to generate a response, -- then return that response to the client. The web application container is -- started with 'simpleHTTP', which takes a configuration and a@@ -108,6 +108,10 @@     -- * SimpleHTTP     , simpleHTTP     , simpleHTTP'+    , simpleHTTP''+    , simpleHTTPWithSocket+    , simpleHTTPWithSocket'+    , bindPort     , parseConfig     -- * ServerPartT     , ServerPartT(..)@@ -162,10 +166,13 @@     , addCookies     , addHeaderM     , setHeaderM+    , ifModifiedSince       -- * guards and building blocks     , guardRq     , dir+    , host+    , withHost     , method     , methodSP     , methodM@@ -174,6 +181,7 @@     , path     , anyPath     , anyPath'+    , trailingSlash     , withData     , withDataFn     , getDataFn@@ -224,11 +232,12 @@ import Happstack.Server.HTTP.Client              (getResponse, unproxify, unrproxify) import Happstack.Data.Xml.HaXml                  (toHaXmlEl) import qualified Happstack.Server.MinHaXML       as H-import qualified Happstack.Server.HTTP.Listen    as Listen (listen) -- So that we can disambiguate 'Writer.listen'+import qualified Happstack.Server.HTTP.Listen    as Listen (listen, listen') -- So that we can disambiguate 'Writer.listen' import Happstack.Server.XSLT                     (XSLTCmd, XSLPath, procLBSIO) import Happstack.Server.SURI                     (ToSURI) import Happstack.Util.Common                     (Seconds, readM) import Happstack.Data                            (Xml, normalize, fromPairs, Element, toXml, toPublicXml) -- used by default implementation of fromData+import Network                                   (listenOn, PortID(..), Socket) import Control.Applicative                       (Applicative, pure, (<*>)) import Control.Concurrent                        (forkIO) import Control.Exception                         (evaluate)@@ -252,6 +261,7 @@ import Control.Monad.Error                       ( ErrorT(ErrorT), runErrorT                                                  , Error, strMsg                                                  , MonadError, throwError, catchError+                                                 , mapErrorT                                                  ) import Control.Monad.Maybe                       (MaybeT(MaybeT), runMaybeT) import Data.Char                                 (ord)@@ -280,7 +290,9 @@                                                  , ArgOrder(Permute)                                                  , getOpt                                                  )+import System.Locale                             (defaultTimeLocale) import System.Process                            (runInteractiveProcess, waitForProcess)+import System.Time                               (CalendarTime, formatCalendarTime) import System.Exit                               (ExitCode(ExitSuccess, ExitFailure))  -- | An alias for WebT when using IO@@ -399,6 +411,10 @@     askRq = ServerPartT $ ask     localRq f m = ServerPartT $ local f (unServerPartT m) +instance (Error e, ServerMonad m) => ServerMonad (ErrorT e m) where+    askRq     = lift askRq+    localRq f = mapErrorT $ localRq f+ ------------------------------- -- HERE BEGINS WebT definitions @@ -412,7 +428,7 @@ -- @ -- -- A simple way of sumerizing this is, if the right side is Append, then the--- right is appended to the left.  If the right side is Set, then the right side+-- right is appended to the left.  If the right side is Set, then the left side -- is ignored.  data SetAppend a = Set a | Append a@@ -676,7 +692,7 @@ simpleHTTP :: (ToMessage a) => Conf -> ServerPartT IO a -> IO () simpleHTTP = simpleHTTP' id --- | a combination of simpleHTTP and 'mapServerPartT'.  See 'mapServerPartT' for a discussion+-- | a combination of simpleHTTP'' and 'mapServerPartT'.  See 'mapServerPartT' for a discussion -- of the first argument of this function. simpleHTTP' :: (ToMessage b, Monad m, Functor m) => (UnWebT m a -> UnWebT IO b)             -> Conf -> ServerPartT m a -> IO ()@@ -689,9 +705,36 @@ simpleHTTP'' :: (ToMessage b, Monad m, Functor m) => ServerPartT m b -> Request -> m Response simpleHTTP'' hs req =  (runWebT $ runServerPartT hs req) >>= (return . (maybe standardNotFound id))     where-        standardNotFound = setHeader "Content-Type" "text/html" $ toResponse notFoundHtml+        standardNotFound = setHeader "Content-Type" "text/html" $ (toResponse notFoundHtml){rsCode=404} +-- | Run simpleHTTP with a previously bound socket. Useful if you want to run+-- happstack as user on port 80. Use something like this:+--+-- > import System.Posix.User (setUserID, UserEntry(..), getUserEntryForName)+-- >+-- > main = do+-- >     let conf = nullConf { port = 80 }+-- >     socket <- bindPort conf+-- >     -- do other stuff as root here+-- >     getUserEntryForName "www" >>= setUserID . userID+-- >     -- finally start handling incoming requests+-- >     tid <- forkIO $ socketSimpleHTTP socket conf impl+--+-- Note: It's important to use the same conf (or at least the same port) for+-- 'bindPort' and 'simpleHTTPWithSocket'.+simpleHTTPWithSocket :: (ToMessage a) => Socket -> Conf -> ServerPartT IO a -> IO ()+simpleHTTPWithSocket = simpleHTTPWithSocket' id +-- | 'simpleHTTP'' with a socket+simpleHTTPWithSocket' :: (ToMessage b, Monad m, Functor m) => (UnWebT m a -> UnWebT IO b)+                      -> Socket -> Conf -> ServerPartT m a -> IO ()+simpleHTTPWithSocket' toIO socket conf hs =+    Listen.listen' socket conf (\req -> runValidator (fromMaybe return (validator conf)) =<< (simpleHTTP'' (mapServerPartT toIO hs) req))++-- | Bind port and return the socket for 'simpleHTTPWithSocket'+bindPort :: Conf -> IO Socket+bindPort conf = listenOn (PortNumber . toEnum . port $ conf)+ -- | This class is used by 'path' to parse a path component into a value. -- At present, the instances for number types (Int, Float, etc) just -- call 'readM'. The instance for 'String' however, just passes the@@ -870,6 +913,23 @@             (p:xs) | p == staticPath -> localRq (\newRq -> newRq{rqPaths = xs}) handle             _ -> mzero +-- | Guard against the host+host :: (ServerMonad m, MonadPlus m) => String -> m a -> m a+host desiredHost handle =+    do rq <- askRq+       case getHeader "host" rq of+         (Just hostBS) | desiredHost == B.unpack hostBS -> handle+         _ -> mzero++-- | Lookup the host header and pass it to the handler+withHost :: (ServerMonad m, MonadPlus m) => (String -> m a) -> m a+withHost handle =+    do rq <- askRq+       case getHeader "host" rq of+         (Just hostBS) -> handle (B.unpack hostBS)+         _ -> mzero++ -- | Pop a path element and parse it using the 'fromReqURI' in the 'FromReqURI' class. path :: (FromReqURI a, MonadPlus m, ServerMonad m) => (a -> m b) -> m b path handle = do@@ -894,6 +954,11 @@ anyPath' = anyPath {-# DEPRECATED anyPath' "Use anyPath" #-} +-- | guard which checks that the Request URI ends in '\/'. +-- Useful for distinguishing between @foo@ and @foo/@+trailingSlash :: (ServerMonad m, MonadPlus m) => m ()+trailingSlash = guardRq $ \rq -> (last (rqUri rq)) == '/'+ -- | used to read parse your request with a RqData (a ReaderT, basically) -- For example here is a simple GET or POST variable based authentication -- guard.  It handles the request with errorHandler if authentication fails.@@ -987,7 +1052,7 @@ -- -- TODO: this would be more useful if it didn\'t call "escape", just like -- proxyServe'-rproxyServe :: (MonadIO m, WebMonad Response m) =>+rproxyServe :: (MonadIO m) =>     String -- ^ defaultHost     -> [(String, String)] -- ^ map to look up hostname mappings.  For the reverse proxy     -> ServerPartT m Response -- ^ the result is a ServerPartT that will reverse proxy for you.@@ -1050,6 +1115,21 @@ addCookies :: (FilterMonad Response m) => [(Seconds, Cookie)] -> m () addCookies = mapM_ (uncurry addCookie) +-- |honor if-modified-since header in Request+-- If the 'Request' includes the if-modified-since header and the+-- Response has not been modified, then return 304 (Not Modified),+-- otherwise return the 'Response'.+ifModifiedSince :: CalendarTime -- ^ mod-time for the Response (MUST NOT be later than server's time of message origination)+                -> Request -- ^ incoming request (used to check for if-modified-since)+                -> Response -- ^ Response to send if there are modifications+                -> Response+ifModifiedSince modTime request response =+    let repr = formatCalendarTime defaultTimeLocale "%a, %d %b %Y %X GMT" modTime+        notmodified = getHeader "if-modified-since" request == Just (B.pack $ repr)+    in if notmodified+          then result 304 "" -- Not Modified+          else setHeader "Last-modified" repr response+ -- | same as setResponseCode status >> return val resp :: (FilterMonad Response m) => Int -> b -> m b resp status val = setResponseCode status >> return val@@ -1144,10 +1224,9 @@     parseHeader = break (':'==) . Base64.decode . B.unpack . B.drop 6     headerName  = "WWW-Authenticate"     headerValue = "Basic realm=\"" ++ realmName ++ "\""-    err = do-        unauthorized ()-        setHeaderM headerName headerValue-        escape' $ toResponse "Not authorized"+    err = escape $ do+            setHeaderM headerName headerValue+            unauthorized $ toResponse "Not authorized"  -------------------------------------------------------------- -- Query/Post data validating
tests/Happstack/Server/Tests.hs view
@@ -16,26 +16,34 @@     "cookieParserTest" ~:     [parseCookies "$Version=1;Cookie1=value1;$Path=\"/testpath\";$Domain=example.com;cookie2=value2"         @?= (Right [-            Cookie "1" "/testpath" "example.com" "cookie1" "value1"-          , Cookie "1" "" "" "cookie2" "value2"+            Cookie "1" "/testpath" "example.com" "cookie1" "value1" False+          , Cookie "1" "" "" "cookie2" "value2" False           ])     ,parseCookies "  \t $Version = \"1\" ; cookie1 = \"randomcrap!@#%^&*()-_+={}[]:;'<>,.?/\\|\" , $Path=/  "         @?= (Right [-            Cookie "1" "/" "" "cookie1" "randomcrap!@#%^&*()-_+={}[]:;'<>,.?/\\|"+            Cookie "1" "/" "" "cookie1" "randomcrap!@#%^&*()-_+={}[]:;'<>,.?/\\|" False           ])     ,parseCookies " cookie1 = value1  "         @?= (Right [-            Cookie "" "" "" "cookie1" "value1"+            Cookie "" "" "" "cookie1" "value1" False           ])     ,parseCookies " $Version=\"1\";buggygooglecookie = valuewith=whereitshouldnotbe  "         @?= (Right [-            Cookie "1" "" "" "buggygooglecookie" "valuewith=whereitshouldnotbe"+            Cookie "1" "" "" "buggygooglecookie" "valuewith=whereitshouldnotbe" False           ])     ]  acceptEncodingParserTest :: Test acceptEncodingParserTest =     "acceptEncodingParserTest" ~:-    [either (Left . show) Right (parse encodings "" " gzip;q=1,*, compress ; q = 0.5 ")-        @?= (Right [("gzip", Just 1),("*", Nothing),("compress", Just 0.5)])-    ]+    map (\(string, result) -> either (Left . show) Right (parse encodings "" string) @?= (Right result)) acceptEncodings+    where+      acceptEncodings =+       [ (" gzip;q=1,*, compress ; q = 0.5 ", [("gzip", Just 1),("*", Nothing),("compress", Just 0.5)])+       , (" compress , gzip", [ ("compress", Nothing), ("gzip", Nothing)])+       , (" ", [])+       , (" *", [("*", Nothing)])+       , (" compress;q=0.5, gzip;q=1.0", [("compress", Just 0.5), ("gzip", Just 1.0)])+       , (" gzip;q=1.0, identity; q=0.5, *;q=0", [("gzip", Just 1.0), ("identity",Just 0.5), ("*", Just 0)])+       , (" x-gzip",[("x-gzip", Nothing)])+       ]