// RFC 3986 URI and RFC 3987 IRI grammar shared by relationship validation
// and package construction. Relationship Types are absolute IRIs without a
// fragment; External Targets are IRI references. Microsoft Office also emits
// the documented legacy `file:///C:\\...` and UNC spellings, so those are
// accepted as a narrow compatibility extension for External Targets only.
///|
fn relationship_uri_pchar(character : Char) -> Bool {
opc_uri_unreserved(character) ||
opc_uri_sub_delimiter(character) ||
character == ':' ||
character == '@'
}
///|
fn relationship_uri_query_or_fragment_char(character : Char) -> Bool {
relationship_uri_pchar(character) || character == '/' || character == '?'
}
///|
fn relationship_uri_userinfo_char(character : Char) -> Bool {
opc_uri_unreserved(character) ||
opc_uri_sub_delimiter(character) ||
character == ':'
}
///|
fn relationship_uri_reg_name_char(character : Char) -> Bool {
opc_uri_unreserved(character) || opc_uri_sub_delimiter(character)
}
///|
fn relationship_iri_private(character : Char) -> Bool {
let value = character.to_int()
(value >= 0xe000 && value <= 0xf8ff) ||
(value >= 0xf0000 && value <= 0xffffd) ||
(value >= 0x100000 && value <= 0x10fffd)
}
///|
fn relationship_iri_unreserved(character : Char) -> Bool {
opc_uri_unreserved(character) || opc_iri_ucschar(character)
}
///|
fn relationship_iri_pchar(character : Char) -> Bool {
relationship_iri_unreserved(character) ||
opc_uri_sub_delimiter(character) ||
character == ':' ||
character == '@'
}
///|
fn relationship_iri_query_char(character : Char) -> Bool {
relationship_iri_pchar(character) ||
relationship_iri_private(character) ||
character == '/' ||
character == '?'
}
///|
fn relationship_iri_fragment_char(character : Char) -> Bool {
relationship_iri_pchar(character) || character == '/' || character == '?'
}
///|
fn relationship_iri_userinfo_char(character : Char) -> Bool {
relationship_iri_unreserved(character) ||
opc_uri_sub_delimiter(character) ||
character == ':'
}
///|
fn relationship_iri_reg_name_char(character : Char) -> Bool {
relationship_iri_unreserved(character) || opc_uri_sub_delimiter(character)
}
///|
fn relationship_identifier_chars_valid(
characters : ArrayView[Char],
start : Int,
end : Int,
allowed : (Char) -> Bool,
) -> Bool {
let mut index = start
while index < end {
if characters[index] == '%' {
if index + 2 >= end ||
!opc_ascii_hex_digit(characters[index + 1]) ||
!opc_ascii_hex_digit(characters[index + 2]) {
return false
}
index = index + 3
} else {
if !allowed(characters[index]) {
return false
}
index = index + 1
}
}
true
}
///|
fn relationship_ipv4_literal_valid(
characters : ArrayView[Char],
start : Int,
end : Int,
) -> Bool {
let mut index = start
let mut components = 0
while index < end {
if components == 4 {
return false
}
let component_start = index
let mut value = 0
while index < end && characters[index].is_ascii_digit() {
value = value * 10 + characters[index].to_int() - '0'.to_int()
index = index + 1
}
let digits = index - component_start
if digits == 0 ||
digits > 3 ||
value > 255 ||
(digits > 1 && characters[component_start] == '0') {
return false
}
components = components + 1
if index == end {
break
}
if characters[index] != '.' {
return false
}
index = index + 1
}
components == 4
}
///|
// RFC 3986 IP-literal validation. A compressed `::` must replace at least
// one 16-bit group; an IPv4 tail occupies the final two groups.
fn relationship_ipv6_literal_valid(
characters : ArrayView[Char],
start : Int,
end : Int,
) -> Bool {
if start >= end {
return false
}
let mut index = start
let mut groups = 0
let mut compressed = false
if characters[index] == ':' {
if index + 1 >= end || characters[index + 1] != ':' {
return false
}
compressed = true
index = index + 2
if index == end {
return true
}
}
while index < end {
let group_start = index
let mut has_dot = false
while index < end && characters[index] != ':' {
if characters[index] == '.' {
has_dot = true
}
index = index + 1
}
if has_dot {
if index != end ||
!relationship_ipv4_literal_valid(characters, group_start, end) {
return false
}
groups = groups + 2
} else {
let digits = index - group_start
if digits == 0 || digits > 4 {
return false
}
for digit_index in group_start.. Bool {
if start >= end || (characters[start] != 'v' && characters[start] != 'V') {
return false
}
let mut index = start + 1
let version_start = index
while index < end && opc_ascii_hex_digit(characters[index]) {
index = index + 1
}
if index == version_start || index >= end || characters[index] != '.' {
return false
}
index = index + 1
let address_start = index
while index < end &&
(
opc_uri_unreserved(characters[index]) ||
opc_uri_sub_delimiter(characters[index]) ||
characters[index] == ':'
) {
index = index + 1
}
index == end && index > address_start
}
///|
fn relationship_uri_authority_valid(
characters : ArrayView[Char],
start : Int,
end : Int,
allow_iri_characters : Bool,
) -> Bool {
let mut host_start = start
let mut userinfo_at = -1
for index in start..= 0 {
return false
}
userinfo_at = index
}
}
if userinfo_at >= 0 {
let valid_userinfo = if allow_iri_characters {
relationship_identifier_chars_valid(
characters, start, userinfo_at, relationship_iri_userinfo_char,
)
} else {
relationship_identifier_chars_valid(
characters, start, userinfo_at, relationship_uri_userinfo_char,
)
}
if !valid_userinfo {
return false
}
host_start = userinfo_at + 1
}
if host_start < end && characters[host_start] == '[' {
let mut close = -1
for index in (host_start + 1)..= 0 {
return false
}
port_at = index
} else if characters[index] == '[' || characters[index] == ']' {
return false
}
}
let host_end = if port_at >= 0 { port_at } else { end }
let valid_reg_name = if allow_iri_characters {
relationship_identifier_chars_valid(
characters, host_start, host_end, relationship_iri_reg_name_char,
)
} else {
relationship_identifier_chars_valid(
characters, host_start, host_end, relationship_uri_reg_name_char,
)
}
if !valid_reg_name {
return false
}
if port_at >= 0 {
for index in (port_at + 1).. Bool {
if start >= end || !characters[start].is_ascii_alphabetic() {
return false
}
for index in (start + 1).. Bool {
if value == "" {
return false
}
let characters = value.to_array()
let mut query_at = characters.length()
let mut fragment_at = characters.length()
for index, character in characters {
if character == '#' {
if fragment_at < characters.length() {
return false
}
fragment_at = index
} else if character == '?' &&
query_at == characters.length() &&
fragment_at == characters.length() {
query_at = index
}
}
let hierarchy_end = if query_at < fragment_at {
query_at
} else {
fragment_at
}
let mut scheme_at = -1
for index in 0..= 0 {
if !relationship_uri_scheme_valid(characters, 0, scheme_at) {
return false
}
path_start = scheme_at + 1
}
if path_start + 1 < hierarchy_end &&
characters[path_start] == '/' &&
characters[path_start + 1] == '/' {
let authority_start = path_start + 2
let mut authority_end = authority_start
while authority_end < hierarchy_end && characters[authority_end] != '/' {
authority_end = authority_end + 1
}
if !relationship_uri_authority_valid(
characters, authority_start, authority_end, allow_iri_characters,
) {
return false
}
path_start = authority_end
}
let valid_path = if allow_iri_characters {
relationship_identifier_chars_valid(characters, path_start, hierarchy_end, fn(
character,
) {
relationship_iri_pchar(character) || character == '/'
})
} else {
relationship_identifier_chars_valid(characters, path_start, hierarchy_end, fn(
character,
) {
relationship_uri_pchar(character) || character == '/'
})
}
if !valid_path {
return false
}
if query_at < characters.length() {
let valid_query = if allow_iri_characters {
relationship_identifier_chars_valid(
characters,
query_at + 1,
fragment_at,
relationship_iri_query_char,
)
} else {
relationship_identifier_chars_valid(
characters,
query_at + 1,
fragment_at,
relationship_uri_query_or_fragment_char,
)
}
if !valid_query {
return false
}
}
if fragment_at < characters.length() {
let valid_fragment = if allow_iri_characters {
relationship_identifier_chars_valid(
characters,
fragment_at + 1,
characters.length(),
relationship_iri_fragment_char,
)
} else {
relationship_identifier_chars_valid(
characters,
fragment_at + 1,
characters.length(),
relationship_uri_query_or_fragment_char,
)
}
if !valid_fragment {
return false
}
}
true
}
///|
fn relationship_has_uri_scheme(value : StringView) -> Bool {
let characters = value.to_array()
for index, character in characters {
if character == ':' {
return relationship_uri_scheme_valid(characters, 0, index)
}
if character == '/' || character == '?' || character == '#' {
return false
}
}
false
}
///|
fn office_file_path_characters_valid(
characters : ArrayView[Char],
start : Int,
end : Int,
) -> Bool {
relationship_identifier_chars_valid(characters, start, end, fn(character) {
relationship_iri_pchar(character) || character == '/' || character == '\\'
})
}
///|
fn office_unc_path_valid(characters : ArrayView[Char]) -> Bool {
if characters.length() < 5 || characters[0] != '\\' || characters[1] != '\\' {
return false
}
let mut server_end = 2
while server_end < characters.length() &&
characters[server_end] != '\\' &&
characters[server_end] != '/' {
server_end = server_end + 1
}
if server_end == 2 ||
server_end == characters.length() ||
!relationship_identifier_chars_valid(
characters, 2, server_end, relationship_iri_reg_name_char,
) {
return false
}
let share_start = server_end + 1
let mut share_end = share_start
while share_end < characters.length() &&
characters[share_end] != '\\' &&
characters[share_end] != '/' {
share_end = share_end + 1
}
share_end > share_start &&
relationship_identifier_chars_valid(
characters, share_start, share_end, relationship_iri_pchar,
) &&
office_file_path_characters_valid(characters, share_end, characters.length())
}
///|
fn office_windows_file_iri_valid(value : StringView) -> Bool {
let owned = value.to_owned()
if !owned.to_lower().has_prefix("file:///") {
return false
}
let characters = owned["file:///".length():].to_array()
if characters.length() >= 3 &&
characters[0].is_ascii_alphabetic() &&
characters[1] == ':' &&
(characters[2] == '\\' || characters[2] == '/') {
return office_file_path_characters_valid(characters, 2, characters.length())
}
office_unc_path_valid(characters)
}
///|
fn is_absolute_relationship_type(value : StringView) -> Bool {
relationship_has_uri_scheme(value) &&
relationship_identifier_reference_valid(value, true) &&
!value.contains("#")
}
///|
fn is_valid_external_relationship_target(value : StringView) -> Bool {
relationship_identifier_reference_valid(value, true) ||
office_windows_file_iri_valid(value)
}