///|
/// Returns the target of the first RFC 8288 Link value whose relation includes
/// `next`, across repeated headers and comma-separated link values.
pub fn parse_link_next(headers : @http.Headers) -> String? {
for value in headers.get_all("link") {
for link in split_link_values(value) {
if parse_next_link(link) is Some(url) {
return Some(url)
}
}
}
None
}
///|
fn split_link_values(value : String) -> Array[String] {
let result = []
let mut part = StringBuilder()
let mut in_angle = false
let mut in_quote = false
let mut escaped = false
for c in value {
if escaped {
part.write_char(c)
escaped = false
} else if in_quote && c == '\\' {
part.write_char(c)
escaped = true
} else if c == '"' && !in_angle {
part.write_char(c)
in_quote = !in_quote
} else if c == '<' && !in_quote {
part.write_char(c)
in_angle = true
} else if c == '>' && !in_quote {
part.write_char(c)
in_angle = false
} else if c == ',' && !in_angle && !in_quote {
result.push(part.to_string())
part = StringBuilder()
} else {
part.write_char(c)
}
}
result.push(part.to_string())
result
}
///|
fn parse_next_link(value : String) -> String? {
let value = value.trim().to_owned()
guard value.has_prefix("<") else { return None }
guard value.find(">") is Some(close) else { return None }
let url = value[1:close].to_owned()
let params = split_link_params(value[close + 1:].to_owned())
for param in params {
guard param.split_once("=") is Some((name, raw)) else { continue }
if name.trim().to_owned().to_lower() != "rel" {
continue
}
let relation = unquote(raw.trim().to_owned())
for word in relation.split(" ") {
let word = word.trim().to_owned()
if !word.is_empty() && word.to_lower() == "next" {
return Some(url)
}
}
}
None
}
///|
fn split_link_params(value : String) -> Array[String] {
let result = []
let mut part = StringBuilder()
let mut in_quote = false
let mut escaped = false
for c in value {
if escaped {
part.write_char(c)
escaped = false
} else if in_quote && c == '\\' {
part.write_char(c)
escaped = true
} else if c == '"' {
part.write_char(c)
in_quote = !in_quote
} else if c == ';' && !in_quote {
result.push(part.to_string())
part = StringBuilder()
} else {
part.write_char(c)
}
}
result.push(part.to_string())
result
}
///|
fn unquote(value : String) -> String {
if value.length() >= 2 && value.has_prefix("\"") && value.has_suffix("\"") {
let result = StringBuilder()
let mut escaped = false
for c in value[1:value.length() - 1] {
if escaped {
result.write_char(c)
escaped = false
} else if c == '\\' {
escaped = true
} else {
result.write_char(c)
}
}
result.to_string()
} else {
value
}
}