packages feed

warp-3.4.16: Network/Wai/Handler/Warp/IO.hs

module Network.Wai.Handler.Warp.IO where

import Control.Exception (mask_)
import qualified Data.ByteString as B (length)
import Data.ByteString.Builder (Builder)
import Data.ByteString.Builder.Extra (Next (Chunk, Done, More), runBuilder)
import Data.IORef (IORef, readIORef, writeIORef)
import Foreign.Ptr (plusPtr)
import Network.Wai.Handler.Warp.Buffer
import Network.Wai.Handler.Warp.Imports
import Network.Wai.Handler.Warp.Types

toBufIOWith
    :: Int -> IORef WriteBuffer -> (ByteString -> IO ()) -> Builder -> IO Integer
toBufIOWith = unsafeToBufIOWithOffset 0

-- | Like 'toBufIOWith' but the first @offset@ bytes of the write buffer
-- are assumed to be already filled (e.g. with a response header composed
-- directly into the buffer). They are flushed together with the first
-- batch of builder output and included in the returned total.
--
-- === WARNING: @offset@ MUST NOT exceed the current buffer size!!!
--
-- This function performs NO bounds checking on @offset@. The builder is
-- handed the pointer @buffer + offset@ with @bufSize - offset@ bytes of
-- claimed free space, so an oversized @offset@ points past the end of the
-- allocation and advertises negative capacity, i.e. out-of-bounds writes
-- and memory corruption. Every caller MUST verify
-- @offset < bufSize@ of the current write buffer first (see the
-- @hdrLen@ check in 'Network.Wai.Handler.Warp.Response.sendRsp').
unsafeToBufIOWithOffset
    :: Int
    -> Int
    -> IORef WriteBuffer
    -> (ByteString -> IO ())
    -> Builder
    -> IO Integer
unsafeToBufIOWithOffset offset0 maxRspBufSize writeBufferRef io builder = do
    writeBuffer <- readIORef writeBufferRef
    loop writeBuffer offset0 firstWriter 0
  where
    firstWriter = runBuilder builder
    loop writeBuffer offset writer bytesSent = do
        let buf = bufBuffer writeBuffer
            size = bufSize writeBuffer
        (len, signal) <- writer (buf `plusPtr` offset) (size - offset)
        bufferIO buf (offset + len) io
        let totalBytesSent = toInteger (offset + len) + bytesSent
        case signal of
            Done -> return totalBytesSent
            More minSize next
                | size < minSize -> do
                    when (minSize > maxRspBufSize) $
                        error $
                            "Sending a Builder response required a buffer of size "
                                ++ show minSize
                                ++ " which is bigger than the specified maximum of "
                                ++ show maxRspBufSize
                                ++ "!"
                    -- The current WriteBuffer is too small to fit the next
                    -- batch of bytes from the Builder so we free it and
                    -- create a new bigger one. Freeing the current buffer,
                    -- creating a new one and writing it to the IORef need
                    -- to be performed atomically to prevent both double
                    -- frees and missed frees. So we mask async exceptions:
                    biggerWriteBuffer <- mask_ $ do
                        bufFree writeBuffer
                        biggerWriteBuffer <- createWriteBuffer minSize
                        -- This doesn't need to be "atomic", since these two
                        -- functions are only used in 'sendResponse', which is
                        -- ultimately only used in 'serveConnection', which does
                        -- not share nor fork the created 'Connection'.
                        writeIORef writeBufferRef biggerWriteBuffer
                        return biggerWriteBuffer
                    loop biggerWriteBuffer 0 next totalBytesSent
                | otherwise -> loop writeBuffer 0 next totalBytesSent
            Chunk bs next -> do
                io bs
                loop writeBuffer 0 next $
                    totalBytesSent + fromIntegral (B.length bs)