///|
fn audit_new() -> HeaderAudit {
{ ok: true, warnings: [], errors: [] }
}
///|
fn audit_warn(audit : HeaderAudit, message : String) -> HeaderAudit {
let warnings : Array[String] = []
for item in audit.warnings {
warnings.push(item)
}
warnings.push(message)
{ ok: audit.ok, warnings, errors: audit.errors }
}
///|
fn audit_error(audit : HeaderAudit, message : String) -> HeaderAudit {
let errors : Array[String] = []
for item in audit.errors {
errors.push(item)
}
errors.push(message)
{ ok: false, warnings: audit.warnings, errors }
}
///|
fn append_audit(left : HeaderAudit, right : HeaderAudit) -> HeaderAudit {
let warnings : Array[String] = []
let errors : Array[String] = []
for item in left.warnings {
warnings.push(item)
}
for item in right.warnings {
warnings.push(item)
}
for item in left.errors {
errors.push(item)
}
for item in right.errors {
errors.push(item)
}
{ ok: left.ok && right.ok, warnings, errors }
}
///|
fn parse_error_label(err : ParseError) -> String {
match err {
EmptyItem(index) => "empty item at " + index.to_string()
MissingSlash(index, value) =>
"missing slash at " + index.to_string() + ": " + value
InvalidToken(index, kind, value) =>
"invalid " + kind + " at " + index.to_string() + ": " + value
InvalidParameter(index, value) =>
"invalid parameter at " + index.to_string() + ": " + value
InvalidQ(index, value) => "invalid q at " + index.to_string() + ": " + value
InvalidWildcard(index, value) =>
"invalid wildcard at " + index.to_string() + ": " + value
InvalidOffer(index, value) =>
"invalid offer at " + index.to_string() + ": " + value
InvalidLanguageTag(index, value) =>
"invalid language tag at " + index.to_string() + ": " + value
}
}
///|
pub fn audit_accept(header : StringView) -> HeaderAudit {
match parse_accept(header) {
Ok(ranges) => {
let mut audit = audit_new()
if ranges.length() == 0 {
audit = audit_warn(
audit, "Accept is empty; server order will decide media",
)
}
for range in ranges {
if range.q == 0 {
audit = audit_warn(
audit,
"media range " + range.raw + " is explicitly refused",
)
}
if range.top == "*" {
audit = audit_warn(
audit, "media wildcard */* may hide client preference",
)
} else if range.sub == "*" {
audit = audit_warn(
audit,
"subtype wildcard " + range.raw + " is broad",
)
}
if range.params.length() > 0 && range.top == "*" {
audit = audit_warn(
audit, "parameters on wildcard media range are unlikely to match",
)
}
}
audit
}
Err(err) =>
audit_error(audit_new(), "Accept parse error: " + parse_error_label(err))
}
}
///|
pub fn audit_accept_language(header : StringView) -> HeaderAudit {
match parse_accept_language(header) {
Ok(ranges) => {
let mut audit = audit_new()
if ranges.length() == 0 {
audit = audit_warn(
audit, "Accept-Language is empty; server order will decide language",
)
}
for range in ranges {
if range.q == 0 {
audit = audit_warn(
audit,
"language range " + range.raw + " is explicitly refused",
)
}
if range.primary == "*" {
audit = audit_warn(audit, "language wildcard allows every locale")
}
if range.subtags.length() > 3 {
audit = audit_warn(
audit,
"language range " + range.raw + " is very specific",
)
}
}
audit
}
Err(err) =>
audit_error(
audit_new(),
"Accept-Language parse error: " + parse_error_label(err),
)
}
}
///|
pub fn audit_accept_encoding(header : StringView) -> HeaderAudit {
match parse_accept_encoding(header) {
Ok(ranges) => {
let mut audit = audit_new()
if ranges.length() == 0 {
audit = audit_warn(
audit, "Accept-Encoding is empty; any coding is acceptable",
)
}
let mut identity_refused = false
for range in ranges {
if range.coding == "identity" && range.q == 0 {
identity_refused = true
}
if range.coding == "*" {
audit = audit_warn(
audit, "encoding wildcard may select an unexpected compression",
)
}
}
if identity_refused {
audit = audit_warn(
audit, "identity coding is refused; uncompressed fallback is disabled",
)
}
audit
}
Err(err) =>
audit_error(
audit_new(),
"Accept-Encoding parse error: " + parse_error_label(err),
)
}
}
///|
pub fn audit_accept_charset(header : StringView) -> HeaderAudit {
match parse_accept_charset(header) {
Ok(ranges) => {
let mut audit = audit_new()
if ranges.length() == 0 {
audit = audit_warn(
audit, "Accept-Charset is empty; any charset is acceptable",
)
}
for range in ranges {
if range.charset == "*" {
audit = audit_warn(audit, "charset wildcard allows every charset")
}
if range.q == 0 {
audit = audit_warn(
audit,
"charset " + range.charset + " is explicitly refused",
)
}
}
audit
}
Err(err) =>
audit_error(
audit_new(),
"Accept-Charset parse error: " + parse_error_label(err),
)
}
}
///|
pub fn audit_vary_header(header : StringView) -> HeaderAudit {
match parse_vary(header) {
Ok(fields) => {
let mut audit = audit_new()
if fields.length() == 0 {
audit = audit_warn(
audit, "Vary is empty; cache key will not include request headers",
)
}
if fields.length() == 1 && fields[0] == "*" {
audit = audit_warn(audit, "Vary: * disables shared cache reuse")
}
for field in fields {
if field == "User-Agent" {
audit = audit_warn(
audit, "Vary: User-Agent can create very large cache keys",
)
}
if field == "Cookie" {
audit = audit_warn(
audit, "Vary: Cookie often prevents effective shared caching",
)
}
}
audit
}
Err(err) =>
audit_error(audit_new(), "Vary parse error: " + parse_error_label(err))
}
}
///|
pub fn audit_cache_control_header(header : StringView) -> HeaderAudit {
match parse_cache_control(header) {
Ok(directives) => {
let summary = match summarize_cache_control(directives) {
Ok(summary) => summary
Err(err) =>
return audit_error(
audit_new(),
"Cache-Control summary error: " + parse_error_label(err),
)
}
let mut audit = audit_new()
if summary.no_store && summary.max_age is Some(_) {
audit = audit_warn(audit, "no-store with max-age is contradictory")
}
if summary.is_public && summary.is_private {
audit = audit_warn(audit, "public and private are both present")
}
if summary.no_cache && summary.immutable {
audit = audit_warn(audit, "no-cache and immutable are both present")
}
if summary.unknown.length() > 0 {
audit = audit_warn(
audit,
"unknown cache directives: " + summary.unknown.length().to_string(),
)
}
audit
}
Err(err) =>
audit_error(
audit_new(),
"Cache-Control parse error: " + parse_error_label(err),
)
}
}
///|
pub fn audit_preferences(preferences : RequestPreferences) -> HeaderAudit {
append_audit(
append_audit(
audit_accept(preferences.accept[:]),
audit_accept_language(preferences.accept_language[:]),
),
append_audit(
audit_accept_encoding(preferences.accept_encoding[:]),
audit_accept_charset(preferences.accept_charset[:]),
),
)
}
///|
pub fn format_audit(audit : HeaderAudit) -> String {
let buf = StringBuilder()
buf.write_string("ok=")
buf.write_string(audit.ok.to_string())
buf.write_string(", warnings=")
buf.write_string(audit.warnings.length().to_string())
buf.write_string(", errors=")
buf.write_string(audit.errors.length().to_string())
if audit.warnings.length() > 0 {
buf.write_string(", first-warning=")
buf.write_string(audit.warnings[0])
}
if audit.errors.length() > 0 {
buf.write_string(", first-error=")
buf.write_string(audit.errors[0])
}
buf.to_string()
}