Add server name + user id parsers
parent
d68de7f2fb
commit
d1336a0e23
|
@ -0,0 +1,198 @@
|
|||
module Internal.Grammar.ServerName exposing (..)
|
||||
{-| 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.
|
||||
-}
|
||||
|
||||
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
|
||||
|
||||
{-| The hostname is the location where the server can be found.
|
||||
|
||||
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 }
|
||||
|
||||
{-| The server name is a combination of a hostname and an optional port.
|
||||
-}
|
||||
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
|
||||
extension IPv6 literals.
|
||||
|
||||
[RFC-1123 §2.2]
|
||||
|
||||
The syntax of a legal Internet host name was specified in RFC-952
|
||||
[DNS:4]. One aspect of host name syntax is hereby changed: the
|
||||
restriction on the first character is relaxed to allow either a
|
||||
letter or a digit. Host software MUST support this more liberal
|
||||
syntax.
|
||||
|
||||
Host software MUST handle host names of up to 63 characters and
|
||||
SHOULD handle host names of up to 255 characters.
|
||||
|
||||
[RFC-952 §Assumptions-1]
|
||||
|
||||
A "name" (Net, Host, Gateway, or Domain name) is a text string up
|
||||
to 24 characters drawn from the alphabet (A-Z), digits (0-9), minus
|
||||
sign (-), and period (.). Note that periods are only allowed when
|
||||
they serve to delimit components of "domain style names". (See
|
||||
RFC-921, "Domain Name System Implementation Schedule", for
|
||||
background).
|
||||
-}
|
||||
dnsNameParser : Parser String
|
||||
dnsNameParser =
|
||||
P.chompIf Char.isAlphaNum
|
||||
|. P.chompWhile (\c -> Char.isAlphaNum c || c == '-' || c == '.')
|
||||
|> P.getChompedString
|
||||
|
||||
{-| Parse a Hostname.
|
||||
-}
|
||||
hostnameParser : Parser HostName
|
||||
hostnameParser =
|
||||
P.oneOf
|
||||
[ P.succeed IPv6
|
||||
|. P.symbol "["
|
||||
|= ipv6Parser
|
||||
|. P.symbol "]"
|
||||
, P.succeed DNS
|
||||
|= dnsNameParser
|
||||
]
|
||||
|
||||
{-| Parse all values to the left of the double colon (::)
|
||||
-}
|
||||
ipv6LeftParser : Parser (List String)
|
||||
ipv6LeftParser =
|
||||
P.oneOf
|
||||
[ P.succeed []
|
||||
|. P.symbol ":"
|
||||
, P.succeed (|>)
|
||||
|= PE.times 1 7 (ipv6NumParser |. P.symbol ":")
|
||||
|= P.oneOf
|
||||
[ P.succeed (\bottom tail -> tail ++ [bottom])
|
||||
|= ipv6NumParser
|
||||
, P.succeed identity
|
||||
]
|
||||
]
|
||||
|
||||
{-| Parse an ordinary IPv6 number
|
||||
-}
|
||||
ipv6NumParser : Parser String
|
||||
ipv6NumParser =
|
||||
P.chompIf Char.isHexDigit
|
||||
|> P.getChompedString
|
||||
|> PE.times 1 4
|
||||
|> P.map String.concat
|
||||
|
||||
{-| Parse an IPv6 Address
|
||||
-}
|
||||
ipv6Parser : Parser IPv6Address
|
||||
ipv6Parser =
|
||||
ipv6LeftParser
|
||||
|> P.andThen
|
||||
(\front ->
|
||||
P.succeed (IPv6Address front)
|
||||
|= ipv6RightParser (8 - List.length front)
|
||||
)
|
||||
|
||||
{-| Parse all values to the right of the double colon (::)
|
||||
-}
|
||||
ipv6RightParser : Int -> Parser (List String)
|
||||
ipv6RightParser n =
|
||||
P.succeed identity
|
||||
|. P.symbol ":"
|
||||
|= P.oneOf
|
||||
[ P.succeed (::)
|
||||
|= ipv6NumParser
|
||||
|= PE.times 1 (n - 1)
|
||||
( P.succeed identity
|
||||
|. P.symbol ":"
|
||||
|= ipv6NumParser
|
||||
)
|
||||
, P.succeed []
|
||||
]
|
||||
|
||||
{-| Convert an IPv6 address to a readable string format
|
||||
-}
|
||||
ipv6ToString : IPv6Address -> String
|
||||
ipv6ToString { front, back } =
|
||||
( if List.length front == 8 then
|
||||
front
|
||||
else if List.length back == 8 then
|
||||
back
|
||||
else
|
||||
List.concat [ front, [""], back ]
|
||||
)
|
||||
|> List.intersperse ":"
|
||||
|> String.concat
|
||||
|
||||
portParser : Parser Int
|
||||
portParser =
|
||||
P.chompIf Char.isDigit
|
||||
|. P.chompWhile Char.isDigit
|
||||
|> P.getChompedString
|
||||
|> P.andThen
|
||||
(\v ->
|
||||
case String.toInt v of
|
||||
Just i ->
|
||||
if 0 <= i && i <= 2^16 - 1 then
|
||||
P.succeed i
|
||||
else
|
||||
P.problem ("Port out of range: " ++ v)
|
||||
|
||||
Nothing ->
|
||||
P.problem "Not a port number"
|
||||
)
|
||||
|
||||
servernameParser : Parser ServerName
|
||||
servernameParser =
|
||||
P.succeed (\h p -> ServerName { host = h, port_ = p } )
|
||||
|= hostnameParser
|
||||
|= P.oneOf
|
||||
[ P.succeed Just
|
||||
|. P.symbol ":"
|
||||
|= portParser
|
||||
, P.succeed Nothing
|
||||
]
|
||||
|
||||
toString : ServerName -> String
|
||||
toString (ServerName { host, port_ }) =
|
||||
let
|
||||
hostString : String
|
||||
hostString =
|
||||
case host of
|
||||
DNS name ->
|
||||
name
|
||||
|
||||
IPv6 { front, back } ->
|
||||
( if List.length front == 8 then
|
||||
front
|
||||
else if List.length back == 8 then
|
||||
back
|
||||
else
|
||||
List.concat [ front, [""], back ]
|
||||
)
|
||||
|> List.intersperse ":"
|
||||
|> String.concat
|
||||
|
||||
portString : String
|
||||
portString =
|
||||
port_
|
||||
|> Maybe.map String.fromInt
|
||||
|> Maybe.map ((++) ":")
|
||||
|> Maybe.withDefault ""
|
||||
in
|
||||
hostString ++ portString
|
|
@ -0,0 +1,68 @@
|
|||
module Internal.Grammar.UserId exposing (..)
|
||||
{-| # 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:
|
||||
|
||||
@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 +.
|
||||
|
||||
The domain of a user ID is the server name of the homeserver which allocated
|
||||
the account.
|
||||
|
||||
The length of a user ID, including the @ sigil and the domain, MUST NOT exceed
|
||||
255 characters.
|
||||
|
||||
The complete grammar for a legal user ID is:
|
||||
|
||||
user_id = "@" user_id_localpart ":" server_name
|
||||
user_id_localpart = 1*user_id_char
|
||||
user_id_char = DIGIT
|
||||
/ %x61-7A ; a-z
|
||||
/ "-" / "." / "=" / "_" / "/" / "+"
|
||||
|
||||
Older versions of this specification were more tolerant of the characters
|
||||
permitted in user ID localparts. There are currently active users whose user
|
||||
IDs do not conform to the permitted character set, and a number of rooms whose
|
||||
history includes events with a sender which does not conform. In order to
|
||||
handle these rooms successfully, clients and servers MUST accept user IDs with
|
||||
localparts from the expanded character set:
|
||||
|
||||
extended_user_id_char = %x21-39 / %x3B-7E ; all ASCII printing chars except :
|
||||
|
||||
-}
|
||||
|
||||
import Internal.Grammar.ServerName as ServerName exposing (ServerName)
|
||||
import Internal.Tools.ParserExtra as PE
|
||||
import Parser as P exposing (Parser, (|.), (|=))
|
||||
|
||||
type UserID = UserID { localpart : String, domain : ServerName }
|
||||
|
||||
localpartParser : Parser String
|
||||
localpartParser =
|
||||
P.chompIf validHistoricalUsernameChar
|
||||
|> P.getChompedString
|
||||
|> 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.symbol "@"
|
||||
|= localpartParser
|
||||
|. P.symbol ":"
|
||||
|= ServerName.servernameParser
|
||||
|
||||
validHistoricalUsernameChar : Char -> Bool
|
||||
validHistoricalUsernameChar c =
|
||||
let
|
||||
i : Int
|
||||
i = Char.toCode c
|
||||
in
|
||||
(0x21 <= i && i <= 0x39) || (0x3B <= i && i <= 0x7E)
|
Loading…
Reference in New Issue