///|
fn parse_non_negative_int(text : String, item : Int) -> Result[Int, ParseError] {
if text.is_empty() {
return Err(InvalidParameter(item, text))
}
let mut value = 0
for c in text.iter() {
match digit_value(c) {
Some(d) => value = value * 10 + d
None => return Err(InvalidParameter(item, text))
}
}
Ok(value)
}
///|
fn parse_cache_directive_item(
raw : String,
order : Int,
) -> Result[CacheDirective, ParseError] {
let piece = clean(raw[:])
if piece.is_empty() {
return Err(EmptyItem(order))
}
match piece.split_once("=") {
Some((name_view, value_view)) => {
let name = clean(name_view).to_lower()
let value = unquote(clean(value_view))
if !is_token(name) {
return Err(InvalidToken(order, "cache directive", name))
}
Ok({ name, value: Some(value), order, raw: piece })
}
None => {
let name = piece.to_lower()
if !is_token(name) {
return Err(InvalidToken(order, "cache directive", name))
}
Ok({ name, value: None, order, raw: piece })
}
}
}
///|
pub fn parse_cache_control(
header : StringView,
) -> Result[Array[CacheDirective], ParseError] {
let text = clean(header)
let directives : Array[CacheDirective] = []
if text.is_empty() {
return Ok(directives)
}
let parts = split_quoted(text, ',')
for i in 0.. directives.push(directive)
Err(err) => return Err(err)
}
}
Ok(directives)
}
///|
pub fn cache_control_has(
directives : Array[CacheDirective],
name : StringView,
) -> Bool {
let wanted = clean(name).to_lower()
for directive in directives {
if directive.name == wanted {
return true
}
}
false
}
///|
pub fn cache_control_value(
directives : Array[CacheDirective],
name : StringView,
) -> String? {
let wanted = clean(name).to_lower()
for directive in directives {
if directive.name == wanted {
return directive.value
}
}
None
}
///|
fn numeric_directive(
directives : Array[CacheDirective],
name : String,
item : Int,
) -> Result[Int?, ParseError] {
match cache_control_value(directives, name[:]) {
Some(value) =>
match parse_non_negative_int(value, item) {
Ok(n) => Ok(Some(n))
Err(err) => Err(err)
}
None => Ok(None)
}
}
///|
fn is_known_cache_directive(name : String) -> Bool {
name == "max-age" ||
name == "s-maxage" ||
name == "no-store" ||
name == "no-cache" ||
name == "private" ||
name == "public" ||
name == "must-revalidate" ||
name == "proxy-revalidate" ||
name == "immutable" ||
name == "stale-while-revalidate" ||
name == "stale-if-error" ||
name == "must-understand" ||
name == "no-transform" ||
name == "only-if-cached"
}
///|
pub fn summarize_cache_control(
directives : Array[CacheDirective],
) -> Result[CachePolicySummary, ParseError] {
let unknown : Array[CacheDirective] = []
for directive in directives {
if !is_known_cache_directive(directive.name) {
unknown.push(directive)
}
}
let max_age = match numeric_directive(directives, "max-age", 0) {
Ok(value) => value
Err(err) => return Err(err)
}
let s_maxage = match numeric_directive(directives, "s-maxage", 0) {
Ok(value) => value
Err(err) => return Err(err)
}
let stale_while_revalidate = match
numeric_directive(directives, "stale-while-revalidate", 0) {
Ok(value) => value
Err(err) => return Err(err)
}
let stale_if_error = match
numeric_directive(directives, "stale-if-error", 0) {
Ok(value) => value
Err(err) => return Err(err)
}
Ok({
no_store: cache_control_has(directives, "no-store"),
no_cache: cache_control_has(directives, "no-cache"),
is_public: cache_control_has(directives, "public"),
is_private: cache_control_has(directives, "private"),
must_revalidate: cache_control_has(directives, "must-revalidate") ||
cache_control_has(directives, "proxy-revalidate"),
immutable: cache_control_has(directives, "immutable"),
max_age,
s_maxage,
stale_while_revalidate,
stale_if_error,
unknown,
})
}
///|
pub fn parse_cache_policy(
header : StringView,
) -> Result[CachePolicySummary, ParseError] {
match parse_cache_control(header) {
Ok(directives) => summarize_cache_control(directives)
Err(err) => Err(err)
}
}
///|
pub fn cache_is_storable(summary : CachePolicySummary) -> Bool {
!summary.no_store
}
///|
pub fn cache_requires_revalidation(summary : CachePolicySummary) -> Bool {
summary.no_cache ||
summary.must_revalidate ||
summary.max_age == Some(0) ||
summary.s_maxage == Some(0)
}
///|
pub fn cache_shared_fresh_lifetime(summary : CachePolicySummary) -> Int? {
match summary.s_maxage {
Some(value) => Some(value)
None => summary.max_age
}
}
///|
pub fn cache_private_fresh_lifetime(summary : CachePolicySummary) -> Int? {
summary.max_age
}
///|
fn write_cache_directive(
buf : StringBuilder,
directive : CacheDirective,
) -> Unit {
buf.write_string(directive.name)
match directive.value {
Some(value) => {
buf.write_char('=')
if value.contains_any(chars=",;\" ") {
buf.write_char('"')
for c in value.iter() {
if c == '"' || c == '\\' {
buf.write_char('\\')
}
buf.write_char(c)
}
buf.write_char('"')
} else {
buf.write_string(value)
}
}
None => ()
}
}
///|
pub fn format_cache_control(directives : Array[CacheDirective]) -> String {
let buf = StringBuilder()
for i in 0.. 0 {
buf.write_string(", ")
}
write_cache_directive(buf, directives[i])
}
buf.to_string()
}
///|
pub fn format_cache_summary(summary : CachePolicySummary) -> String {
let buf = StringBuilder()
buf.write_string("storable=")
buf.write_string(cache_is_storable(summary).to_string())
buf.write_string(", revalidate=")
buf.write_string(cache_requires_revalidation(summary).to_string())
match cache_shared_fresh_lifetime(summary) {
Some(value) => {
buf.write_string(", shared-max-age=")
buf.write_string(value.to_string())
}
None => ()
}
match summary.stale_while_revalidate {
Some(value) => {
buf.write_string(", stale-while-revalidate=")
buf.write_string(value.to_string())
}
None => ()
}
match summary.stale_if_error {
Some(value) => {
buf.write_string(", stale-if-error=")
buf.write_string(value.to_string())
}
None => ()
}
if summary.unknown.length() > 0 {
buf.write_string(", unknown=")
buf.write_string(summary.unknown.length().to_string())
}
buf.to_string()
}