///|
pub type Callback = async (&@io.Writer) -> Unit
///|
struct Writer {
target : &@io.Writer
boundary : String
mut started : Bool
mut finished : Bool
}
///|
/// Create a multipart writer. The caller must choose a boundary that does not
/// occur as a delimiter in any part (RFC 2046 section 5.1.1).
pub fn Writer::Writer(
target : &@io.Writer,
boundary~ : String,
) -> Self raise MultipartError {
validate_boundary(boundary)
{ target, boundary, started: false, finished: false, }
}
///|
/// Write a part without buffering its body. Call parts sequentially, and use
/// the callback's writer only until the callback returns.
pub async fn Writer::write_part(
self : Self,
headers~ : @http.Headers,
callback : Callback,
) -> Unit {
guard !self.finished else {
raise MultipartError("multipart writer is finished")
}
for name, value in headers {
validate_header(name.0, value)
}
if self.started {
self.target.write("\r\n")
}
self.started = true
self.target.write("--\{self.boundary}\r\n")
for name, value in headers {
self.target.write("\{name}: \{value}\r\n")
}
self.target.write("\r\n")
callback(self.target)
}
///|
/// Stream a reader's remaining bytes as one part, leaving both streams open.
pub async fn Writer::write_reader(
self : Self,
headers~ : @http.Headers,
source : &@io.Reader,
) -> Unit {
self.write_part(headers~, target => target.write_reader(source))
}
///|
/// Write the closing delimiter without closing the underlying writer.
/// Repeated calls have no effect.
pub async fn Writer::finish(self : Self) -> Unit {
guard !self.finished else { return }
self.finished = true
if self.started {
self.target.write("\r\n")
}
self.target.write("--\{self.boundary}--\r\n")
}
///|
// RFC 2046 section 5.1.1: 1..70 bchars, with no trailing space.
fn validate_boundary(boundary : String) -> Unit raise MultipartError {
guard boundary.length() > 0 && boundary.length() <= 70 else {
raise MultipartError("multipart boundary must contain 1 to 70 characters")
}
for ch in boundary {
match ch {
'a'..='z'
| 'A'..='Z'
| '0'..='9'
| '\''
| '('
| ')'
| '+'
| '_'
| ','
| '-'
| '.'
| '/'
| ':'
| '='
| '?'
| ' ' => ()
_ => raise MultipartError("invalid character in multipart boundary")
}
}
if boundary.has_suffix(" ") {
raise MultipartError("multipart boundary must not end in a space")
}
}