///|
pub struct FieldCollector {
limits : FieldCollectorLimits
fields : Array[FormField]
mut active_meta : @model.PartMeta?
body : @buffer.Buffer
mut total_field_bytes : Int64
mut message_ended : Bool
}
///|
fn validate_collector_limits(
limits : FieldCollectorLimits,
) -> Unit raise FormError {
if limits.max_fields <= 0 {
raise InvalidCollectorLimits(reason="max_fields must be positive")
}
if limits.max_field_bytes <= 0L || limits.max_total_field_bytes <= 0L {
raise InvalidCollectorLimits(reason="field byte limits must be positive")
}
}
///|
pub fn FieldCollector::new(
limits : FieldCollectorLimits,
) -> FieldCollector raise FormError {
validate_collector_limits(limits)
{
limits,
fields: [],
active_meta: None,
body: Buffer(size_hint=256),
total_field_bytes: 0L,
message_ended: false,
}
}
///|
fn FieldCollector::begin(
self : FieldCollector,
meta : @model.PartMeta,
) -> Unit raise FormError {
if self.message_ended || self.active_meta is Some(_) {
raise UnexpectedEvent(
reason="PartBegin is not valid in the current collector state",
)
}
if meta.filename is Some(_) {
raise FilePartRejected(part_index=meta.index)
}
let observed = self.fields.length() + 1
if observed > self.limits.max_fields {
raise FieldCountExceeded(limit=self.limits.max_fields, observed~)
}
self.active_meta = Some(meta)
self.body.reset()
}
///|
fn FieldCollector::append(
self : FieldCollector,
bytes : Bytes,
) -> Unit raise FormError {
match self.active_meta {
None => raise UnexpectedEvent(reason="PartData without an active field")
Some(_) => ()
}
let observed_field = self.body.length().to_int64() + bytes.length().to_int64()
if observed_field > self.limits.max_field_bytes {
raise FieldLimitExceeded(
limit=self.limits.max_field_bytes,
observed=observed_field,
)
}
let observed_total = self.total_field_bytes + bytes.length().to_int64()
if observed_total > self.limits.max_total_field_bytes {
raise FieldLimitExceeded(
limit=self.limits.max_total_field_bytes,
observed=observed_total,
)
}
self.body.write_bytes(bytes)
self.total_field_bytes = observed_total
}
///|
fn FieldCollector::end(
self : FieldCollector,
index : Int,
) -> Unit raise FormError {
let meta = match self.active_meta {
Some(meta) => meta
None => raise UnexpectedEvent(reason="PartEnd without an active field")
}
if meta.index != index {
raise UnexpectedEvent(
reason="PartEnd index does not match the active field",
)
}
self.fields.push({ meta, value: self.body.to_bytes() })
self.body.reset()
self.active_meta = None
}
///|
/// Consume parser events without buffering file parts.
pub fn FieldCollector::consume(
self : FieldCollector,
events : Array[@model.MultipartEvent],
) -> Unit raise FormError {
for event in events {
match event {
PartBegin(meta) => self.begin(meta)
PartData(bytes) => self.append(bytes)
PartEnd(index) => self.end(index)
MessageEnd => {
if self.active_meta is Some(_) {
raise UnexpectedEvent(reason="MessageEnd before PartEnd")
}
self.message_ended = true
}
}
}
}
///|
/// Return an owned snapshot after the parser has emitted `MessageEnd`.
pub fn FieldCollector::finish(
self : FieldCollector,
) -> Array[FormField] raise FormError {
if !self.message_ended || self.active_meta is Some(_) {
raise UnexpectedEvent(reason="collector cannot finish before MessageEnd")
}
self.fields.copy()
}
///|
fn ascii_decode(value : Bytes, field_name : String) -> String raise FormError {
for byte in value {
if byte.to_int() > 127 {
raise InvalidTextEncoding(field_name~, charset="us-ascii")
}
}
@utf8.decode(value) catch {
_ => raise InvalidTextEncoding(field_name~, charset="us-ascii")
}
}
///|
fn decode_field(
field : FormField,
charset : String,
) -> DecodedField raise FormError {
let normalized = charset.to_lower()
let value = if normalized == "utf-8" || normalized == "utf8" {
@utf8.decode(field.value) catch {
_ =>
raise InvalidTextEncoding(
field_name=field.meta.name,
charset=normalized,
)
}
} else if normalized == "us-ascii" || normalized == "ascii" {
ascii_decode(field.value, field.meta.name)
} else {
raise InvalidTextEncoding(field_name=field.meta.name, charset=normalized)
}
{ name: field.meta.name, value, content_type: field.meta.content_type }
}
///|
/// Decode collected fields using an explicit default or a `_charset_` field.
/// Only UTF-8 and US-ASCII are accepted by the strict decoding policy.
pub fn FieldCollector::decoded_fields(
self : FieldCollector,
default_charset? : String = "utf-8",
) -> Array[DecodedField] raise FormError {
let mut charset = default_charset
for field in self.fields {
if field.meta.name == "_charset_" {
charset = ascii_decode(field.value, field.meta.name)
.trim()
.to_owned()
.to_lower()
}
}
let decoded : Array[DecodedField] = []
for field in self.fields {
if field.meta.name != "_charset_" {
decoded.push(decode_field(field, charset))
}
}
decoded
}