Skip to content

Commit cb72ebf

Browse files
authored
Merge pull request #40 from clue-labs/utf8
Use replacement character for invalid UTF-8 and handle null bytes as per EventSource specs
2 parents f156e70 + 31cb9ed commit cb72ebf

File tree

2 files changed

+41
-4
lines changed

2 files changed

+41
-4
lines changed

src/MessageEvent.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ public static function parse($data, $lastEventId, &$retryTime = 0.0)
2929
$value = (string) substr($value, 1);
3030
}
3131
if ($name === 'data') {
32-
$data .= $value . "\n";
33-
} elseif ($name === 'id') {
34-
$id = $value;
32+
$data .= self::utf8($value) . "\n";
33+
} elseif ($name === 'id' && \strpos($value, "\x00") === false) {
34+
$id = self::utf8($value);
3535
} elseif ($name === 'event' && $value !== '') {
36-
$type = $value;
36+
$type = self::utf8($value);
3737
} elseif ($name === 'retry' && $value === (string)(int)$value && $value >= 0) {
3838
$retryTime = $value * 0.001;
3939
}
@@ -46,6 +46,12 @@ public static function parse($data, $lastEventId, &$retryTime = 0.0)
4646
return new self($data, $id, $type);
4747
}
4848

49+
/** @return string */
50+
private static function utf8($string)
51+
{
52+
return \htmlspecialchars_decode(\htmlspecialchars($string, \ENT_NOQUOTES | \ENT_SUBSTITUTE, 'utf-8'));
53+
}
54+
4955
/**
5056
* @internal
5157
* @param string $data

tests/MessageEventTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ public function testParseDataWithCarrigeReturnAndNewlineOverTwoLines()
5656
$this->assertEquals("hello\n", $message->data);
5757
}
5858

59+
public function testParseDataWithNonUtf8AndNullBytesReturnsDataWithUnicodeReplacement()
60+
{
61+
$message = MessageEvent::parse("data: h\x00ll\xFF!", '');
62+
63+
$this->assertEquals("h\x00ll�!", $message->data);
64+
}
65+
5966
public function testParseReturnsMessageWithIdFromStream()
6067
{
6168
$message = MessageEvent::parse("data: hello\r\nid: 1", '');
@@ -88,6 +95,22 @@ public function testParseWithMultipleIdsReturnsMessageWithLastEventIdFromStream(
8895
$this->assertEquals('2', $message->lastEventId);
8996
}
9097

98+
public function testParseWithIdWithNonUtf8BytesReturnsMessageWithLastEventIdFromStreamWithUnicodeReplacement()
99+
{
100+
$message = MessageEvent::parse("data: hello\nid: h\xFFllo!", '');
101+
102+
$this->assertEquals("hello", $message->data);
103+
$this->assertEquals("h�llo!", $message->lastEventId);
104+
}
105+
106+
public function testParseWithIdWithNullByteReturnsMessageWithLastEventIdFromLastEventId()
107+
{
108+
$message = MessageEvent::parse("data: hello\nid: h\x00llo!", '1');
109+
110+
$this->assertEquals("hello", $message->data);
111+
$this->assertEquals('1', $message->lastEventId);
112+
}
113+
91114
public function testParseReturnsMessageWithTypeFromStream()
92115
{
93116
$message = MessageEvent::parse("data: hello\r\nevent: join", '');
@@ -120,6 +143,14 @@ public function testParseWithEmptyEventReturnsMessageWithDefaultMessageType()
120143
$this->assertEquals('message', $message->type);
121144
}
122145

146+
public function testParseWithEventTypeWithNonUtf8AndNullBytesReturnsTypeWithUnicodeReplacement()
147+
{
148+
$message = MessageEvent::parse("data: hello\nevent: h\x00ll\xFF!", '');
149+
150+
$this->assertEquals("hello", $message->data);
151+
$this->assertEquals("h\x00ll�!", $message->type);
152+
}
153+
123154
public function retryTimeDataProvider()
124155
{
125156
return [

0 commit comments

Comments
 (0)