module Ecluse.Core.Security.Authority (
HostPort (..),
hostAddress,
hostPortAddress,
hostPortAddressWithDefault,
splitHostPort,
refuseCredentialMaterial,
authorityLabel,
dialledAuthorityLabel,
credentialFreeUrl,
) where
import Data.Text qualified as T
import Ecluse.Core.Text (afterFirst, httpPrefix, isPrefixOfLowered, readDecimalText)
data HostPort = HostPort
{ HostPort -> Text
hpHost :: Text
, HostPort -> Word16
hpPort :: Word16
}
deriving stock (HostPort -> HostPort -> Bool
(HostPort -> HostPort -> Bool)
-> (HostPort -> HostPort -> Bool) -> Eq HostPort
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: HostPort -> HostPort -> Bool
== :: HostPort -> HostPort -> Bool
$c/= :: HostPort -> HostPort -> Bool
/= :: HostPort -> HostPort -> Bool
Eq, Eq HostPort
Eq HostPort =>
(HostPort -> HostPort -> Ordering)
-> (HostPort -> HostPort -> Bool)
-> (HostPort -> HostPort -> Bool)
-> (HostPort -> HostPort -> Bool)
-> (HostPort -> HostPort -> Bool)
-> (HostPort -> HostPort -> HostPort)
-> (HostPort -> HostPort -> HostPort)
-> Ord HostPort
HostPort -> HostPort -> Bool
HostPort -> HostPort -> Ordering
HostPort -> HostPort -> HostPort
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: HostPort -> HostPort -> Ordering
compare :: HostPort -> HostPort -> Ordering
$c< :: HostPort -> HostPort -> Bool
< :: HostPort -> HostPort -> Bool
$c<= :: HostPort -> HostPort -> Bool
<= :: HostPort -> HostPort -> Bool
$c> :: HostPort -> HostPort -> Bool
> :: HostPort -> HostPort -> Bool
$c>= :: HostPort -> HostPort -> Bool
>= :: HostPort -> HostPort -> Bool
$cmax :: HostPort -> HostPort -> HostPort
max :: HostPort -> HostPort -> HostPort
$cmin :: HostPort -> HostPort -> HostPort
min :: HostPort -> HostPort -> HostPort
Ord, Int -> HostPort -> ShowS
[HostPort] -> ShowS
HostPort -> String
(Int -> HostPort -> ShowS)
-> (HostPort -> String) -> ([HostPort] -> ShowS) -> Show HostPort
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> HostPort -> ShowS
showsPrec :: Int -> HostPort -> ShowS
$cshow :: HostPort -> String
show :: HostPort -> String
$cshowList :: [HostPort] -> ShowS
showList :: [HostPort] -> ShowS
Show)
hostAddress :: Text -> Text
hostAddress :: Text -> Text
hostAddress Text
raw = Text -> Text
T.toLower (Text -> ((Text, Text) -> Text) -> Maybe (Text, Text) -> Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text
"" (Text, Text) -> Text
forall a b. (a, b) -> a
fst (Text -> Maybe (Text, Text)
splitHostPort (Text -> Text
authorityOf Text
raw)))
hostPortAddress :: Text -> Maybe HostPort
hostPortAddress :: Text -> Maybe HostPort
hostPortAddress = Word16 -> Text -> Maybe HostPort
hostPortAddressWithDefault Word16
443
hostPortAddressWithDefault :: Word16 -> Text -> Maybe HostPort
hostPortAddressWithDefault :: Word16 -> Text -> Maybe HostPort
hostPortAddressWithDefault Word16
portless Text
raw = do
let authority :: Text
authority = Text -> Text
authorityOf Text
raw
(host, rest) <- Text -> Maybe (Text, Text)
splitHostPort Text
authority
guard (not (T.null host))
port <- effectivePort portless authority rest
pure (HostPort (T.toLower host) port)
authorityLabel :: Text -> Text
authorityLabel :: Text -> Text
authorityLabel = Word16 -> Text -> Text
authorityLabelWithDefault Word16
443
dialledAuthorityLabel :: Text -> Text
dialledAuthorityLabel :: Text -> Text
dialledAuthorityLabel Text
raw = Word16 -> Text -> Text
authorityLabelWithDefault Word16
portless Text
raw
where
portless :: Word16
portless = if LowerPrefix -> Text -> Bool
isPrefixOfLowered LowerPrefix
httpPrefix Text
raw then Word16
80 else Word16
443
authorityLabelWithDefault :: Word16 -> Text -> Text
authorityLabelWithDefault :: Word16 -> Text -> Text
authorityLabelWithDefault Word16
portless = Text -> (HostPort -> Text) -> Maybe HostPort -> Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text
unresolvedAuthority HostPort -> Text
renderHostPort (Maybe HostPort -> Text)
-> (Text -> Maybe HostPort) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word16 -> Text -> Maybe HostPort
hostPortAddressWithDefault Word16
portless
credentialFreeUrl :: Text -> Text
credentialFreeUrl :: Text -> Text
credentialFreeUrl Text
raw = Text
scheme Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
authorityOf Text
raw Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
path
where
(Text
written, Text
separator) = HasCallStack => Text -> Text -> (Text, Text)
Text -> Text -> (Text, Text)
T.breakOn Text
"://" Text
raw
(Text
scheme, Text
afterScheme) = case Text -> Text -> Maybe Text
T.stripPrefix Text
"://" Text
separator of
Just Text
rest -> (Text
written Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"://", Text
rest)
Maybe Text
Nothing -> (Text
"", Text
raw)
path :: Text
path = (Char -> Bool) -> Text -> Text
T.takeWhile (Char -> String -> Bool
forall (f :: * -> *) a.
(Foldable f, DisallowElem f, Eq a) =>
a -> f a -> Bool
`notElem` [Char
'?', Char
'#']) ((Char -> Bool) -> Text -> Text
T.dropWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'/') Text
afterScheme)
unresolvedAuthority :: Text
unresolvedAuthority :: Text
unresolvedAuthority = Text
"<unresolved>"
renderHostPort :: HostPort -> Text
renderHostPort :: HostPort -> Text
renderHostPort (HostPort Text
host Word16
port)
| Text
":" Text -> Text -> Bool
`T.isInfixOf` Text
host = Text
"[" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
host Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"]:" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Word16 -> Text
forall b a. (Show a, IsString b) => a -> b
show Word16
port
| Bool
otherwise = Text
host Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
":" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Word16 -> Text
forall b a. (Show a, IsString b) => a -> b
show Word16
port
effectivePort :: Word16 -> Text -> Text -> Maybe Word16
effectivePort :: Word16 -> Text -> Text -> Maybe Word16
effectivePort Word16
portless Text
authority Text
rest = case Text -> Text -> Maybe Text
T.stripPrefix Text
":" Text
rest of
Just Text
written -> Text -> Maybe Word16
parsePort Text
written
Maybe Text
Nothing
| Bool -> Bool
not (Text -> Bool
T.null Text
rest) -> Maybe Word16
forall a. Maybe a
Nothing
| Text
":" Text -> Text -> Bool
`T.isSuffixOf` Text
authority -> Maybe Word16
forall a. Maybe a
Nothing
| Bool
otherwise -> Word16 -> Maybe Word16
forall a. a -> Maybe a
Just Word16
portless
parsePort :: Text -> Maybe Word16
parsePort :: Text -> Maybe Word16
parsePort Text
t = do
Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Int -> Text -> Text
T.take Int
1 Text
t Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
/= Text
"0")
n <- Text -> Maybe Integer
forall a. Integral a => Text -> Maybe a
readDecimalText Text
t :: Maybe Integer
guard (n >= 1 && n <= 65535)
pure (fromInteger n)
carriesUserinfo :: Text -> Bool
carriesUserinfo :: Text -> Bool
carriesUserinfo = Text -> Text -> Bool
T.isInfixOf Text
"@" (Text -> Bool) -> (Text -> Text) -> Text -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text
authoritySpan
refuseCredentialMaterial :: Text -> Text -> Either Text ()
refuseCredentialMaterial :: Text -> Text -> Either Text ()
refuseCredentialMaterial Text
subject Text
url
| Text -> Bool
carriesUserinfo Text
url =
Text -> Either Text ()
forall a b. a -> Either a b
Left (Text
subject Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" must not carry userinfo (a credential belongs in its own configuration key)")
| Text
"?" Text -> Text -> Bool
`T.isInfixOf` Text
url = Text -> Either Text ()
forall a b. a -> Either a b
Left (Text
subject Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" must not carry a query string")
| Text
"#" Text -> Text -> Bool
`T.isInfixOf` Text
url = Text -> Either Text ()
forall a b. a -> Either a b
Left (Text
subject Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" must not carry a fragment")
| Bool
otherwise = () -> Either Text ()
forall a b. b -> Either a b
Right ()
authoritySpan :: Text -> Text
authoritySpan :: Text -> Text
authoritySpan Text
raw = (Char -> Bool) -> Text -> Text
T.takeWhile (Char -> String -> Bool
forall (f :: * -> *) a.
(Foldable f, DisallowElem f, Eq a) =>
a -> f a -> Bool
`notElem` [Char
'/', Char
'?', Char
'#']) (Text -> Text -> Text
afterFirst Text
"://" Text
raw)
authorityOf :: Text -> Text
authorityOf :: Text -> Text
authorityOf = Text -> Text -> Text
afterLast Text
"@" (Text -> Text) -> (Text -> Text) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text
authoritySpan
afterLast :: Text -> Text -> Text
afterLast :: Text -> Text -> Text
afterLast Text
needle Text
hay =
let (Text
pre, Text
post) = HasCallStack => Text -> Text -> (Text, Text)
Text -> Text -> (Text, Text)
T.breakOnEnd Text
needle Text
hay
in if Text -> Bool
T.null Text
pre then Text
hay else Text
post
splitHostPort :: Text -> Maybe (Text, Text)
splitHostPort :: Text -> Maybe (Text, Text)
splitHostPort Text
authority
| Text -> Bool
T.null Text
authority = Maybe (Text, Text)
forall a. Maybe a
Nothing
| Bool
otherwise = case Text -> Text -> Maybe Text
T.stripPrefix Text
"[" Text
authority of
Just Text
rest -> case HasCallStack => Text -> Text -> (Text, Text)
Text -> Text -> (Text, Text)
T.breakOn Text
"]" Text
rest of
(Text
_, Text
"") -> Maybe (Text, Text)
forall a. Maybe a
Nothing
(Text
inner, Text
afterBracket) -> (Text, Text) -> Maybe (Text, Text)
forall a. a -> Maybe a
Just (Text
inner, Int -> Text -> Text
T.drop Int
1 Text
afterBracket)
Maybe Text
Nothing -> case HasCallStack => Text -> Text -> (Text, Text)
Text -> Text -> (Text, Text)
T.breakOn Text
":" Text
authority of
(Text
"", Text
_) -> Maybe (Text, Text)
forall a. Maybe a
Nothing
(Text
h, Text
"") -> (Text, Text) -> Maybe (Text, Text)
forall a. a -> Maybe a
Just (Text
h, Text
"")
(Text
h, Text
p) -> if Text
p Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
":" then (Text, Text) -> Maybe (Text, Text)
forall a. a -> Maybe a
Just (Text
h, Text
"") else (Text, Text) -> Maybe (Text, Text)
forall a. a -> Maybe a
Just (Text
h, Text
p)