packages feed

wai-app-file-cgi 0.1.2 → 0.2.0

raw patch · 10 files changed

+168/−180 lines, 10 filesdep +arraydep +hashmapdep +http-datedep −timePVP ok

version bump matches the API change (PVP)

Dependencies added: array, hashmap, http-date

Dependencies removed: time

API changes (from Hackage documentation)

- Network.Wai.Application.Classic.Types: AppSpec :: ByteString -> FilePath -> (FilePath -> Bool) -> (Request -> Status -> RspBody -> IO ()) -> AppSpec
+ Network.Wai.Application.Classic.Types: AppSpec :: ByteString -> ByteString -> (ByteString -> Bool) -> (Request -> Status -> RspBody -> IO ()) -> AppSpec
- Network.Wai.Application.Classic.Types: BodyFile :: FilePath -> Range -> RspBody
+ Network.Wai.Application.Classic.Types: BodyFile :: ByteString -> Range -> RspBody
- Network.Wai.Application.Classic.Types: CgiRoute :: ByteString -> FilePath -> CgiRoute
+ Network.Wai.Application.Classic.Types: CgiRoute :: ByteString -> ByteString -> CgiRoute
- Network.Wai.Application.Classic.Types: FileRoute :: ByteString -> FilePath -> FileRoute
+ Network.Wai.Application.Classic.Types: FileRoute :: ByteString -> ByteString -> FileRoute
- Network.Wai.Application.Classic.Types: cgiDst :: CgiRoute -> FilePath
+ Network.Wai.Application.Classic.Types: cgiDst :: CgiRoute -> ByteString
- Network.Wai.Application.Classic.Types: fileDst :: FileRoute -> FilePath
+ Network.Wai.Application.Classic.Types: fileDst :: FileRoute -> ByteString
- Network.Wai.Application.Classic.Types: indexFile :: AppSpec -> FilePath
+ Network.Wai.Application.Classic.Types: indexFile :: AppSpec -> ByteString
- Network.Wai.Application.Classic.Types: isHTML :: AppSpec -> FilePath -> Bool
+ Network.Wai.Application.Classic.Types: isHTML :: AppSpec -> ByteString -> Bool

Files

