purebred-email-0.5: src/Data/MIME/Boundary.hs
-- This file is part of purebred-email
-- Copyright (C) 2021 Fraser Tweedale
--
-- purebred-email is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Affero General Public License for more details.
--
-- You should have received a copy of the GNU Affero General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
module Data.MIME.Boundary
(
Boundary
, unBoundary
, makeBoundary
) where
import Control.Monad (replicateM)
import qualified Data.ByteString as B
import qualified Data.ByteString.Internal as B
import Data.ByteString.Char8 as C8
import System.Random.Stateful
-- | MIME boundary. Use 'makeBoundary' to construct, and 'unBoundary'
-- to unwrap.
--
-- Use the 'Uniform' instance to generate a random @Boundary@ to use
-- when constructing messages. For example:
--
-- @
-- 'getStdRandom' 'uniform' :: MonadIO m => m Boundary
-- 'getStdRandom' 'uniform' :: IO Boundary
-- @
--
newtype Boundary = Boundary B.ByteString
deriving (Eq, Show)
unBoundary :: Boundary -> B.ByteString
unBoundary (Boundary s) = s
-- Boundary smart constructor that checks validity
makeBoundary :: B.ByteString -> Either B.ByteString Boundary
makeBoundary s
| B.null s = Left s
| B.length s > 70 = Left s
| B.any (not . validBchar) s = Left s
| B.last s == 0x20 = Left s
| otherwise = Right $ Boundary s
where
validBchar c =
c >= 0x2c && c <= 0x3a -- ',', '-', '.', '/', '0'..'9', ':'
|| c >= 0x41 && c <= 0x5a -- 'A'..'Z'
|| c >= 0x61 && c <= 0x7a -- 'a'..'z'
|| c >= 0x27 && c <= 0x29 -- '\'', '(', ')'
|| c == 0x2b {- '+' -}
|| c == 0x5f {- '_' -}
|| c == 0x3d {- '=' -}
|| c == 0x3f {- '?' -}
|| c == 0x20 {- ' ' -}
-- | Generate a random 'Boundary'
genBoundary :: (StatefulGen g m) => g -> m Boundary
genBoundary g = do
let
blen = 64
bchars = C8.pack $ ['0'..'9'] <> ['a'..'z'] <> ['A'..'Z'] <> "'()+_,-./:=?"
chars <-
replicateM blen $ B.index bchars <$> uniformRM (0, B.length bchars - 1) g
pure . Boundary $ B.unsafePackLenBytes blen chars
instance Uniform Boundary where
uniformM = genBoundary