Skip to main content

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}