This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "snap-core".

The branch, master has been updated
       via  ef6c351c14594517dea3a5159d75af38c47700f5 (commit)
      from  6dc149cd34142785d1460a757b219008a9d16fe6 (commit)


Summary of changes:
 snap-core.cabal                         |    3 +
 src/Snap/Internal/Http/Types.hs         |   11 ++-
 src/Snap/Internal/Parsing.hs            |   26 +++++
 src/Snap/Internal/Types.hs              |   19 ++++-
 src/Snap/Iteratee.hs                    |   92 ++++++++++++++++--
 src/Snap/Types.hs                       |    1 +
 src/Snap/Util/FileServe.hs              |  157 +++++++++++++++++++++++++++++--
 src/Snap/Util/GZip.hs                   |   14 +---
 test/runTestsAndCoverage.sh             |    5 +-
 test/snap-core-testsuite.cabal          |    1 +
 test/suite/Snap/Util/FileServe/Tests.hs |   35 ++++++-
 11 files changed, 322 insertions(+), 42 deletions(-)
 create mode 100644 src/Snap/Internal/Parsing.hs

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit ef6c351c14594517dea3a5159d75af38c47700f5
Author: Gregory Collins <[email protected]>
Date:   Fri Sep 24 21:25:20 2010 -0400

    Add 'Range:' support to fileServe

diff --git a/snap-core.cabal b/snap-core.cabal
index cea34b3..1f264d3 100644
--- a/snap-core.cabal
+++ b/snap-core.cabal
@@ -140,6 +140,7 @@ Library
     Snap.Util.GZip
 
   other-modules:
+    Snap.Internal.Parsing,
     Snap.Internal.Routing,
     Snap.Internal.Types
 
@@ -148,6 +149,7 @@ Library
     base >= 4 && < 5,
     bytestring,
     bytestring-nums,
+    bytestring-show >= 0.3.2 && < 0.4,
     cereal >= 0.3 && < 0.4,
     containers,
     deepseq >= 1.1 && <1.2,
@@ -189,6 +191,7 @@ Executable snap
     base >= 4 && < 5,
     bytestring,
     bytestring-nums,
+    bytestring-show >= 0.3.2 && < 0.4,
     cereal >= 0.3 && < 0.4,
     containers,
     deepseq >= 1.1 && <1.2,
diff --git a/src/Snap/Internal/Http/Types.hs b/src/Snap/Internal/Http/Types.hs
index 66016d0..d10f638 100644
--- a/src/Snap/Internal/Http/Types.hs
+++ b/src/Snap/Internal/Http/Types.hs
@@ -351,8 +351,12 @@ instance HasHeaders Headers where
 -- response type
 ------------------------------------------------------------------------------
 
-data ResponseBody = Enum (forall a . Enumerator a) -- ^ output body is 
enumerator
-                  | SendFile FilePath              -- ^ output body is 
sendfile()
+data ResponseBody = Enum (forall a . Enumerator a)
+                      -- ^ output body is enumerator
+
+                  | SendFile FilePath (Maybe (Int64,Int64))
+                      -- ^ output body is sendfile(), optional second argument
+                      --   is a byte range to send
 
 
 ------------------------------------------------------------------------------
@@ -365,7 +369,8 @@ rspBodyMap f b      = Enum $ f $ rspBodyToEnum b
 ------------------------------------------------------------------------------
 rspBodyToEnum :: ResponseBody -> Enumerator a
 rspBodyToEnum (Enum e) = e
-rspBodyToEnum (SendFile fp) = I.enumFile fp
+rspBodyToEnum (SendFile fp Nothing)  = I.enumFile fp
+rspBodyToEnum (SendFile fp (Just s)) = I.enumFilePartial fp s
 
 
 ------------------------------------------------------------------------------
