sse_core/decode.rs
1use alloc::{borrow::Cow, string::String, sync::Arc, vec, vec::Vec};
2use core::{
3 fmt, iter, mem,
4 num::{NonZeroU8, NonZeroUsize},
5 str,
6};
7use thiserror::Error;
8
9use bytes::Buf;
10use memchr::{memchr, memchr2};
11
12/// Represents a single Server-Sent Event message.
13#[derive(Debug, Clone, PartialEq, Eq)]
14#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
15pub struct MessageEvent {
16 /// The event name (defaults to `"message"`).
17 pub event: Cow<'static, str>,
18 /// The payload data.
19 pub data: String,
20 /// The `Last-Event-ID` sent by the server, if any.
21 pub last_event_id: Option<Arc<str>>,
22}
23
24/// Commands and payloads yielded by the SSE stream.
25#[derive(Debug, Clone, PartialEq, Eq)]
26#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
27pub enum SseEvent {
28 /// A standard data message.
29 Message(MessageEvent),
30 /// A server request to change the client's reconnect time (in milliseconds).
31 Retry(u32),
32}
33
34/// Error indicating that a parsed field exceeded the maximum allowed buffer size.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash, Error)]
36#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
37#[error("payload exceeded the allotted buffer size limit")]
38pub struct PayloadTooLargeError;
39
40const MAX_DEBUG_SIZE: usize = 200;
41
42struct ShowBigStr<'a>(&'a str);
43
44impl fmt::Debug for ShowBigStr<'_> {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 let mut end = self.0.len().min(MAX_DEBUG_SIZE);
47 while !self.0.is_char_boundary(end) {
48 end -= 1;
49 }
50 let s = &self.0[..end];
51
52 fmt::Debug::fmt(s, f)?;
53 if end < self.0.len() {
54 write!(f, "... ({} bytes total)", self.0.len())?;
55 }
56
57 Ok(())
58 }
59}
60
61struct ShowBigBuf<'a>(&'a [u8]);
62
63impl fmt::Debug for ShowBigBuf<'_> {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 let (buf, truncated) = match self.0.len() {
66 ..=MAX_DEBUG_SIZE => (self.0, false),
67 _ => (&self.0[..MAX_DEBUG_SIZE], true),
68 };
69
70 let mut chunks = buf.utf8_chunks().peekable();
71
72 f.write_str("\"")?;
73 while let Some(chunk) = chunks.next() {
74 fmt::Display::fmt(&chunk.valid().escape_debug(), f)?;
75
76 let invalid = chunk.invalid();
77 if invalid.is_empty() {
78 continue;
79 }
80
81 // If we truncated and this is the very last chunk, the invalid bytes
82 // are almost certainly just a sliced multi-byte UTF-8 character.
83 if truncated && chunks.peek().is_none() {
84 break;
85 }
86
87 for &byte in invalid {
88 write!(f, "\\x{byte:02X}")?;
89 }
90 }
91
92 if truncated {
93 write!(f, "\"... ({} bytes total)", self.0.len())?;
94 } else {
95 f.write_str("\"")?;
96 }
97
98 Ok(())
99 }
100}
101
102#[derive(Debug, Clone, Copy)]
103enum ValueMode {
104 Data,
105 Event,
106 Retry,
107 Id,
108}
109
110impl ValueMode {
111 pub const fn field_name(self) -> &'static str {
112 match self {
113 Self::Data => "data",
114 Self::Event => "event",
115 Self::Retry => "retry",
116 Self::Id => "id",
117 }
118 }
119}
120
121#[derive(Debug, Clone, Copy)]
122enum Mode {
123 Bom { bytes_read: u8 },
124 Field(Option<(ValueMode, NonZeroU8)>),
125 Value(ValueMode),
126 Ignore,
127 PostCr,
128 PostColon(ValueMode),
129}
130
131/// The core state-machine parser for SSE.
132///
133/// This decoder does not perform any I/O. It consumes bytes from a given buffer
134/// and yields parsed [`SseEvent`]s. It is suitable for `no_std` environments.
135#[derive(Clone)]
136pub struct SseDecoder {
137 mode: Mode,
138 last_event_id: Option<Arc<str>>,
139 staged_last_event_id: Option<Arc<str>>,
140 last_event_id_buf: Vec<u8>,
141 event_buf: Vec<u8>,
142 data_buf: Vec<u8>,
143 retry_buf: Option<u32>,
144 max_payload_size: NonZeroUsize,
145 corrupted: bool,
146}
147
148impl SseDecoder {
149 /// Creates a new decoder with the default payload size limit of 512KiB.
150 ///
151 /// # Example
152 /// ```rust
153 /// # use bytes::{Buf, Bytes};
154 /// # use sse_core::{SseDecoder, SseEvent};
155 /// # fn main() -> Result<(), sse_core::PayloadTooLargeError> {
156 /// let mut decoder = SseDecoder::new();
157 /// let mut buf = Bytes::from("data: standard stream\n\n");
158 ///
159 /// let event = decoder.next(&mut buf).transpose()?;
160 /// assert!(event.is_some());
161 /// # Ok(())
162 /// # }
163 /// ```
164 #[inline]
165 #[must_use]
166 pub fn new() -> Self {
167 Self::with_limit(NonZeroUsize::new(512 * 1024).unwrap())
168 }
169
170 /// Creates a new decoder with a custom maximum payload size limit.
171 ///
172 /// This is useful in memory-constrained environments or when connecting to
173 /// untrusted servers to prevent memory exhaustion from unbounded input.
174 ///
175 /// The limit applies independently to each of the three values an event
176 /// accumulates — its data, its name and its ID — so peak memory is a small
177 /// multiple of `max_payload_size` rather than exactly that. See
178 /// [`next()`](Self::next) for what each of them accumulates.
179 ///
180 /// # Example
181 /// ```rust
182 /// # use core::num::NonZeroUsize;
183 /// # use bytes::Bytes;
184 /// # use sse_core::{SseDecoder, SseEvent};
185 /// # fn main() -> Result<(), sse_core::PayloadTooLargeError> {
186 /// // Create a strict decoder that rejects payloads over 1024 bytes
187 /// let limit = NonZeroUsize::new(1024).unwrap();
188 /// let mut decoder = SseDecoder::with_limit(limit);
189 ///
190 /// let mut buf = Bytes::from("data: small payload\n\n");
191 /// let Some(event) = decoder.next(&mut buf) else {
192 /// panic!();
193 /// };
194 /// assert!(event.is_ok());
195 /// # Ok(())
196 /// # }
197 /// ```
198 #[inline]
199 #[must_use]
200 pub fn with_limit(max_payload_size: NonZeroUsize) -> Self {
201 Self {
202 mode: Mode::Bom { bytes_read: 0 },
203 last_event_id: None,
204 staged_last_event_id: None,
205 last_event_id_buf: vec![],
206 event_buf: vec![],
207 data_buf: vec![],
208 retry_buf: None,
209 max_payload_size,
210 corrupted: false,
211 }
212 }
213
214 /// Returns the current `Last-Event-ID` known to the decoder, if any.
215 #[inline]
216 #[must_use]
217 pub fn last_event_id(&self) -> Option<&Arc<str>> {
218 self.last_event_id.as_ref()
219 }
220
221 /// Resets the decoder state for a new connection, explicitly overriding
222 /// the currently tracked `Last-Event-ID`.
223 ///
224 /// This method clears all internal byte buffers and resets the parser, but
225 /// instead of keeping the previous ID (like [`reconnect()`](Self::reconnect))
226 /// or dropping it (like [`clear()`](Self::clear)), it injects the provided ID.
227 ///
228 /// It is typically used to prime the state machine with a known ID
229 /// (e.g., from a local database) right before feeding the decoder bytes
230 /// from a newly established connection.
231 #[inline]
232 pub fn reconnect_with_id(&mut self, id: Option<Arc<str>>) {
233 self.last_event_id = id;
234 self.reconnect();
235 }
236
237 /// Resets the decoder state completely, dropping the current `Last-Event-ID`.
238 ///
239 /// This clears all internal byte buffers and purges the parser's state,
240 /// effectively starting fresh. Because it drops the `Last-Event-ID`, the
241 /// next connection will start from the present moment rather than resuming.
242 ///
243 /// * To reset the state but **keep** the current ID, use [`reconnect()`](Self::reconnect).
244 /// * To reset the state and **inject** a specific ID, use [`reconnect_with_id()`](Self::reconnect_with_id).
245 #[inline]
246 pub fn clear(&mut self) {
247 self.reconnect_with_id(None);
248 }
249
250 /// Resets the buffer state for a new connection while retaining the `Last-Event-ID`.
251 ///
252 /// This clears the internal byte buffers to prepare for a fresh stream of data,
253 /// but safely preserves the most recently parsed `Last-Event-ID`. This ensures
254 /// that when you reconnect to the server, you can resume exactly where you left off.
255 ///
256 /// * To reset the state and **drop** the ID, use [`clear()`](Self::clear).
257 /// * To reset the state and **override** the ID, use [`reconnect_with_id()`](Self::reconnect_with_id).
258 #[inline]
259 pub fn reconnect(&mut self) {
260 self.mode = Mode::Bom { bytes_read: 0 };
261 self.clear_bufs();
262 self.corrupted = false;
263 }
264
265 fn mark_corrupted(&mut self) {
266 self.clear_bufs();
267 self.corrupted = true
268 }
269
270 /// Appends the newline separating two `data` lines, subject to the same limit
271 /// as the line contents. `consume_until_newline` never sees these bytes, so
272 /// without this a stream of empty `data` lines would grow `data_buf` unbounded.
273 ///
274 /// A *trailing* separator is allowed to sit one byte past the limit, since it is
275 /// popped at dispatch rather than delivered; anything appended after it is
276 /// checked against the full length and so still errors.
277 fn push_data_newline(&mut self) -> Result<(), PayloadTooLargeError> {
278 if self.max_payload_size.get() < self.data_buf.len() {
279 self.mark_corrupted();
280 return Err(PayloadTooLargeError);
281 }
282 self.data_buf.push(b'\n');
283 Ok(())
284 }
285
286 #[inline]
287 fn clear_bufs(&mut self) {
288 self.data_buf.clear();
289 self.event_buf.clear();
290 self.last_event_id_buf.clear();
291 self.staged_last_event_id = self.last_event_id.clone();
292 }
293
294 /// An event boundary that yields nothing — the blank line after a comment, a
295 /// keepalive, a discarded event — is by far the most common one on an idle
296 /// stream, so that case is kept small enough to inline into [`next()`](Self::next)
297 /// and the message-building tail is pushed out of line into
298 /// [`dispatch_message()`](Self::dispatch_message).
299 #[inline]
300 fn dispatch(&mut self, cr: bool) -> Option<SseEvent> {
301 self.mode = match cr {
302 true => Mode::PostCr,
303 false => Mode::Field(None),
304 };
305
306 if self.corrupted {
307 self.corrupted = false;
308 // bufs should be clear already
309 return None;
310 }
311
312 self.last_event_id = self.staged_last_event_id.clone();
313
314 // No data means no event, per the spec's "if the data buffer is empty" step.
315 if self.data_buf.is_empty() {
316 self.event_buf.clear();
317 return None;
318 }
319
320 self.dispatch_message()
321 }
322
323 /// The allocating half of [`dispatch()`](Self::dispatch), split out so that
324 /// inlining the no-event fast path above does not drag this into every one of
325 /// its call sites.
326 fn dispatch_message(&mut self) -> Option<SseEvent> {
327 // The trailing separator appended by the last `data` line is not part of
328 // the payload.
329 if let Some(b'\n') = self.data_buf.last() {
330 self.data_buf.pop();
331 }
332
333 // Hand the accumulated bytes over to the `String` rather than copying them
334 // into a fresh allocation: on a large event that copy costs more than the
335 // rest of the decoder put together. `data_buf` is replaced with an empty
336 // buffer of the same capacity so the next event still accumulates without
337 // reallocating, which leaves peak memory where it was — the copying version
338 // also held the payload twice for the duration of the copy.
339 let data = match String::from_utf8(mem::take(&mut self.data_buf)) {
340 Ok(data) => {
341 self.data_buf = Vec::with_capacity(data.capacity());
342 data
343 }
344 Err(err) => {
345 self.data_buf = err.into_bytes();
346 String::from_utf8_lossy(&self.data_buf).into_owned()
347 }
348 };
349 self.data_buf.clear();
350
351 let event = match &*self.event_buf {
352 b"" => Cow::Borrowed("message"),
353 event_buf => Cow::Owned(from_utf8_lossy(event_buf).into_owned()),
354 };
355 self.event_buf.clear();
356
357 Some(SseEvent::Message(MessageEvent {
358 data,
359 event,
360 last_event_id: self.last_event_id.clone(),
361 }))
362 }
363
364 /// Consumes bytes from the provided buffer and attempts to yield an event.
365 ///
366 /// The decoder does not store unparsed bytes internally. It reads directly
367 /// from the provided buffer, advancing the buffer's cursor only for the bytes
368 /// it successfully parses.
369 ///
370 /// If `Ok(None)` is returned, the provided buffer has been exhausted and
371 /// more bytes are needed to complete the current event. You should fetch more
372 /// data, append it to your buffer, and call `next()` again.
373 ///
374 /// # Example
375 /// ```
376 /// use bytes::{Buf, Bytes};
377 /// # use sse_core::{SseDecoder, SseEvent};
378 ///
379 /// let mut decoder = SseDecoder::new();
380 /// let mut buffer = Bytes::from("data: hello\n\n");
381 ///
382 /// // Call next() in a loop to drain all available events
383 /// while let Some(event) = decoder.next(&mut buffer) {
384 /// println!("Received: {event:?}");
385 /// }
386 ///
387 /// // When next() returns None, the decoder is waiting for more data.
388 /// assert!(buffer.is_empty());
389 /// ```
390 ///
391 /// # Errors
392 ///
393 /// Returns a [`PayloadTooLargeError`] when an event's accumulated data, name
394 /// or ID would exceed the maximum payload size configured for this decoder.
395 /// The limit is *not* per line:
396 ///
397 /// * `data` accumulates every `data:` line of the current event, joined by
398 /// newlines, so the limit bounds the whole event payload and is only reset
399 /// once the event is dispatched.
400 /// * `event` and `id` are reset by each `event:` / `id:` field, so for those
401 /// the limit does bound a single line.
402 ///
403 /// The offending event is discarded rather than silently truncated: the
404 /// decoder skips the rest of the current event and resynchronizes at the next
405 /// event boundary, so it stays usable after the error. `retry:` fields are
406 /// still honoured while the discarded event is being skipped.
407 pub fn next(&mut self, buf: &mut impl Buf) -> Option<Result<SseEvent, PayloadTooLargeError>> {
408 // # 9.2.5 Parsing an event stream
409 //
410 // stream = [ bom ] *event
411 // event = *( comment / field ) end-of-line
412 // comment = colon *any-char end-of-line
413 // field = 1*name-char [ colon [ space ] *any-char ] end-of-line
414 // end-of-line = ( cr lf / cr / lf )
415 //
416 // ; characters
417 // lf = %x000A ; U+000A LINE FEED (LF)
418 // cr = %x000D ; U+000D CARRIAGE RETURN (CR)
419 // space = %x0020 ; U+0020 SPACE
420 // colon = %x003A ; U+003A COLON (:)
421 // bom = %xFEFF ; U+FEFF BYTE ORDER MARK
422 // name-char = %x0000-0009 / %x000B-000C / %x000E-0039 / %x003B-10FFFF
423 // ; a scalar value other than U+000A LINE FEED (LF), U+000D CARRIAGE RETURN (CR), or U+003A COLON (:)
424 // any-char = %x0000-0009 / %x000B-000C / %x000E-10FFFF
425 // ; a scalar value other than U+000A LINE FEED (LF) or U+000D CARRIAGE RETURN (CR)
426
427 loop {
428 let chunk = buf.chunk();
429 if chunk.is_empty() {
430 return None;
431 }
432
433 match &mut self.mode {
434 Mode::Bom { bytes_read } => {
435 let b0 = chunk[0];
436
437 const BOM: &[u8; 3] = b"\xef\xbb\xbf";
438
439 if b0 != BOM[*bytes_read as usize] {
440 self.mode = match *bytes_read {
441 0 => Mode::Field(None),
442 _ => Mode::Ignore,
443 };
444 continue;
445 }
446
447 buf.advance(1);
448 *bytes_read += 1;
449
450 if BOM.len() <= *bytes_read as usize {
451 self.mode = Mode::Field(None);
452 }
453 }
454 Mode::Field(None) => {
455 let b0 = chunk[0];
456 buf.advance(1);
457 let mode = match b0 {
458 b'd' if !self.corrupted => ValueMode::Data,
459 b'e' if !self.corrupted => ValueMode::Event,
460 b'i' if !self.corrupted => ValueMode::Id,
461 b'r' => ValueMode::Retry,
462
463 b'\n' | b'\r' => match self.dispatch(b0 == b'\r') {
464 Some(ev) => return Some(Ok(ev)),
465 None => continue,
466 },
467
468 _ => {
469 self.mode = Mode::Ignore;
470
471 // Skip the rest of the line here rather than looping back
472 // through the `self.mode` dispatch: comments are the most
473 // common line shape in a keepalive-heavy stream, and the
474 // extra trip through the state machine costs more than the
475 // scan it guards.
476 consume_until_newline(&mut self.mode, None, self.max_payload_size, buf)
477 .expect("there should be no payload to grow too large");
478
479 // A comment is usually the last line of its event — a
480 // keepalive is nothing but a comment and the blank line
481 // after it — so consume that blank line here too when it
482 // is the empty dispatch, which is the only outcome a
483 // keepalive can have. Anything that would actually yield
484 // an event, or resume in another mode, is left to the
485 // regular path below rather than duplicated here.
486 let blank_line_follows = matches!(self.mode, Mode::Field(None))
487 && buf.chunk().first() == Some(&b'\n');
488
489 if blank_line_follows && !self.corrupted && self.data_buf.is_empty() {
490 buf.advance(1);
491 self.last_event_id = self.staged_last_event_id.clone();
492 self.event_buf.clear();
493 }
494 continue;
495 }
496 };
497 self.mode = Mode::Field(Some((mode, NonZeroU8::new(1).unwrap())));
498 }
499 &mut Mode::Field(Some((mode, ref mut len))) => {
500 let cmp = &mode.field_name().as_bytes()[len.get() as usize..];
501 if iter::zip(chunk, cmp).any(|(ch0, ch1)| ch0 != ch1) {
502 self.mode = Mode::Ignore;
503 continue;
504 }
505 let Some(&b_post) = chunk.get(cmp.len()) else {
506 *len = NonZeroU8::new(len.get() + chunk.len() as u8).unwrap();
507 buf.advance(chunk.len());
508 continue;
509 };
510 buf.advance(cmp.len() + 1);
511
512 match b_post {
513 b'\n' => self.mode = Mode::Field(None),
514 b'\r' => self.mode = Mode::PostCr,
515 b':' => {
516 match mode {
517 ValueMode::Data => {}
518 ValueMode::Event => self.event_buf.clear(),
519 ValueMode::Id => self.last_event_id_buf.clear(),
520 ValueMode::Retry => self.retry_buf = None,
521 }
522
523 // Skip the optional space here rather than spending a
524 // whole trip through the dispatch on a single byte. If
525 // the chunk ends on the colon there is nothing to look
526 // at yet, so fall back to resuming in `PostColon`.
527 self.mode = match buf.chunk().first() {
528 Some(&b' ') => {
529 buf.advance(1);
530 Mode::Value(mode)
531 }
532 Some(_) => Mode::Value(mode),
533 None => Mode::PostColon(mode),
534 };
535 continue;
536 }
537 _ => {
538 self.mode = Mode::Ignore;
539 continue;
540 }
541 }
542
543 // A field name with no colon carries the empty string as its
544 // value, so the target buffer must be reset, not left alone.
545 // `retry` is the exception: it accumulates nothing, and the
546 // `retry:` form already clears `retry_buf` before parsing into it.
547 match mode {
548 ValueMode::Data => {
549 if let Err(err) = self.push_data_newline() {
550 return Some(Err(err));
551 }
552 }
553 ValueMode::Event => self.event_buf.clear(),
554 ValueMode::Id => {
555 self.last_event_id_buf.clear();
556 self.staged_last_event_id = None;
557 }
558 ValueMode::Retry => {}
559 }
560 }
561 Mode::Value(ValueMode::Retry) => {
562 let mut advanced = 0;
563 let mut return_event = false;
564
565 for &b in chunk {
566 advanced += 1;
567 match b {
568 b'0'..=b'9' => {
569 let digit = (b & 0xf) as _;
570
571 let retry_buf = self.retry_buf.unwrap_or(0);
572 let Some(retry_buf) = retry_buf.checked_mul(10) else {
573 self.mode = Mode::Ignore;
574 break;
575 };
576 let Some(retry_buf) = retry_buf.checked_add(digit) else {
577 self.mode = Mode::Ignore;
578 break;
579 };
580 self.retry_buf = Some(retry_buf);
581 }
582 b'\r' => {
583 self.mode = Mode::PostCr;
584 return_event = true;
585 break;
586 }
587 b'\n' => {
588 self.mode = Mode::Field(None);
589 return_event = true;
590 break;
591 }
592 _ => {
593 self.mode = Mode::Ignore;
594 break;
595 }
596 }
597 }
598
599 buf.advance(advanced);
600
601 if let (true, Some(retry_buf)) = (return_event, self.retry_buf) {
602 return Some(Ok(SseEvent::Retry(retry_buf)));
603 }
604 }
605 Mode::Value(ValueMode::Data) => {
606 match consume_until_newline(
607 &mut self.mode,
608 Some(&mut self.data_buf),
609 self.max_payload_size,
610 buf,
611 ) {
612 Ok(true) => {
613 if let Err(err) = self.push_data_newline() {
614 return Some(Err(err));
615 }
616 }
617 Ok(false) => {}
618 Err(err) => {
619 self.mark_corrupted();
620 return Some(Err(err));
621 }
622 }
623 }
624 Mode::Value(ValueMode::Event) => {
625 if let Err(err) = consume_until_newline(
626 &mut self.mode,
627 Some(&mut self.event_buf),
628 self.max_payload_size,
629 buf,
630 ) {
631 self.mark_corrupted();
632 return Some(Err(err));
633 }
634 }
635 Mode::Value(ValueMode::Id) => {
636 match consume_until_newline(
637 &mut self.mode,
638 Some(&mut self.last_event_id_buf),
639 self.max_payload_size,
640 buf,
641 ) {
642 Ok(true) => {
643 if memchr(0, &self.last_event_id_buf).is_none() {
644 self.staged_last_event_id = match &*self.last_event_id_buf {
645 [] => None,
646 buf => Some(from_utf8_lossy(buf).into()),
647 };
648 }
649 self.last_event_id_buf.clear();
650 }
651 Ok(false) => {}
652 Err(err) => {
653 self.mark_corrupted();
654 return Some(Err(err));
655 }
656 }
657 }
658 Mode::Ignore => {
659 consume_until_newline(&mut self.mode, None, self.max_payload_size, buf)
660 .expect("there should be no payload to grow too large");
661 }
662 Mode::PostCr => {
663 if chunk[0] == b'\n' {
664 buf.advance(1);
665 }
666 self.mode = Mode::Field(None);
667 }
668 Mode::PostColon(value) => {
669 if chunk[0] == b' ' {
670 buf.advance(1);
671 }
672 self.mode = Mode::Value(*value);
673 }
674 }
675 }
676 }
677}
678
679impl Default for SseDecoder {
680 fn default() -> Self {
681 Self::new()
682 }
683}
684
685impl fmt::Debug for SseDecoder {
686 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
687 f.debug_struct("SseDecoder")
688 .field("mode", &self.mode)
689 .field(
690 "last_event_id",
691 &self.last_event_id.as_deref().map(ShowBigStr),
692 )
693 .field(
694 "staged_last_event_id",
695 &self.staged_last_event_id.as_deref().map(ShowBigStr),
696 )
697 .field("last_event_id_buf", &ShowBigBuf(&self.last_event_id_buf))
698 .field("event_buf", &ShowBigBuf(&self.event_buf))
699 .field("data_buf", &ShowBigBuf(&self.data_buf))
700 .field("retry_buf", &self.retry_buf)
701 .field("max_payload_size", &self.max_payload_size)
702 .finish()
703 }
704}
705
706/// Drop-in replacement for [`String::from_utf8_lossy`] that validates with
707/// [`str::from_utf8`] first.
708///
709/// The lossy conversion walks its input with the scalar `Utf8Chunks` iterator
710/// even when the input needs no repair at all, which measures ~7x slower than
711/// `str::from_utf8`'s word-at-a-time ASCII scan. Every event carrying a payload
712/// goes through here, so the valid case is worth splitting out; malformed input
713/// still takes the same lossy path it always did.
714#[inline]
715fn from_utf8_lossy(buf: &[u8]) -> Cow<'_, str> {
716 match str::from_utf8(buf) {
717 Ok(s) => Cow::Borrowed(s),
718 Err(_) => String::from_utf8_lossy(buf),
719 }
720}
721
722fn consume_until_newline(
723 mode: &mut Mode,
724 mut out: Option<&mut Vec<u8>>,
725 max_size: NonZeroUsize,
726 buf: &mut impl Buf,
727) -> Result<bool, PayloadTooLargeError> {
728 loop {
729 let chunk = buf.chunk();
730 if chunk.is_empty() {
731 return Ok(false);
732 };
733
734 let Some(i) = memchr2(b'\r', b'\n', chunk) else {
735 if let Some(out) = out.as_deref_mut() {
736 if max_size.get() < out.len() + chunk.len() {
737 out.clear();
738 *mode = Mode::Ignore;
739 return Err(PayloadTooLargeError);
740 }
741 out.extend_from_slice(chunk);
742 }
743 buf.advance(chunk.len());
744 continue;
745 };
746
747 if let Some(out) = out.as_deref_mut() {
748 if max_size.get() < out.len() + i {
749 out.clear();
750 *mode = Mode::Ignore;
751 return Err(PayloadTooLargeError);
752 }
753 out.extend_from_slice(&chunk[..i]);
754 }
755
756 *mode = match chunk[i] {
757 b'\r' => Mode::PostCr,
758 b'\n' => Mode::Field(None),
759 _ => unreachable!(),
760 };
761
762 buf.advance(i + 1);
763
764 return Ok(true);
765 }
766}
767
768#[test]
769fn hard_parse() -> Result<(), PayloadTooLargeError> {
770 use core::slice;
771
772 // Based on https://github.com/jpopesculian/eventsource-stream/blob/v0.2.3/tests/eventsource-stream.rs
773 let bytes = "\u{FEFF}data: x
774
775:
776
777event: my-event\r
778data:line1
779data: line2
780data
781:
782id: my-id
783:should be ignored too\rretry:42
784retry:
785
786data:second
787
788data:ignored
789";
790
791 let mut decoder = SseDecoder::new();
792
793 let events = bytes
794 .bytes()
795 .filter_map(|b| decoder.next(&mut slice::from_ref(&b)))
796 .collect::<Result<Vec<_>, PayloadTooLargeError>>()?;
797
798 let id = Some("my-id".into());
799
800 assert_eq!(
801 events,
802 &[
803 SseEvent::Message(MessageEvent {
804 event: "message".into(),
805 data: "x".into(),
806 last_event_id: None
807 }),
808 SseEvent::Retry(42),
809 SseEvent::Message(MessageEvent {
810 event: "my-event".into(),
811 data: "line1\nline2\n".into(),
812 last_event_id: id.clone()
813 }),
814 SseEvent::Message(MessageEvent {
815 event: "message".into(),
816 data: "second".into(),
817 last_event_id: id.clone()
818 })
819 ]
820 );
821
822 // Feeding the whole buffer at once has to agree byte for byte with feeding it
823 // one byte at a time. The two exercise different code: several arms take a
824 // short cut when the bytes they need are already in the chunk, and those short
825 // cuts are unreachable when every chunk is a single byte.
826 let mut decoder = SseDecoder::new();
827 let mut buf = bytes.as_bytes();
828 let whole = iter::from_fn(|| decoder.next(&mut buf)).collect::<Result<Vec<_>, _>>()?;
829 assert_eq!(whole, events);
830
831 Ok(())
832}
833
834/// Per the spec, a line with no colon is a field whose value is the empty string,
835/// so a bare `event` / `id` must *reset* its buffer rather than leave the previous
836/// value in place. Regression test: `id` leaking across events also leaks into the
837/// `Last-Event-ID` sent on reconnect.
838#[test]
839fn test_valueless_fields() {
840 fn messages(bytes: &str) -> Vec<(String, String, Option<String>)> {
841 let mut decoder = SseDecoder::new();
842 let mut buf = bytes.as_bytes();
843 let mut out = vec![];
844 while let Some(SseEvent::Message(msg)) = decoder.next(&mut buf).transpose().unwrap() {
845 out.push((
846 msg.event.into_owned(),
847 msg.data,
848 msg.last_event_id.map(|id| id.to_string()),
849 ));
850 }
851 out
852 }
853
854 // A bare `event` resets the event type back to the "message" default.
855 assert_eq!(
856 messages("event: custom\nevent\ndata: x\n\n"),
857 [("message".into(), "x".into(), None)],
858 );
859
860 // A bare `id` clears the ID, exactly like the `id:` form does.
861 for bytes in [
862 "id: abc\ndata: 1\n\nid\ndata: 2\n\n",
863 "id: abc\ndata: 1\n\nid:\ndata: 2\n\n",
864 ] {
865 assert_eq!(
866 messages(bytes),
867 [
868 ("message".into(), "1".into(), Some("abc".into())),
869 ("message".into(), "2".into(), None),
870 ],
871 "for {bytes:?}",
872 );
873 }
874
875 // A bare `data` still appends an empty line rather than resetting.
876 assert_eq!(
877 messages("data: a\ndata\ndata: b\n\n"),
878 [("message".into(), "a\n\nb".into(), None)],
879 );
880}
881
882/// Valueless `data` lines never reach `consume_until_newline`, so the separator each
883/// one appends has to be counted on its own. Regression test: without that check a
884/// server could grow `data_buf` a byte at a time, with no bound and no error, and
885/// have the oversized event delivered anyway.
886#[test]
887fn test_valueless_data_respects_the_limit() {
888 let limit = NonZeroUsize::new(10).unwrap();
889
890 // A payload made only of separators is still bounded by the limit.
891 let input = "data\n".repeat(100) + "\n";
892 let mut buf = input.as_bytes();
893 let mut decoder = SseDecoder::with_limit(limit);
894 assert_eq!(decoder.next(&mut buf), Some(Err(PayloadTooLargeError)));
895
896 // The rest of the event is skipped as a unit, leaving the decoder usable.
897 assert_eq!(decoder.next(&mut buf), None);
898 assert!(buf.is_empty());
899
900 let mut buf: &[u8] = b"data: after\n\n";
901 assert_eq!(
902 decoder.next(&mut buf),
903 Some(Ok(SseEvent::Message(MessageEvent {
904 event: "message".into(),
905 data: "after".into(),
906 last_event_id: None,
907 }))),
908 );
909
910 // A payload that exactly fills the limit is still legal, whatever it is made of.
911 let input = "data\n".repeat(limit.get() + 1) + "\n";
912 let mut buf = input.as_bytes();
913 let mut decoder = SseDecoder::with_limit(limit);
914 assert_eq!(
915 decoder.next(&mut buf),
916 Some(Ok(SseEvent::Message(MessageEvent {
917 event: "message".into(),
918 data: "\n".repeat(limit.get()),
919 last_event_id: None,
920 }))),
921 );
922}
923
924/// Invalid UTF-8 is lossy-converted rather than rejected, in every field that
925/// accumulates text. Regression test for [`from_utf8_lossy`]: it validates with
926/// `str::from_utf8` first and only falls back to the lossy walk, so the repaired
927/// output has to stay byte-for-byte what `String::from_utf8_lossy` produced.
928#[test]
929fn test_invalid_utf8_is_lossy() {
930 let mut buf: &[u8] =
931 b"event: ev\xffent\nid: my\xff-id\ndata: he\xed\xa0\x80llo\ndata: \xf0\x9f\x92\xa9 ok\n\n";
932
933 let mut decoder = SseDecoder::new();
934 let Some(Ok(SseEvent::Message(msg))) = decoder.next(&mut buf) else {
935 panic!("expected a message");
936 };
937
938 assert_eq!(msg.event, String::from_utf8_lossy(b"ev\xffent"));
939 assert_eq!(
940 msg.data,
941 String::from_utf8_lossy(b"he\xed\xa0\x80llo\n\xf0\x9f\x92\xa9 ok")
942 );
943 assert_eq!(
944 msg.last_event_id.as_deref(),
945 Some(&*String::from_utf8_lossy(b"my\xff-id")),
946 );
947
948 // A valid multi-byte sequence must survive untouched.
949 assert!(msg.data.contains('\u{1F4A9}'));
950 assert!(buf.is_empty());
951}
952
953#[test]
954fn test_reconnect() {
955 let mut stream1: &[u8] = b"
956id: my-id
957
958event: my-event
959data:line1
960:
961data: line2
962id: ignored1
963";
964
965 let mut stream2: &[u8] = b"
966
967data: data
968
969id: final
970
971id: ignored2
972";
973
974 let my_id = Some("my-id".into());
975
976 let mut decoder = SseDecoder::new();
977
978 assert_eq!(decoder.next(&mut stream1), None);
979 assert_eq!(decoder.last_event_id(), my_id.as_ref());
980 assert!(stream1.is_empty());
981
982 decoder.reconnect();
983
984 // Check that the buffer was cleared
985 assert_eq!(
986 decoder.next(&mut stream2),
987 Some(Ok(SseEvent::Message(MessageEvent {
988 event: "message".into(),
989 data: "data".into(),
990 last_event_id: my_id,
991 }))),
992 );
993 assert_eq!(decoder.next(&mut stream2), None);
994 assert_eq!(decoder.last_event_id().map(|id| &**id), Some("final"));
995 assert!(stream2.is_empty());
996}
997
998#[test]
999fn test_limits() {
1000 let mut stream: &[u8] = b"
1001data: 0123456789
1002id: my-id
1003
1004id: 01234567890
1005data: thing
1006event: ev
1007
1008data: mid
1009
1010event: jojo
1011id: ignored
1012data: 01234
1013data: 56789
1014retry: 10
1015
1016event: final
1017data
1018
1019";
1020
1021 let my_id = Some("my-id".into());
1022
1023 let mut decoder = SseDecoder::with_limit(NonZeroUsize::new(10).unwrap());
1024
1025 assert_eq!(
1026 decoder.next(&mut stream),
1027 Some(Ok(SseEvent::Message(MessageEvent {
1028 event: "message".into(),
1029 data: "0123456789".into(),
1030 last_event_id: my_id.clone(),
1031 })))
1032 );
1033 assert_eq!(decoder.next(&mut stream), Some(Err(PayloadTooLargeError)));
1034 assert_eq!(
1035 decoder.next(&mut stream),
1036 Some(Ok(SseEvent::Message(MessageEvent {
1037 event: "message".into(),
1038 data: "mid".into(),
1039 last_event_id: my_id.clone()
1040 })))
1041 );
1042 assert_eq!(decoder.next(&mut stream), Some(Err(PayloadTooLargeError)));
1043 assert_eq!(decoder.next(&mut stream), Some(Ok(SseEvent::Retry(10))));
1044 assert_eq!(
1045 decoder.next(&mut stream),
1046 Some(Ok(SseEvent::Message(MessageEvent {
1047 event: "final".into(),
1048 data: "".into(),
1049 last_event_id: my_id.clone()
1050 })))
1051 );
1052 assert!(stream.is_empty());
1053}