use crate::ContentFormat;
use crate::field_value::Charset;
use std::any::Any;
pub type Convert = fn(Vec<u8>, Charset) -> Option<Box<dyn Any>>;
#[derive(Clone, Copy)]
pub enum ItemKind {
Scalar(Convert),
Upload,
Group(&'static GroupDeclaration),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Container {
Required,
Optional,
Array(usize),
OptionalArray(usize),
}
impl Container {
pub const fn count_limit(self) -> Option<usize> {
match self {
Self::Array(limit) | Self::OptionalArray(limit) => Some(limit),
Self::Required | Self::Optional => None,
}
}
}
pub struct FieldDeclaration {
pub name: &'static str,
pub kind: ItemKind,
pub container: Container,
pub size_limit: Option<usize>,
pub accept: &'static [&'static str],
}
impl FieldDeclaration {
pub(crate) fn size_limit(&self) -> usize {
match (self.size_limit, self.kind) {
(Some(limit), _) => limit,
(None, ItemKind::Upload) => UPLOAD_SIZE_LIMIT,
(None, _) => FIELD_SIZE_LIMIT,
}
}
fn multiplicity(&self) -> usize {
self.container.count_limit().unwrap_or(1)
}
}
pub struct GroupDeclaration {
pub fields: &'static [FieldDeclaration],
}
impl GroupDeclaration {
pub(crate) fn position(&self, name: &str) -> Option<usize> {
self.fields.iter().position(|field| field.name == name)
}
}
pub const FIELD_SIZE_LIMIT: usize = 64 * 1024;
pub const UPLOAD_SIZE_LIMIT: usize = 128 * 1024 * 1024;
pub const COUNT_LIMIT: usize = 128;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Limits {
pub depth_limit: usize,
pub content_size_limit: usize,
pub part_count_limit: usize,
pub header_size_limit: usize,
pub header_count_limit: usize,
pub preamble_size_limit: usize,
pub epilogue_size_limit: usize,
pub pair_count_limit: usize,
pub url_encoded_size_limit: usize,
}
impl Limits {
pub const DEFAULT: Self = Self {
depth_limit: 8,
content_size_limit: 256 * 1024 * 1024,
part_count_limit: 128,
header_size_limit: 8 * 1024,
header_count_limit: 64,
preamble_size_limit: 64 * 1024,
epilogue_size_limit: 1024,
pair_count_limit: 1024,
url_encoded_size_limit: 2 * 1024 * 1024,
};
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Formats {
pub multipart_form_data: bool,
pub url_encoded: bool,
}
impl Formats {
pub const ALL: Self = Self {
multipart_form_data: true,
url_encoded: true,
};
pub(crate) fn accepts(self, format: ContentFormat) -> bool {
match format {
ContentFormat::MultipartFormData => self.multipart_form_data,
ContentFormat::UrlEncoded => self.url_encoded,
}
}
}
pub struct ContentDeclaration {
pub group: &'static GroupDeclaration,
pub limits: Limits,
pub formats: Formats,
}
pub const fn depth(group: &GroupDeclaration) -> usize {
let mut deepest = 0;
let mut index = 0;
while index < group.fields.len() {
let field = &group.fields[index];
let mut depth = 1;
if field.container.count_limit().is_some() {
depth += 1;
}
if let ItemKind::Group(group) = field.kind {
depth += self::depth(group);
}
if depth > deepest {
deepest = depth;
}
index += 1;
}
deepest
}
const BOUNDARY_LINE_SIZE: usize = 2 + 2 + 70 + 2;
const CLOSING_LINE_SIZE: usize = 2 + 2 + 70 + 2 + 2;
const EMPTY_LINE_SIZE: usize = 2;
impl ContentDeclaration {
pub(crate) fn body_size_limit(&self, format: ContentFormat) -> Option<usize> {
if !self.formats.accepts(format) {
return None;
}
Some(match format {
ContentFormat::MultipartFormData => self.multipart_body_size_limit(),
ContentFormat::UrlEncoded => self.url_encoded_body_size_limit(),
})
}
pub(crate) fn longest_name(&self) -> usize {
let mut pairs = Vec::new();
url_encoded_pairs(self.group, 0, 1, &mut pairs);
pairs.iter().map(|pair| pair.name).max().unwrap_or(0)
}
fn multipart_body_size_limit(&self) -> usize {
let limits = &self.limits;
let parts = limits.part_count_limit.min(item_count(self.group));
let part = BOUNDARY_LINE_SIZE
.saturating_add(limits.header_size_limit)
.saturating_add(EMPTY_LINE_SIZE);
let content = limits.content_size_limit.min(content_size(self.group));
limits
.preamble_size_limit
.saturating_add(parts.saturating_mul(part))
.saturating_add(content)
.saturating_add(CLOSING_LINE_SIZE)
.saturating_add(limits.epilogue_size_limit)
}
fn url_encoded_body_size_limit(&self) -> usize {
let mut pairs = Vec::new();
url_encoded_pairs(self.group, 0, 1, &mut pairs);
pairs.sort_unstable_by_key(|pair| std::cmp::Reverse(pair.size));
let (mut remaining, mut size) = (self.limits.pair_count_limit, 0usize);
for pair in pairs {
let count = pair.multiplicity.min(remaining);
size = size.saturating_add(pair.size.saturating_mul(count));
remaining -= count;
}
size.min(self.limits.url_encoded_size_limit)
}
}
fn item_count(group: &GroupDeclaration) -> usize {
group.fields.iter().fold(0, |count, field| {
let items = match field.kind {
ItemKind::Group(group) => item_count(group),
ItemKind::Scalar(_) | ItemKind::Upload => 1,
};
count.saturating_add(field.multiplicity().saturating_mul(items))
})
}
fn content_size(group: &GroupDeclaration) -> usize {
group.fields.iter().fold(0, |size, field| {
let item = match field.kind {
ItemKind::Group(group) => content_size(group),
ItemKind::Scalar(_) | ItemKind::Upload => field.size_limit(),
};
size.saturating_add(field.multiplicity().saturating_mul(item))
})
}
struct Pair {
name: usize,
size: usize,
multiplicity: usize,
}
fn url_encoded_pairs(
group: &GroupDeclaration,
prefix: usize,
multiplicity: usize,
pairs: &mut Vec<Pair>,
) {
for field in group.fields {
let brackets = if prefix == 0 { 0 } else { 2 };
let array = if field.container.count_limit().is_some() {
2
} else {
0
};
let name = prefix + field.name.len() + brackets + array;
let multiplicity = multiplicity.saturating_mul(field.multiplicity());
match field.kind {
ItemKind::Group(group) => url_encoded_pairs(group, name, multiplicity, pairs),
ItemKind::Scalar(_) | ItemKind::Upload => {
let size = name
.saturating_mul(3)
.saturating_add(field.size_limit().saturating_mul(3))
.saturating_add(2);
pairs.push(Pair {
name,
size,
multiplicity,
});
}
}
}
}