///|
fn mime_token_character(char : UInt16) -> Bool {
let n = char.to_int()
n > 32 &&
n < 127 &&
!"()<>@,;:\\\"/[]?=".contains(char.to_char().unwrap().to_string())
}
///|
fn mime_token_end(text : String, start : Int32) -> Int32 {
for end = start
end < text.length() && mime_token_character(text[end])
end = end + 1 {
continue
} nobreak {
end
}
}
///|
fn skip_mime_space(text : String, start : Int32) -> Int32 {
for end = start
end < text.length() && (text[end] == ' ' || text[end] == '\t')
end = end + 1 {
continue
} nobreak {
end
}
}
///|
fn parse_mime_type(text : String) -> (String, Map[String, String])? {
if text.length() < 3 || text.length() > 255 {
return None
}
for char in text {
let n = char.to_int()
if (n < 32 && n != 9) || n >= 127 {
return None
}
}
let start = skip_mime_space(text, 0)
let slash = mime_token_end(text, start)
if slash == start || slash >= text.length() || text[slash] != '/' {
return None
}
let subtype_end = mime_token_end(text, slash + 1)
if subtype_end == slash + 1 {
return None
}
let base = string_slice(text, start, subtype_end).to_lower()
if base.contains("*") {
return None
}
let parameters : Map[String, String] = Map([])
let mut index = subtype_end
while true {
index = skip_mime_space(text, index)
if index == text.length() {
return Some((base, parameters))
}
if text[index] != ';' {
return None
}
let name_start = skip_mime_space(text, index + 1)
let name_end = mime_token_end(text, name_start)
if name_end == name_start {
return None
}
let name = string_slice(text, name_start, name_end).to_lower()
if parameters.contains(name) {
return None
}
index = skip_mime_space(text, name_end)
if index >= text.length() || text[index] != '=' {
return None
}
index = skip_mime_space(text, index + 1)
if index >= text.length() {
return None
}
let value = if text[index] == '"' {
index += 1
let builder = StringBuilder()
let mut closed = false
while index < text.length() {
let char = text[index]
index += 1
if char == '"' {
closed = true
break
}
if char == '\\' {
if index == text.length() {
return None
}
builder.write_char(text[index].to_char().unwrap())
index += 1
} else {
builder.write_char(char.to_char().unwrap())
}
}
if !closed {
return None
}
builder.to_string()
} else {
let end = mime_token_end(text, index)
if end == index {
return None
}
let value = string_slice(text, index, end)
index = end
value
}
parameters[name] = if name == "charset" { value.to_lower() } else { value }
} nobreak {
None
}
}
///|
/// Accept registered media types with optional MIME parameters, within IPP's
/// 255-byte ASCII bound. Wildcard ranges and duplicate parameters are rejected.
/// ```mbt check
/// test {
/// assert_true(@lib.valid_mime_type("text/plain; charset=UTF-8"))
/// assert_false(@lib.valid_mime_type("text/*"))
/// }
/// ```
pub fn valid_mime_type(text : String) -> Bool {
parse_mime_type(text) is Some(_)
}
///|
fn mime_types_equal(left : String, right : String) -> Bool {
match (parse_mime_type(left), parse_mime_type(right)) {
(Some(a), Some(b)) => a == b
_ => false
}
}