///|
/// Resolve relative URL to absolute
fn resolve_url(base_url : String, href : String) -> String {
// Skip empty, fragment-only, or javascript URLs
if href.length() == 0 ||
href.has_prefix("#") ||
href.has_prefix("javascript:") ||
href.has_prefix("mailto:") ||
href.has_prefix("tel:") {
return ""
}
// Already absolute
if href.has_prefix("http://") || href.has_prefix("https://") {
return href
}
if href.has_prefix("data:") ||
href.has_prefix("about:") ||
href.has_prefix("blob:") ||
href.has_prefix("file:") {
return href
}
// Protocol-relative
if href.has_prefix("//") {
let protocol = if base_url.has_prefix("https://") {
"https:"
} else {
"http:"
}
return protocol + href
}
// Extract base from URL
let protocol_end = match base_url.find("://") {
Some(i) => i
None => return href
}
let after_protocol = base_url.unsafe_substring(
start=protocol_end + 3,
end=base_url.length(),
)
let path_start = after_protocol.find("/")
let origin = match path_start {
Some(i) => base_url.unsafe_substring(start=0, end=protocol_end + 3 + i)
None => base_url
}
// Root-relative
if href.has_prefix("/") {
return origin + href
}
// Relative - append to current path
let last_slash = base_url.rev_find("/")
match last_slash {
Some(i) if i > protocol_end + 2 =>
base_url.unsafe_substring(start=0, end=i + 1) + href
_ => origin + "/" + href
}
}
///|
fn make_substr(chars : Array[Char], start : Int, end : Int) -> String {
let buf = StringBuilder::new()
for i = start; i < end; i = i + 1 {
buf.write_char(chars[i])
}
buf.to_string()
}
///|
fn hex_digit_to_int(c : Char) -> Int? {
if c >= '0' && c <= '9' {
Some(c.to_int() - '0'.to_int())
} else if c >= 'a' && c <= 'f' {
Some(c.to_int() - 'a'.to_int() + 10)
} else if c >= 'A' && c <= 'F' {
Some(c.to_int() - 'A'.to_int() + 10)
} else {
None
}
}
///|
fn percent_decode_data_url_payload(payload : String) -> String {
let chars = payload.to_array()
let buf = StringBuilder::new()
let mut i = 0
while i < chars.length() {
if chars[i] == '%' && i + 2 < chars.length() {
match (hex_digit_to_int(chars[i + 1]), hex_digit_to_int(chars[i + 2])) {
(Some(high), Some(low)) => {
buf.write_char((high * 16 + low).unsafe_to_char())
i = i + 3
}
_ => {
buf.write_char(chars[i])
i = i + 1
}
}
} else {
buf.write_char(chars[i])
i = i + 1
}
}
buf.to_string()
}
///|
fn decode_sync_navigable_html_url(url : String) -> String? {
if url == "about:blank" {
return Some("")
}
if !url.has_prefix("data:") {
return None
}
let chars = url.to_array()
let mut comma_idx = -1
for i = 5; i < chars.length(); i = i + 1 {
if chars[i] == ',' {
comma_idx = i
break
}
}
if comma_idx < 0 {
return None
}
let header = make_substr(chars, 5, comma_idx)
if header.contains(";base64") {
return None
}
let mut payload_end = chars.length()
for i = comma_idx + 1; i < chars.length(); i = i + 1 {
if chars[i] == '?' || chars[i] == '#' {
payload_end = i
break
}
}
let payload = make_substr(chars, comma_idx + 1, payload_end)
Some(percent_decode_data_url_payload(payload))
}