#[cfg(test)]
mod tests;
const UTF8_BOM: &str = "\u{feff}";
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SseEvent {
pub(crate) event: String,
pub(crate) data: String,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum SseWireError {
#[error("SSE event exceeded the {limit} byte limit (buffered at least {observed} bytes)")]
EventTooLarge { limit: usize, observed: usize },
#[error("SSE stream contained invalid UTF-8")]
InvalidUtf8,
}
#[derive(Debug)]
pub(crate) struct SseParser {
line: Vec<u8>,
data: String,
event: Option<String>,
pending_cr: bool,
at_stream_start: bool,
max_event_bytes: usize,
}
impl SseParser {
pub(crate) fn new(max_event_bytes: usize) -> Self {
Self {
line: Vec::new(),
data: String::new(),
event: None,
pending_cr: false,
at_stream_start: true,
max_event_bytes,
}
}
pub(crate) fn feed(&mut self, bytes: &[u8]) -> Result<Vec<SseEvent>, SseWireError> {
let mut events = Vec::new();
for byte in bytes {
let byte = *byte;
if self.pending_cr {
self.pending_cr = false;
if byte == b'\n' {
continue;
}
}
match byte {
b'\n' => self.end_line(&mut events)?,
b'\r' => {
self.pending_cr = true;
self.end_line(&mut events)?;
}
_ => {
self.line.push(byte);
self.check_bounds()?;
}
}
}
Ok(events)
}
fn check_bounds(&self) -> Result<(), SseWireError> {
let observed = self.line.len() + self.data.len();
if observed > self.max_event_bytes {
return Err(SseWireError::EventTooLarge {
limit: self.max_event_bytes,
observed,
});
}
Ok(())
}
fn end_line(&mut self, events: &mut Vec<SseEvent>) -> Result<(), SseWireError> {
let line = std::mem::take(&mut self.line);
let line = std::str::from_utf8(&line).map_err(|_| SseWireError::InvalidUtf8)?;
let line = if std::mem::take(&mut self.at_stream_start) {
line.strip_prefix(UTF8_BOM).unwrap_or(line)
} else {
line
};
if line.is_empty() {
if let Some(event) = self.take_event() {
events.push(event);
}
return Ok(());
}
if line.starts_with(':') {
return Ok(());
}
let (field, value) = match line.split_once(':') {
Some((field, value)) => (field, value.strip_prefix(' ').unwrap_or(value)),
None => (line, ""),
};
match field {
"event" => self.event = Some(value.to_string()),
"data" => {
self.data.push_str(value);
self.data.push('\n');
self.check_bounds()?;
}
_ => {}
}
Ok(())
}
fn take_event(&mut self) -> Option<SseEvent> {
let event = self.event.take();
let mut data = std::mem::take(&mut self.data);
if data.is_empty() {
return None;
}
data.pop();
Some(SseEvent {
event: event.unwrap_or_else(|| "message".to_string()),
data,
})
}
}