Add server name module
parent
077605bcbc
commit
cd0c687307
|
@ -1,13 +1,29 @@
|
|||
module Internal.Grammar.ServerName exposing (..)
|
||||
{-| A homeserver is uniquely identified by its server name. The server name
|
||||
module Internal.Grammar.ServerName exposing
|
||||
( ServerName, toString, fromString
|
||||
, servernameParser
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
|
||||
# Server name
|
||||
|
||||
A homeserver is uniquely identified by its server name. The server name
|
||||
represents the address at which the homeserver in question can be reached by
|
||||
other homeservers.
|
||||
|
||||
@docs ServerName, toString, fromString
|
||||
|
||||
|
||||
## Parser
|
||||
|
||||
@docs serverNameParser
|
||||
|
||||
-}
|
||||
|
||||
import Internal.Tools.ParserExtra as PE
|
||||
import Parser as P exposing (Parser, (|.), (|=))
|
||||
import Internal.Config.Log exposing (Log, log)
|
||||
import Internal.Config.Text as Text
|
||||
import Parser as P exposing ((|.), (|=), Parser)
|
||||
|
||||
|
||||
{-| The hostname is the location where the server can be found.
|
||||
|
||||
|
@ -15,19 +31,25 @@ Notice how the Matrix spec specifies that the hostname can either be a DNS name,
|
|||
an IPv4Address or an IPv6Address. Since the IPv4Address is compatible with the
|
||||
specification of DNS names, however, and RFC1123 (section 2.1) does not require
|
||||
a client to distinguish them, we treat IPv4Addresses like DNS names.
|
||||
|
||||
-}
|
||||
type HostName
|
||||
= DNS String
|
||||
| IPv6 IPv6Address
|
||||
|
||||
|
||||
{-| The IPv6Address is represented by a list of items BEFORE and AFTER the
|
||||
double colons (::).
|
||||
-}
|
||||
type alias IPv6Address = { front : List String, back : List String }
|
||||
type alias IPv6Address =
|
||||
{ front : List String, back : List String }
|
||||
|
||||
|
||||
{-| The server name is a combination of a hostname and an optional port.
|
||||
-}
|
||||
type ServerName = ServerName { host : HostName, port_ : Maybe Int }
|
||||
type ServerName
|
||||
= ServerName { host : HostName, port_ : Maybe Int }
|
||||
|
||||
|
||||
{-| Parser for the DNS name record. The Matrix spec bases its grammar on the
|
||||
standard for internet host names, as specified by RFC1123, section 2.1, with an
|
||||
|
@ -52,6 +74,7 @@ extension IPv6 literals.
|
|||
they serve to delimit components of "domain style names". (See
|
||||
RFC-921, "Domain Name System Implementation Schedule", for
|
||||
background).
|
||||
|
||||
-}
|
||||
dnsNameParser : Parser String
|
||||
dnsNameParser =
|
||||
|
@ -59,6 +82,23 @@ dnsNameParser =
|
|||
|. P.chompWhile (\c -> Char.isAlphaNum c || c == '-' || c == '.')
|
||||
|> P.getChompedString
|
||||
|
||||
|
||||
fromString : String -> Maybe ServerName
|
||||
fromString s =
|
||||
P.run (servernameParser |. P.end) s
|
||||
|> (\out ->
|
||||
case out of
|
||||
Ok _ ->
|
||||
out
|
||||
|
||||
Err e ->
|
||||
Debug.log "No parse" e
|
||||
|> always (Debug.log "original" s)
|
||||
|> always out
|
||||
)
|
||||
|> Result.toMaybe
|
||||
|
||||
|
||||
{-| Parse a Hostname.
|
||||
-}
|
||||
hostnameParser : Parser HostName
|
||||
|
@ -72,6 +112,7 @@ hostnameParser =
|
|||
|= dnsNameParser
|
||||
]
|
||||
|
||||
|
||||
{-| Parse all values to the left of the double colon (::)
|
||||
-}
|
||||
ipv6LeftParser : Parser (List String)
|
||||
|
@ -82,12 +123,13 @@ ipv6LeftParser =
|
|||
, P.succeed (|>)
|
||||
|= PE.times 1 7 (ipv6NumParser |. P.symbol ":")
|
||||
|= P.oneOf
|
||||
[ P.succeed (\bottom tail -> tail ++ [bottom])
|
||||
[ P.succeed (\bottom tail -> tail ++ [ bottom ])
|
||||
|= ipv6NumParser
|
||||
, P.succeed identity
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
{-| Parse an ordinary IPv6 number
|
||||
-}
|
||||
ipv6NumParser : Parser String
|
||||
|
@ -97,6 +139,7 @@ ipv6NumParser =
|
|||
|> PE.times 1 4
|
||||
|> P.map String.concat
|
||||
|
||||
|
||||
{-| Parse an IPv6 Address
|
||||
-}
|
||||
ipv6Parser : Parser IPv6Address
|
||||
|
@ -108,37 +151,47 @@ ipv6Parser =
|
|||
|= ipv6RightParser (8 - List.length front)
|
||||
)
|
||||
|
||||
|
||||
{-| Parse all values to the right of the double colon (::)
|
||||
-}
|
||||
ipv6RightParser : Int -> Parser (List String)
|
||||
ipv6RightParser n =
|
||||
if n > 0 then
|
||||
P.succeed identity
|
||||
|. P.symbol ":"
|
||||
|= P.oneOf
|
||||
[ P.succeed (::)
|
||||
|= ipv6NumParser
|
||||
|= PE.times 1 (n - 1)
|
||||
( P.succeed identity
|
||||
|= PE.times 0
|
||||
(n - 1)
|
||||
(P.succeed identity
|
||||
|. P.symbol ":"
|
||||
|= ipv6NumParser
|
||||
)
|
||||
, P.succeed []
|
||||
]
|
||||
|
||||
else
|
||||
P.succeed []
|
||||
|
||||
|
||||
{-| Convert an IPv6 address to a readable string format
|
||||
-}
|
||||
ipv6ToString : IPv6Address -> String
|
||||
ipv6ToString { front, back } =
|
||||
( if List.length front == 8 then
|
||||
(if List.length front == 8 then
|
||||
front
|
||||
|
||||
else if List.length back == 8 then
|
||||
back
|
||||
|
||||
else
|
||||
List.concat [ front, [""], back ]
|
||||
List.concat [ front, [ "" ], back ]
|
||||
)
|
||||
|> List.intersperse ":"
|
||||
|> String.concat
|
||||
|
||||
|
||||
portParser : Parser Int
|
||||
portParser =
|
||||
P.chompIf Char.isDigit
|
||||
|
@ -148,8 +201,9 @@ portParser =
|
|||
(\v ->
|
||||
case String.toInt v of
|
||||
Just i ->
|
||||
if 0 <= i && i <= 2^16 - 1 then
|
||||
if 0 <= i && i <= 2 ^ 16 - 1 then
|
||||
P.succeed i
|
||||
|
||||
else
|
||||
P.problem ("Port out of range: " ++ v)
|
||||
|
||||
|
@ -157,9 +211,10 @@ portParser =
|
|||
P.problem "Not a port number"
|
||||
)
|
||||
|
||||
|
||||
servernameParser : Parser ServerName
|
||||
servernameParser =
|
||||
P.succeed (\h p -> ServerName { host = h, port_ = p } )
|
||||
P.succeed (\h p -> ServerName { host = h, port_ = p })
|
||||
|= hostnameParser
|
||||
|= P.oneOf
|
||||
[ P.succeed Just
|
||||
|
@ -168,6 +223,7 @@ servernameParser =
|
|||
, P.succeed Nothing
|
||||
]
|
||||
|
||||
|
||||
toString : ServerName -> String
|
||||
toString (ServerName { host, port_ }) =
|
||||
let
|
||||
|
@ -178,15 +234,21 @@ toString (ServerName { host, port_ }) =
|
|||
name
|
||||
|
||||
IPv6 { front, back } ->
|
||||
( if List.length front == 8 then
|
||||
front
|
||||
(if List.length front == 8 then
|
||||
List.intersperse ":" front
|
||||
|
||||
else if List.length back == 8 then
|
||||
back
|
||||
List.intersperse ":" back
|
||||
|
||||
else
|
||||
List.concat [ front, [""], back ]
|
||||
List.concat
|
||||
[ List.intersperse ":" front
|
||||
, [ "::" ]
|
||||
, List.intersperse ":" back
|
||||
]
|
||||
)
|
||||
|> List.intersperse ":"
|
||||
|> String.concat
|
||||
|> (\i -> "[" ++ i ++ "]")
|
||||
|
||||
portString : String
|
||||
portString =
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
module Internal.Grammar.UserId exposing (..)
|
||||
{-| # User ids
|
||||
|
||||
{-|
|
||||
|
||||
|
||||
# User ids
|
||||
|
||||
Users within Matrix are uniquely identified by their Matrix user ID. The user
|
||||
ID is namespaced to the homeserver which allocated the account and has the form:
|
||||
|
@ -7,7 +11,7 @@ ID is namespaced to the homeserver which allocated the account and has the form:
|
|||
@localpart:domain
|
||||
|
||||
The localpart of a user ID is an opaque identifier for that user. It MUST NOT
|
||||
be empty, and MUST contain only the characters a-z, 0-9, ., _, =, -, /, and +.
|
||||
be empty, and MUST contain only the characters a-z, 0-9, ., \_, =, -, /, and +.
|
||||
|
||||
The domain of a user ID is the server name of the homeserver which allocated
|
||||
the account.
|
||||
|
@ -36,9 +40,17 @@ localparts from the expanded character set:
|
|||
|
||||
import Internal.Grammar.ServerName as ServerName exposing (ServerName)
|
||||
import Internal.Tools.ParserExtra as PE
|
||||
import Parser as P exposing (Parser, (|.), (|=))
|
||||
import Parser as P exposing ((|.), (|=), Parser)
|
||||
|
||||
|
||||
type UserID
|
||||
= UserID { localpart : String, domain : ServerName }
|
||||
|
||||
|
||||
fromString : String -> Maybe UserID
|
||||
fromString =
|
||||
P.run userIdParser >> Result.toMaybe
|
||||
|
||||
type UserID = UserID { localpart : String, domain : ServerName }
|
||||
|
||||
localpartParser : Parser String
|
||||
localpartParser =
|
||||
|
@ -47,22 +59,26 @@ localpartParser =
|
|||
|> PE.times 1 255
|
||||
|> P.map String.concat
|
||||
|
||||
|
||||
toString : UserID -> String
|
||||
toString (UserID { localpart, domain }) =
|
||||
String.concat [ "@", localpart, ":", ServerName.toString domain ]
|
||||
|
||||
|
||||
userIdParser : Parser UserID
|
||||
userIdParser =
|
||||
P.succeed (\l d -> UserID { localpart = l, domain = d } )
|
||||
P.succeed (\l d -> UserID { localpart = l, domain = d })
|
||||
|. P.symbol "@"
|
||||
|= localpartParser
|
||||
|. P.symbol ":"
|
||||
|= ServerName.servernameParser
|
||||
|
||||
|
||||
validHistoricalUsernameChar : Char -> Bool
|
||||
validHistoricalUsernameChar c =
|
||||
let
|
||||
i : Int
|
||||
i = Char.toCode c
|
||||
i =
|
||||
Char.toCode c
|
||||
in
|
||||
(0x21 <= i && i <= 0x39) || (0x3B <= i && i <= 0x7E)
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
module Test.Grammar.ServerName exposing (..)
|
||||
|
||||
import Expect
|
||||
import Fuzz exposing (Fuzzer)
|
||||
import Internal.Grammar.ServerName as SN
|
||||
import Test exposing (..)
|
||||
|
||||
|
||||
dnsFuzzer : Fuzzer String
|
||||
dnsFuzzer =
|
||||
Fuzz.map2
|
||||
(\head tail ->
|
||||
String.fromList (head :: tail)
|
||||
)
|
||||
("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|> String.toList
|
||||
|> Fuzz.oneOfValues
|
||||
)
|
||||
("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-."
|
||||
|> String.toList
|
||||
|> Fuzz.oneOfValues
|
||||
|> Fuzz.listOfLengthBetween 0 (255 - 1)
|
||||
)
|
||||
|
||||
|
||||
hostnameFuzzer : Fuzzer String
|
||||
hostnameFuzzer =
|
||||
Fuzz.oneOf
|
||||
[ dnsFuzzer
|
||||
, ipv4Fuzzer
|
||||
, Fuzz.map (\x -> "[" ++ x ++ "]") ipv6Fuzzer
|
||||
]
|
||||
|
||||
|
||||
ipv4Fuzzer : Fuzzer String
|
||||
ipv4Fuzzer =
|
||||
Fuzz.intRange 0 255
|
||||
|> Fuzz.listOfLength 4
|
||||
|> Fuzz.map
|
||||
(List.map String.fromInt
|
||||
>> List.intersperse "."
|
||||
>> String.concat
|
||||
)
|
||||
|
||||
|
||||
ipv6Fuzzer : Fuzzer String
|
||||
ipv6Fuzzer =
|
||||
let
|
||||
num : Fuzzer String
|
||||
num =
|
||||
"0123456789abcdefABCDEF"
|
||||
|> String.toList
|
||||
|> Fuzz.oneOfValues
|
||||
|> Fuzz.listOfLength 4
|
||||
|> Fuzz.map String.fromList
|
||||
in
|
||||
Fuzz.oneOf
|
||||
[ Fuzz.listOfLength 8 num
|
||||
|> Fuzz.map (List.intersperse ":")
|
||||
|> Fuzz.map String.concat
|
||||
, Fuzz.listOfLengthBetween 0 7 num
|
||||
|> Fuzz.andThen
|
||||
(\front ->
|
||||
num
|
||||
|> Fuzz.listOfLengthBetween 0 (8 - List.length front)
|
||||
|> Fuzz.map
|
||||
(\back ->
|
||||
[ front
|
||||
|> List.intersperse ":"
|
||||
, [ "::" ]
|
||||
, back
|
||||
|> List.intersperse ":"
|
||||
]
|
||||
|> List.concat
|
||||
|> String.concat
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
portFuzzer : Fuzzer String
|
||||
portFuzzer =
|
||||
Fuzz.oneOf
|
||||
[ Fuzz.constant ""
|
||||
, Fuzz.intRange 0 65535
|
||||
|> Fuzz.map (\p -> ":" ++ String.fromInt p)
|
||||
]
|
||||
|
||||
|
||||
serverNameFuzzer : Fuzzer String
|
||||
serverNameFuzzer =
|
||||
Fuzz.map2 (++) hostnameFuzzer portFuzzer
|
||||
|
||||
|
||||
suite : Test
|
||||
suite =
|
||||
describe "Server name tests"
|
||||
[ describe "Checking correct values"
|
||||
[ fuzz serverNameFuzzer
|
||||
"Correct server names validate"
|
||||
(\server ->
|
||||
SN.fromString server
|
||||
|> Maybe.map SN.toString
|
||||
|> Maybe.map (String.replace "::" ":")
|
||||
|> Expect.equal (Just <| String.replace "::" ":" server)
|
||||
)
|
||||
, test "Checking spec examples"
|
||||
(\() ->
|
||||
let
|
||||
examples : List String
|
||||
examples =
|
||||
[ "matrix.org"
|
||||
, "matrix.org:8888"
|
||||
, "1.2.3.4"
|
||||
, "1.2.3.4:1234"
|
||||
, "[1234:5678::abcd]"
|
||||
, "[1234:5678::abcd]:5678"
|
||||
]
|
||||
in
|
||||
examples
|
||||
|> List.map SN.fromString
|
||||
|> List.map ((/=) Nothing)
|
||||
|> Expect.equalLists
|
||||
(List.repeat (List.length examples) True)
|
||||
)
|
||||
]
|
||||
]
|
Loading…
Reference in New Issue