@@ -11,6 +11,8 @@ import Data.Either (isRight)
1111import Data.List.Split (wordsBy)
1212import Data.Maybe (fromMaybe, isJust)
1313import Data.Monoid ((<>))
14+import Data.Time (parseTimeM, defaultTimeLocale,
15+ rfc822DateFormat, UTCTime)
1416import GHC.Generics (Generic)
1517import Network.HTTP.Client (Manager, Request, Response, httpLbs,
1618 method, requestHeaders, responseBody,
@@ -100,12 +102,43 @@ createContainsValidLocation
100102 return [resp, resp2]
101103 else return [resp]
102104
103−{-
104−getsHaveLastModifiedHeader :: ResponsePredicate
105+-- | [__Optional__]
106+--
107+-- The @Last-Modified@ header represents the time a resource was last
108+-- modified. It is used to drive caching and conditional requests.
109+--
110+-- When using this mechanism, the server adds the @Last-Modified@ header to
111+-- responses. Clients may then make requests with the @If-Modified-Since@
112+-- header to conditionally request resources. If the resource has not
113+-- changed since that date, the server responds with a status code of 304
114+-- (@Not Modified@) without a response body.
115+--
116+-- The @Last-Modified@ header can also be used in conjunction with the
117+-- @If-Unmodified-Since@ header to drive optimistic concurrency.
118+--
119+-- The @Last-Modified@ date must be in RFC 822 format.
120+--
121+-- __References__:
122+--
123+-- * 304 Not Modified: <https://tools.ietf.org/html/rfc7232#section-4.1 RFC 7232 Section 4.1>
124+-- * Last-Modified header: <https://tools.ietf.org/html/rfc7232#section-2.2 RFC 7232 Section 2.2>
125+-- * If-Modified-Since header: <https://tools.ietf.org/html/rfc7232#section-3.3 RFC 7232 Section 3.3>
126+-- * If-Unmodified-Since header: <https://tools.ietf.org/html/rfc7232#section-3.4 RFC 7232 Section 3.4>
127+-- * Date format: <https://tools.ietf.org/html/rfc2616#section-3.3 RFC 2616 Section 3.3>
128+--
129+-- #SINCECURRENT#
130+getsHaveLastModifiedHeader :: RequestPredicate
105131getsHaveLastModifiedHeader
106− = ResponsePredicate "getsHaveLastModifiedHeader" (\resp ->
132+ = RequestPredicate $ \req mgr ->
133+ if (method req == methodGet)
134+ then do
135+ resp <- httpLbs req mgr
136+ unless (hasValidHeader "Last-Modified" isRFC822Date resp) $ do
137+ throw $ PredicateFailure "getsHaveLastModifiedHeader" (Just req) resp
138+ return [resp]
139+ else return []
140+
107141
108−-}
109142
110143-- | [__RFC Compliance__]
111144--
@@ -354,6 +387,12 @@ hasValidHeader hdr p r = case lookup (mk hdr) (responseHeaders r) of
354387 Nothing -> False
355388 Just v -> p v
356389
390+isRFC822Date :: SBS.ByteString -> Bool
391+isRFC822Date s
392+ = case parseTimeM True defaultTimeLocale rfc822DateFormat (SBSC.unpack s) of
393+ Nothing -> False
394+ Just (_ :: UTCTime) -> True
395+
357396status2XX :: Monad m => Response b -> String -> m ()
358397status2XX r t
359398 | status200 <= responseStatus r && responseStatus r < status300