///|
fn url_hex(c : Char) -> Bool {
c is ('0'..<='9' | 'a'..<='f' | 'A'..<='F')
}
///|
fn url_ipv6(host : String) -> Bool {
let halves = host.split("::").to_array()
if halves.length() > 2 {
return false
}
let mut groups = 0
let mut ipv4 = false
for half in halves {
if half.is_empty() {
continue
}
for group in half.split(":") {
if ipv4 {
return false
}
if group.contains(".") {
ipv4 = true
if !host.has_suffix(group) {
return false
}
let octets = group.split(".").to_array()
if octets.length() != 4 {
return false
}
for octet in octets {
if octet.is_empty() ||
octet.length() > 3 ||
(octet.length() > 1 && octet.has_prefix("0")) {
return false
}
for c in octet; value = 0 {
if !(c is ('0'..<='9')) {
return false
}
let value = value * 10 + c.to_int() - '0'.to_int()
if value > 255 {
return false
}
continue value
}
}
groups += 2
} else {
if group.is_empty() || group.length() > 4 || !group.iter().all(url_hex) {
return false
}
groups += 1
}
}
}
if halves.length() == 2 {
groups < 8
} else {
groups == 8
}
}
///|
fn url_port(port : String) -> Bool {
for c in port; value = 0 {
if !(c is ('0'..<='9')) {
return false
}
let value = value * 10 + c.to_int() - '0'.to_int()
if value > 65535 {
return false
}
continue value
} nobreak {
true
}
}
///|
// Syntax only: never fetch, normalize, or open a URL during decoding.
fn valid_elicitation_url(url : String) -> Bool {
for c in url; escaped = 0 {
if c <= ' ' ||
c == '\u{7f}' ||
c is ('\\' | '<' | '>' | '"' | '{' | '}' | '|' | '^' | '`') {
return false
}
if escaped > 0 {
if !url_hex(c) {
return false
}
continue escaped - 1
}
continue if c == '%' { 2 } else { 0 }
} nobreak {
if escaped != 0 {
return false
}
}
guard url.split_once(":") is Some((scheme, rest)) &&
!scheme.is_empty() &&
!rest.is_empty() else {
return false
}
let mut first = true
for c in scheme {
if first {
if !(c is ('a'..<='z' | 'A'..<='Z')) {
return false
}
first = false
} else if !(c is ('a'..<='z' | 'A'..<='Z' | '0'..<='9' | '+' | '-' | '.')) {
return false
}
}
let network = ["http", "https", "ws", "wss", "ftp"].contains(
scheme.to_lower().to_owned(),
)
if !rest.has_prefix("//") {
return !network
}
let authority = StringBuilder()
for c in rest[2:] {
if c is ('/' | '?' | '#') {
break
}
authority.write_char(c)
}
let authority = authority.to_string()
let hostport = match authority.split_once("@") {
Some((_, host)) => host
None => authority
}
if hostport.contains("@") {
return false
}
if hostport.is_empty() {
return scheme.to_lower() == "file"
}
if hostport.has_prefix("[") {
guard hostport.split_once("]") is Some((host, tail)) &&
url_ipv6(host[1:].to_owned()) else {
return false
}
return tail.is_empty() ||
(tail.has_prefix(":") && url_port(tail[1:].to_owned()))
}
let host = match hostport.split_once(":") {
Some((host, port)) => {
if !url_port(port.to_owned()) {
return false
}
host
}
None => hostport
}
!host.is_empty() && !host.contains("[") && !host.contains("]")
}