diff --git a/src/Snap/Internal/Parsing.hs b/src/Snap/Internal/Parsing.hs
new file mode 100644
index 0000000..7e1bca8
--- /dev/null
+++ b/src/Snap/Internal/Parsing.hs
@@ -0,0 +1,26 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Snap.Internal.Parsing where
+
+import           Control.Monad
+import           Data.Attoparsec.Char8 hiding (Done)
+import qualified Data.Attoparsec.Char8 as Atto
+import           Data.ByteString.Char8 (ByteString)
+import           Data.ByteString.Nums.Careless.Int (int)
+import           Data.Int
+
+------------------------------------------------------------------------------
+fullyParse :: ByteString -> Parser a -> Either String a
+fullyParse s p =
+    case r' of
+      (Fail _ _ e)    -> Left e
+      (Partial _)     -> Left "parse failed"
+      (Atto.Done _ x) -> Right x
+  where
+    r  = parse p s
+    r' = feed r ""
+
+
+------------------------------------------------------------------------------
+parseNum :: Parser Int64
+parseNum = liftM int $ Atto.takeWhile1 Atto.isDigit
diff --git a/src/Snap/Internal/Types.hs b/src/Snap/Internal/Types.hs
index 37429c0..89dc694 100644
--- a/src/Snap/Internal/Types.hs
+++ b/src/Snap/Internal/Types.hs
@@ -14,6 +14,7 @@ import           Data.ByteString.Char8 (ByteString)
 import qualified Data.ByteString.Char8 as S
 import qualified Data.ByteString.Lazy.Char8 as L
 import qualified Data.CIByteString as CIB
+import           Data.Int
 import           Data.IORef
 import qualified Data.Iteratee as Iter
 import           Data.Maybe
@@ -480,7 +481,23 @@ writeLazyText s = writeLBS $ LT.encodeUtf8 s
 -- If the response body is modified (using 'modifyResponseBody'), the file will
 -- be read using @mmap()@.
 sendFile :: FilePath -> Snap ()
-sendFile f = modifyResponse $ \r -> r { rspBody = SendFile f }
+sendFile f = modifyResponse $ \r -> r { rspBody = SendFile f Nothing }
+
+
+------------------------------------------------------------------------------
+-- | Sets the output to be the contents of the specified file, within the given
+-- (start,end) range.
+--
+-- Calling 'sendFilePartial' will overwrite any output queued to be sent in the
+-- 'Response'. If the response body is not modified after the call to
+-- 'sendFilePartial', Snap will use the efficient @sendfile()@ system call on
+-- platforms that support it.
+--
+-- If the response body is modified (using 'modifyResponseBody'), the file will
+-- be read using @mmap()@.
+sendFilePartial :: FilePath -> (Int64,Int64) -> Snap ()
+sendFilePartial f rng = modifyResponse $ \r ->
+                        r { rspBody = SendFile f (Just rng) }
 
 
 ------------------------------------------------------------------------------
