module Network.S3.URL
( canonicalRequest
) where
import Data.Time.Format (formatTime, defaultTimeLocale)
import Data.ByteString.Char8 (pack)
import Blaze.ByteString.Builder (Builder, fromByteString)
import qualified Network.HTTP.Types.URI as HTTP
import Data.Function (on)
import Data.List (sortBy, intersperse)
import Data.Monoid ((<>))
import Data.Maybe (fromMaybe)
import Network.S3.Types
sortS3Headers :: [S3Header] -> [S3Header]
sortS3Headers = sortBy (compare `on` (fst . getS3Header))
canonicalRequest :: S3Request -> Builder
canonicalRequest S3Request{..} =
let
qs = sortBy (compare `on` fst) queryString
emptyHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
bodyHash = fromMaybe emptyHash payloadHash
hashHeader = s3Header "x-amz-content-sha256" bodyHash
hostHeader = s3Header "host" (bucketName <> ".s3.amazonaws.com")
seconds = pack (formatTime defaultTimeLocale "T%M%H%SZ" requestTime)
date = pack (formatTime defaultTimeLocale "%Y%m%d" requestTime)
timeHeader = s3Header "x-amz-date" (date <> seconds)
headers = sortS3Headers (timeHeader : hostHeader : hashHeader : s3headers)
headerKeys = map (fst . getS3Header) headers
httpMethod = renderS3Method s3method
canonicalURI = HTTP.urlEncodeBuilder False objectName
canonicalQS = HTTP.renderQueryText False (HTTP.queryToQueryText qs)
canonicalHeaders = foldMap s3HeaderBuilder headers
signedHeaders = foldMap fromByteString (intersperse ";" headerKeys)
hashedPayload = fromByteString bodyHash
uriBuilder =
httpMethod <> "\n/" <>
canonicalURI <> "\n" <>
canonicalQS <> "\n" <>
canonicalHeaders <> "\n" <>
signedHeaders <> "\n" <>
hashedPayload
in
uriBuilder
renderS3Method :: S3Method -> Builder
renderS3Method method =
case method of
S3GET -> "GET"
S3PUT -> "PUT"
S3HEAD -> "HEAD"
S3DELETE -> "DELETE"