///|
/// One media-type parameter after syntactic unquoting.
pub(all) struct Parameter {
name : String
value : String
} derive(Debug, Eq)
///|
/// Parsed multipart/form-data Content-Type.
pub(all) struct MediaType {
media_type : String
boundary : String
parameters : Array[Parameter]
} derive(Debug, Eq)
///|
/// One part header. Header names are normalized to lowercase while values
/// remain untrusted protocol text.
pub(all) struct HeaderField {
name : String
value : String
} derive(Debug, Eq)
///|
/// Metadata emitted before a part body.
///
/// `filename` is untrusted metadata and must never be used as a filesystem
/// path without an application-owned policy.
pub(all) struct PartMeta {
index : Int
name : String
filename : String?
content_type : String?
headers : Array[HeaderField]
} derive(Debug, Eq)
///|
/// Parser and writer resource limits. All limits are hard, fail-closed
/// boundaries; exceeding one must not produce a successful message.
pub(all) struct Limits {
max_total_bytes : Int64
max_parts : Int
max_headers_per_part : Int
max_header_bytes : Int
max_header_line_bytes : Int
max_part_bytes : Int64
} derive(Debug, Eq)
///|
/// Conservative defaults for examples and initial integrations.
///
/// Applications are expected to choose limits appropriate for their threat
/// model before accepting untrusted uploads.
pub fn default_limits() -> Limits {
{
max_total_bytes: 1073741824L,
max_parts: 128,
max_headers_per_part: 32,
max_header_bytes: 32768,
max_header_line_bytes: 8192,
max_part_bytes: 1073741824L,
}
}
///|
/// Validate a resource-limit configuration before it is attached to a parser
/// or writer. This is public to implementation packages but intentionally is
/// not re-exported by the root facade: applications normally construct a
/// `Limits` value and pass it to `MultipartParser::new` or
/// `MultipartWriter::new`.
pub fn validate_limits(limits : Limits) -> Unit raise MultipartError {
if limits.max_total_bytes <= 0L {
raise LimitExceeded(
kind=TotalBytes,
limit=limits.max_total_bytes,
observed=0L,
)
}
if limits.max_parts <= 0 {
raise LimitExceeded(
kind=Parts,
limit=limits.max_parts.to_int64(),
observed=0L,
)
}
if limits.max_headers_per_part <= 0 {
raise LimitExceeded(
kind=HeadersPerPart,
limit=limits.max_headers_per_part.to_int64(),
observed=0L,
)
}
if limits.max_header_bytes <= 0 {
raise LimitExceeded(
kind=HeaderBytes,
limit=limits.max_header_bytes.to_int64(),
observed=0L,
)
}
if limits.max_header_line_bytes <= 0 ||
limits.max_header_line_bytes > limits.max_header_bytes {
raise LimitExceeded(
kind=HeaderLineBytes,
limit=limits.max_header_line_bytes.to_int64(),
observed=0L,
)
}
if limits.max_part_bytes <= 0L {
raise LimitExceeded(
kind=PartBytes,
limit=limits.max_part_bytes,
observed=0L,
)
}
}
///|
/// Resource dimension that exceeded its configured limit.
pub(all) enum LimitKind {
TotalBytes
Parts
HeadersPerPart
HeaderBytes
HeaderLineBytes
PartBytes
} derive(Debug, Eq)
///|
/// Public lifecycle phase used in deterministic errors and diagnostics.
pub(all) enum ParserPhase {
Preamble
Headers
Body
Epilogue
Done
Failed
} derive(Debug, Eq)
///|
/// Ordered, binary-safe parser output.
pub(all) enum MultipartEvent {
PartBegin(PartMeta)
PartData(Bytes)
PartEnd(Int)
MessageEnd
} derive(Debug, Eq)
///|
/// Redacted counters for one parser instance.
///
/// The value deliberately contains only counts and byte sizes. It never
/// stores body bytes, header values, field names, or filenames.
pub(all) struct ParserStats {
bytes_seen : Int64
parts_started : Int
parts_completed : Int
header_bytes : Int64
body_bytes : Int64
events_emitted : Int64
max_pending_body_bytes : Int
max_matcher_retained_bytes : Int
max_buffered_bytes : Int
} derive(Debug, Eq)
///|
/// A stable, redacted view of a protocol error for logs and metrics.
pub(all) struct MultipartDiagnostic {
code : String
phase : ParserPhase?
offset : Int64?
part_index : Int?
limit_kind : LimitKind?
limit : Int64?
observed : Int64?
} derive(Debug, Eq)
///|
/// Exact protocol, resource, and lifecycle failures.
pub(all) suberror MultipartError {
InvalidContentType(reason~ : String)
InvalidBoundary(reason~ : String)
MalformedHeader(part_index~ : Int, reason~ : String)
MissingDisposition(part_index~ : Int)
MissingName(part_index~ : Int)
UnexpectedEof(phase~ : ParserPhase, offset~ : Int64)
LimitExceeded(kind~ : LimitKind, limit~ : Int64, observed~ : Int64)
InvalidState(operation~ : String, phase~ : ParserPhase)
BoundaryCollision(part_index~ : Int)
BodyLengthMismatch(part_index~ : Int, expected~ : Int64, observed~ : Int64)
Unsupported(feature~ : String)
} derive(Debug, Eq)
///|
/// Convert a protocol error into log-safe structured data.
///
/// Reasons, header values, field names, filenames, and body bytes are never
/// copied into the result.
pub fn redact_error(error : MultipartError) -> MultipartDiagnostic {
match error {
InvalidContentType(..) =>
{
code: "invalid_content_type",
phase: None,
offset: None,
part_index: None,
limit_kind: None,
limit: None,
observed: None,
}
InvalidBoundary(..) =>
{
code: "invalid_boundary",
phase: None,
offset: None,
part_index: None,
limit_kind: None,
limit: None,
observed: None,
}
MalformedHeader(part_index~, ..) =>
{
code: "malformed_header",
phase: Some(Headers),
offset: None,
part_index: Some(part_index),
limit_kind: None,
limit: None,
observed: None,
}
MissingDisposition(part_index~) =>
{
code: "missing_disposition",
phase: Some(Headers),
offset: None,
part_index: Some(part_index),
limit_kind: None,
limit: None,
observed: None,
}
MissingName(part_index~) =>
{
code: "missing_name",
phase: Some(Headers),
offset: None,
part_index: Some(part_index),
limit_kind: None,
limit: None,
observed: None,
}
UnexpectedEof(phase~, offset~) =>
{
code: "unexpected_eof",
phase: Some(phase),
offset: Some(offset),
part_index: None,
limit_kind: None,
limit: None,
observed: None,
}
LimitExceeded(kind~, limit~, observed~) =>
{
code: "limit_exceeded",
phase: None,
offset: None,
part_index: None,
limit_kind: Some(kind),
limit: Some(limit),
observed: Some(observed),
}
InvalidState(phase~, ..) =>
{
code: "invalid_state",
phase: Some(phase),
offset: None,
part_index: None,
limit_kind: None,
limit: None,
observed: None,
}
BoundaryCollision(part_index~) =>
{
code: "boundary_collision",
phase: None,
offset: None,
part_index: Some(part_index),
limit_kind: None,
limit: None,
observed: None,
}
BodyLengthMismatch(part_index~, expected~, observed~) =>
{
code: "body_length_mismatch",
phase: Some(Body),
offset: None,
part_index: Some(part_index),
limit_kind: None,
limit: Some(expected),
observed: Some(observed),
}
Unsupported(..) =>
{
code: "unsupported",
phase: None,
offset: None,
part_index: None,
limit_kind: None,
limit: None,
observed: None,
}
}
}