packages feed

mime-mail-ses 0.2.2.2 → 0.4.5

raw patch · 8 files changed

Files

+ ChangeLog.md view
@@ -0,0 +1,43 @@+## 0.4.5++* Bump lower dependency bounds+* Remove CPP+* Use `ram` instead of `memory` as a dependency. `memory` is abandoned.++## 0.4.4++* Use `crypton` instead of `cryptohash` as a dependency. `cryptohash` is abandoned.+* Avoid deprecated conduit functions.+* New maintainer: Janus Troelsen++## 0.4.3++* Add function 'sendMailSESWithResponse'.++## 0.4.2++* Add executable `send-aws` for sending e-mails via Amazon SES from command line.+* Update Signature Version for Amazon SES from 3 to 4.++## 0.4.1++* Add 'renderSendMailSESGlobal' and 'sendMailSESGlobal' function which uses the global manager.++## 0.4.0.0++* Support IAM temp credentials. This is a backwards-incompatible change that adds+  a field to the SES type to represent (optional) session tokens when using roles+  attached to EC2 instances.+* Make fields in the `SES` data type strict to promote warnings to errors.++## 0.3.2.3++http-client 0.5 support++## 0.3.2.2++`time` 1.5 support++## 0.3.2++Expose `SESException` datatype and constructor.
LICENSE view
@@ -1,30 +1,20 @@-Copyright (c)2011, Michael Snoyman--All rights reserved.--Redistribution and use in source and binary forms, with or without-modification, are permitted provided that the following conditions are met:--    * Redistributions of source code must retain the above copyright-      notice, this list of conditions and the following disclaimer.+Copyright (c) 2014 Michael Snoyman, http://www.yesodweb.com/ -    * Redistributions in binary form must reproduce the above-      copyright notice, this list of conditions and the following-      disclaimer in the documentation and/or other materials provided-      with the distribution.+Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions: -    * Neither the name of Michael Snoyman nor the names of other-      contributors may be used to endorse or promote products derived-      from this software without specific prior written permission.+The above copyright notice and this permission notice shall be+included in all copies or substantial portions of the Software. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Network/Mail/Mime/SES.hs view
@@ -2,69 +2,101 @@ {-# LANGUAGE OverloadedStrings  #-} module Network.Mail.Mime.SES     ( sendMailSES+    , sendMailSESWithResponse+    , sendMailSESGlobal     , renderSendMailSES+    , renderSendMailSESGlobal     , SES (..)+    , usEast1+    , usWest2+    , euWest1+    , SESException (..)     ) where  import           Control.Exception           (Exception, throwIO) import           Control.Monad.IO.Class      (MonadIO, liftIO)-import           Crypto.Hash                 (Digest, SHA256, hmac,-                                              hmacGetDigest)-import           Data.Byteable               (toBytes) import           Data.ByteString             (ByteString) import           Data.ByteString.Base64      (encode) import qualified Data.ByteString.Char8       as S8 import qualified Data.ByteString.Lazy        as L-import           Data.Conduit                (Sink, await, ($$), (=$))+import           Data.Conduit                (ConduitT, await, connect, (.|)) import           Data.Text                   (Text) import qualified Data.Text                   as T+import qualified Data.Text.Encoding          as E import           Data.Time                   (getCurrentTime)-import           Data.Time.Format            (formatTime) import           Data.Typeable               (Typeable)+import           Data.Void                   (Void) import           Data.XML.Types              (Content (ContentText), Event (EventBeginElement, EventContent))-import           Network.HTTP.Client         (Manager, checkStatus, parseUrl,+import           Network.HTTP.Client         (Manager,                                               requestHeaders, responseBody,                                               responseStatus, urlEncodedBody,                                               withResponse) import           Network.HTTP.Client.Conduit (bodyReaderSource) import           Network.HTTP.Types          (Status)+import           Network.HTTP.Client.TLS     (getGlobalManager) import           Network.Mail.Mime           (Mail, renderMail')-import           System.Locale               (defaultTimeLocale) import           Text.XML.Stream.Parse       (def, parseBytes) +import Network.Mail.Mime.SES.Internal+ data SES = SES-    { sesFrom      :: ByteString-    , sesTo        :: [ByteString]-    , sesAccessKey :: ByteString-    , sesSecretKey :: ByteString+    { sesFrom         :: !ByteString+    , sesTo           :: ![ByteString]+    , sesAccessKey    :: !ByteString+    , sesSecretKey    :: !ByteString+    , sesSessionToken :: !(Maybe ByteString)+    , sesRegion       :: !Text     }+  deriving Show  renderSendMailSES :: MonadIO m => Manager -> SES -> Mail -> m () renderSendMailSES m ses mail = liftIO (renderMail' mail) >>= sendMailSES m ses -sendMailSES :: MonadIO m => Manager -> SES -> L.ByteString -> m ()-sendMailSES manager ses msg = liftIO $ do+-- | @since 0.4.1+-- Same as 'renderSendMailSES' but uses the global 'Manager'.+renderSendMailSESGlobal :: MonadIO m => SES -> Mail -> m ()+renderSendMailSESGlobal ses mail = do+  mgr <- liftIO getGlobalManager+  renderSendMailSES mgr ses mail++sendMailSES :: MonadIO m => Manager -> SES +            -> L.ByteString -- ^ Raw message data. You must ensure that+                            -- the message format complies with+                            -- Internet email standards regarding+                            -- email header fields, MIME types, and+                            -- MIME encoding.+            -> m ()+sendMailSES manager ses msg =+  sendMailSESWithResponse manager ses msg checkForError++-- | @since 0.4.3+-- Generalised version of 'sendMailSES' which allows customising the final return type.+sendMailSESWithResponse :: MonadIO m => Manager -> SES+                        -> L.ByteString+                        -> (Status -> ConduitT Event Void IO a)+                        -- ^ What to do with the HTTP 'Status' returned in the 'Response'.+                        -> m a+sendMailSESWithResponse manager ses msg onResponseStatus = liftIO $ do     now <- getCurrentTime-    let date = S8.pack $ format now-        sig = makeSig date $ sesSecretKey ses-    req' <- parseUrl "https://email.us-east-1.amazonaws.com"-    let auth = S8.concat-            [ "AWS3-HTTPS AWSAccessKeyId="-            , sesAccessKey ses-            , ", Algorithm=HmacSHA256, Signature="-            , sig-            ]-    let req = urlEncodedBody qs $ req'-            { requestHeaders =-                [ ("Date", date)-                , ("X-Amzn-Authorization", auth)-                ]-            , checkStatus = \_ _ _ -> Nothing-            }-    withResponse req manager $ \res ->-           bodyReaderSource (responseBody res)-        $$ parseBytes def-        =$ checkForError (responseStatus res)+    requestBase <- buildRequest (concat ["https://email.", T.unpack (sesRegion ses) , ".amazonaws.com"])+    let headers =+          [ ("Date", formatAmazonTime now)+          ]+          ++ case sesSessionToken ses of+               Just token -> [("X-Amz-Security-Token", token)]+               Nothing    -> []+    let tentativeRequest = urlEncodedBody qs $ requestBase {requestHeaders = headers}+        canonicalRequest = canonicalizeRequest tentativeRequest+        stringToSign = makeStringToSign "ses" now (E.encodeUtf8 (sesRegion ses)) canonicalRequest+        sig = makeSig "ses" now (E.encodeUtf8 (sesRegion ses)) (sesSecretKey ses) stringToSign+        authorizationString = makeAuthorizationString "ses" now (E.encodeUtf8 (sesRegion ses))+                              (patchedRequestHeaders tentativeRequest) (sesAccessKey ses) sig+        finalRequest = tentativeRequest {requestHeaders = ("Authorization", authorizationString): requestHeaders tentativeRequest}+    withResponse finalRequest manager $ \res ->+           bodyReaderSource (responseBody res) `connect`+             (parseBytes def+                .| onResponseStatus (responseStatus res)+             )   where     qs =           ("Action", "SendRawEmail")@@ -72,9 +104,21 @@         : ("RawMessage.Data", encode $ S8.concat $ L.toChunks msg)         : zipWith mkDest [1 :: Int ..] (sesTo ses)     mkDest num addr = (S8.pack $ "Destinations.member." ++ show num, addr)-    format = formatTime defaultTimeLocale "%a, %e %b %Y %H:%M:%S %z" -checkForError :: Status -> Sink Event IO ()+-- | @since 0.4.1+-- Same as 'sendMailSES' but uses the global 'Manager'.+sendMailSESGlobal :: MonadIO m => SES +                  -> L.ByteString -- ^ Raw message data. You must ensure that+                                  -- the message format complies with+                                  -- Internet email standards regarding+                                  -- email header fields, MIME types, and+                                  -- MIME encoding.+                  -> m ()+sendMailSESGlobal ses msg = do+  mgr <- liftIO getGlobalManager+  sendMailSES mgr ses msg++checkForError :: Status -> ConduitT Event Void IO () checkForError status = do     name <- getFirstStart     if name == errorResponse@@ -113,6 +157,9 @@             , seRequestId = reqid             } +-- |+--+-- Exposed since: 0.3.2 data SESException = SESException     { seStatus    :: !Status     , seCode      :: !Text@@ -122,6 +169,11 @@     deriving (Show, Typeable) instance Exception SESException -makeSig :: ByteString -> ByteString -> ByteString-makeSig payload key =-    encode $ toBytes (hmacGetDigest $ hmac key payload :: Digest SHA256)+usEast1 :: Text+usEast1 = "us-east-1"++usWest2 :: Text+usWest2 = "us-west-2"++euWest1 :: Text+euWest1 = "eu-west-1"
+ Network/Mail/Mime/SES/Internal.hs view
@@ -0,0 +1,123 @@+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE OverloadedStrings  #-}++module Network.Mail.Mime.SES.Internal where++import           Crypto.Hash                 (SHA256, hash)+import           Crypto.MAC.HMAC             (hmac, hmacGetDigest)+import           Data.Bifunctor              (bimap)+import           Data.ByteArray              (convert)+import           Data.ByteString             (ByteString)+import qualified Data.ByteString             as B+import           Data.ByteString.Base16      as Base16+import qualified Data.ByteString.Char8       as S8+import qualified Data.ByteString.Lazy        as L+import           Data.Char                   (toLower)+import           Data.CaseInsensitive        (CI)+import qualified Data.CaseInsensitive        as CI+import           Data.List                   (sort)++import           Data.Time                   (UTCTime)+import           Data.Time.Format            (formatTime)+import           Network.HTTP.Client         (Request, RequestBody(RequestBodyLBS, RequestBodyBS),+                                             parseRequest,+                                             method, host, path, requestHeaders, queryString, requestBody+                                             )+import           Data.Time                   (defaultTimeLocale)++-- | Create a canonical request according to <https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html>.+makeCanonicalRequest :: ByteString -> ByteString -> ByteString -> [(CI ByteString, ByteString)] -> ByteString -> ByteString+makeCanonicalRequest requesMethod requestPath requestQueryString headers payload = S8.intercalate "\n"+  [ requesMethod+  , requestPath+  , requestQueryString+  , S8.concat . fmap (\ (name, value) -> name <> ":" <> value <> "\n")+    . sort . fmap (bimap (bytesToLowerCase . CI.original) id)+    $ headers+  , makeListOfHeaders $ headers+  , unaryHashBase16 $ payload+  ]++canonicalizeRequest :: Request -> ByteString+canonicalizeRequest request+  = makeCanonicalRequest+  (method request)+  (path request)+  (queryString request)+  (patchedRequestHeaders request)+  (requestBodyAsByteString request)++-- | Create a string to sign according to <https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html>.+makeStringToSign :: ByteString -> UTCTime -> ByteString -> ByteString -> ByteString+makeStringToSign service time region canonicalRequest = S8.intercalate "\n"+  [ "AWS4-HMAC-SHA256"+  , formatAmazonTime time+  , makeCredentialScope service time region+  , unaryHashBase16 canonicalRequest+  ]++-- | Create a signature according to <https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html>.+makeSig :: ByteString -> UTCTime -> ByteString -> ByteString -> ByteString -> ByteString+makeSig service time region secret stringToSign =+  let f = flip keyedHash+  in Base16.encode+     . f stringToSign+     . f "aws4_request"+     . f service+     . f region+     . f (formatAmazonDate time)+     $ ("AWS4" <> secret)++-- | Create an authorization string according to <https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html>.+makeAuthorizationString :: ByteString -> UTCTime -> ByteString -> [(CI ByteString, ByteString)] -> ByteString -> ByteString -> ByteString+makeAuthorizationString service time region headers keyId sig = S8.concat+            [ "AWS4-HMAC-SHA256 Credential="+                <> keyId+                <> "/"+                <> makeCredentialScope service time region+            , ", SignedHeaders=" <> makeListOfHeaders headers+            , ", Signature=" <> sig+            ]++formatAmazonTime :: UTCTime -> ByteString+formatAmazonTime = S8.pack . formatTime defaultTimeLocale "%Y%m%dT%H%M%SZ"++formatAmazonDate :: UTCTime -> ByteString+formatAmazonDate = S8.pack . formatTime defaultTimeLocale "%Y%m%d"++buildRequest :: String -> IO Request+buildRequest url = do+  requestBase <- (parseRequest url)+  return requestBase++requestBodyAsByteString :: Request -> ByteString+requestBodyAsByteString request = case requestBody request of+                                    RequestBodyBS x -> x+                                    RequestBodyLBS x -> L.toStrict x+                                    _ -> error "Not implemented."++requestBodyLength :: Request -> Int+requestBodyLength = B.length . requestBodyAsByteString++makeListOfHeaders :: [(CI ByteString, ByteString)] -> ByteString+makeListOfHeaders = S8.intercalate ";" . sort . fmap (bytesToLowerCase . CI.original . fst)++patchedRequestHeaders :: Request -> [(CI ByteString, ByteString)]+patchedRequestHeaders request = requestHeaders request +++      [ (CI.mk "Host", host request)+      , (CI.mk "Content-Length", S8.pack . show $ requestBodyLength request)+      -- @http-client@ [adds the @Content-Length@ header automatically when sending the request](https://hackage.haskell.org/package/http-client-0.7.1/docs/Network-HTTP-Client.html#v:requestHeaders),+      -- so we have to reconstruct it by hand.+      ]++makeCredentialScope :: ByteString -> UTCTime -> ByteString -> ByteString+makeCredentialScope service time region = S8.intercalate "/" [formatAmazonDate time, region, service, "aws4_request"]++bytesToLowerCase :: ByteString -> ByteString+bytesToLowerCase = S8.pack . fmap toLower . S8.unpack++unaryHashBase16 :: ByteString -> ByteString+unaryHashBase16 = Base16.encode . convert . hash @ByteString @SHA256++keyedHash :: ByteString -> ByteString -> ByteString+keyedHash key payload = convert . hmacGetDigest $ hmac @ByteString @ByteString @SHA256 key payload
+ README.md view
@@ -0,0 +1,23 @@+## mime-mail-ses++Send mime-mail messages via Amazon SES++### send-aws++The build target `mime-mail-ses:exe:send-aws` is a command line executable that+allows you to send an e-mail via Amazon SES. Most parameters are supplied+through command line options; the AWS secret key and the body of the message are+read from standard input.++Example:++    % cabal run :send-aws -- \+        --subject 'Checking if AWS works.' \+        --from 'your e-mail address' \+        --to 'your e-mail address' \+        --key 'your key ID' \+        --region 'us-east-2'+    Up to date+    Enter AWS secret: your secret key+    Enter message below.+    This is a test letter sent from command line.
mime-mail-ses.cabal view
@@ -1,32 +1,62 @@ Name:                mime-mail-ses-Version:             0.2.2.2+Version:             0.4.5 Synopsis:            Send mime-mail messages via Amazon SES+description:         Hackage documentation generation is not reliable. For up to date documentation, please see: <http://www.stackage.org/package/mime-mail-ses>. Homepage:            http://github.com/snoyberg/mime-mail License:             MIT License-file:        LICENSE Author:              Michael Snoyman-Maintainer:          michael@snoyman.com+Maintainer:          Janus Troelsen <ysangkok@gmail.com> Category:            Web Build-type:          Simple-Cabal-version:       >=1.6+Cabal-version:       2.0+extra-doc-files:     ChangeLog.md+                     README.md  Library-  Exposed-modules:     Network.Mail.Mime.SES-  Build-depends:       base               >= 4         && < 5-                     , base64-bytestring  >= 0.1-                     , bytestring         >= 0.9-                     , time               >= 1.1-                     , old-locale-                     , http-client        >= 0.2.2.2-                     , http-client-conduit-                     , http-conduit       >= 0.2.0.1-                     , mime-mail          >= 0.3-                     , transformers       >= 0.2-                     , http-types         >= 0.6.8+  Default-Language:    Haskell2010+  Exposed-modules:     Network.Mail.Mime.SES, Network.Mail.Mime.SES.Internal+  Build-depends:       base               >= 4.16.4    && < 5+                     , base16-bytestring  ^>= 1.0+                     , base64-bytestring  ^>= 1.2+                     , bytestring         >= 0.11.4    && < 0.13+                     , case-insensitive   ^>= 1.2+                     , conduit            ^>= 1.3+                     , crypton            ^>= 1.1+                     , http-client        ^>= 0.7+                     , http-client-tls    ^>= 0.4+                     , http-conduit       ^>= 2.3+                     , http-types         ^>= 0.12+                     , ram                ^>= 0.22+                     , mime-mail          ^>= 0.5+                     , text+                     , time               >= 1.11.1.1                      , xml-conduit                      , xml-types-                     , text-                     , conduit-                     , cryptohash         >= 0.7.3-                     , byteable   ghc-options: -Wall++Executable send-aws+  default-language: Haskell2010+  hs-source-dirs: send-aws+  Main-Is: Main.hs+  build-depends: base+               , http-client+               , http-client-tls+               , mime-mail+               , mime-mail-ses+               , optparse-applicative+               , text++Test-suite by-the-book+           default-language: Haskell2010+           type: exitcode-stdio-1.0+           hs-source-dirs: test/by-the-book+           main-is: Main.hs+           build-depends: base+                        , bytestring+                        , case-insensitive+                        , mime-mail-ses+                        , tasty+                        , tasty-hunit+                        , time+           ghc-options: -Wall
+ send-aws/Main.hs view
@@ -0,0 +1,80 @@+{-# language RecordWildCards #-}+{-# language OverloadedStrings #-}+{-# language CPP #-}++module Main where++import Data.Text (Text)+import qualified Data.Text as Text+import qualified Data.Text.IO as Text+import qualified Data.Text.Lazy.IO as LazyText+import Data.Text.Encoding+import Network.HTTP.Client+import Network.HTTP.Client.TLS+import Network.Mail.Mime+import Network.Mail.Mime.SES+import Options.Applicative+import Options.Applicative.Types+import System.IO++#if MIN_VERSION_base(4, 11, 0)+#else+import           Data.Monoid ((<>))+#endif++main = do+  (Options {..}, manager) <- pure (,)+    <*> execParser obtainOptions+    <*> newManager tlsManagerSettings++  putStr "Enter AWS secret: "+  hFlush stdout+  secret <- Text.getLine+  putStrLn "Enter message below."+  message <- LazyText.getContents++  let letter = (emptyMail from)+        { mailTo = to+        , mailHeaders = [("Subject", subject)]+        , mailParts = [[plainPart message]]+        }+  renderSendMailSES manager (makeSES letter key secret region) letter++data Options = Options+  { subject :: Text+  , from :: Address+  , to :: [Address]+  , key :: Text+  , region :: Text+  }++obtainOptions :: ParserInfo Options+obtainOptions = info (parseOptions <**> helper) (fullDesc <> progDesc "Send a message via Amazon Simple Email Service.")+  where+    parseOptions :: Parser Options+    parseOptions = pure Options+      <*> option readText (metavar "..." <> long "subject" <> help "The `subject` header of the letter.")+      <*> option readAddress (metavar "..." <> long "from" <> help "Source address.")+      <*> some (option readAddress (metavar "..." <> long "to" <> help "A destination."))+      <*> option readText (metavar "..." <> long "key" <> help "AWS access key identifier.")+      <*> option readText (metavar "..." <> long "region" <> help "AWS region to connect to.")++    -- Monomorphic readers are required in place of simple `strOption` ≡+    -- `option str` because `str` has been monomorphic prior to+    -- `optparse-applicative` version 0.14 and we want to support that.++    readText :: ReadM Text+    readText = Text.pack <$> readerAsk++    readAddress :: ReadM Address+    readAddress = Address Nothing <$> readText++makeSES :: Mail -> Text -> Text -> Text -> SES+makeSES Mail {..} key secret region = SES+  { sesFrom = (encodeUtf8 . addressEmail) mailFrom+  , sesTo = fmap (encodeUtf8 . addressEmail) mailTo+  , sesAccessKey = encodeUtf8 key+  , sesSecretKey = encodeUtf8 secret+  , sesSessionToken = Nothing+  , sesRegion = region+  }
+ test/by-the-book/Main.hs view
@@ -0,0 +1,69 @@+{-# language TypeApplications #-}+{-# language OverloadedStrings #-}++module Main where++import Data.ByteString (ByteString)+import qualified Data.ByteString as ByteString+import Data.CaseInsensitive (CI)+import Data.Time++import Test.Tasty+import Test.Tasty.HUnit+import Network.Mail.Mime.SES.Internal++-- Examples taken from <https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html>.++time :: UTCTime+time = read @UTCTime "2015-08-30 12:36:00 +0000"++service, secret, keyId, region, queryString, canonicalRequest, stringToSign, sig, authorizationString :: ByteString+service = "iam"+keyId = "AKIDEXAMPLE"+secret = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"+region = "us-east-1"+queryString = "Action=ListUsers&Version=2010-05-08"+canonicalRequest = ByteString.intercalate "\n"+  [ "GET"+  , "/"+  , "Action=ListUsers&Version=2010-05-08"+  , "content-type:application/x-www-form-urlencoded; charset=utf-8"+  , "host:iam.amazonaws.com"+  , "x-amz-date:20150830T123600Z"+  , ""+  , "content-type;host;x-amz-date"+  , "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"+  ]++stringToSign = ByteString.intercalate "\n"+  [ "AWS4-HMAC-SHA256"+  , "20150830T123600Z"+  , "20150830/us-east-1/iam/aws4_request"+  , "f536975d06c0309214f805bb90ccff089219ecd68b2577efef23edd43b7e1a59"+  ]++sig = "5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7"++authorizationString = "AWS4-HMAC-SHA256 \+                      \Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request\+                      \, SignedHeaders=content-type;host;x-amz-date\+                      \, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7"++headers :: [(CI ByteString, ByteString)]+headers =+  [ ("Host", "iam.amazonaws.com")+  , ("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")+  , ("X-Amz-Date", "20150830T123600Z")+  ]++main :: IO ( )+main = defaultMain $ testGroup "Check Sig Version 4 internals."+  [ testCase "Correctly generates a canonical request"+    $ assertEqual "" canonicalRequest (makeCanonicalRequest "GET" "/" queryString headers "")+  , testCase "Correctly generates a string to sign"+    $ assertEqual "" stringToSign (makeStringToSign service time region canonicalRequest)+  , testCase "Correctly generates a cryptographic signature"+    $ assertEqual "" sig (makeSig service time  region  secret stringToSign)+  , testCase "Correctly generates an authorization string"+    $ assertEqual "" authorizationString (makeAuthorizationString service time region headers keyId sig)+  ]