///|
pub(all) struct CanonicalNetworkHttpUrl {
scheme : String
host : String
port : Int
path : String
query : String
}
///|
pub fn network_intercept_matches_phase(
options : Map[String, Json],
phase : String,
) -> Bool {
match options.get("phases") {
Some(Array(phases)) =>
for item in phases {
match item {
String(name) if name == phase => return true
_ => ()
}
}
_ => ()
}
false
}
///|
pub fn network_option_contexts_match(
options : Map[String, Json],
ancestry : Array[String],
) -> Bool {
match options.get("contexts") {
Some(Array(contexts)) => {
if contexts.length() == 0 {
return true
}
for item in contexts {
match item {
String(ctx_id) if array_contains_string(ancestry, ctx_id) =>
return true
_ => ()
}
}
false
}
_ => true
}
}
///|
pub fn network_url_patterns_match(
options : Map[String, Json],
url : String,
) -> Bool {
match options.get("urlPatterns") {
Some(Array(patterns)) => {
if patterns.length() == 0 {
return true
}
for entry in patterns {
if network_url_pattern_entry_matches(entry, url) {
return true
}
}
false
}
_ => true
}
}
///|
fn network_url_pattern_entry_matches(entry : Json, url : String) -> Bool {
match entry {
Object(map) =>
match map.get("type") {
Some(String("string")) =>
match map.get("pattern") {
Some(String(pattern)) =>
network_string_url_pattern_matches(pattern, url)
_ => false
}
Some(String("pattern")) => network_object_url_pattern_matches(map, url)
_ => false
}
_ => false
}
}
///|
fn network_string_url_pattern_matches(
pattern_url : String,
target_url : String,
) -> Bool {
match
(
canonicalize_network_http_url(pattern_url),
canonicalize_network_http_url(target_url),
) {
(Some(pattern), Some(target)) =>
pattern.scheme == target.scheme &&
pattern.host == target.host &&
pattern.port == target.port &&
pattern.path == target.path &&
pattern.query == target.query
_ => pattern_url == target_url
}
}
///|
fn network_object_url_pattern_matches(
pattern : Map[String, Json],
target_url : String,
) -> Bool {
let target = match canonicalize_network_http_url(target_url) {
Some(target) => target
None => return false
}
let target_path_token = if target.path.has_prefix("/") {
target.path.unsafe_substring(start=1, end=target.path.length())
} else {
target.path
}
match pattern.get("protocol") {
Some(String(protocol)) =>
if normalize_network_pattern_protocol(protocol) != target.scheme {
return false
}
_ => ()
}
match pattern.get("hostname") {
Some(String(hostname)) =>
if hostname.to_lower() != target.host {
return false
}
_ => ()
}
match pattern.get("port") {
Some(String(port)) => if port != target.port.to_string() { return false }
_ => ()
}
match pattern.get("pathname") {
Some(String(pathname)) =>
if pathname == "" {
if target.path != "" && target.path != "/" {
return false
}
} else if pathname.has_prefix("/") {
if target.path != pathname {
return false
}
} else if target_path_token != pathname {
return false
}
_ => ()
}
match pattern.get("search") {
Some(String(search)) =>
if search == "" {
if target.query != "" {
return false
}
} else if target.query != search {
return false
}
_ => ()
}
true
}
///|
pub fn canonicalize_network_http_url(url : String) -> CanonicalNetworkHttpUrl? {
let scheme = match extract_network_url_scheme(url) {
Some(scheme) =>
if scheme == "http" || scheme == "https" {
scheme
} else {
return None
}
None => return None
}
let authority = match extract_http_url_authority(url) {
Some(authority) => authority
None => return None
}
let host_port = extract_authority_host_port(authority)
let (host, port) = match parse_network_host_and_port(host_port, scheme) {
Some(result) => result
None => return None
}
let path = extract_http_url_path(url).unwrap_or("/")
let query = extract_network_url_query(url)
Some({ scheme, host, port, path, query })
}
///|
fn extract_network_url_scheme(url : String) -> String? {
match find_substring(url, "://", 0) {
Some(idx) => Some(url.unsafe_substring(start=0, end=idx).to_lower())
None => None
}
}
///|
fn parse_network_host_and_port(
host_port : String,
scheme : String,
) -> (String, Int)? {
if host_port == "" {
return None
}
let chars = host_port.to_array()
let mut colon_idx = -1
let mut colon_count = 0
for i = 0; i < chars.length(); i = i + 1 {
if chars[i] == ':' {
colon_idx = i
colon_count += 1
}
}
if colon_count > 1 {
return None
}
if colon_idx < 0 {
return Some((host_port.to_lower(), default_port_for_network_scheme(scheme)))
}
let host = host_port.unsafe_substring(start=0, end=colon_idx)
let port_text = host_port.unsafe_substring(
start=colon_idx + 1,
end=host_port.length(),
)
let port = match parse_decimal_string(port_text) {
Some(port) => port
None => return None
}
if host == "" || port < 0 || port > 65535 {
return None
}
Some((host.to_lower(), port))
}
///|
fn default_port_for_network_scheme(scheme : String) -> Int {
if scheme == "https" {
443
} else {
80
}
}
///|
pub fn extract_network_url_query(url : String) -> String {
let without_fragment = strip_url_fragment(url)
match find_substring(without_fragment, "?", 0) {
Some(idx) =>
without_fragment.unsafe_substring(
start=idx + 1,
end=without_fragment.length(),
)
None => ""
}
}
///|
fn normalize_network_pattern_protocol(protocol : String) -> String {
if protocol.has_suffix(":") {
protocol.unsafe_substring(start=0, end=protocol.length() - 1).to_lower()
} else {
protocol.to_lower()
}
}
///|
fn extract_http_url_authority(url : String) -> String? {
match find_substring(url, "://", 0) {
Some(scheme_idx) => {
let host_start = scheme_idx + 3
let chars = url.to_array()
let mut host_end = chars.length()
for i = host_start; i < chars.length(); i = i + 1 {
let ch = chars[i]
if ch == '/' || ch == '?' || ch == '#' {
host_end = i
break
}
}
if host_start >= host_end {
None
} else {
Some(url.unsafe_substring(start=host_start, end=host_end))
}
}
None => None
}
}
///|
fn extract_authority_host_port(authority : String) -> String {
let chars = authority.to_array()
let mut at_index = -1
for i = 0; i < chars.length(); i = i + 1 {
if chars[i] == '@' {
at_index = i
}
}
if at_index < 0 {
return authority
}
authority.unsafe_substring(start=at_index + 1, end=authority.length())
}
///|
fn extract_http_url_path(url : String) -> String? {
match find_substring(url, "://", 0) {
Some(scheme_idx) => {
let host_start = scheme_idx + 3
let chars = url.to_array()
let mut path_start = chars.length()
for i = host_start; i < chars.length(); i = i + 1 {
let ch = chars[i]
if ch == '/' {
path_start = i
break
}
if ch == '?' || ch == '#' {
return Some("/")
}
}
if path_start >= chars.length() {
return Some("/")
}
let mut path_end = chars.length()
for i = path_start; i < chars.length(); i = i + 1 {
let ch = chars[i]
if ch == '?' || ch == '#' {
path_end = i
break
}
}
Some(url.unsafe_substring(start=path_start, end=path_end))
}
None => None
}
}
///|
fn strip_url_fragment(url : String) -> String {
match find_substring(url, "#", 0) {
Some(idx) => url.unsafe_substring(start=0, end=idx)
None => url
}
}
///|
fn find_substring(haystack : String, needle : String, start : Int) -> Int? {
if needle.length() == 0 || start < 0 || start >= haystack.length() {
return None
}
let hay_chars = haystack.to_array()
let needle_chars = needle.to_array()
if needle_chars.length() > hay_chars.length() {
return None
}
for i = start; i <= hay_chars.length() - needle_chars.length(); i = i + 1 {
let mut matched = true
for j = 0; j < needle_chars.length(); j = j + 1 {
if hay_chars[i + j] != needle_chars[j] {
matched = false
break
}
}
if matched {
return Some(i)
}
}
None
}
///|
fn parse_decimal_string(value : String) -> Int? {
if value.length() == 0 {
return None
}
let mut parsed = 0
for c in value.iter() {
if c < '0' || c > '9' {
return None
}
parsed = parsed * 10 + (c.to_int() - '0'.to_int())
}
Some(parsed)
}
///|
fn array_contains_string(items : Array[String], target : String) -> Bool {
for item in items {
if item == target {
return true
}
}
false
}