Skip to content

Commit 08a3496

Browse files
authored
Fix usage of split_inclusive in event parser (#31)
* Use cimg/rust 1.59 * Fix usage of split_inclusive in event parser logic It was relying on old behavior where Some(empty slice) would be returned if there were no matches. This was updated in more recent rust versions to return None, but CircleCI was using a much older version of rust than I have locally. * Add a property-test to exercise event parsing logic
1 parent 94b9742 commit 08a3496

File tree

3 files changed

+42
-37
lines changed

3 files changed

+42
-37
lines changed

.circleci/config.yml

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,14 @@ version: 2
22
jobs:
33
build:
44
docker:
5-
- image: circleci/rust:latest
6-
5+
- image: cimg/rust:1.59.0
76
steps:
87
- checkout
9-
108
- run:
11-
name: Install clippy
12-
command: rustup component add clippy
13-
- run:
14-
name: Install rustfmt
15-
command: rustup component add rustfmt
16-
9+
name: Check Version
10+
command: |
11+
cargo --version
12+
rustc --version
1713
- run:
1814
name: Check consistent formatting
1915
command: cargo fmt && git diff --exit-code

eventsource-client/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ maplit = "1.0.1"
2727
simplelog = "0.5.3"
2828
tokio = { version = "1.2.0", features = ["macros", "rt-multi-thread"] }
2929
test-case = "1.2.3"
30+
proptest = "1.0.0"
31+
3032

3133
[features]
3234
default = ["rustls"]

eventsource-client/src/event_parser.rs

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -281,42 +281,39 @@ impl EventParser {
281281
// incomplete lines from previous chunks.
282282
fn decode_and_buffer_lines(&mut self, chunk: Bytes) {
283283
let mut lines = chunk.split_inclusive(|&b| b == b'\n' || b == b'\r');
284-
285284
// The first and last elements in this split are special. The spec requires lines to be
286285
// terminated. But lines may span chunks, so:
287286
// * the last line, if non-empty (i.e. if chunk didn't end with a line terminator),
288287
// should be buffered as an incomplete line
289288
// * the first line should be appended to the incomplete line, if any
290289

291290
if let Some(incomplete_line) = self.incomplete_line.as_mut() {
292-
let line = lines
293-
.next()
294-
// split always returns at least one item
295-
.unwrap();
296-
trace!(
297-
"extending line from previous chunk: {:?}+{:?}",
298-
logify(incomplete_line),
299-
logify(line)
300-
);
291+
if let Some(line) = lines.next() {
292+
trace!(
293+
"extending line from previous chunk: {:?}+{:?}",
294+
logify(incomplete_line),
295+
logify(line)
296+
);
301297

302-
self.last_char_was_cr = false;
303-
if !line.is_empty() {
304-
// Checking the last character handles lines where the last character is a
305-
// terminator, but also where the entire line is a terminator.
306-
match line.last().unwrap() {
307-
b'\r' => {
308-
incomplete_line.extend_from_slice(&line[..line.len() - 1]);
309-
let il = self.incomplete_line.take();
310-
self.complete_lines.push_back(il.unwrap());
311-
self.last_char_was_cr = true;
312-
}
313-
b'\n' => {
314-
incomplete_line.extend_from_slice(&line[..line.len() - 1]);
315-
let il = self.incomplete_line.take();
316-
self.complete_lines.push_back(il.unwrap());
317-
}
318-
_ => incomplete_line.extend_from_slice(line),
319-
};
298+
self.last_char_was_cr = false;
299+
if !line.is_empty() {
300+
// Checking the last character handles lines where the last character is a
301+
// terminator, but also where the entire line is a terminator.
302+
match line.last().unwrap() {
303+
b'\r' => {
304+
incomplete_line.extend_from_slice(&line[..line.len() - 1]);
305+
let il = self.incomplete_line.take();
306+
self.complete_lines.push_back(il.unwrap());
307+
self.last_char_was_cr = true;
308+
}
309+
b'\n' => {
310+
incomplete_line.extend_from_slice(&line[..line.len() - 1]);
311+
let il = self.incomplete_line.take();
312+
self.complete_lines.push_back(il.unwrap());
313+
}
314+
_ => incomplete_line.extend_from_slice(line),
315+
};
316+
}
320317
}
321318
}
322319

@@ -375,6 +372,7 @@ impl EventParser {
375372
#[cfg(test)]
376373
mod tests {
377374
use super::{Error::*, *};
375+
use proptest::proptest;
378376
use test_case::test_case;
379377

380378
fn field<'a>(key: &'a str, value: &'a str) -> Result<Option<(&'a str, &'a str)>> {
@@ -671,4 +669,13 @@ mod tests {
671669
std::fs::read(format!("test-data/{}", name))
672670
.unwrap_or_else(|_| panic!("couldn't read {}", name))
673671
}
672+
673+
proptest! {
674+
#[test]
675+
fn test_decode_and_buffer_lines_does_not_crash(next in "(\r\n|\r|\n)*event: [^\n\r:]*(\r\n|\r|\n)", previous in "(\r\n|\r|\n)*event: [^\n\r:]*(\r\n|\r|\n)") {
676+
let mut parser = EventParser::new();
677+
parser.incomplete_line = Some(previous.as_bytes().to_vec());
678+
parser.decode_and_buffer_lines(Bytes::from(next));
679+
}
680+
}
674681
}

0 commit comments

Comments
 (0)