Add server name + user id parsers

parser
Bram 2024-03-29 07:15:27 +01:00
parent d68de7f2fb
commit d1336a0e23
2 changed files with 266 additions and 0 deletions

View File

@ -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

View File

@ -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)