@@ -77,7 +77,7 @@ public class ServerSentEventReader(private val source: BufferedSource) {
7777 // Comment line: latest wins. Per WHATWG SSE §9.2.6 the optional single
7878 // leading space after the `:` is stripped (same rule applied to field values).
7979 if (line[0 ] == ' :' ) {
80- comment = line.substring( 1 ).removePrefix( " " )
80+ comment = valueFrom(line, 1 )
8181 hasField = true
8282 continue
8383 }
@@ -91,14 +91,7 @@ public class ServerSentEventReader(private val source: BufferedSource) {
9191 rawValue = " "
9292 } else {
9393 field = line.substring(0 , colon)
94- // Per spec: if the value starts with U+0020 SPACE, drop one.
95- val afterColon = colon + 1
96- rawValue =
97- if (afterColon < line.length && line[afterColon] == ' ' ) {
98- line.substring(afterColon + 1 )
99- } else {
100- line.substring(afterColon)
101- }
94+ rawValue = valueFrom(line, colon + 1 )
10295 }
10396
10497 when (field) {
@@ -200,18 +193,31 @@ public class ServerSentEventReader(private val source: BufferedSource) {
200193 * representable range. The spec is silent on overflow; ignoring is conservative.
201194 */
202195 private fun parseRetryMillis (value : String ): Long? {
203- if (value.isEmpty()) return null
204- var result = 0L
205- for (c in value) {
206- if (c !in ' 0' .. ' 9' ) return null
207- val digit = (c - ' 0' ).toLong()
208- // Detect overflow before it happens so we don't wrap around.
209- if (result > (Long .MAX_VALUE - digit) / DECIMAL_BASE ) return null
210- result = result * DECIMAL_BASE + digit
211- }
212- return result
196+ // Spec allows ASCII digits only; this guard rejects signs and any other
197+ // non-digit up front (toLongOrNull would otherwise accept a leading +/-).
198+ // toLongOrNull then parses base-10 and returns null on empty input or on
199+ // values past Long.MAX_VALUE — the same overflow boundary as the former
200+ // hand-rolled loop.
201+ if (value.any { it !in ' 0' .. ' 9' }) return null
202+ return value.toLongOrNull()
213203 }
214204
205+ /* *
206+ * Returns the value portion of [line] starting at [start], dropping a single
207+ * leading U+0020 SPACE if present. WHATWG SSE §9.2.6 applies this optional
208+ * single-space strip to both comment text and field values, so both code
209+ * paths share this one implementation.
210+ */
211+ private fun valueFrom (
212+ line : String ,
213+ start : Int ,
214+ ): String =
215+ if (start < line.length && line[start] == ' ' ) {
216+ line.substring(start + 1 )
217+ } else {
218+ line.substring(start)
219+ }
220+
215221 private companion object {
216222 private const val LF : Byte = 0x0A
217223 private const val CR : Byte = 0x0D
@@ -221,9 +227,6 @@ public class ServerSentEventReader(private val source: BufferedSource) {
221227 // Initial capacity for the per-event `data` line accumulator. SSE servers tend to
222228 // emit small numbers of data lines per event (one is typical); 4 covers the long tail.
223229 private const val DATA_ACCUMULATOR_INITIAL_CAP = 4
224-
225- // Numeric base for `retry:` digit parsing — SSE retry values are unsigned decimals.
226- private const val DECIMAL_BASE = 10L
227230 }
228231
229232 /* *
@@ -237,20 +240,16 @@ public class ServerSentEventReader(private val source: BufferedSource) {
237240 private var count: Int = 0
238241
239242 fun append (b : Byte ) {
240- if (count == bytes.size) grow(count + 1 )
243+ if (count == bytes.size) grow()
241244 bytes[count++ ] = b
242245 }
243246
244247 fun toUtf8 (): String = if (count == 0 ) " " else String (bytes, 0 , count, Charsets .UTF_8 )
245248
246- private fun grow (minCapacity : Int ) {
247- val oldCap = bytes.size
248- val newCap =
249- when {
250- oldCap == 0 -> INITIAL_CAP
251- oldCap < minCapacity -> maxOf(oldCap * 2 , minCapacity)
252- else -> oldCap
253- }
249+ // Called only when the array is full (count == bytes.size), so the first
250+ // allocation is INITIAL_CAP and every later growth simply doubles.
251+ private fun grow () {
252+ val newCap = if (bytes.isEmpty()) INITIAL_CAP else bytes.size * 2
254253 bytes = bytes.copyOf(newCap)
255254 }
256255
0 commit comments