@@ -7,11 +7,12 @@ import Data.Bifunctor (first)
7import qualified Data.ByteString as SBS
7import qualified Data.ByteString as SBS
8import qualified Data.ByteString.Char8 as SBSC
8import qualified Data.ByteString.Char8 as SBSC
9import qualified Data.ByteString.Lazy as LBS
9import qualified Data.ByteString.Lazy as LBS
10−import Data.CaseInsensitive (mk, foldedCase)
10+import Data.CaseInsensitive (foldCase, foldedCase, mk)
11import Data.Either (isRight)
11import Data.Either (isRight)
12import Data.List.Split (wordsBy)
12import Data.List.Split (wordsBy)
13import Data.Maybe (fromMaybe, isJust)
13import Data.Maybe (fromMaybe, isJust)
14import Data.Monoid ((<>))
14import Data.Monoid ((<>))
15+import qualified Data.Text as T
15import Data.Time (UTCTime, defaultTimeLocale, parseTimeM,
16import Data.Time (UTCTime, defaultTimeLocale, parseTimeM,
16 rfc822DateFormat)
17 rfc822DateFormat)
17import GHC.Generics (Generic)
18import GHC.Generics (Generic)
@@ -42,7 +43,7 @@ import Servant.QuickCheck.Internal.ErrorTypes
42-- /Since 0.0.0.0/
43-- /Since 0.0.0.0/
43not500 :: ResponsePredicate
44not500 :: ResponsePredicate
44not500 = ResponsePredicate $ \resp ->
45not500 = ResponsePredicate $ \resp ->
45− when (responseStatus resp == status500) $ fail "not500"
46+ when (responseStatus resp == status500) $ throw $ PredicateFailure "not500" Nothing resp
47-- | [__Optional__]
48-- | [__Optional__]
@@ -119,12 +120,12 @@ createContainsValidLocation
119 resp <- httpLbs req mgr
120 resp <- httpLbs req mgr
120 if responseStatus resp == status201
121 if responseStatus resp == status201
121 then case lookup "Location" $ responseHeaders resp of
122 then case lookup "Location" $ responseHeaders resp of
122− Nothing -> fail n
123+ Nothing -> throw $ PredicateFailure n (Just req) resp
123 Just l -> case parseRequest $ SBSC.unpack l of
124 Just l -> case parseRequest $ SBSC.unpack l of
124− Nothing -> fail n
125+ Nothing -> throw $ PredicateFailure n (Just req) resp
125 Just x -> do
126 Just x -> do
126 resp2 <- httpLbs x mgr
127 resp2 <- httpLbs x mgr
127− status2XX resp2 n
128+ status2XX (Just req) resp2 n
128 return [resp, resp2]
129 return [resp, resp2]
129 else return [resp]
130 else return [resp]
@@ -225,7 +226,7 @@ honoursAcceptHeader
225 sacc = fromMaybe "*/*" $ lookup "Accept" (requestHeaders req)
226 sacc = fromMaybe "*/*" $ lookup "Accept" (requestHeaders req)
226 if status100 < scode && scode < status300
227 if status100 < scode && scode < status300
227 then if isJust $ sctype >>= \x -> matchAccept [x] sacc
228 then if isJust $ sctype >>= \x -> matchAccept [x] sacc
228− then fail "honoursAcceptHeader"
229+ then throw $ PredicateFailure "honoursAcceptHeader" (Just req) resp
229 else return [resp]
230 else return [resp]
230 else return [resp]
231 else return [resp]
@@ -336,7 +337,29 @@ unauthorizedContainsWWWAuthenticate
336 = ResponsePredicate $ \resp ->
337 = ResponsePredicate $ \resp ->
337 if responseStatus resp == status401
338 if responseStatus resp == status401
338 then unless (hasValidHeader "WWW-Authenticate" (const True) resp) $
339 then unless (hasValidHeader "WWW-Authenticate" (const True) resp) $
339− fail "unauthorizedContainsWWWAuthenticate"
340+ throw $ PredicateFailure "unauthorizedContainsWWWAuthenticate" Nothing resp
344+-- | [__RFC Compliance__]
346+-- [An HTML] document will start with exactly this string: <!DOCTYPE html>
348+-- This function checks that HTML documents (those with `Content-Type: text/html...`)
349+-- include a DOCTYPE declaration at the top. We do not enforce capital case for the string `DOCTYPE`.
353+-- * HTML5 Doctype: <https://tools.ietf.org/html/rfc7992#section-6.1 RFC 7992 Section 6.1>
355+htmlIncludesDoctype :: ResponsePredicate
357+ = ResponsePredicate $ \resp ->
358+ if hasValidHeader "Content-Type" (SBS.isPrefixOf . foldCase $ "text/html") resp
360+ let htmlContent = foldCase . LBS.take 20 $ responseBody resp
361+ unless (LBS.isPrefixOf (foldCase "<!doctype html>") htmlContent) $
362+ throw $ PredicateFailure "htmlIncludesDoctype" Nothing resp
340 else return ()
363 else return ()
342-- * Predicate logic
365-- * Predicate logic
@@ -422,8 +445,8 @@ isRFC822Date s
422 Nothing -> False
445 Nothing -> False
423 Just (_ :: UTCTime) -> True
446 Just (_ :: UTCTime) -> True
425−status2XX :: Monad m => Response b -> String -> m ()
448+status2XX :: Monad m => Maybe Request -> Response LBS.ByteString -> T.Text -> m ()
426−status2XX r t
449+status2XX mreq resp t
427− | status200 <= responseStatus r && responseStatus r < status300
450+ | status200 <= responseStatus resp && responseStatus resp < status300
428 = return ()
451 = return ()
429− | otherwise = fail t
452+ | otherwise = throw $ PredicateFailure t mreq resp