///|
pub fn canonical_charset(input : StringView) -> String {
let value = clean(input).to_lower().replace_all(old="_", new="-")
match value {
"utf8" => "utf-8"
"unicode-1-1-utf-8" => "utf-8"
"latin1" => "iso-8859-1"
"latin-1" => "iso-8859-1"
"iso8859-1" => "iso-8859-1"
"usascii" => "us-ascii"
"ascii" => "us-ascii"
"cp1252" => "windows-1252"
"win-1252" => "windows-1252"
"shift-jis" => "shift_jis"
other => other
}
}
///|
fn parse_charset_range_item(
raw : String,
order : Int,
) -> Result[CharsetRange, ParseError] {
let pieces = split_quoted(raw, ';')
let charset = clean(pieces[0][:])
if charset.is_empty() {
return Err(EmptyItem(order))
}
let canonical = if charset == "*" {
"*"
} else {
canonical_charset(charset[:])
}
if canonical != "*" && !is_token(canonical) {
return Err(InvalidToken(order, "charset", canonical))
}
let mut q = 1000
for i in 1..
if param.name == "q" {
match parse_q(param.value, order) {
Ok(parsed) => q = parsed
Err(err) => return Err(err)
}
} else {
return Err(InvalidParameter(order, piece))
}
Err(err) => return Err(err)
}
}
}
Ok({ charset: canonical, q, order, raw: clean(raw[:]) })
}
///|
fn parse_charset_offer_item(
raw : String,
index : Int,
) -> Result[CharsetOffer, ParseError] {
let charset = clean(raw[:])
if charset.is_empty() || charset == "*" {
return Err(InvalidOffer(index, raw))
}
let canonical = canonical_charset(charset[:])
if !is_token(canonical) {
return Err(InvalidToken(index, "charset", canonical))
}
Ok({ charset, canonical, index })
}
///|
pub fn parse_accept_charset(
header : StringView,
) -> Result[Array[CharsetRange], ParseError] {
let text = clean(header)
let ranges : Array[CharsetRange] = []
if text.is_empty() {
return Ok(ranges)
}
let parts = split_quoted(text, ',')
for i in 0.. ranges.push(range)
Err(err) => return Err(err)
}
}
Ok(ranges)
}
///|
pub fn parse_charset(value : StringView) -> Result[CharsetOffer, ParseError] {
parse_charset_offer_item(clean(value), 0)
}
///|
fn default_charset_range() -> CharsetRange {
{ charset: "*", q: 1000, order: 0, raw: "*" }
}
///|
fn charset_specificity(range : CharsetRange) -> Int {
if range.charset == "*" {
0
} else {
1
}
}
///|
fn charset_range_matches(range : CharsetRange, offer : CharsetOffer) -> Bool {
range.charset == "*" || range.charset == offer.canonical
}
///|
fn normalize_charset_ranges(
ranges : Array[CharsetRange],
) -> Array[CharsetRange] {
if ranges.length() == 0 {
[default_charset_range()]
} else {
ranges
}
}
///|
fn format_charset_range(range : CharsetRange) -> String {
let buf = StringBuilder()
buf.write_string(range.charset)
if range.q != 1000 {
buf.write_string(";q=")
buf.write_string(quality_to_string(range.q))
}
buf.to_string()
}
///|
fn charset_reason(
offer : CharsetOffer,
range : CharsetRange,
specificity : Int,
) -> String {
let buf = StringBuilder()
buf.write_string(offer.canonical)
buf.write_string(" matched ")
buf.write_string(format_charset_range(range))
buf.write_string(" with q=")
buf.write_string(quality_to_string(range.q))
buf.write_string(", specificity=")
buf.write_string(specificity.to_string())
if offer.charset != offer.canonical {
buf.write_string(", alias=")
buf.write_string(offer.charset)
}
buf.to_string()
}
///|
fn better_charset(
candidate : CharsetCandidate,
current : CharsetCandidate,
) -> Bool {
if candidate.quality != current.quality {
candidate.quality > current.quality
} else if candidate.specificity != current.specificity {
candidate.specificity > current.specificity
} else if candidate.matched_range.order != current.matched_range.order {
candidate.matched_range.order < current.matched_range.order
} else {
candidate.offer.index < current.offer.index
}
}
///|
fn better_charset_range_for_offer(
candidate : CharsetRange,
current : CharsetRange,
) -> Bool {
let candidate_specificity = charset_specificity(candidate)
let current_specificity = charset_specificity(current)
if candidate_specificity != current_specificity {
candidate_specificity > current_specificity
} else {
candidate.order < current.order
}
}
///|
fn effective_charset_range(
offer : CharsetOffer,
ranges : Array[CharsetRange],
) -> CharsetRange? {
let mut best : CharsetRange? = None
for range in ranges {
if charset_range_matches(range, offer) {
if (match best {
None => true
Some(current) => better_charset_range_for_offer(range, current)
}) {
best = Some(range)
}
}
}
best
}
///|
fn charset_explanation(best : CharsetCandidate?) -> String {
match best {
Some(candidate) => {
let buf = StringBuilder()
buf.write_string("selected ")
buf.write_string(candidate.offer.canonical)
buf.write_string(": ")
buf.write_string(candidate.reason)
buf.to_string()
}
None => "no acceptable charset matched"
}
}
///|
pub fn negotiate_charset(
accept_charset : StringView,
offers : Array[String],
) -> Result[CharsetDecision, ParseError] {
let ranges = match parse_accept_charset(accept_charset) {
Ok(parsed) => normalize_charset_ranges(parsed)
Err(err) => return Err(err)
}
let matches : Array[CharsetCandidate] = []
let mut best : CharsetCandidate? = None
for offer_index in 0.. offer
Err(err) => return Err(err)
}
match effective_charset_range(offer, ranges) {
Some(range) =>
if range.q > 0 {
let specificity = charset_specificity(range)
let candidate : CharsetCandidate = {
offer,
matched_range: range,
quality: range.q,
specificity,
reason: charset_reason(offer, range, specificity),
}
matches.push(candidate)
if (match best {
None => true
Some(current) => better_charset(candidate, current)
}) {
best = Some(candidate)
}
}
None => ()
}
}
let selected = match best {
Some(candidate) => Some(candidate.offer.canonical)
None => None
}
let quality = match best {
Some(candidate) => candidate.quality
None => 0
}
Ok({
selected,
quality,
candidate: best,
matches,
explanation: charset_explanation(best),
})
}
///|
pub fn charset_is_acceptable(
accept_charset : StringView,
charset : StringView,
) -> Result[Bool, ParseError] {
match negotiate_charset(accept_charset, [charset.to_owned()]) {
Ok(decision) => Ok(decision.selected is Some(_))
Err(err) => Err(err)
}
}
///|
pub fn format_charset_decision(decision : CharsetDecision) -> String {
let buf = StringBuilder()
buf.write_string(decision.explanation)
buf.write_string(" (matches=")
buf.write_string(decision.matches.length().to_string())
buf.write_char(')')
buf.to_string()
}