@@ -3,9 +3,11 @@
33{-# LANGUAGE TupleSections #-}
44{-# LANGUAGE RankNTypes #-}
55{-# LANGUAGE CPP #-}
6+{-# LANGUAGE ViewPatterns #-}
67
78module Network.Wai.Handler.Warp.Response (
89 sendResponse
10+ , sanitizeHeaderValue -- for testing
911 , fileRange -- for testing
1012 , warpVersion
1113 , defaultServerValue
@@ -27,13 +29,14 @@ import Control.Monad (unless, when)
2729import Data.Array ((!))
2830import Data.ByteString (ByteString)
2931import qualified Data.ByteString as S
32+import qualified Data.ByteString.Char8 as S8
3033import Data.ByteString.Builder (byteString, Builder)
3134import Data.ByteString.Builder.Extra (flush)
3235import qualified Data.ByteString.Char8 as B (pack)
3336import qualified Data.CaseInsensitive as CI
3437import Data.Function (on)
3538import Data.List (deleteBy)
36−import Data.Maybe (isJust, fromMaybe)
39+import Data.Maybe
3740#if MIN_VERSION_base(4,5,0)
3841# if __GLASGOW_HASKELL__ < 709
3942import Data.Monoid (mempty)
@@ -44,6 +47,7 @@ import Data.Monoid (mappend, mempty)
4447#endif
4548import Data.Streaming.Blaze (newBlazeRecv, reuseBufferStrategy)
4649import Data.Version (showVersion)
50+import Data.Word8 (_cr, _lf)
4751import qualified Network.HTTP.Types as H
4852import Network.Wai
4953import Network.Wai.Handler.Warp.Buffer (toBuilderBuffer)
@@ -143,26 +147,7 @@ sendResponse :: ByteString -- ^ default server value
143147 -> IO ByteString -- ^ source from client, for raw response
144148 -> Response -- ^ HTTP response including status code and response header.
145149 -> IO Bool -- ^ Returing True if the connection is persistent.
146−sendResponse defServer conn ii req reqidxhdr src response =
147− sendResponse' defServer conn ii req reqidxhdr src response'
148− where
149− doesContainCRLF :: [ByteString] -> Bool
150− doesContainCRLF = or . map (S.any (\w -> w == 10 || w == 13))
151− hsv = map snd $ responseHeaders response
152− response'
153− | doesContainCRLF hsv = response400
154− | otherwise = response
155− response400 = responseLBS H.badRequest400 [(H.hContentType, "text/plain; charset=utf-8")] "Bad Request"
156−
157−sendResponse' :: ByteString -- ^ default server value
158− -> Connection
159− -> InternalInfo
160− -> Request -- ^ HTTP request.
161− -> IndexedHeader -- ^ Indexed header of HTTP request.
162− -> IO ByteString -- ^ source from client, for raw response
163− -> Response -- ^ HTTP response including status code and response header.
164− -> IO Bool -- ^ Returing True if the connection is persistent.
165−sendResponse' defServer conn ii req reqidxhdr src response = do
150+sendResponse defServer conn ii req reqidxhdr src (sanitizeHeaders -> response) = do
166151 hs <- addServerAndDate hs0
167152 if hasBody s then do
168153 -- HEAD comes here even if it does not have body.
@@ -198,7 +183,22 @@ sendResponse' defServer conn ii req reqidxhdr src response = do
198183 ResponseStream {} -> isKeepAlive
199184 ResponseRaw {} -> False
200185
201−----------------------------------------------------------------
186+sanitizeHeaders :: Response -> Response
187+sanitizeHeaders = mapResponseHeaders (map (fmap sanitizeHeaderValue))
188+
189+sanitizeHeaderValue :: ByteString -> ByteString
190+sanitizeHeaderValue v
191+ | containsNewlines v = case S8.lines $ S.filter (/= _cr) v of
192+ x : xs -> S.intercalate "\r\n" (x : mapMaybe addSpaceIfMissing xs)
193+ [] -> ""
194+ | otherwise = v
195+ where
196+ containsNewlines = S.any (\w -> w == _cr || w == _lf)
197+ addSpaceIfMissing line = case S8.uncons line of
198+ Nothing -> Nothing
199+ Just (first, _) -> Just $ if first == ' ' || first == '\t'
200+ then line
201+ else " " <> line
202202
203203data Rsp = RspFile FilePath (Maybe FilePart) (Maybe HeaderValue) Bool (IO ())
204204 | RspBuilder Builder Bool