///|
enum EncoderState {
AwaitingPart
WritingPart
Encoded
}
///|
/// Incrementally converts multipart events into binary wire chunks. The
/// encoder retains only its state and the boundary; returned chunks can be
/// written directly to an HTTP body sink.
pub struct StreamingEncoder {
boundary : String
mut state : EncoderState
mut failed : MultipartError?
}
///|
/// Creates an encoder for a raw multipart boundary. Invalid boundaries are
/// reported by the first `push` call.
pub fn StreamingEncoder::new(boundary : String) -> StreamingEncoder {
StreamingEncoder::{
boundary,
state: AwaitingPart,
failed: if is_valid_boundary(boundary) {
None
} else {
Some(MultipartError::InvalidBoundary)
},
}
}
///|
/// Creates an encoder from an HTTP multipart/form-data Content-Type value.
pub fn StreamingEncoder::from_content_type(
content_type : String,
) -> Result[StreamingEncoder, MultipartError] {
match boundary_from_content_type(content_type) {
Ok(boundary) => Ok(StreamingEncoder::new(boundary))
Err(error) => Err(error)
}
}
///|
/// Returns a Content-Type value suitable for the encoded body.
pub fn StreamingEncoder::content_type(self : StreamingEncoder) -> String {
"multipart/form-data; boundary=\"" + self.boundary + "\""
}
///|
/// Encodes one event. `PartBegin` emits a boundary and headers, `PartData`
/// returns the original binary payload without text conversion, `PartEnd`
/// emits the required CRLF, and `Finished` emits the closing boundary.
///
/// Events must follow `PartBegin`, zero or more `PartData`, `PartEnd`, then
/// another part or `Finished`. Any error is terminal.
pub fn StreamingEncoder::push(
self : StreamingEncoder,
event : StreamEvent,
) -> Result[Bytes, MultipartError] {
match self.failed {
Some(error) => return Err(error)
None => ()
}
match self.encode_event(event) {
Ok(chunk) => Ok(chunk)
Err(error) => {
self.failed = Some(error)
Err(error)
}
}
}
///|
fn StreamingEncoder::encode_event(
self : StreamingEncoder,
event : StreamEvent,
) -> Result[Bytes, MultipartError] {
match event {
PartBegin(headers) => {
guard self.state is AwaitingPart else {
return Err(MultipartError::InvalidStreamEvent)
}
match validate_form_data_headers(headers) {
Ok(_) => ()
Err(error) => return Err(error)
}
let mut output = "--" + self.boundary + "\r\n"
for header in headers {
guard is_valid_header_name(header.name) &&
is_valid_header_value(header.value) else {
return Err(
MultipartError::InvalidHeader(header.name + ": " + header.value),
)
}
output = output + header.name + ": " + header.value + "\r\n"
}
self.state = WritingPart
Ok(@utf8.encode(output + "\r\n"))
}
PartData(data) => {
guard self.state is WritingPart else {
return Err(MultipartError::InvalidStreamEvent)
}
Ok(data)
}
PartEnd => {
guard self.state is WritingPart else {
return Err(MultipartError::InvalidStreamEvent)
}
self.state = AwaitingPart
Ok(b"\r\n")
}
Finished => {
guard self.state is AwaitingPart else {
return Err(MultipartError::InvalidStreamEvent)
}
self.state = Encoded
Ok(@utf8.encode("--" + self.boundary + "--\r\n"))
}
}
}