in src/js/builtins/EventSource.ts [194:342]
data(socket: Socket, buffer: Buffer) {
const self = socket.data;
switch (self.#state) {
case 0: {
let text = buffer.toString();
const headers_idx = text.indexOf("\r\n\r\n");
if (headers_idx === -1) {
// wait headers
self.#data_buffer += text;
return;
}
if (self.#data_buffer.length) {
self.#data_buffer += text;
text = self.#data_buffer;
self.#data_buffer = "";
}
const headers = text.substring(0, headers_idx);
const status_idx = headers.indexOf("\r\n");
if (status_idx === -1) {
self.#state = 2;
self.dispatchEvent(new ErrorEvent("error", { error: new Error("Invalid HTTP request") }));
socket.end();
return;
}
const status = headers.substring(0, status_idx);
if (status !== "HTTP/1.1 200 OK") {
self.#state = 2;
self.dispatchEvent(new ErrorEvent("error", { error: new Error(status) }));
socket.end();
return;
}
let start_idx = status_idx + 1;
let mime_type_ok = false;
let content_length = -1;
for (;;) {
let header_idx = headers.indexOf("\r\n", start_idx);
// No text/event-stream mime type
if (header_idx === -1) {
if (start_idx >= headers.length) {
if (!mime_type_ok) {
self.#state = 2;
self.dispatchEvent(
new ErrorEvent("error", {
error: new Error(
`EventSource's response has no MIME type and "text/event-stream" is required. Aborting the connection.`,
),
}),
);
socket.end();
}
return;
}
header_idx = headers.length;
}
const header = headers.substring(start_idx + 1, header_idx);
const header_name_idx = header.indexOf(":");
const header_name = header.substring(0, header_name_idx);
const is_content_type =
header_name.localeCompare("content-type", undefined, { sensitivity: "accent" }) === 0;
start_idx = header_idx + 1;
if (is_content_type) {
if (header.endsWith(" text/event-stream")) {
mime_type_ok = true;
} else {
// wrong mime type
self.#state = 2;
self.dispatchEvent(
new ErrorEvent("error", {
error: new Error(
`EventSource's response has a MIME type that is not "text/event-stream". Aborting the connection.`,
),
}),
);
socket.end();
return;
}
} else {
const is_content_length =
header_name.localeCompare("content-length", undefined, { sensitivity: "accent" }) === 0;
if (is_content_length) {
content_length = parseInt(header.substring(header_name_idx + 1).trim(), 10);
if (content_length !== content_length || content_length <= 0) {
self.dispatchEvent(
new ErrorEvent("error", {
error: new Error(`EventSource's Content-Length is invalid. Aborting the connection.`),
}),
);
socket.end();
return;
}
if (mime_type_ok) {
break;
}
} else {
const is_transfer_encoding =
header_name.localeCompare("transfer-encoding", undefined, { sensitivity: "accent" }) === 0;
if (is_transfer_encoding) {
if (header.substring(header_name_idx + 1).trim() !== "chunked") {
self.dispatchEvent(
new ErrorEvent("error", {
error: new Error(`EventSource's Transfer-Encoding is invalid. Aborting the connection.`),
}),
);
socket.end();
return;
}
content_length = 0;
if (mime_type_ok) {
break;
}
}
}
}
}
self.#content_length = content_length;
self.#state = 1;
self.dispatchEvent(new Event("open"));
const chunks = text.substring(headers_idx + 4);
EventSource.#ProcessChunk(self, chunks, 0);
if (self.#content_length > 0) {
self.#received_length += chunks.length;
if (self.#received_length >= self.#content_length) {
self.#state = 2;
socket.end();
}
}
return;
}
case 1:
EventSource.#ProcessChunk(self, buffer.toString(), 2);
if (self.#content_length > 0) {
self.#received_length += buffer.byteLength;
if (self.#received_length >= self.#content_length) {
self.#state = 2;
socket.end();
}
}
return;
default:
break;
}
},