diff --git a/src/Snap/Iteratee.hs b/src/Snap/Iteratee.hs
index 2caf633..2b87aab 100644
--- a/src/Snap/Iteratee.hs
+++ b/src/Snap/Iteratee.hs
@@ -1,7 +1,9 @@
 {-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE CPP #-}
+{-# LANGUAGE DeriveDataTypeable #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE PackageImports #-}
+{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeSynonymInstances #-}
 
 -- | Snap Framework type aliases and utilities for iteratees. Note that as a
@@ -28,6 +30,8 @@ module Snap.Iteratee
   , enumBS
   , enumLBS
   , enumFile
+  , enumFilePartial
+  , InvalidRangeException
 
     -- ** Conversion to/from 'WrappedByteString'
   , fromWrap
@@ -47,6 +51,7 @@ module Snap.Iteratee
 ------------------------------------------------------------------------------
 import             Control.Monad
 import             Control.Monad.CatchIO
+import             Control.Exception (Exception, SomeException)
 import             Data.ByteString (ByteString)
 import qualified   Data.ByteString as S
 import qualified   Data.ByteString.Unsafe as S
@@ -60,6 +65,7 @@ import qualified   Data.Iteratee.Base.StreamChunk as SC
 import             Data.Iteratee.WrappedByteString
 import qualified   Data.ListLike as LL
 import             Data.Monoid (mappend)
+import             Data.Typeable
 import             Foreign
 import             Foreign.C.Types
 import             GHC.ForeignPtr
@@ -68,7 +74,6 @@ import             System.IO
 import "monads-fd" Control.Monad.Trans (liftIO)
 
 #ifndef PORTABLE
-import           Control.Exception (SomeException)
 import           System.IO.Posix.MMap
 import           System.PosixCompat.Files
 #endif
@@ -411,18 +416,53 @@ takeNoMoreThan n' iter =
 
 
 ------------------------------------------------------------------------------
+{-# INLINE _enumFile #-}
 _enumFile :: FilePath -> Iteratee IO a -> IO (Iteratee IO a)
 _enumFile fp iter = do
     h  <- liftIO $ openBinaryFile fp ReadMode
-    i' <- enumHandle h iter
-    return (i' `finally` liftIO (hClose h))
+    enumHandle h iter `finally` hClose h
+
+
+------------------------------------------------------------------------------
+data InvalidRangeException = InvalidRangeException
+   deriving (Typeable)
+
+instance Show InvalidRangeException where
+    show InvalidRangeException = "Invalid range"
+
+instance Exception InvalidRangeException
+
+
+------------------------------------------------------------------------------
+{-# INLINE _enumFilePartial #-}
+_enumFilePartial :: FilePath
+                 -> (Int64,Int64)
+                 -> Iteratee IO a
+                 -> IO (Iteratee IO a)
+_enumFilePartial fp (start,end) iter = do
+    let len = end - start
+
+    h  <- liftIO $ openBinaryFile fp ReadMode
+    unless (start == 0) $
+           hSeek h AbsoluteSeek $ toInteger start
+
+    let i' = joinI $ takeExactly len iter
+
+    enumHandle h i' `finally` hClose h
 
 
 enumFile :: FilePath -> Iteratee IO a -> IO (Iteratee IO a)
+enumFilePartial :: FilePath
+                -> (Int64,Int64)
+                -> Iteratee IO a
+                -> IO (Iteratee IO a)
 
 #ifdef PORTABLE
 
 enumFile = _enumFile
+enumFilePartial fp rng@(start,end) iter = do
+    when (end < start) $ throw InvalidRangeException
+    _enumFilePartial fp rng iter
 
 #else
 
@@ -430,19 +470,53 @@ enumFile = _enumFile
 maxMMapFileSize :: FileOffset
 maxMMapFileSize = 41943040
 
+tooBigForMMap :: FilePath -> IO Bool
+tooBigForMMap fp = do
+    stat <- getFileStatus fp
+    return $ fileSize stat > maxMMapFileSize
+
+
 enumFile fp iter = do
     -- for small files we'll use mmap to save ourselves a copy, otherwise we'll
     -- stream it
-    stat <- getFileStatus fp
-    if fileSize stat > maxMMapFileSize
+    tooBig <- tooBigForMMap fp
+
+    if tooBig
       then _enumFile fp iter
       else do
-        es <- (try $
-               liftM WrapBS $
-               unsafeMMapFile fp) :: IO (Either SomeException 
(WrappedByteString Word8))
+        es <- try $
+              liftM WrapBS $
+              unsafeMMapFile fp
 
         case es of
-          (Left e)  -> return $ throwErr $ Err $ "IO error" ++ show e
+          (Left (e :: SomeException)) -> return $ throwErr
+                                                $ Err
+                                                $ "IO error" ++ show e
+
           (Right s) -> liftM liftI $ runIter iter $ Chunk s
 
+
+enumFilePartial fp rng@(start,end) iter = do
+    when (end < start) $ throw InvalidRangeException
+
+    let len = end - start
+
+    tooBig <- tooBigForMMap fp
+
+    if tooBig
+      then _enumFilePartial fp rng iter
+      else do
+        es <- try $ unsafeMMapFile fp
+
+        case es of
+          (Left (e::SomeException)) -> return $ throwErr
+                                              $ Err
+                                              $ "IO error" ++ show e
+
+          (Right s) -> liftM liftI $ runIter iter
+                                   $ Chunk
+                                   $ WrapBS
+                                   $ S.take (fromEnum len)
+                                   $ S.drop (fromEnum start) s
+
 #endif
diff --git a/src/Snap/Types.hs b/src/Snap/Types.hs
index bf18d4b..512326a 100644
--- a/src/Snap/Types.hs
+++ b/src/Snap/Types.hs
@@ -106,6 +106,7 @@ module Snap.Types
   , writeText
   , writeLBS
   , sendFile
+  , sendFilePartial
 
     -- * Iteratee
   , Enumerator
diff --git a/src/Snap/Util/FileServe.hs b/src/Snap/Util/FileServe.hs
index 3a01289..c9b60cf 100644
--- a/src/Snap/Util/FileServe.hs
+++ b/src/Snap/Util/FileServe.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE ScopedTypeVariables #-}
@@ -15,23 +16,32 @@ module Snap.Util.FileServe
 ) where
 
 ------------------------------------------------------------------------------
+import           Control.Applicative
 import           Control.Monad
 import           Control.Monad.Trans
+import           Data.Attoparsec.Char8 hiding (Done)
 import qualified Data.ByteString.Char8 as S
+import qualified Data.ByteString.Lazy.Char8 as L
 import           Data.ByteString.Char8 (ByteString)
+import           Data.Int
 import           Data.Map (Map)
 import qualified Data.Map as Map
 import           Data.Maybe (fromMaybe)
+import           Prelude hiding (show, Show)
+import qualified Prelude
 import           System.Directory
 import           System.FilePath
 import           System.PosixCompat.Files
-
+import           Text.Show.ByteString hiding (runPut)
 ------------------------------------------------------------------------------
+import           Snap.Internal.Debug
+import           Snap.Internal.Parsing
+import           Snap.Iteratee hiding (drop)
 import           Snap.Types
 
 
 ------------------------------------------------------------------------------
--- | A type alias for MIME type 
+-- | A type alias for MIME type
 type MimeMap = Map FilePath ByteString
 
 
@@ -218,32 +228,55 @@ fileServeSingle' :: ByteString        -- ^ MIME type 
mapping
                  -> Snap ()
 fileServeSingle' mime fp = do
     req <- getRequest
-    
+
+    -- check "If-Modified-Since" and "If-Range" headers
     let mbH = getHeader "if-modified-since" req
     mbIfModified <- liftIO $ case mbH of
                                Nothing  -> return Nothing
                                (Just s) -> liftM Just $ parseHttpTime s
 
+    mbIfRange <- liftIO $ case getHeader "if-range" req of
+                            Nothing  -> return Nothing
+                            (Just s) -> liftM Just $ parseHttpTime s
+
+
     -- check modification time and bug out early if the file is not modified.
     filestat <- liftIO $ getFileStatus fp
     let mt = modificationTime filestat
-    maybe (return ()) (chkModificationTime mt) mbIfModified
+    maybe (return ()) (\lt -> when (mt <= lt) notModified) mbIfModified
 
     let sz = fromIntegral $ fileSize filestat
     lm <- liftIO $ formatHttpTime mt
 
+    -- ok, at this point we know the last-modified time and the
+    -- content-type. set those.
     modifyResponse $ setHeader "Last-Modified" lm
+                   . setHeader "Accept-Ranges" "bytes"
                    . setContentType mime
-                   . setContentLength sz
-    sendFile fp
 
-  where
-    --------------------------------------------------------------------------
-    chkModificationTime mt lt = when (mt <= lt) notModified
 
+    -- now check: is this a range request? If there is an 'If-Range' header
+    -- with an old modification time we skip this check and send a 200 response
+    let skipRangeCheck = maybe (False)
+                               (\lt -> mt > lt)
+                               mbIfRange
+
+
+    -- checkRangeReq checks for a Range: header in the request and sends a
+    -- partial response if it matches.
+    wasRange <- if skipRangeCheck
+                  then return False
+                  else checkRangeReq req fp sz
+
+    -- if we didn't have a range request, we just do normal sendfile
+    unless wasRange $ do
+      modifyResponse $ setResponseCode 200
+      sendFile fp
+
+  where
     --------------------------------------------------------------------------
     notModified = finishWith $
-                  setResponseStatus 304 "Not Modified" emptyResponse
+                  setResponseCode 304 emptyResponse
 
 
 ------------------------------------------------------------------------------
@@ -262,3 +295,107 @@ fileType mm f =
 ------------------------------------------------------------------------------
 defaultMimeType :: ByteString
 defaultMimeType = "application/octet-stream"
+
+
+------------------------------------------------------------------------------
+data RangeReq = RangeReq { _rangeFirst :: !Int64
+                         , _rangeLast  :: !(Maybe Int64)
+                         }
+              | SuffixRangeReq { _suffixLength :: !Int64 }
+  deriving (Eq, Prelude.Show)
+
+
+------------------------------------------------------------------------------
+rangeParser :: Parser RangeReq
+rangeParser = string "bytes=" *> (byteRangeSpec <|> suffixByteRangeSpec)
+  where
+    byteRangeSpec = do
+        start <- parseNum
+        end   <- option Nothing $ liftM Just (char '-' *> parseNum)
+
+        return $ RangeReq start end
+
+    suffixByteRangeSpec = liftM SuffixRangeReq $ char '-' *> parseNum
+
+
+------------------------------------------------------------------------------
+checkRangeReq :: Request -> FilePath -> Int64 -> Snap Bool
+checkRangeReq req fp sz = do
+    dbg $ "checkRangeReq, fp=" ++ fp ++ ", sz=" ++ Prelude.show sz
+    maybe (return False)
+          (\s -> either (const $ return False)
+                        withRange
+                        (fullyParse s rangeParser))
+          (getHeader "range" req)
+
+  where
+    withRange rng@(RangeReq start mend) = do
+        dbg $ "withRange: got Range request: " ++ Prelude.show rng
+        let end = fromMaybe (sz-1) mend
+        dbg $ "withRange: start=" ++ Prelude.show start
+                  ++ ", end=" ++ Prelude.show end
+
+        if start < 0 || end < start || start >= sz || end >= sz
+           then send416
+           else send206 start end
+
+    withRange rng@(SuffixRangeReq nbytes) = do
+        dbg $ "withRange: got Range request: " ++ Prelude.show rng
+        let end   = sz-1
+        let start = sz - nbytes
+
+        dbg $ "withRange: start=" ++ Prelude.show start
+                  ++ ", end=" ++ Prelude.show end
+
+        if start < 0 || end < start || start >= sz || end >= sz
+           then send416
+           else send206 start end
+
+    -- note: start and end INCLUSIVE here
+    send206 start end = do
+        dbg "inside send206"
+        let crng = S.concat $
+                   L.toChunks $
+                   L.concat [ "bytes "
+                            , show start
+                            , "-"
+                            , show end
+                            , "/"
+                            , show sz ]
+
+        modifyResponse $ setResponseCode 206
+                       . setHeader "Content-Range" crng
+
+        dbg $ "send206: sending range (" ++ Prelude.show start
+                ++ "," ++ Prelude.show (end+1) ++ ") to sendFilePartial"
+
+        -- end here was inclusive, sendFilePartial is exclusive
+        sendFilePartial fp (start,end+1)
+        return True
+
+
+    send416 = do
+        dbg "inside send416"
+        -- if there's an "If-Range" header in the request, then we just send
+        -- back 200
+        if getHeader "If-Range" req /= Nothing
+           then return False
+           else do
+               let crng = S.concat $
+                          L.toChunks $
+                          L.concat ["bytes */", show sz]
+               
+               modifyResponse $ setResponseCode 416
+                              . setHeader "Content-Range" crng
+                              . setContentLength 0
+                              . deleteHeader "Content-Type"
+                              . deleteHeader "Content-Encoding"
+                              . deleteHeader "Transfer-Encoding"
+                              . setResponseBody (enumBS "")
+               
+               return True
+
+
+
+dbg :: (MonadIO m) => String -> m ()
+dbg s = debug $ "FileServe:" ++ s
diff --git a/src/Snap/Util/GZip.hs b/src/Snap/Util/GZip.hs
index 63a1ee9..fa65db0 100644
--- a/src/Snap/Util/GZip.hs
+++ b/src/Snap/Util/GZip.hs
@@ -15,7 +15,6 @@ import           Control.Exception
 import           Control.Monad
 import           Control.Monad.Trans
 import           Data.Attoparsec.Char8 hiding (Done)
-import qualified Data.Attoparsec.Char8 as Atto
 import qualified Data.ByteString.Lazy.Char8 as L
 import           Data.ByteString.Char8 (ByteString)
 import           Data.Iteratee.WrappedByteString
@@ -27,6 +26,7 @@ import           Prelude hiding (catch, takeWhile)
 
 ------------------------------------------------------------------------------
 import           Snap.Internal.Debug
+import           Snap.Internal.Parsing
 import           Snap.Iteratee hiding (Enumerator)
 import           Snap.Types
 
@@ -263,18 +263,6 @@ compressEnumerator compFunc enum iteratee = do
 
 
 ------------------------------------------------------------------------------
-fullyParse :: ByteString -> Parser a -> Either String a
-fullyParse s p =
-    case r' of
-      (Fail _ _ e)    -> Left e
-      (Partial _)     -> Left "parse failed"
-      (Atto.Done _ x) -> Right x
-  where
-    r  = parse p s
-    r' = feed r ""
-
-
-------------------------------------------------------------------------------
 -- We're not gonna bother with quality values; we'll do gzip or compress in
 -- that order.
 acceptParser :: Parser [ByteString]
diff --git a/test/runTestsAndCoverage.sh b/test/runTestsAndCoverage.sh
index 260c339..5f2479f 100755
--- a/test/runTestsAndCoverage.sh
+++ b/test/runTestsAndCoverage.sh
@@ -2,7 +2,10 @@
 
 set -e
 
-export DEBUG=testsuite
+if [ "x$DEBUG" == "x" ]; then
+    export DEBUG=testsuite
+fi
+
 SUITE=./dist/build/testsuite/testsuite
 
 export LC_ALL=C
diff --git a/test/snap-core-testsuite.cabal b/test/snap-core-testsuite.cabal
index 180ef35..593cfd7 100644
--- a/test/snap-core-testsuite.cabal
+++ b/test/snap-core-testsuite.cabal
@@ -25,6 +25,7 @@ Executable testsuite
     base >= 4 && < 5,
     bytestring,
     bytestring-nums,
+    bytestring-show >= 0.3.2 && < 0.4,
     cereal >= 0.3 && < 0.4,
     containers,
     deepseq >= 1.1 && <1.2,
diff --git a/test/suite/Snap/Util/FileServe/Tests.hs 
b/test/suite/Snap/Util/FileServe/Tests.hs
index bf99a90..6b1a5c9 100644
--- a/test/suite/Snap/Util/FileServe/Tests.hs
+++ b/test/suite/Snap/Util/FileServe/Tests.hs
@@ -7,7 +7,7 @@ module Snap.Util.FileServe.Tests
 
 import           Control.Monad
 import           Data.ByteString (ByteString)
-import qualified Data.ByteString.Char8 as B
+import qualified Data.ByteString.Char8 as S
 import qualified Data.ByteString.Lazy.Char8 as L
 import           Data.IORef
 import qualified Data.Map as Map
@@ -24,7 +24,9 @@ import           Snap.Iteratee
 
 tests :: [Test]
 tests = [ testFs
-        , testFsSingle ]
+        , testFsSingle
+        , testRangeOK
+        , testRangeBad ]
 
 
 expect404 :: IO Response -> IO ()
@@ -49,12 +51,21 @@ goIfModifiedSince m s lm = do
     liftM snd (run $ runSnap m (const $ return ()) r)
 
 
+goRange :: Snap a -> ByteString -> (Int,Int) -> IO Response
+goRange m s (start,end) = do
+    rq' <- mkRequest s
+    let rq = setHeader "Range"
+                       (S.pack $ "bytes=" ++ show start ++ "-" ++ show end)
+                       rq'
+    liftM snd (run $ runSnap m (const $ return ()) rq)
+
+
 mkRequest :: ByteString -> IO Request
 mkRequest uri = do
     enum <- newIORef $ SomeEnumerator return
     return $ Request "foo" 80 "foo" 999 "foo" 1000 "foo" False Map.empty
                      enum Nothing GET (1,1) [] "" uri "/"
-                     (B.concat ["/",uri]) "" Map.empty
+                     (S.concat ["/",uri]) "" Map.empty
 
 fs :: Snap ()
 fs = do
@@ -68,7 +79,7 @@ fsSingle = do
 
 
 testFs :: Test
-testFs = testCase "fileServe" $ do
+testFs = testCase "fileServe/multi" $ do
     r1 <- go fs "foo.bin"
     b1 <- getBody r1
 
@@ -123,7 +134,7 @@ testFs = testCase "fileServe" $ do
 
 
 testFsSingle :: Test
-testFsSingle = testCase "fileServeSingle" $ do
+testFsSingle = testCase "fileServe/Single" $ do
     r1 <- go fsSingle "foo.html"
     b1 <- getBody r1
 
@@ -135,6 +146,20 @@ testFsSingle = testCase "fileServeSingle" $ do
     assertEqual "foo.html size" (Just 4) (rspContentLength r1)
 
 
+testRangeOK :: Test
+testRangeOK = testCase "fileServe/range/ok" $ do
+    r1 <- goRange fsSingle "foo.html" (1,2)
+    b1 <- getBody r1
+
+    assertEqual "foo.html partial" "OO" b1
+    assertEqual "foo.html partial size" (Just 2) (rspContentLength r1)
+
+
+testRangeBad :: Test
+testRangeBad = testCase "fileServe/range/bad" $ do
+    r1 <- goRange fsSingle "foo.html" (1,17)
+    assertEqual "bad range" 416 (rspStatus r1)
+
 
 coverMimeMap :: (Monad m) => m ()
 coverMimeMap = Prelude.mapM_ f $ Map.toList defaultMimeTypes
-----------------------------------------------------------------------


hooks/post-receive
-- 
snap-core
_______________________________________________
Snap mailing list
[email protected]
http://mailman-mail5.webfaction.com/listinfo/snap

Reply via email to