///|
pub fn is_multipart_form_data_content_type(header : String) -> Bool {
match MediaType::parse(header) {
Ok(media) => media.typ == "multipart" && media.subtype == "form-data"
Err(_) => false
}
}
///|
pub fn media_type_without_params(
header : String,
) -> Result[String, MultipartError] {
match MediaType::parse(header) {
Ok(media) => Ok(media.typ + "/" + media.subtype)
Err(err) => Err(err)
}
}
///|
pub fn format_media_type(media : MediaType) -> String {
let pieces = Array::new()
pieces.push(media.typ + "/" + media.subtype)
let mut i = 0
while i < media.params.length() {
let (name, value) = media.params[i]
pieces.push(name + "=" + quote_header_value(value))
i = i + 1
}
pieces.join("; ")
}
///|
pub fn format_content_disposition(disposition : ContentDisposition) -> String {
let pieces = Array::new()
pieces.push(disposition.disposition)
let mut i = 0
while i < disposition.params.length() {
let (name, value) = disposition.params[i]
pieces.push(name + "=" + quote_header_value(value))
i = i + 1
}
pieces.join("; ")
}
///|
pub fn quote_header_value(value : String) -> String {
if is_token(value) {
value
} else {
"\"" + escape_quoted(value) + "\""
}
}
///|
pub fn normalize_header_name(name : String) -> String {
let out = Array::new()
let mut upper_next = true
let mut i = 0
while i < name.length() {
let code = name[i]
if code == 45 {
out.push('-')
upper_next = true
} else if upper_next {
out.push(upper_ascii_char(code))
upper_next = false
} else {
out.push(lower_ascii_char(code))
}
i = i + 1
}
String::from_array(out)
}
///|
pub fn header_names(headers : Array[(String, String)]) -> Array[String] {
let names = Array::new()
let mut i = 0
while i < headers.length() {
let (name, _) = headers[i]
names.push(name)
i = i + 1
}
names
}
///|
pub fn has_header(headers : Array[(String, String)], name : String) -> Bool {
header_value(headers, name) is Some(_)
}
///|
pub fn remove_header(
headers : Array[(String, String)],
name : String,
) -> Array[(String, String)] {
let out = Array::new()
let mut i = 0
while i < headers.length() {
let (header_name, value) = headers[i]
if header_name.compare_ignore_ascii_case(name) != 0 {
out.push((header_name, value))
}
i = i + 1
}
out
}
///|
pub fn set_header(
headers : Array[(String, String)],
name : String,
value : String,
) -> Result[Array[(String, String)], MultipartError] {
if !is_safe_header_name(name) {
return Err(InvalidHeader(name))
}
let out = Array::new()
let mut replaced = false
let mut i = 0
while i < headers.length() {
let (header_name, header_value) = headers[i]
if header_name.compare_ignore_ascii_case(name) == 0 {
out.push((header_name, strip_header_linebreaks(value)))
replaced = true
} else {
out.push((header_name, header_value))
}
i = i + 1
}
if !replaced {
out.push((name, strip_header_linebreaks(value)))
}
Ok(out)
}
///|
pub fn parse_content_disposition_header(
value : String,
) -> Result[ContentDisposition, MultipartError] {
ContentDisposition::parse(value)
}
///|
pub fn parse_media_type_header(
value : String,
) -> Result[MediaType, MultipartError] {
MediaType::parse(value)
}
///|
pub fn filename_from_disposition(disposition : ContentDisposition) -> String? {
match disposition.param("filename*") {
Some(value) =>
match decode_rfc5987_value(value) {
Ok(decoded) => Some(safe_filename(decoded))
Err(_) =>
match disposition.param("filename") {
Some(fallback) => Some(safe_filename(fallback))
None => None
}
}
None =>
match disposition.param("filename") {
Some(value) => Some(safe_filename(value))
None => None
}
}
}
///|
pub fn decode_rfc5987_value(value : String) -> Result[String, MultipartError] {
match value.split_once("''") {
Some((charset_view, encoded_view)) => {
let charset = to_lower_ascii(charset_view.to_owned())
if charset != "utf-8" && charset != "us-ascii" {
return Err(InvalidContentDisposition(value))
}
decode_percent_ascii(encoded_view.to_owned())
}
None => decode_percent_ascii(value)
}
}
///|
pub fn encode_rfc5987_value(value : String) -> String {
"UTF-8''" + percent_encode_header_value(value)
}
///|
pub fn percent_encode_header_value(value : String) -> String {
let out = Array::new()
let mut i = 0
while i < value.length() {
let code = value[i]
if is_rfc5987_attr_char(code) {
out.push(char_at(value, i))
} else {
out.push('%')
out.push(hex_digit((code.to_int() >> 4) & 15))
out.push(hex_digit(code.to_int() & 15))
}
i = i + 1
}
String::from_array(out)
}
///|
pub fn decode_percent_ascii(value : String) -> Result[String, MultipartError] {
let out = Array::new()
let mut i = 0
while i < value.length() {
let code = value[i]
if code == 37 {
if i + 2 >= value.length() {
return Err(InvalidContentDisposition(value))
}
let hi = hex_value(value[i + 1])
let lo = hex_value(value[i + 2])
if hi < 0 || lo < 0 {
return Err(InvalidContentDisposition(value))
}
out.push((hi * 16 + lo : Int).unsafe_to_char())
i = i + 3
} else {
out.push(char_at(value, i))
i = i + 1
}
}
Ok(String::from_array(out))
}
///|
fn hex_value(code : UInt16) -> Int {
if code >= 48 && code <= 57 {
code.to_int() - 48
} else if code >= 65 && code <= 70 {
code.to_int() - 55
} else if code >= 97 && code <= 102 {
code.to_int() - 87
} else {
-1
}
}
///|
fn hex_digit(value : Int) -> Char {
if value >= 0 && value <= 9 {
(value + 48).unsafe_to_char()
} else {
(value - 10 + 65).unsafe_to_char()
}
}
///|
fn upper_ascii_char(code : UInt16) -> Char {
if code >= 97 && code <= 122 {
(code.to_int() - 32).unsafe_to_char()
} else {
code.unsafe_to_char()
}
}
///|
fn lower_ascii_char(code : UInt16) -> Char {
if code >= 65 && code <= 90 {
(code.to_int() + 32).unsafe_to_char()
} else {
code.unsafe_to_char()
}
}
///|
fn is_rfc5987_attr_char(code : UInt16) -> Bool {
(code >= 48 && code <= 57) ||
(code >= 65 && code <= 90) ||
(code >= 97 && code <= 122) ||
code == 33 ||
code == 35 ||
code == 36 ||
code == 38 ||
code == 43 ||
code == 45 ||
code == 46 ||
code == 94 ||
code == 95 ||
code == 96 ||
code == 124 ||
code == 126
}