summaryrefslogtreecommitdiffstats
path: root/src/Data/MIME/Extended.hs
blob: 773d0b7ea42f0a497764dd2f5bf31ea052e4f439 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Data.MIME.Extended
    ( module Data.MIME
    ) where

import Control.Lens hiding ((.=))
import Data.Aeson
import Data.ByteString (ByteString)
import Data.ByteString.Extended ()
import Data.CaseInsensitive (CI)
import Data.Either (fromRight)
import Data.List.NonEmpty (NonEmpty)
import Data.MIME
import Data.MIME.EncodedWord
import Data.Maybe (fromMaybe)
import qualified Data.ByteString.Char8 as C8
import qualified Data.CaseInsensitive as CI
import qualified Data.Vector


-- Data.MIME.Boundary.Extra
makeBoundaryUnsafe :: ByteString -> Boundary
makeBoundaryUnsafe = either (error . C8.unpack) id . makeBoundary



instance ToJSON (Message s MIME) where
    toJSON (Message h b) =
      object
          [ "headers" .= toJSON h
          , "body" .=
            case b of
              Part b' -> toJSON b'
              Encapsulated b' -> toJSON b'
              Multipart _ _ b' -> toJSON b'
              FailedParse _ msg -> toJSON msg
          ]

instance FromJSON (Message s MIME) where
    parseJSON =
        withObject "MIMEMessage" $ \v -> do
          h <- v .: "headers"
          b <-
            case h ^. contentType of
              ContentType "message" "rfc822" _ ->
                Encapsulated <$> v .: "body"

              ContentType "multipart" _ _ ->
                makeMultipart h <$> v .: "body"

              _ ->
                Part <$> v .: "body"
          pure $ Message h b


instance ToJSON Headers where
    toJSON (Headers h) =
        Array . Data.Vector.fromList . map toJSON' $ h
      where
        toJSON' (k, v) =
          object
            [ "key" .= toJSON k
            , "value" .= toJSON (decodeEncodedWords defaultCharsets v)
            ]

instance FromJSON Headers where
    parseJSON =
        withArray "Headers" $ \v -> do
          x <- mapM parseJSON' $ Data.Vector.toList v
          pure $ Headers x
      where
        parseJSON' =
            withObject "Header" $ \v ->
              (,) <$> v .: "key"
                  <*> encodeEncodedWords `fmap` (v .: "value")


makeMultipart :: HasHeaders s => s -> NonEmpty MIMEMessage -> MIME
makeMultipart h =
    Multipart sub boundary
  where
    ( sub, boundary ) =
      either dummyPrep id $ prepMultipart (h ^. contentType)

    dummyPrep :: (Show a) => a -> (MultipartSubtype, Boundary)
    dummyPrep err =
      ( Unrecognised $ CI.mk $ C8.pack $ show err
      , fromRight (makeBoundaryUnsafe "bad_boundary") $
          makeBoundary $ fromMaybe "no_boundary_found" $
            firstOf (contentType . mimeBoundary) h
      )

    -- Stuff below this line taken from Data.MIME.mime'

    prepMultipart :: ContentType
                     -> Either MIMEParseError (MultipartSubtype, Boundary)
    prepMultipart ct =
      (,) <$> parseSubtype ct <*> parseBoundary ct

    parseSubtype :: ContentType
                    -> Either MIMEParseError MultipartSubtype
    parseSubtype ct = case view ctSubtype ct of
      "mixed"         -> pure Mixed
      "alternative"   -> pure Alternative
      "digest"        -> pure Digest
      "parallel"      -> pure Parallel
      "multilingual"  -> pure Multilingual
      "report"        -> Report <$> getRequiredParam "report-type" ct
      "signed"        -> Signed
                          <$> getRequiredParam "protocol" ct
                          <*> getRequiredParam "micalg" ct
      "encrypted"     -> Encrypted <$> getRequiredParam "protocol" ct
      "related"       -> Related
                          <$> ( getRequiredParam "type" ct
                              >>= \s ->
                                    maybe
                                      (Left $ InvalidParameterValue "type" s)
                                      Right
                                      (preview (parsed parseContentType) s)
                              )
                          <*> getOptionalParam "start" ct
                          <*> getOptionalParam "start-info" ct
      unrecognised    -> pure $ Unrecognised unrecognised

    parseBoundary :: HasParameters s => s -> Either MIMEParseError Boundary
    parseBoundary ct =
      getRequiredParam "boundary" ct
      >>= over _Left (InvalidParameterValue "boundary") . makeBoundary

    getRequiredParam :: HasParameters s =>
                        CI ByteString -> s -> Either MIMEParseError ByteString
    getRequiredParam k =
      maybe (Left $ RequiredParameterMissing k) Right . preview (rawParameter k)

    getOptionalParam :: HasParameters s =>
                        CI ByteString -> s -> Either a (Maybe ByteString)
    getOptionalParam k =
      Right . preview (rawParameter k)

data MIMEParseError
  = RequiredParameterMissing (CI ByteString)
  | InvalidParameterValue (CI ByteString) ByteString
  | MultipartParseFail
  | EncapsulatedMessageParseFail
  deriving (Eq, Show)