Network/Wai/Application/Classic/CGI.hs view
@@ -9,7 +9,8 @@ import Control.Monad (when) import Control.Monad.IO.Class (liftIO) import Data.ByteString (ByteString)-import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString as BS hiding (unpack)+import qualified Data.ByteString.Char8 as BS (readInt, unpack) import Data.CaseInsensitive hiding (map) import Data.Enumerator hiding (map, filter, drop, break) import qualified Data.Enumerator.Binary as EB@@ -21,7 +22,6 @@ import Network.Wai.Application.Classic.Header import Network.Wai.Application.Classic.Types import Network.Wai.Application.Classic.Utils-import System.FilePath import System.IO import System.Process @@ -131,9 +131,9 @@   where     parseField bs = (mk key, val)       where-        (key,val) = case BS.break (==':') bs of+        (key,val) = case BS.breakByte 58 bs of -- ':'             kv@(_,"") -> kv-            (k,v) -> let v' = BS.dropWhile (==' ') $ BS.tail v in (k,v')+            (k,v) -> let v' = BS.dropWhile (==32) $ BS.tail v in (k,v') -- ' '  takeHeader :: Iteratee ByteString IO (Maybe [ByteString]) takeHeader = ENL.head >>= maybe (return Nothing) $. \l ->@@ -141,14 +141,14 @@        then return (Just [])        else takeHeader >>= maybe (return Nothing) (return . Just . (l:)) -pathinfoToCGI :: ByteString -> FilePath -> ByteString -> (FilePath, String, String)+pathinfoToCGI :: ByteString -> ByteString -> ByteString -> (FilePath, String, String) pathinfoToCGI src dst path = (prog, scriptName, pathinfo)   where-    src' = BS.unpack src-    path' = drop (BS.length src) $ BS.unpack path-    (prog',pathinfo) = break (== '/') path'-    prog = dst </> prog'-    scriptName = src' </> prog'+    path' = BS.drop (BS.length src) path+    (prog',pathinfo') = BS.breakByte pathSep path'+    prog = BS.unpack (dst </> prog')+    scriptName = BS.unpack (src </> prog')+    pathinfo = BS.unpack pathinfo'  ---------------------------------------------------------------- 
− Network/Wai/Application/Classic/Date.hs
@@ -1,51 +0,0 @@-module Network.Wai.Application.Classic.Date (parseDate, utcToDate) where--import Data.ByteString (ByteString)-import qualified Data.ByteString.Char8 as BS-import Data.Time-import Locale-import Control.Monad---- xxx: ClockTime -> UTCTime-------------------------------------------------------------------parseDate :: ByteString -> Maybe UTCTime-parseDate bs = rfc1123Date cs `mplus` rfc850Date cs `mplus` asctimeDate cs-  where-    cs = BS.unpack bs--------------------------------------------------------------------rfc1123Format :: String-rfc1123Format = "%a, %d %b %Y %H:%M:%S GMT"--rfc850Format :: String-rfc850Format  = "%A, %d-%b-%y %H:%M:%S GMT"---- xxx: allows "Nov 6" as well as "Nov  6", sigh.-asctimeFormat :: String-asctimeFormat = "%a %b %e %H:%M:%S %Y"--preferredFormat :: String-preferredFormat = rfc1123Format--------------------------------------------------------------------rfc1123Date :: String -> Maybe UTCTime-rfc1123Date = parseTime defaultTimeLocale preferredFormat--rfc850Date :: String -> Maybe UTCTime-rfc850Date str = parseTime defaultTimeLocale rfc850Format str >>= y2k-  where-    y2k utct = let (y,m,d) = toGregorian $ utctDay utct-               in if y < 1950-                  then Just utct { utctDay = fromGregorian (y+100) m d }-                  else Just utct--asctimeDate :: String -> Maybe UTCTime-asctimeDate = parseTime defaultTimeLocale asctimeFormat--------------------------------------------------------------------utcToDate :: UTCTime -> ByteString-utcToDate = BS.pack . formatTime defaultTimeLocale preferredFormat
Network/Wai/Application/Classic/Field.hs view
@@ -2,63 +2,63 @@  module Network.Wai.Application.Classic.Field where +import Control.Arrow (first) import Control.Monad (mplus)-import Data.List-import qualified Data.Map as M (lookup)-import Data.Time-import Network.Wai.Application.Classic.Date-import Network.Wai.Application.Classic.Lang-import Network.Wai.Application.Classic.Header-import Network.Wai.Application.Static (defaultMimeTypes, defaultMimeType, MimeType) import Data.ByteString (ByteString)-import Data.ByteString.Char8 ()+import qualified Data.ByteString as BS hiding (pack)+import Data.ByteString.Char8 as BS (pack)+import Data.HashMap (Map)+import qualified Data.HashMap as M+import Data.Map as Map (toList) import Data.Maybe+import Network.HTTP.Date import Network.HTTP.Types import Network.Wai+import Network.Wai.Application.Classic.Header+import Network.Wai.Application.Classic.Lang+import Network.Wai.Application.Static (defaultMimeTypes, defaultMimeType, MimeType)  ---------------------------------------------------------------- -languages :: Request -> [String]+languages :: Request -> [ByteString] languages req = maybe [] parseLang $ lookupRequestField fkAcceptLanguage req -ifModifiedSince :: Request -> Maybe UTCTime+ifModifiedSince :: Request -> Maybe HTTPDate ifModifiedSince = lookupAndParseDate fkIfModifiedSince -ifUnmodifiedSince :: Request -> Maybe UTCTime+ifUnmodifiedSince :: Request -> Maybe HTTPDate ifUnmodifiedSince = lookupAndParseDate fkIfUnmodifiedSince -ifRange :: Request -> Maybe UTCTime+ifRange :: Request -> Maybe HTTPDate ifRange = lookupAndParseDate fkIfRange -lookupAndParseDate :: ByteString -> Request -> Maybe UTCTime-lookupAndParseDate key req = lookupRequestField key req >>= parseDate+lookupAndParseDate :: ByteString -> Request -> Maybe HTTPDate+lookupAndParseDate key req = lookupRequestField key req >>= parseHTTPDate  ----------------------------------------------------------------  textPlain :: ResponseHeaders textPlain = [("Content-Type", "text/plain")] -newHeader :: FilePath -> UTCTime -> ResponseHeaders-newHeader file mtime = [-    ("Content-Type", mimeType file)-  , ("Last-Modified", utcToDate mtime)+newHeader :: Bool -> ByteString -> HTTPDate -> ResponseHeaders+newHeader ishtml file mtime = [+    ("Content-Type", if ishtml then "text/html" else mimeType file)+  , ("Last-Modified", formatHTTPDate mtime)   ] -mimeType :: FilePath -> MimeType-mimeType file =fromMaybe defaultMimeType . foldl1' mplus . map lok $ targets+mimeType :: ByteString -> MimeType+mimeType file =fromMaybe defaultMimeType . foldr1 mplus . map lok $ targets   where     targets = extensions file-    lok x = M.lookup x defaultMimeTypes+    lok x = M.lookup x defaultMimeTypes' -extensions :: FilePath -> [String]-extensions file = entire : exts+extensions :: ByteString -> [ByteString]+extensions file = exts   where-    exts' = split ('.'==) file-    exts = if exts' == [] then [] else tail exts'-    entire = foldr (\x y -> x ++ '.' : y) "" exts+    entire = case BS.breakByte 46 file of -- '.'+        (_,"") -> ""+        (_,x)  -> BS.tail x+    exts = if entire == "" then [] else entire : BS.split 46 file -split :: (Char -> Bool) -> String -> [String]-split _ "" = []-split p xs = case break p xs of-    (ys,"")   -> [ys]-    (ys,_:zs) -> ys : split p zs+defaultMimeTypes' :: Map ByteString MimeType+defaultMimeTypes' = M.fromList $ map (first BS.pack) $ Map.toList defaultMimeTypes
Network/Wai/Application/Classic/File.hs view
@@ -5,7 +5,9 @@   ) where  import Control.Monad.IO.Class (liftIO)-import qualified Data.ByteString.Char8 as BS+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS hiding (unpack, pack)+import qualified Data.ByteString.Char8 as BS (unpack, pack) import qualified Data.ByteString.Lazy.Char8 as BL () import Network.HTTP.Types import Network.Wai@@ -13,6 +15,7 @@ import Network.Wai.Application.Classic.FileInfo import Network.Wai.Application.Classic.MaybeIter import Network.Wai.Application.Classic.Types+import Network.Wai.Application.Classic.Utils  ---------------------------------------------------------------- @@ -42,42 +45,52 @@         "HEAD" -> processHEAD req file ishtml rfile         _      -> return notAllowed     liftIO $ logger spec req st body-    let hdr' = addHeader hdr+    let hdr' = addServer hdr     case body of         NoBody     -> return $ responseLBS st hdr' ""         BodyLBS bd -> return $ responseLBS st hdr' bd-        BodyFile afile (Entire _)-            -> return $ ResponseFile st hdr' afile Nothing-        BodyFile afile (Part skip len)-            -> return $ ResponseFile st hdr' afile (Just (FilePart skip len))+        BodyFile afile (Entire len) -> do+            let hdr'' = addLength hdr' len+            return $ ResponseFile st hdr'' (BS.unpack afile) Nothing+        BodyFile afile (Part skip len) -> do+            let hdr'' = addLength hdr' len+            return $ ResponseFile st hdr'' (BS.unpack afile) (Just (FilePart skip len))   where     method = requestMethod req     path = pathinfoToFilePath req filei     file = addIndex spec path     ishtml = isHTML spec file     rfile = redirectPath spec path-    addHeader hdr = ("Server", softwareName spec) : hdr+    addServer hdr = ("Server", softwareName spec) : hdr+    addLength hdr len = ("Content-Length", BS.pack . show $ len) : hdr  ---------------------------------------------------------------- -processGET :: Request -> FilePath -> Bool -> Maybe FilePath -> Rsp+type Lang = Maybe ByteString++langSuffixes :: Request -> [Lang]+langSuffixes req = map (Just . BS.cons 46) (languages req) ++ [Nothing, Just ".en"] -- '.'++----------------------------------------------------------------++processGET :: Request -> ByteString -> Bool -> Maybe ByteString -> Rsp processGET req file ishtml rfile = runAny [-    tryGet req file ishtml langs-  , tryRedirect req rfile langs+    tryGet req file ishtml+  , tryRedirect req rfile   , just notFound   ]-  where-    langs = map ('.':) (languages req) ++ ["",".en"] -tryGet :: Request -> FilePath -> Bool -> [String] -> MRsp-tryGet req file True langs = runAnyMaybe $ map (tryGetFile req file) langs-tryGet req file _    _     = tryGetFile req file ""+tryGet :: Request -> ByteString -> Bool -> MRsp+tryGet req file True  = runAnyMaybe $ map (tryGetFile req file True) langs+  where+    langs = langSuffixes req+tryGet req file False = tryGetFile req file False Nothing -tryGetFile :: Request -> FilePath -> String -> MRsp-tryGetFile req file lang = do-    let file' = if null lang then file else file ++ lang+tryGetFile :: Request -> ByteString -> Bool -> Lang -> MRsp+tryGetFile req file ishtml mlang = do+    let file' = maybe file (file +++) mlang     liftIO (fileInfo file') |>| \(size, mtime) -> do-      let hdr = newHeader file mtime+      let hdr = newHeader ishtml file mtime           Just pst = ifmodified req size mtime -- never Nothing                  ||| ifunmodified req size mtime                  ||| ifrange req size mtime@@ -91,24 +104,24 @@  ---------------------------------------------------------------- -processHEAD :: Request -> FilePath -> Bool -> Maybe FilePath -> Rsp+processHEAD :: Request -> ByteString -> Bool -> Maybe ByteString -> Rsp processHEAD req file ishtml rfile = runAny [-    tryHead req file ishtml langs-  , tryRedirect req rfile langs+    tryHead req file ishtml+  , tryRedirect req rfile   , just notFound   ]-  where-    langs = map ('.':) (languages req) ++ ["",".en"] -tryHead :: Request -> FilePath -> Bool -> [String] -> MRsp-tryHead req file True langs = runAnyMaybe $ map (tryHeadFile req file) langs-tryHead req file _    _     = tryHeadFile req file ""+tryHead :: Request -> ByteString -> Bool -> MRsp+tryHead req file True  = runAnyMaybe $ map (tryHeadFile req file True) langs+  where+    langs = langSuffixes req+tryHead req file False= tryHeadFile req file False Nothing -tryHeadFile :: Request -> FilePath -> String -> MRsp-tryHeadFile req file lang = do-    let file' = if null lang then file else file ++ lang+tryHeadFile :: Request -> ByteString -> Bool -> Lang -> MRsp+tryHeadFile req file ishtml mlang = do+    let file' = maybe file (file +++) mlang     liftIO (fileInfo file') |>| \(size, mtime) -> do-      let hdr = newHeader file mtime+      let hdr = newHeader ishtml file mtime           Just pst = ifmodified req size mtime -- never Nothing                  ||| Just (Full statusOK)       case pst of@@ -117,21 +130,22 @@  ---------------------------------------------------------------- -tryRedirect  :: Request -> Maybe FilePath -> [String] -> MRsp-tryRedirect _   Nothing     _     = nothing-tryRedirect req (Just file) langs =+tryRedirect  :: Request -> Maybe ByteString -> MRsp+tryRedirect _   Nothing     = nothing+tryRedirect req (Just file) =     runAnyMaybe $ map (tryRedirectFile req file) langs+  where+    langs = langSuffixes req -tryRedirectFile :: Request -> FilePath -> String -> MRsp-tryRedirectFile req file lang = do-    let file' = file ++ lang+tryRedirectFile :: Request -> ByteString -> Lang -> MRsp+tryRedirectFile req file mlang = do+    let file' = maybe file (file +++) mlang     minfo <- liftIO $ fileInfo file'     case minfo of       Nothing -> nothing       Just _  -> just $ RspSpec statusMovedPermanently hdr NoBody   where     hdr = [("Location", redirectURL)]-    (+++) = BS.append     redirectURL = "http://"               +++ serverName req               +++ ":"
Network/Wai/Application/Classic/FileInfo.hs view
@@ -1,51 +1,55 @@ module Network.Wai.Application.Classic.FileInfo where +import Control.Exception import Data.ByteString (ByteString)-import qualified Data.ByteString.Char8 as BS-import Data.Time-import Data.Time.Clock.POSIX+import qualified Data.ByteString as BS hiding (unpack)+import qualified Data.ByteString.Char8 as BS (unpack)+import Network.HTTP.Date import Network.HTTP.Types import Network.Wai import Network.Wai.Application.Classic.Field import Network.Wai.Application.Classic.Header import Network.Wai.Application.Classic.Range import Network.Wai.Application.Classic.Types-import System.Directory-import System.FilePath+import Network.Wai.Application.Classic.Utils import System.Posix.Files  ---------------------------------------------------------------- -fileInfo :: FilePath -> IO (Maybe (Integer, UTCTime))-fileInfo file = do-    exist <- doesFileExist file-    if exist-       then do-         fs <- getFileStatus file-         let size = fromIntegral . fileSize $ fs-             mtime = posixSecondsToUTCTime . realToFrac . modificationTime $ fs-         return $ Just (size, mtime)+-- This function is slow.+-- So, let's avoid using doesFileExist which uses getFilesStatus.+fileInfo :: ByteString -> IO (Maybe (Integer, HTTPDate))+fileInfo file = handle nothing $ do+    fs <- getFileStatus (BS.unpack file)+    if doesExist fs+       then return $ Just (size fs, mtime fs)        else return Nothing+  where+    nothing :: IOException -> IO (Maybe (Integer, HTTPDate))+    nothing _ = return Nothing+    size = fromIntegral . fileSize+    mtime = epochTimeToHTTPDate . modificationTime+    doesExist = not . isDirectory  ----------------------------------------------------------------  data StatusAux = Full Status | Partial Integer Integer deriving Show -ifmodified :: Request -> Integer -> UTCTime -> Maybe StatusAux+ifmodified :: Request -> Integer -> HTTPDate -> Maybe StatusAux ifmodified req size mtime = do     date <- ifModifiedSince req     if date /= mtime        then unconditional req size mtime        else Just (Full statusNotModified) -ifunmodified :: Request -> Integer -> UTCTime -> Maybe StatusAux+ifunmodified :: Request -> Integer -> HTTPDate -> Maybe StatusAux ifunmodified req size mtime = do     date <- ifUnmodifiedSince req     if date == mtime        then unconditional req size mtime        else Just (Full statusPreconditionFailed) -ifrange :: Request -> Integer -> UTCTime -> Maybe StatusAux+ifrange :: Request -> Integer -> HTTPDate -> Maybe StatusAux ifrange req size mtime = do     date <- ifRange req     rng  <- lookupRequestField fkRange req@@ -53,7 +57,7 @@        then Just (Full statusOK)        else range size rng -unconditional :: Request -> Integer -> UTCTime -> Maybe StatusAux+unconditional :: Request -> Integer -> HTTPDate -> Maybe StatusAux unconditional req size _ =     maybe (Just (Full statusOK)) (range size) $ lookupRequestField fkRange req @@ -62,23 +66,22 @@   Nothing         -> Just (Full statusRequestedRangeNotSatisfiable)   Just (skip,len) -> Just (Partial skip len) - ---------------------------------------------------------------- -pathinfoToFilePath :: Request -> FileRoute -> FilePath+pathinfoToFilePath :: Request -> FileRoute -> ByteString pathinfoToFilePath req filei = path'   where     path = rawPathInfo req     src = fileSrc filei     dst = fileDst filei-    path' = dst </> drop (BS.length src) (BS.unpack path)+    path' = dst </> BS.drop (BS.length src) path -addIndex :: AppSpec -> FilePath -> FilePath+addIndex :: AppSpec -> ByteString -> ByteString addIndex spec path   | hasTrailingPathSeparator path = path </> indexFile spec   | otherwise                     = path -redirectPath :: AppSpec -> FilePath -> Maybe FilePath+redirectPath :: AppSpec -> ByteString -> Maybe ByteString redirectPath spec path   | hasTrailingPathSeparator path = Nothing   | otherwise                     = Just (path </> indexFile spec)
Network/Wai/Application/Classic/Lang.hs view
@@ -3,12 +3,14 @@ module Network.Wai.Application.Classic.Lang (parseLang) where  import Control.Applicative hiding (many, optional)-import Data.Attoparsec.Char8 hiding (take)-import Data.ByteString.Char8 hiding (map, count, take)-import Data.List+import Data.Attoparsec (Parser, takeWhile, parse, feed, Result(..))+import Data.Attoparsec.Char8 (char, string, count, many, space, digit, option, sepBy1)+import Data.ByteString.Char8 hiding (map, count, take, takeWhile)+import Data.List (sortBy) import Data.Ord+import Prelude hiding (takeWhile) -parseLang :: ByteString -> [String]+parseLang :: ByteString -> [ByteString] parseLang bs = case feed (parse acceptLanguage bs) "" of     Done _ ls -> map fst $ sortBy detrimental ls     _         -> []@@ -17,20 +19,14 @@  ---------------------------------------------------------------- -acceptLanguage :: Parser [(String,Int)]+acceptLanguage :: Parser [(ByteString,Int)] acceptLanguage = rangeQvalue `sepBy1` (spaces *> char ',' *> spaces) -rangeQvalue :: Parser (String,Int)+rangeQvalue :: Parser (ByteString,Int) rangeQvalue = (,) <$> languageRange <*> quality -languageRange :: Parser String-languageRange = (++) <$> language <*> sublang--language :: Parser String-language = many1 letter_ascii--sublang :: Parser String-sublang = option "" ((:) <$> char '-' <*> many1 letter_ascii)+languageRange :: Parser ByteString+languageRange = takeWhile (\w -> w /= 32 && w /= 44 && w /= 59)  quality :: Parser Int quality = option 1000 (string ";q=" *> qvalue)
Network/Wai/Application/Classic/MaybeIter.hs view
@@ -32,12 +32,14 @@  ---------------------------------------------------------------- +infixr 5 >>|, |>|, |||+ (>>|) :: Maybe a -> (a -> MaybeIter b) -> MaybeIter b v >>| act =     case v of       Nothing -> nothing       Just x  -> act x-+       (|>|) :: MaybeIter a -> (a -> MaybeIter b) -> MaybeIter b a |>| act = do     v <- a
Network/Wai/Application/Classic/Types.hs view
@@ -9,9 +9,9 @@     -- | Name specified to Server: in HTTP response.     softwareName :: ByteString     -- | A file name of an index file.-  , indexFile :: FilePath+  , indexFile :: ByteString     -- | Whether this is an HTML or not.-  , isHTML :: FilePath -> Bool+  , isHTML :: ByteString -> Bool     -- | A function for logging.   , logger :: Request -> Status -> RspBody -> IO ()   }@@ -20,14 +20,14 @@     -- | Path prefix to be matched to 'pathInfo'.     fileSrc :: ByteString     -- | Path prefix to an actual file system.-  , fileDst :: FilePath+  , fileDst :: ByteString   }  data CgiRoute = CgiRoute {     -- | Path prefix to be matched to 'pathInfo'.     cgiSrc :: ByteString     -- | Path prefix to an actual file system.-  , cgiDst :: FilePath+  , cgiDst :: ByteString   }  data RspSpec = RspSpec {@@ -45,7 +45,7 @@     -- | Body as Lazy ByteString.   | BodyLBS BL.ByteString     -- | Body as a file.-  | BodyFile FilePath Range+  | BodyFile ByteString Range  data Range =     -- | Entire file showing its file size
Network/Wai/Application/Classic/Utils.hs view
@@ -1,9 +1,15 @@+{-# LANGUAGE OverloadedStrings #-}+ module Network.Wai.Application.Classic.Utils where  import Control.Applicative-import Network.Socket (getNameInfo, SockAddr, NameInfoFlag(..))-import Data.Maybe+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import Data.ByteString.Char8 () import Data.List (isPrefixOf)+import Data.Maybe+import Data.Word+import Network.Socket (getNameInfo, SockAddr, NameInfoFlag(..))  {-|   A type for IP address in numeric string representation.@@ -22,3 +28,21 @@       | "::ffff:" `isPrefixOf` x = drop 7 x       | otherwise                = x +pathSep :: Word8+pathSep = 47++hasTrailingPathSeparator :: ByteString -> Bool+hasTrailingPathSeparator "" = False+hasTrailingPathSeparator path+  | BS.last path == pathSep = True+  | otherwise               = False++infixr +++++(+++) :: ByteString -> ByteString -> ByteString+(+++) = BS.append++(</>) :: ByteString -> ByteString -> ByteString+s1 </> s2+  | hasTrailingPathSeparator s1 = s1 +++ s2+  | otherwise                   = s1 +++ (pathSep `BS.cons` s2)
wai-app-file-cgi.cabal view
@@ -1,5 +1,5 @@ Name:                   wai-app-file-cgi-Version:                0.1.2+Version:                0.2.0 Author:                 Kazu Yamamoto <kazu@iij.ad.jp> Maintainer:             Kazu Yamamoto <kazu@iij.ad.jp> License:                BSD3@@ -21,8 +21,7 @@                         Network.Wai.Application.Classic.File                         Network.Wai.Application.Classic.Types                         Network.Wai.Application.Classic.Header-  Other-Modules:        Network.Wai.Application.Classic.Date-                        Network.Wai.Application.Classic.EnumLine+  Other-Modules:        Network.Wai.Application.Classic.EnumLine                         Network.Wai.Application.Classic.Field                         Network.Wai.Application.Classic.FileInfo                         Network.Wai.Application.Classic.Lang@@ -30,12 +29,13 @@                         Network.Wai.Application.Classic.Range                         Network.Wai.Application.Classic.Utils   Build-Depends:        base >= 4 && < 5, process, haskell98,-                        network, transformers, time,+                        network, transformers,                         filepath, directory, unix,                         containers, attoparsec,                         wai, enumerator >= 0.4.9,                         bytestring, blaze-builder,-                        wai-app-static, http-types, case-insensitive+                        wai-app-static, http-types, http-date,+                        case-insensitive, hashmap, array Source-Repository head   Type:                 git   Location:             git://github.com/kazu-yamamoto/wai-app-file-cgi.git