///|
pub suberror MultipartError {
MultipartError(String)
} derive(Debug)
///|
/// A sequential, streaming MIME multipart reader (RFC 2046 section 5.1.1).
/// The source must be limited to the enclosing message body.
struct Reader {
input : ReaderState
/// Whether the first boundary has been read.
mut started : Bool
/// Last returned part. `next_part` reads and discards any remaining body.
mut current : PartReader?
}
///|
/// `boundary` is the decoded Content-Type parameter, without quotes or `--`.
pub fn Reader::Reader(
source : &@io.Reader,
boundary~ : String,
) -> Self raise MultipartError {
validate_boundary(boundary)
{
input: {
source,
delimiter: @utf8.encode("\r\n--\{boundary}"),
// Treat the start of the message as a line start when scanning the preamble.
pending: b"\r\n",
cursor: 0,
ready: 0,
eof: false,
finished: false,
},
started: false,
current: None,
}
}
///|
/// Read the next part's headers and a reader limited to its body. Advancing
/// drains the previous part; retained readers then return EOF. Preamble and
/// epilogue are ignored. Bodies are returned without transfer decoding.
/// Header folding is unfolded; repeated names are comma-joined as in @http.Headers.
/// Calls on this reader and its current part must be made sequentially.
// async@0.21.3 requires ReaderBuffer to implement io.Reader, but marks its
// constructor internal. No public adapter supports this pull-based part reader.
#warnings("-alert_internal")
pub async fn Reader::next_part(self : Self) -> (@http.Headers, &@io.Reader)? {
if self.current is Some(part) {
while @io.Reader::read_some(part) is Some(_) {
()
}
self.current = None
} else if !self.started {
while self.input.scan_body() {
self.input.cursor += self.input.ready
self.input.ready = 0
}
self.started = true
}
if self.input.finished {
return None
}
let headers = self.input.read_headers()
let part = PartReader::{
input: self.input,
buffer: @io.ReaderBuffer::new(),
at_start: true,
done: false,
}
self.current = Some(part)
Some((headers, part))
}
///|
// Required by io.Reader::_get_internal_buffer in async@0.21.3.
#warnings("-alert_internal")
priv struct PartReader {
input : ReaderState
buffer : @io.ReaderBuffer
mut at_start : Bool
mut done : Bool
}
///|
impl @io.Reader for PartReader with fn _get_internal_buffer(self) {
self.buffer
}
///|
impl @io.Reader for PartReader with fn _direct_read(
self,
dst,
offset~,
max_len~,
) {
if self.done || max_len == 0 {
return 0
}
if self.at_start {
self.at_start = false
// RFC 2046 permits a body-part containing only MIME headers. Its boundary's
// CRLF was consumed as the blank header line, so match without it here.
let length = self.input.delimiter.length() - 2
let boundary = for i = 0; i < length; i = i + 1 {
guard self.input.ensure(i + 1) else {
raise MultipartError("Unexpected EOF before multipart boundary")
}
if self.input.pending[self.input.cursor + i] !=
self.input.delimiter[i + 2] {
break false
}
} nobreak {
true
}
if boundary && self.input.consume_boundary(length) {
self.done = true
return 0
}
}
if !self.input.scan_body() {
self.done = true
return 0
}
let count = @cmp.minimum(self.input.ready, max_len)
dst.blit_from_bytes(offset, self.input.pending, self.input.cursor, count)
self.input.cursor += count
self.input.ready -= count
count
}