|
| 1 | +{-# LANGUAGE LambdaCase #-} |
| 2 | +{-# LANGUAGE OverloadedStrings #-} |
| 3 | + |
| 4 | +-- | Purely functional version of "Distribution.Server.Util.Validators" |
| 5 | +-- for testing the validators. |
| 6 | + |
| 7 | +module Distribution.Server.Util.Validators.Internal where |
| 8 | + |
| 9 | +import Control.Monad (unless) |
| 10 | +import Control.Monad.Except (MonadError(..)) |
| 11 | + |
| 12 | +import Data.Char (isSpace, isPrint) |
| 13 | +import Data.Text (Text) |
| 14 | +import qualified Data.Text as T |
| 15 | + |
| 16 | +import Distribution.Pretty (Pretty(..)) |
| 17 | +import Distribution.Server.Users.Types (isValidUserNameChar) |
| 18 | + |
| 19 | +-- Set up doctest to deal with text literals. |
| 20 | + |
| 21 | +-- $setup |
| 22 | +-- >>> :set -XOverloadedStrings |
| 23 | + |
| 24 | +-- | Basic sanity checking on names. |
| 25 | +-- |
| 26 | +-- >>> validName "Innocent User" |
| 27 | +-- Right () |
| 28 | +-- |
| 29 | +-- >>> validName "Mr. X is the greatest super duper dude of all!" |
| 30 | +-- Right () |
| 31 | +-- |
| 32 | +-- >>> validName "I am also a developer, maintainer, blogger, for Haskell, Hackage, Cabal, Stackage" |
| 33 | +-- Left NameTooLong |
| 34 | +-- |
| 35 | +-- >>> validName "My name has beeps \BEL, newlines \n, and \t tabs" |
| 36 | +-- Left NameNotPrintable |
| 37 | +-- |
| 38 | +validName :: Text -> Either InvalidName () |
| 39 | +validName str = do |
| 40 | + unless (T.length str <= 70) $ throwError NameTooLong |
| 41 | + unless (T.all isPrint str) $ throwError NameNotPrintable |
| 42 | + |
| 43 | +-- | Errors produced by 'validName' check. |
| 44 | + |
| 45 | +data InvalidName |
| 46 | + = NameTooLong -- ^ More than 70 characters long. |
| 47 | + | NameNotPrintable -- ^ Contains unprintable characters. |
| 48 | + deriving (Eq, Show) |
| 49 | + |
| 50 | +instance Pretty InvalidName where |
| 51 | + pretty = \case |
| 52 | + NameTooLong -> "Sorry, we didn't expect names to be longer than 70 characters." |
| 53 | + NameNotPrintable -> "Unexpected character in name, please use only printable Unicode characters." |
| 54 | + |
| 55 | +-- | Basic sanity checking on user names. |
| 56 | +-- |
| 57 | +-- >>> validUserName "innocent_user_42" |
| 58 | +-- Right () |
| 59 | +-- |
| 60 | +-- >>> validUserName "mr_X_stretches_the_Limit_of_50_characters_01234567" |
| 61 | +-- Right () |
| 62 | +-- |
| 63 | +-- >>> validUserName "01234" |
| 64 | +-- Right () |
| 65 | +-- |
| 66 | +-- >>> validUserName "dashes-not-allowed" |
| 67 | +-- Left UserNameInvalidChar |
| 68 | +-- |
| 69 | +-- >>> validUserName "questions_not_allowed?" |
| 70 | +-- Left UserNameInvalidChar |
| 71 | +-- |
| 72 | +-- >>> validUserName "my_Ego_busts_the_Limit_of_50_characters_01234567890" |
| 73 | +-- Left UserNameTooLong |
| 74 | +-- |
| 75 | +validUserName :: T.Text -> Either InvalidUserName () |
| 76 | +validUserName str = do |
| 77 | + unless (T.length str <= 50) $ throwError UserNameTooLong |
| 78 | + unless (T.all isValidUserNameChar str) $ throwError UserNameInvalidChar |
| 79 | + |
| 80 | +-- | Errors produced by 'validUserName' check. |
| 81 | + |
| 82 | +data InvalidUserName |
| 83 | + = UserNameTooLong -- ^ More than 50 characters long. |
| 84 | + | UserNameInvalidChar -- ^ Contains character not matching 'isValidUserNameChar'. |
| 85 | + deriving (Eq, Show) |
| 86 | + |
| 87 | +instance Pretty InvalidUserName where |
| 88 | + pretty = \case |
| 89 | + UserNameTooLong -> "Sorry, we didn't expect login names to be longer than 50 characters." |
| 90 | + UserNameInvalidChar -> "Sorry, login names have to be ASCII characters only or _, no spaces or other symbols." |
| 91 | + |
| 92 | +-- | Basic sanity checking in email. |
| 93 | +-- |
| 94 | +-- >>> validEmail "[email protected]" |
| 95 | +-- Right () |
| 96 | +-- |
| 97 | +-- >>> validEmail "[email protected]" |
| 98 | +-- Right () |
| 99 | +-- |
| 100 | +-- >>> validEmail "Emmanuel.Lauterbachs.Cousin@mailrelay.tor.amazon-aws.bill-me.cold-fusion.bogus-domain.phantasy-promi.darknet.de" |
| 101 | +-- Left EmailTooLong |
| 102 | +-- |
| 103 | +-- >>> validEmail "\BELlingcat@a\nonymous.\to" |
| 104 | +-- Left EmailNotPrintable |
| 105 | +-- |
| 106 | +-- >>> validEmail "ich-im-aether" |
| 107 | +-- Left EmailBadFormat |
| 108 | +-- |
| 109 | +-- >>> validEmail "ich@guuugle@kom" |
| 110 | +-- Left EmailBadFormat |
| 111 | +-- |
| 112 | +-- >>> validEmail "Windows User @ Company . com" |
| 113 | +-- Left EmailHasSpace |
| 114 | +-- |
| 115 | +-- >>> validEmail "Name<[email protected]>" |
| 116 | +-- Left EmailHasAngle |
| 117 | +-- |
| 118 | +validEmail :: T.Text -> Either InvalidEmail () |
| 119 | +validEmail str = do |
| 120 | + unless (T.length str <= 100) $ throwError EmailTooLong |
| 121 | + unless (T.all isPrint str) $ throwError EmailNotPrintable |
| 122 | + unless hasAtSomewhere $ throwError EmailBadFormat |
| 123 | + unless (T.all (not.isSpace) str) $ throwError EmailHasSpace |
| 124 | + unless (T.all (not.isAngle) str) $ throwError EmailHasAngle |
| 125 | + where |
| 126 | + isAngle c = c == '<' || c == '>' |
| 127 | + hasAtSomewhere = case T.break (== '@') str of |
| 128 | + (before, rest) |
| 129 | + | Just (_, after) <- T.uncons rest -> |
| 130 | + not $ or |
| 131 | + [ T.null before |
| 132 | + , T.null after |
| 133 | + , '@' `T.elem` after |
| 134 | + ] |
| 135 | + | otherwise -> False |
| 136 | + |
| 137 | +-- | Errors produced by 'validEmail' check. |
| 138 | + |
| 139 | +data InvalidEmail |
| 140 | + = EmailTooLong -- ^ More than 100 characters long. |
| 141 | + | EmailNotPrintable -- ^ Contains unprintable characters. |
| 142 | + | EmailBadFormat -- ^ Doesn't have exactly one @ sign. |
| 143 | + | EmailHasSpace -- ^ Contains spaces. |
| 144 | + | EmailHasAngle -- ^ Contains angle brackets. |
| 145 | + deriving (Eq, Show) |
| 146 | + |
| 147 | +instance Pretty InvalidEmail where |
| 148 | + pretty = \case |
| 149 | + EmailTooLong -> "Sorry, we didn't expect email addresses to be longer than 100 characters." |
| 150 | + EmailNotPrintable -> "Unexpected character in email address, please use only printable Unicode characters." |
| 151 | + EmailBadFormat -> "Oops, that doesn't look like an email address." |
| 152 | + EmailHasSpace -> "Oops, no spaces in email addresses please." |
| 153 | + EmailHasAngle -> "Please use just the email address, not \"name\" <[email protected]> style." |
0 commit comments