1
#pragma once
2

            
3
#include <cstdint>
4
#include <string>
5
#include <tuple>
6
#include <utility>
7

            
8
#include "absl/strings/string_view.h"
9
#include "absl/types/optional.h"
10

            
11
namespace Envoy {
12
namespace Http {
13
namespace Sse {
14

            
15
/**
16
 * Parser for Server-Sent Events (SSE) format.
17
 * Implements the SSE specification: https://html.spec.whatwg.org/multipage/server-sent-events.html
18
 *
19
 * This parser handles:
20
 * - Multiple line ending formats (CR, LF, CRLF)
21
 * - Comment lines (lines starting with ':')
22
 * - Multiple data fields (concatenated with newlines)
23
 * - Partial events split across chunks
24
 * - End-of-stream handling
25
 *
26
 * Example usage with Envoy's Buffer::OwnedImpl:
27
 *   Buffer::OwnedImpl buffer_;
28
 *   // ... append incoming data with buffer_.add(data) ...
29
 *   const uint64_t length = buffer_.length();
30
 *   absl::string_view buffer_view(static_cast<const char*>(buffer_.linearize(length)), length);
31
 *   while (!buffer_view.empty()) {
32
 *     auto result = findEventEnd(buffer_view, end_stream);
33
 *     if (result.event_start == absl::string_view::npos) break;
34
 *
35
 *     auto event_str = buffer_view.substr(result.event_start, result.event_end -
36
 * result.event_start); auto event = parseEvent(event_str); if (event.data.has_value()) {
37
 *       // Process event.data.value()
38
 *     }
39
 *     buffer_view = buffer_view.substr(result.next_start);
40
 *   }
41
 *   buffer_.drain(length - buffer_view.size());
42
 */
43
class SseParser {
44
public:
45
  /**
46
   * Represents a parsed SSE event.
47
   * Supports 'data', 'id', 'event', and 'retry' fields per the SSE specification.
48
   */
49
  struct ParsedEvent {
50
    // The concatenated data field values. Per SSE spec, multiple data fields are joined with
51
    // newlines. absl::nullopt if no data fields present, empty string if data field exists but
52
    // empty.
53
    absl::optional<std::string> data;
54
    // The event ID. absl::nullopt if no id field is present.
55
    absl::optional<std::string> id;
56
    // The event type. absl::nullopt if no event field is present.
57
    absl::optional<std::string> event_type;
58
    // The reconnection time in milliseconds. absl::nullopt if no retry field is present.
59
    absl::optional<uint64_t> retry;
60
  };
61

            
62
  /**
63
   * Result of finding the end of an SSE event in a buffer.
64
   */
65
  struct FindEventEndResult {
66
    // Where the event content begins (after BOM if present).
67
    size_t event_start;
68
    // Where the event content ends (excluding trailing blank line).
69
    size_t event_end;
70
    // Where to continue parsing for the next event.
71
    size_t next_start;
72
  };
73

            
74
  /**
75
   * Parses an SSE event and extracts fields.
76
   * Currently extracts only the 'data' field. Per SSE spec, multiple data fields are joined with
77
   * newlines.
78
   *
79
   * @param event the complete SSE event string (from blank line to blank line).
80
   * @return parsed event with available fields populated.
81
   */
82
  static ParsedEvent parseEvent(absl::string_view event);
83

            
84
  /**
85
   * Finds the end of the next SSE event in the buffer.
86
   * An event ends with a blank line (two consecutive line breaks).
87
   * Automatically handles UTF-8 BOM at the start of the stream.
88
   *
89
   * @param buffer the buffer to search for an event.
90
   * @param end_stream whether this is the end of the stream (affects partial line handling).
91
   * @return FindEventEndResult with event_start, event_end, and next_start positions.
92
   *         All fields are set to npos if no complete event is found.
93
   */
94
  static FindEventEndResult findEventEnd(absl::string_view buffer, bool end_stream);
95

            
96
private:
97
  /**
98
   * Parses an SSE field line into {field_name, field_value}.
99
   * Handles comments (lines starting with ':') and strips leading space from value.
100
   *
101
   * @param line a single line from an SSE event.
102
   * @return a pair of {field_name, field_value}. Returns {"", ""} for empty lines or comments.
103
   */
104
  static std::pair<absl::string_view, absl::string_view> parseFieldLine(absl::string_view line);
105

            
106
  /**
107
   * Finds the end of the current line, handling CR, LF, and CRLF line endings.
108
   * Per SSE spec, all three line ending formats are supported.
109
   *
110
   * @param str the string to search for a line ending.
111
   * @param end_stream whether this is the end of the stream (affects partial line handling).
112
   * @return a pair of {line_end, next_line_start} positions.
113
   *         Returns {npos, npos} if no complete line is found.
114
   */
115
  static std::pair<size_t, size_t> findLineEnd(absl::string_view str, bool end_stream);
116
};
117

            
118
} // namespace Sse
119
} // namespace Http
120
} // namespace Envoy